diff options
Diffstat (limited to '')
137 files changed, 80821 insertions, 0 deletions
diff --git a/editeng/AllLangMoTarget_editeng.mk b/editeng/AllLangMoTarget_editeng.mk new file mode 100644 index 000000000..b2e00ba32 --- /dev/null +++ b/editeng/AllLangMoTarget_editeng.mk @@ -0,0 +1,11 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. + +$(eval $(call gb_AllLangMoTarget_AllLangMoTarget,editeng)) + +# vim: set noet sw=4 ts=4: diff --git a/editeng/CppunitTest_editeng_borderline.mk b/editeng/CppunitTest_editeng_borderline.mk new file mode 100644 index 000000000..d5fee7c23 --- /dev/null +++ b/editeng/CppunitTest_editeng_borderline.mk @@ -0,0 +1,55 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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 . +# + +$(eval $(call gb_CppunitTest_CppunitTest,editeng_borderline)) + +$(eval $(call gb_CppunitTest_add_exception_objects,editeng_borderline, \ + editeng/qa/items/borderline_test \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,editeng_borderline, \ + xo \ + basegfx \ + editeng \ + lng \ + svt \ + tk \ + vcl \ + svl \ + sot \ + utl \ + tl \ + comphelper \ + ucbhelper \ + cppuhelper \ + cppu \ + sal \ + salhelper \ + i18nlangtag \ + i18nutil \ +)) + +$(eval $(call gb_CppunitTest_use_externals,editeng_borderline,\ + boost_headers \ + icuuc \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,editeng_borderline)) + +# vim: set noet sw=4 ts=4: diff --git a/editeng/CppunitTest_editeng_core.mk b/editeng/CppunitTest_editeng_core.mk new file mode 100644 index 000000000..3b3a999e8 --- /dev/null +++ b/editeng/CppunitTest_editeng_core.mk @@ -0,0 +1,78 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# 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/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitTest,editeng_core)) + +$(eval $(call gb_CppunitTest_add_exception_objects,editeng_core, \ + editeng/qa/unit/core-test \ +)) + +$(eval $(call gb_CppunitTest_use_library_objects,editeng_core,editeng)) + +$(eval $(call gb_CppunitTest_use_libraries,editeng_core, \ + basegfx \ + comphelper \ + cppu \ + cppuhelper \ + i18nlangtag \ + i18nutil \ + lng \ + sal \ + salhelper \ + sax \ + sot \ + sfx \ + svl \ + svt \ + test \ + tk \ + tl \ + ucbhelper \ + unotest \ + utl \ + vcl \ + xo \ +)) + +$(eval $(call gb_CppunitTest_use_externals,editeng_core,\ + boost_headers \ + icuuc \ + libxml2 \ +)) + +$(eval $(call gb_CppunitTest_set_include,editeng_core,\ + -I$(SRCDIR)/editeng/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,editeng_core)) + +$(eval $(call gb_CppunitTest_use_ure,editeng_core)) +$(eval $(call gb_CppunitTest_use_vcl,editeng_core)) + +$(eval $(call gb_CppunitTest_use_components,editeng_core,\ + configmgr/source/configmgr \ + framework/util/fwk \ + i18npool/util/i18npool \ + i18npool/source/search/i18nsearch \ + linguistic/source/lng \ + sfx2/util/sfx \ + ucb/source/core/ucb1 \ + ucb/source/ucp/file/ucpfile1 \ + unoxml/source/service/unoxml \ + sax/source/expatwrap/expwrap \ +)) + +$(eval $(call gb_CppunitTest_use_configuration,editeng_core)) + +$(eval $(call gb_CppunitTest_use_more_fonts,editeng_core)) + +# vim: set noet sw=4 ts=4: diff --git a/editeng/CppunitTest_editeng_lookuptree.mk b/editeng/CppunitTest_editeng_lookuptree.mk new file mode 100644 index 000000000..cdc421123 --- /dev/null +++ b/editeng/CppunitTest_editeng_lookuptree.mk @@ -0,0 +1,55 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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 . +# + +$(eval $(call gb_CppunitTest_CppunitTest,editeng_lookuptree)) + +$(eval $(call gb_CppunitTest_add_exception_objects,editeng_lookuptree, \ + editeng/qa/lookuptree/lookuptree_test \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,editeng_lookuptree, \ + xo \ + basegfx \ + editeng \ + lng \ + svt \ + tk \ + vcl \ + svl \ + sot \ + utl \ + tl \ + comphelper \ + ucbhelper \ + cppuhelper \ + cppu \ + sal \ + salhelper \ + i18nlangtag \ + i18nutil \ +)) + +$(eval $(call gb_CppunitTest_use_externals,editeng_lookuptree,\ + boost_headers \ + icuuc \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,editeng_lookuptree)) + +# vim: set noet sw=4 ts=4: diff --git a/editeng/CustomTarget_generated.mk b/editeng/CustomTarget_generated.mk new file mode 100644 index 000000000..77fb4f7bf --- /dev/null +++ b/editeng/CustomTarget_generated.mk @@ -0,0 +1,28 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_CustomTarget_CustomTarget,editeng/generated)) + +editeng_SRC := $(SRCDIR)/editeng/source/misc +editeng_PY := $(SRCDIR)/solenv/bin/gentoken.py +editeng_INC := $(call gb_CustomTarget_get_workdir,editeng/generated) + +$(editeng_INC)/tokens.hxx $(editeng_INC)/tokens.gperf : $(editeng_SRC)/tokens.txt $(editeng_PY) \ + $(call gb_ExternalExecutable_get_dependencies,python) + mkdir -p $(editeng_INC) + $(call gb_ExternalExecutable_get_command,python) $(editeng_PY) $(editeng_SRC)/tokens.txt $(editeng_INC)/tokens.gperf + +$(editeng_INC)/tokens.cxx : $(editeng_INC)/tokens.gperf + $(GPERF) --compare-strncmp --readonly-tables --output-file=$(editeng_INC)/tokens.cxx $(editeng_INC)/tokens.gperf + sed -i $(if $(filter MACOSX,$(OS_FOR_BUILD)),'') -e "s/(char\*)0/(char\*)0, XML_TOKEN_INVALID/g" $(editeng_INC)/tokens.cxx + sed -i $(if $(filter MACOSX,$(OS_FOR_BUILD)),'') -e "/^#line/d" $(editeng_INC)/tokens.cxx + +$(call gb_CustomTarget_get_target,editeng/generated) : $(editeng_INC)/tokens.cxx + +# vim: set noet sw=4 ts=4: diff --git a/editeng/IwyuFilter_editeng.yaml b/editeng/IwyuFilter_editeng.yaml new file mode 100644 index 000000000..a75d143ba --- /dev/null +++ b/editeng/IwyuFilter_editeng.yaml @@ -0,0 +1,48 @@ +--- +assumeFilename: editeng/source/editeng/impedit.cxx +excludelist: + editeng/source/editeng/impedit.hxx: + # Complete type needed for pointer arithmetic + - editeng/SpellPortions.hxx + editeng/source/accessibility/AccessibleContextBase.cxx: + # Actually used + - com/sun/star/accessibility/XAccessibleEventListener.hpp + editeng/source/accessibility/AccessibleEditableTextPara.cxx: + # Needed for macro defines + - editeng/unoprnms.hxx + editeng/source/accessibility/AccessibleImageBullet.hxx: + # base class has to be a complete type + - com/sun/star/accessibility/XAccessibleComponent.hpp + - com/sun/star/accessibility/XAccessibleContext.hpp + - com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp + - com/sun/star/accessibility/XAccessible.hpp + - com/sun/star/lang/XServiceInfo.hpp + editeng/source/editeng/impedit2.cxx: + # Needed for ValueRestorationGuard + - comphelper/flagguard.hxx + editeng/source/items/CustomPropertyField.cxx: + # Actually used + - com/sun/star/document/XDocumentProperties.hpp + editeng/source/items/frmitems.cxx: + # Needed for rtl::math::round + - rtl/math.hxx + editeng/source/items/textitem.cxx: + # Needed for rtl::math::round + - rtl/math.hxx + editeng/source/misc/SvXMLAutoCorrectExport.cxx: + # Actually used + - com/sun/star/xml/sax/XDocumentHandler.hpp + editeng/source/misc/unolingu.cxx: + # NEeded for OSL_DEBUG_LEVEL > 1 + - com/sun/star/frame/XStorable.hpp + editeng/source/uno/unotext.cxx: + # Needed for macro defines + - editeng/unoprnms.hxx + # Needed for UnoType call to work + - com/sun/star/lang/Locale.hpp + editeng/source/xml/xmltxtexp.cxx: + # Needed for UnoType call to work + - com/sun/star/lang/Locale.hpp + editeng/source/xml/xmltxtimp.cxx: + # Needed for macro defines + - editeng/unoprnms.hxx diff --git a/editeng/Library_editeng.mk b/editeng/Library_editeng.mk new file mode 100644 index 000000000..6602793e3 --- /dev/null +++ b/editeng/Library_editeng.mk @@ -0,0 +1,168 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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 . +# + +$(eval $(call gb_Library_Library,editeng)) + +$(eval $(call gb_Library_set_include,editeng,\ + $$(INCLUDE) \ + -I$(SRCDIR)/editeng/inc \ + -I$(SRCDIR)/editeng/source/editeng \ +)) + +$(eval $(call gb_Library_use_custom_headers,editeng,editeng/generated)) + +$(eval $(call gb_Library_set_precompiled_header,editeng,editeng/inc/pch/precompiled_editeng)) + +$(eval $(call gb_Library_add_defs,editeng,\ + -DEDITENG_DLLIMPLEMENTATION \ +)) + +$(eval $(call gb_Library_use_sdk_api,editeng)) + +ifneq ($(ENABLE_WASM_STRIP_ACCESSIBILITY),TRUE) +$(eval $(call gb_Library_add_exception_objects,editeng,\ + editeng/source/accessibility/AccessibleComponentBase \ + editeng/source/accessibility/AccessibleContextBase \ + editeng/source/accessibility/AccessibleEditableTextPara \ + editeng/source/accessibility/AccessibleHyperlink \ + editeng/source/accessibility/AccessibleImageBullet \ + editeng/source/accessibility/AccessibleParaManager \ + editeng/source/accessibility/AccessibleSelectionBase \ + editeng/source/accessibility/AccessibleStaticTextBase \ + editeng/source/accessibility/AccessibleStringWrap \ +)) +endif + +$(eval $(call gb_Library_add_exception_objects,editeng,\ + editeng/source/editeng/editattr \ + editeng/source/editeng/editdata \ + editeng/source/editeng/editdbg \ + editeng/source/editeng/editdoc \ + editeng/source/editeng/editeng \ + editeng/source/editeng/editobj \ + editeng/source/editeng/editsel \ + editeng/source/editeng/editundo \ + editeng/source/editeng/editview \ + editeng/source/editeng/edtspell \ + editeng/source/editeng/eehtml \ + editeng/source/editeng/eeobj \ + editeng/source/editeng/eerdll \ + editeng/source/editeng/eertfpar \ + editeng/source/editeng/fieldupdater \ + editeng/source/editeng/impedit \ + editeng/source/editeng/impedit2 \ + editeng/source/editeng/impedit3 \ + editeng/source/editeng/impedit4 \ + editeng/source/editeng/impedit5 \ + editeng/source/editeng/misspellrange \ + editeng/source/editeng/section \ + editeng/source/editeng/textconv \ + editeng/source/items/borderline \ + editeng/source/items/bulitem \ + editeng/source/items/CustomPropertyField \ + editeng/source/items/charhiddenitem \ + editeng/source/items/flditem \ + editeng/source/items/frmitems \ + editeng/source/items/itemtype \ + editeng/source/items/justifyitem \ + editeng/source/items/numitem \ + editeng/source/items/legacyitem \ + editeng/source/items/optitems \ + editeng/source/items/paperinf \ + editeng/source/items/paraitem \ + editeng/source/items/svdfield \ + editeng/source/items/svxfont \ + editeng/source/items/textitem \ + editeng/source/items/writingmodeitem \ + editeng/source/items/xmlcnitm \ + editeng/source/misc/acorrcfg \ + editeng/source/misc/edtdlg \ + editeng/source/misc/forbiddencharacterstable \ + editeng/source/misc/hangulhanja \ + editeng/source/misc/splwrap \ + editeng/source/misc/svxacorr \ + editeng/source/misc/SvXMLAutoCorrectTokenHandler \ + editeng/source/misc/SvXMLAutoCorrectExport \ + editeng/source/misc/SvXMLAutoCorrectImport \ + editeng/source/misc/swafopt \ + editeng/source/misc/txtrange \ + editeng/source/misc/unolingu \ + editeng/source/misc/urlfieldhelper \ + editeng/source/outliner/outleeng \ + editeng/source/outliner/outlin2 \ + editeng/source/outliner/outliner \ + editeng/source/outliner/outlobj \ + editeng/source/outliner/outlundo \ + editeng/source/outliner/outlvw \ + editeng/source/outliner/overflowingtxt \ + editeng/source/outliner/paralist \ + editeng/source/rtf/rtfitem \ + editeng/source/rtf/svxrtf \ + editeng/source/uno/unoedhlp \ + editeng/source/uno/unoedprx \ + editeng/source/uno/unoedsrc \ + editeng/source/uno/unofdesc \ + editeng/source/uno/unofield \ + editeng/source/uno/UnoForbiddenCharsTable \ + editeng/source/uno/unofored \ + editeng/source/uno/unoforou \ + editeng/source/uno/unoipset \ + editeng/source/uno/unonrule \ + editeng/source/uno/unopracc \ + editeng/source/uno/unotext \ + editeng/source/uno/unotext2 \ + editeng/source/uno/unoviwou \ + editeng/source/xml/xmltxtexp \ + editeng/source/xml/xmltxtimp \ + editeng/source/lookuptree/Trie \ +)) + +# add libraries to be linked to editeng; again these names need to be given as +# specified in Repository.mk +$(eval $(call gb_Library_use_libraries,editeng,\ + xo \ + basegfx \ + lng \ + svt \ + tk \ + vcl \ + svl \ + sot \ + sfx \ + utl \ + tl \ + comphelper \ + ucbhelper \ + cppuhelper \ + cppu \ + sal \ + salhelper \ + sax \ + i18nlangtag \ + i18nutil \ +)) + +$(eval $(call gb_Library_use_externals,editeng,\ + boost_headers \ + icuuc \ + icu_headers \ + libxml2 \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/editeng/Makefile b/editeng/Makefile new file mode 100644 index 000000000..8b7c035af --- /dev/null +++ b/editeng/Makefile @@ -0,0 +1,12 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../solenv/gbuild/partial_build.mk +# vim: set noet sw=4 ts=4: diff --git a/editeng/Module_editeng.mk b/editeng/Module_editeng.mk new file mode 100644 index 000000000..6fedbcb60 --- /dev/null +++ b/editeng/Module_editeng.mk @@ -0,0 +1,39 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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 . +# + +$(eval $(call gb_Module_Module,editeng)) + +$(eval $(call gb_Module_add_targets,editeng,\ + CustomTarget_generated \ + Library_editeng \ + UIConfig_editeng \ +)) + +$(eval $(call gb_Module_add_l10n_targets,editeng,\ + AllLangMoTarget_editeng \ +)) + +$(eval $(call gb_Module_add_check_targets,editeng,\ + $(if $(and $(filter $(COM),MSC),$(MERGELIBS)),, \ + CppunitTest_editeng_core) \ + CppunitTest_editeng_borderline \ + CppunitTest_editeng_lookuptree \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/editeng/README.md b/editeng/README.md new file mode 100644 index 000000000..2407c0735 --- /dev/null +++ b/editeng/README.md @@ -0,0 +1,14 @@ +# Edit Engine + +In OpenOffice.org build DEV300m72 this module was split off from `svx` but it +has no dependencies on `svx` (nor on `sfx2`) while in turn `svx` depends on editeng + +Read more in the mailing list post: +<http://www.mail-archive.com/dev@openoffice.org/msg13237.html> + +If you build LibreOffice with `dbgutil`, you have some extended debug keys: + +- Ctrl+Alt+F1 - draws the paragraph rectangles in different colors +- Ctrl+Alt+F11 - toggles dumping the edit engine state to the + "editenginedump.log" on draw +- Ctrl+Alt+F12 - dumps the current edit engine state to "editenginedump.log" diff --git a/editeng/UIConfig_editeng.mk b/editeng/UIConfig_editeng.mk new file mode 100644 index 000000000..dbe62f5b2 --- /dev/null +++ b/editeng/UIConfig_editeng.mk @@ -0,0 +1,16 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_UIConfig_UIConfig,editeng)) + +$(eval $(call gb_UIConfig_add_uifiles,editeng,\ + editeng/uiconfig/ui/spellmenu \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/editeng/inc/AccessibleStringWrap.hxx b/editeng/inc/AccessibleStringWrap.hxx new file mode 100644 index 000000000..f3888fd67 --- /dev/null +++ b/editeng/inc/AccessibleStringWrap.hxx @@ -0,0 +1,50 @@ +/* -*- 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 <sal/types.h> +#include <rtl/ustring.hxx> + +class OutputDevice; +class SvxFont; +namespace tools { class Rectangle; } +class Point; + + +// AccessibleStringWrap declaration + + +class AccessibleStringWrap +{ +public: + + AccessibleStringWrap( OutputDevice& rDev, SvxFont& rFont, OUString aText ); + + void GetCharacterBounds( sal_Int32 nIndex, tools::Rectangle& rRect ); + sal_Int32 GetIndexAtPoint( const Point& rPoint ); + +private: + + OutputDevice& mrDev; + SvxFont& mrFont; + OUString maText; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/editattr.hxx b/editeng/inc/editattr.hxx new file mode 100644 index 000000000..502a5b084 --- /dev/null +++ b/editeng/inc/editattr.hxx @@ -0,0 +1,406 @@ +/* -*- 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 <editeng/eeitem.hxx> +#include <svl/poolitem.hxx> +#include <optional> +#include <tools/color.hxx> +#include <tools/debug.hxx> + +class SvxFont; +class SvxFontItem; +class SvxWeightItem; +class SvxPostureItem; +class SvxShadowedItem; +class SvxEscapementItem; +class SvxContourItem; +class SvxCrossedOutItem; +class SvxUnderlineItem; +class SvxOverlineItem; +class SvxFontHeightItem; +class SvxCharScaleWidthItem; +class SvxColorItem; +class SvxAutoKernItem; +class SvxKerningItem; +class SvxWordLineModeItem; +class SvxFieldItem; +class SvxLanguageItem; +class SvxEmphasisMarkItem; +class SvxCharReliefItem; +class SfxVoidItem; +class OutputDevice; +class SvxCaseMapItem; +class SfxGrabBagItem; + +#define CH_FEATURE_OLD (sal_uInt8) 0xFF +#define CH_FEATURE u'\x0001' + +// DEF_METRIC: For my pool, the DefMetric should always appear when +// GetMetric (nWhich)! +// => To determine the DefMetric simply use GetMetric(0) +#define DEF_METRIC 0 + + + +// bFeature: Attribute must not expand/shrink, length is always 1 +// bEdge: Attribute will not expand, if you want to expand just on the edge +class EditCharAttrib +{ + const SfxPoolItem* pItem; + + sal_Int32 nStart; + sal_Int32 nEnd; + bool bFeature :1; + bool bEdge :1; + +public: + EditCharAttrib( const SfxPoolItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + virtual ~EditCharAttrib(); + + EditCharAttrib(const EditCharAttrib&) = delete; + EditCharAttrib& operator=(const EditCharAttrib&) = delete; + + void dumpAsXml(xmlTextWriterPtr pWriter) const; + + sal_uInt16 Which() const { return pItem->Which(); } + const SfxPoolItem* GetItem() const { return pItem; } + + sal_Int32& GetStart() { return nStart; } + sal_Int32& GetEnd() { return nEnd; } + + sal_Int32 GetStart() const { return nStart; } + sal_Int32 GetEnd() const { return nEnd; } + + inline sal_Int32 GetLen() const; + + inline void MoveForward( sal_Int32 nDiff ); + inline void MoveBackward( sal_Int32 nDiff ); + + inline void Expand( sal_Int32 nDiff ); + inline void Collaps( sal_Int32 nDiff ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ); + + bool IsIn( sal_Int32 nIndex ) const + { return ( ( nStart <= nIndex ) && ( nEnd >= nIndex ) ); } + bool IsInside( sal_Int32 nIndex ) const + { return ( ( nStart < nIndex ) && ( nEnd > nIndex ) ); } + bool IsEmpty() const + { return nStart == nEnd; } + + bool IsFeature() const { return bFeature; } + void SetFeature( bool b) { bFeature = b; } + + bool IsEdge() const { return bEdge; } + void SetEdge( bool b ) { bEdge = b; } +}; + +inline sal_Int32 EditCharAttrib::GetLen() const +{ + DBG_ASSERT( nEnd >= nStart, "EditCharAttrib: nEnd < nStart!" ); + return nEnd-nStart; +} + +inline void EditCharAttrib::MoveForward( sal_Int32 nDiff ) +{ + DBG_ASSERT( SAL_MAX_INT32 - nDiff > nEnd, "EditCharAttrib: MoveForward?!" ); + nStart = nStart + nDiff; + nEnd = nEnd + nDiff; +} + +inline void EditCharAttrib::MoveBackward( sal_Int32 nDiff ) +{ + DBG_ASSERT( (nStart - nDiff) >= 0, "EditCharAttrib: MoveBackward?!" ); + nStart = nStart - nDiff; + nEnd = nEnd - nDiff; +} + +inline void EditCharAttrib::Expand( sal_Int32 nDiff ) +{ + DBG_ASSERT( SAL_MAX_INT32 - nDiff > nEnd, "EditCharAttrib: Expand?!" ); + DBG_ASSERT( !bFeature, "Please do not expand any features!" ); + nEnd = nEnd + nDiff; +} + +inline void EditCharAttrib::Collaps( sal_Int32 nDiff ) +{ + DBG_ASSERT( nEnd - nDiff >= nStart, "EditCharAttrib: Collaps?!" ); + DBG_ASSERT( !bFeature, "Please do not shrink any Features!" ); + nEnd = nEnd - nDiff; +} + + + +class EditCharAttribFont final : public EditCharAttrib +{ +public: + EditCharAttribFont( const SvxFontItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribWeight final : public EditCharAttrib +{ +public: + EditCharAttribWeight( const SvxWeightItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + +class EditCharAttribItalic final : public EditCharAttrib +{ +public: + EditCharAttribItalic( const SvxPostureItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribShadow final : public EditCharAttrib +{ +public: + EditCharAttribShadow( const SvxShadowedItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribEscapement final : public EditCharAttrib +{ +public: + EditCharAttribEscapement( const SvxEscapementItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribOutline final : public EditCharAttrib +{ +public: + EditCharAttribOutline( const SvxContourItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribStrikeout final : public EditCharAttrib +{ +public: + EditCharAttribStrikeout( const SvxCrossedOutItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribCaseMap final : public EditCharAttrib +{ +public: + EditCharAttribCaseMap( const SvxCaseMapItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribUnderline final : public EditCharAttrib +{ +public: + EditCharAttribUnderline( const SvxUnderlineItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribOverline final : public EditCharAttrib +{ +public: + EditCharAttribOverline( const SvxOverlineItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribEmphasisMark final : public EditCharAttrib +{ +public: + EditCharAttribEmphasisMark( const SvxEmphasisMarkItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribRelief final : public EditCharAttrib +{ +public: + EditCharAttribRelief( const SvxCharReliefItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribFontHeight final : public EditCharAttrib +{ +public: + EditCharAttribFontHeight( const SvxFontHeightItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribFontWidth final : public EditCharAttrib +{ +public: + EditCharAttribFontWidth( const SvxCharScaleWidthItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribColor final : public EditCharAttrib +{ +public: + EditCharAttribColor( const SvxColorItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + +class EditCharAttribBackgroundColor final : public EditCharAttrib +{ +public: + EditCharAttribBackgroundColor(const SvxColorItem& rAttr, + sal_Int32 nStart, + sal_Int32 nEnd ); + virtual void SetFont(SvxFont& rFont, OutputDevice* pOutDev) override; +}; + + + +class EditCharAttribLanguage final : public EditCharAttrib +{ +public: + EditCharAttribLanguage( const SvxLanguageItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribTab final : public EditCharAttrib +{ +public: + EditCharAttribTab( const SfxVoidItem& rAttr, sal_Int32 nPos ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribLineBreak final : public EditCharAttrib +{ +public: + EditCharAttribLineBreak( const SfxVoidItem& rAttr, sal_Int32 nPos ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribField final : public EditCharAttrib +{ + OUString aFieldValue; + std::optional<Color> mxTxtColor; + std::optional<Color> mxFldColor; + + EditCharAttribField& operator = ( const EditCharAttribField& rAttr ) = delete; + +public: + EditCharAttribField( const SvxFieldItem& rAttr, sal_Int32 nPos ); + EditCharAttribField( const EditCharAttribField& rAttr ); + virtual ~EditCharAttribField() override; + + bool operator == ( const EditCharAttribField& rAttr ) const; + bool operator != ( const EditCharAttribField& rAttr ) const + { return !(operator == ( rAttr ) ); } + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; + std::optional<Color>& GetTextColor() { return mxTxtColor; } + std::optional<Color>& GetFieldColor() { return mxFldColor; } + + const OUString& GetFieldValue() const { return aFieldValue;} + void SetFieldValue(const OUString& rVal); + + void Reset(); +}; + + + +class EditCharAttribPairKerning final : public EditCharAttrib +{ +public: + EditCharAttribPairKerning( const SvxAutoKernItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribKerning final : public EditCharAttrib +{ +public: + EditCharAttribKerning( const SvxKerningItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + + +class EditCharAttribWordLineMode final : public EditCharAttrib +{ +public: + EditCharAttribWordLineMode( const SvxWordLineModeItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + virtual void SetFont( SvxFont& rFont, OutputDevice* pOutDev ) override; +}; + + +class EditCharAttribGrabBag final : public EditCharAttrib +{ +public: + EditCharAttribGrabBag( const SfxGrabBagItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/editdoc.hxx b/editeng/inc/editdoc.hxx new file mode 100644 index 000000000..6ce00d05c --- /dev/null +++ b/editeng/inc/editdoc.hxx @@ -0,0 +1,832 @@ +/* -*- 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 "editattr.hxx" +#include "edtspell.hxx" +#include "eerdll2.hxx" +#include <editeng/svxfont.hxx> +#include <svl/itemset.hxx> +#include <svl/style.hxx> +#include <svl/itempool.hxx> +#include <svl/languageoptions.hxx> +#include <tools/lineend.hxx> +#include <o3tl/typed_flags_set.hxx> + +#include <cstddef> +#include <memory> +#include <string_view> +#include <vector> + +class ImpEditEngine; +class SvxTabStop; +enum class TextRotation; + + +#define CHARPOSGROW 16 +#define DEFTAB 720 + +void CreateFont( SvxFont& rFont, const SfxItemSet& rSet, bool bSearchInParent = true, SvtScriptType nScriptType = SvtScriptType::NONE ); +sal_uInt16 GetScriptItemId( sal_uInt16 nItemId, SvtScriptType nScriptType ); +bool IsScriptItemValid( sal_uInt16 nItemId, short nScriptType ); + +EditCharAttrib* MakeCharAttrib( SfxItemPool& rPool, const SfxPoolItem& rAttr, sal_Int32 nS, sal_Int32 nE ); + +class ContentNode; +class EditDoc; + +struct EPaM +{ + sal_Int32 nPara; + sal_Int32 nIndex; + + EPaM() : nPara(0), nIndex(0) {} + EPaM( sal_Int32 nP, sal_Int32 nI ) : nPara(nP), nIndex(nI) {} + EPaM( const EPaM& r) : nPara(r.nPara), nIndex(r.nIndex) {} + EPaM& operator = ( const EPaM& r ) { nPara = r.nPara; nIndex = r.nIndex; return *this; } + inline bool operator == ( const EPaM& r ) const; + inline bool operator < ( const EPaM& r ) const; +}; + +inline bool EPaM::operator < ( const EPaM& r ) const +{ + return ( nPara < r.nPara ) || ( ( nPara == r.nPara ) && nIndex < r.nIndex ); +} + +inline bool EPaM::operator == ( const EPaM& r ) const +{ + return ( nPara == r.nPara ) && ( nIndex == r.nIndex ); +} + +struct ScriptTypePosInfo +{ + short nScriptType; + sal_Int32 nStartPos; + sal_Int32 nEndPos; + + ScriptTypePosInfo( short Type, sal_Int32 Start, sal_Int32 End ) + : nScriptType(Type) + , nStartPos(Start) + , nEndPos(End) + { + } +}; + +typedef std::vector<ScriptTypePosInfo> ScriptTypePosInfos; + +struct WritingDirectionInfo +{ + sal_uInt8 nType; + sal_Int32 nStartPos; + sal_Int32 nEndPos; + + WritingDirectionInfo( sal_uInt8 Type, sal_Int32 Start, sal_Int32 End ) + : nType(Type) + , nStartPos(Start) + , nEndPos(End) + { + } +}; + + +typedef std::vector<WritingDirectionInfo> WritingDirectionInfos; + +class ContentAttribsInfo +{ +private: + typedef std::vector<std::unique_ptr<EditCharAttrib> > CharAttribsType; + + SfxItemSet aPrevParaAttribs; + CharAttribsType aPrevCharAttribs; + +public: + ContentAttribsInfo( SfxItemSet aParaAttribs ); + + const SfxItemSet& GetPrevParaAttribs() const { return aPrevParaAttribs; } + const CharAttribsType& GetPrevCharAttribs() const { return aPrevCharAttribs; } + + void RemoveAllCharAttribsFromPool(SfxItemPool& rPool) const; + void AppendCharAttrib(EditCharAttrib* pNew); +}; + + + +typedef std::vector<Color> SvxColorList; + + + + +class ItemList +{ +private: + typedef std::vector<const SfxPoolItem*> DummyItemList; + DummyItemList aItemPool; + sal_Int32 CurrentItem; + +public: + ItemList(); + const SfxPoolItem* First(); + const SfxPoolItem* Next(); + sal_Int32 Count() const { return aItemPool.size(); }; + void Insert( const SfxPoolItem* pItem ); + void Clear() { aItemPool.clear(); }; +}; + + + +class ContentAttribs +{ +private: + SfxStyleSheet* pStyle; + SfxItemSetFixed<EE_PARA_START, EE_CHAR_END> aAttribSet; + +public: + ContentAttribs( SfxItemPool& rItemPool ); + + void dumpAsXml(xmlTextWriterPtr pWriter) const; + + SvxTabStop FindTabStop( sal_Int32 nCurPos, sal_uInt16 nDefTab ); + SfxItemSet& GetItems() { return aAttribSet; } + const SfxItemSet& GetItems() const { return aAttribSet; } + const SfxStyleSheet* GetStyleSheet() const { return pStyle; } + SfxStyleSheet* GetStyleSheet() { return pStyle; } + void SetStyleSheet( SfxStyleSheet* pS ); + + const SfxPoolItem& GetItem( sal_uInt16 nWhich ) const; + template<class T> + const T& GetItem( TypedWhichId<T> nWhich ) const + { + return static_cast<const T&>(GetItem(sal_uInt16(nWhich))); + } + bool HasItem( sal_uInt16 nWhich ) const; +}; + + + +class CharAttribList +{ +public: + typedef std::vector<std::unique_ptr<EditCharAttrib> > AttribsType; + +private: + AttribsType aAttribs; + SvxFont aDefFont; // faster than ever from the pool! + bool bHasEmptyAttribs; + +public: + CharAttribList(); + ~CharAttribList(); + + void dumpAsXml(xmlTextWriterPtr pWriter) const; + + void DeleteEmptyAttribs( SfxItemPool& rItemPool ); + + const EditCharAttrib* FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos ) const; + EditCharAttrib* FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos ); + const EditCharAttrib* FindNextAttrib( sal_uInt16 nWhich, sal_Int32 nFromPos ) const; + EditCharAttrib* FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos ); + const EditCharAttrib* FindFeature( sal_Int32 nPos ) const; + + + void ResortAttribs(); + void OptimizeRanges( SfxItemPool& rItemPool ); + + sal_Int32 Count() const; + + void InsertAttrib( EditCharAttrib* pAttrib ); + + SvxFont& GetDefFont() { return aDefFont; } + + bool HasEmptyAttribs() const { return bHasEmptyAttribs; } + void SetHasEmptyAttribs(bool b); + bool HasBoundingAttrib( sal_Int32 nBound ) const; + bool HasAttrib( sal_Int32 nStartPos, sal_Int32 nEndPos ) const; + + AttribsType& GetAttribs() { return aAttribs;} + const AttribsType& GetAttribs() const { return aAttribs;} + + void Remove(const EditCharAttrib* p); + void Remove(sal_Int32 nPos); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + static void DbgCheckAttribs(CharAttribList const& rAttribs); +#endif +}; + + + +class ContentNode +{ +private: + OUString maString; + ContentAttribs aContentAttribs; + CharAttribList aCharAttribList; + std::unique_ptr<WrongList> mpWrongList; + + void UnExpandPosition( sal_Int32 &rStartPos, bool bBiasStart ); + +public: + ContentNode( SfxItemPool& rItemPool ); + ContentNode( const OUString& rStr, const ContentAttribs& rContentAttribs ); + ~ContentNode(); + ContentNode(const ContentNode&) = delete; + ContentNode& operator=(const ContentNode&) = delete; + + void dumpAsXml(xmlTextWriterPtr pWriter) const; + + ContentAttribs& GetContentAttribs() { return aContentAttribs; } + const ContentAttribs& GetContentAttribs() const { return aContentAttribs; } + CharAttribList& GetCharAttribs() { return aCharAttribList; } + const CharAttribList& GetCharAttribs() const { return aCharAttribList; } + + void ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNewChars, SfxItemPool& rItemPool ); + void CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDelChars, SfxItemPool& rItemPool ); + void AppendAttribs( ContentNode* pNextNode ); + void CopyAndCutAttribs( ContentNode* pPrevNode, SfxItemPool& rPool, bool bKeepEndingAttribs ); + + void SetStyleSheet( SfxStyleSheet* pS, bool bRecalcFont = true ); + void SetStyleSheet( SfxStyleSheet* pS, const SvxFont& rFontFromStyle ); + SfxStyleSheet* GetStyleSheet() { return aContentAttribs.GetStyleSheet(); } + + void CreateDefFont(); + + void EnsureWrongList(); + WrongList* GetWrongList(); + const WrongList* GetWrongList() const; + void SetWrongList( WrongList* p ); + + void CreateWrongList(); + void DestroyWrongList(); + + bool IsFeature( sal_Int32 nPos ) const; + + sal_Int32 Len() const; + const OUString& GetString() const { return maString;} + + /// return length including expanded fields + sal_Int32 GetExpandedLen() const; + /// return content including expanded fields + OUString GetExpandedText(sal_Int32 nStartPos = 0, sal_Int32 nEndPos = -1) const; + /// re-write offsets in the expanded text to string offsets + void UnExpandPositions( sal_Int32 &rStartPos, sal_Int32 &rEndPos ); + + void SetChar(sal_Int32 nPos, sal_Unicode c); + void Insert(std::u16string_view rStr, sal_Int32 nPos); + void Append(std::u16string_view rStr); + void Erase(sal_Int32 nPos); + void Erase(sal_Int32 nPos, sal_Int32 nCount); + OUString Copy(sal_Int32 nPos) const; + OUString Copy(sal_Int32 nPos, sal_Int32 nCount) const; + sal_Unicode GetChar(sal_Int32 nPos) const; +}; + + + +class EditPaM +{ +private: + ContentNode* pNode; + sal_Int32 nIndex; + +public: + EditPaM(); + EditPaM(ContentNode* p, sal_Int32 n); + + const ContentNode* GetNode() const { return pNode;} + ContentNode* GetNode() { return pNode;} + void SetNode(ContentNode* p); + + sal_Int32 GetIndex() const { return nIndex; } + void SetIndex( sal_Int32 n ) { nIndex = n; } + + bool DbgIsBuggy( EditDoc const & rDoc ) const; + + friend bool operator == ( const EditPaM& r1, const EditPaM& r2 ); + friend bool operator != ( const EditPaM& r1, const EditPaM& r2 ); + bool operator !() const { return !pNode && !nIndex; } +}; + +enum class PortionKind +{ + TEXT = 0, + TAB = 1, + LINEBREAK = 2, + FIELD = 3, + HYPHENATOR = 4 +}; + +enum class DeleteMode { + Simple, RestOfWord, RestOfContent +}; + +enum class AsianCompressionFlags { + Normal = 0x00, + Kana = 0x01, + PunctuationLeft = 0x02, + PunctuationRight = 0x04, +}; +namespace o3tl { + template<> struct typed_flags<AsianCompressionFlags> : is_typed_flags<AsianCompressionFlags, 0x07> {}; +} + + + +// struct ExtraPortionInfos + +struct ExtraPortionInfo +{ + tools::Long nOrgWidth; + tools::Long nWidthFullCompression; + + tools::Long nPortionOffsetX; + + sal_uInt16 nMaxCompression100thPercent; + + AsianCompressionFlags nAsianCompressionTypes; + bool bFirstCharIsRightPunktuation; + bool bCompressed; + + std::unique_ptr<sal_Int32[]> pOrgDXArray; + std::vector< sal_Int32 > lineBreaksList; + + + ExtraPortionInfo(); + ~ExtraPortionInfo(); + + void SaveOrgDXArray( const sal_Int32* pDXArray, sal_Int32 nLen ); +}; + + + +class TextPortion +{ +private: + std::unique_ptr<ExtraPortionInfo> xExtraInfos; + sal_Int32 nLen; + Size aOutSz; + PortionKind nKind; + sal_uInt8 nRightToLeftLevel; + sal_Unicode nExtraValue; + + +public: + TextPortion( sal_Int32 nL ) + : nLen( nL ) + , aOutSz( -1, -1 ) + , nKind( PortionKind::TEXT ) + , nRightToLeftLevel( 0 ) + , nExtraValue( 0 ) + { + } + + TextPortion( const TextPortion& r ) + : nLen( r.nLen ) + , aOutSz( r.aOutSz ) + , nKind( r.nKind ) + , nRightToLeftLevel( r.nRightToLeftLevel ) + , nExtraValue( r.nExtraValue ) + { + } + + + sal_Int32 GetLen() const { return nLen; } + void SetLen( sal_Int32 nL ) { nLen = nL; } + + Size& GetSize() { return aOutSz; } + const Size& GetSize() const { return aOutSz; } + + void SetKind(PortionKind n) { nKind = n; } + PortionKind GetKind() const { return nKind; } + + void SetRightToLeftLevel( sal_uInt8 n ) { nRightToLeftLevel = n; } + sal_uInt8 GetRightToLeftLevel() const { return nRightToLeftLevel; } + bool IsRightToLeft() const { return (nRightToLeftLevel&1); } + + sal_Unicode GetExtraValue() const { return nExtraValue; } + void SetExtraValue( sal_Unicode n ) { nExtraValue = n; } + + ExtraPortionInfo* GetExtraInfos() const { return xExtraInfos.get(); } + void SetExtraInfos( ExtraPortionInfo* p ) { xExtraInfos.reset(p); } +}; + + + +class TextPortionList +{ + typedef std::vector<std::unique_ptr<TextPortion> > PortionsType; + PortionsType maPortions; + +public: + TextPortionList(); + ~TextPortionList(); + + void Reset(); + sal_Int32 FindPortion( + sal_Int32 nCharPos, sal_Int32& rPortionStart, bool bPreferStartingPortion = false) const; + sal_Int32 GetStartPos(sal_Int32 nPortion); + void DeleteFromPortion(sal_Int32 nDelFrom); + sal_Int32 Count() const; + const TextPortion& operator[](sal_Int32 nPos) const; + TextPortion& operator[](sal_Int32 nPos); + + void Append(TextPortion* p); + void Insert(sal_Int32 nPos, TextPortion* p); + void Remove(sal_Int32 nPos); + sal_Int32 GetPos(const TextPortion* p) const; +}; + +class ParaPortion; + + + +class EditLine +{ +public: + typedef std::vector<sal_Int32> CharPosArrayType; + +private: + CharPosArrayType aPositions; + sal_Int32 nTxtWidth; + sal_Int32 nStartPosX; + sal_Int32 nStart; // could be replaced by nStartPortion + sal_Int32 nEnd; // could be replaced by nEndPortion + sal_Int32 nStartPortion; + sal_Int32 nEndPortion; + sal_uInt16 nHeight; // Total height of the line + sal_uInt16 nTxtHeight; // Pure Text height + sal_uInt16 nMaxAscent; + bool bHangingPunctuation:1; + bool bInvalid:1; // for skillful formatting + +public: + EditLine(); + EditLine( const EditLine& ); + ~EditLine(); + + bool IsIn( sal_Int32 nIndex ) const + { return ( (nIndex >= nStart ) && ( nIndex < nEnd ) ); } + + bool IsIn( sal_Int32 nIndex, bool bInclEnd ) const + { return ( ( nIndex >= nStart ) && ( bInclEnd ? ( nIndex <= nEnd ) : ( nIndex < nEnd ) ) ); } + + void SetStart( sal_Int32 n ) { nStart = n; } + sal_Int32 GetStart() const { return nStart; } + sal_Int32& GetStart() { return nStart; } + + void SetEnd( sal_Int32 n ) { nEnd = n; } + sal_Int32 GetEnd() const { return nEnd; } + sal_Int32& GetEnd() { return nEnd; } + + void SetStartPortion( sal_Int32 n ) { nStartPortion = n; } + sal_Int32 GetStartPortion() const { return nStartPortion; } + sal_Int32& GetStartPortion() { return nStartPortion; } + + void SetEndPortion( sal_Int32 n ) { nEndPortion = n; } + sal_Int32 GetEndPortion() const { return nEndPortion; } + sal_Int32& GetEndPortion() { return nEndPortion; } + + void SetHeight( sal_uInt16 nH, sal_uInt16 nTxtH = 0 ); + sal_uInt16 GetHeight() const { return nHeight; } + sal_uInt16 GetTxtHeight() const { return nTxtHeight; } + + void SetTextWidth( sal_Int32 n ) { nTxtWidth = n; } + sal_Int32 GetTextWidth() const { return nTxtWidth; } + + void SetMaxAscent( sal_uInt16 n ) { nMaxAscent = n; } + sal_uInt16 GetMaxAscent() const { return nMaxAscent; } + + void SetHangingPunctuation( bool b ) { bHangingPunctuation = b; } + bool IsHangingPunctuation() const { return bHangingPunctuation; } + + sal_Int32 GetLen() const { return nEnd - nStart; } + + sal_Int32 GetStartPosX() const { return nStartPosX; } + void SetStartPosX( sal_Int32 start ); + Size CalcTextSize( ParaPortion& rParaPortion ); + + bool IsInvalid() const { return bInvalid; } + bool IsValid() const { return !bInvalid; } + void SetInvalid() { bInvalid = true; } + void SetValid() { bInvalid = false; } + + bool IsEmpty() const { return nEnd <= nStart; } + + CharPosArrayType& GetCharPosArray() { return aPositions;} + const CharPosArrayType& GetCharPosArray() const { return aPositions;} + + EditLine* Clone() const; + + EditLine& operator = ( const EditLine& rLine ); + friend bool operator == ( const EditLine& r1, const EditLine& r2 ); +}; + + + +class EditLineList +{ + typedef std::vector<std::unique_ptr<EditLine> > LinesType; + LinesType maLines; + +public: + EditLineList(); + ~EditLineList(); + + void Reset(); + void DeleteFromLine(sal_Int32 nDelFrom); + sal_Int32 FindLine(sal_Int32 nChar, bool bInclEnd); + sal_Int32 Count() const; + const EditLine& operator[](sal_Int32 nPos) const; + EditLine& operator[](sal_Int32 nPos); + + void Append(EditLine* p); + void Insert(sal_Int32 nPos, EditLine* p); +}; + + + +class ParaPortion +{ + friend class ImpEditEngine; // to adjust the height +private: + EditLineList aLineList; + TextPortionList aTextPortionList; + ContentNode* pNode; + tools::Long nHeight; + + ScriptTypePosInfos aScriptInfos; + WritingDirectionInfos aWritingDirectionInfos; + + sal_Int32 nInvalidPosStart; + sal_Int32 nFirstLineOffset; // For Writer-LineSpacing-Interpretation + sal_Int32 nBulletX; + sal_Int32 nInvalidDiff; + + bool bInvalid : 1; + bool bSimple : 1; // only linear Tap + bool bVisible : 1; // Belongs to the node! + bool bForceRepaint : 1; + + ParaPortion( const ParaPortion& ) = delete; + +public: + ParaPortion( ContentNode* pNode ); + ~ParaPortion(); + + sal_Int32 GetLineNumber( sal_Int32 nIndex ) const; + + EditLineList& GetLines() { return aLineList; } + const EditLineList& GetLines() const { return aLineList; } + + bool IsInvalid() const { return bInvalid; } + bool IsSimpleInvalid() const { return bSimple; } + void SetValid() { bInvalid = false; bSimple = true;} + + bool MustRepaint() const { return bForceRepaint; } + void SetMustRepaint( bool bRP ) { bForceRepaint = bRP; } + + sal_Int32 GetBulletX() const { return nBulletX; } + void SetBulletX( sal_Int32 n ) { nBulletX = n; } + + void MarkInvalid( sal_Int32 nStart, sal_Int32 nDiff); + void MarkSelectionInvalid( sal_Int32 nStart ); + + void SetVisible( bool bVisible ); + bool IsVisible() const { return bVisible; } + + bool IsEmpty() { return GetTextPortions().Count() == 1 && GetTextPortions()[0].GetLen() == 0; } + + tools::Long GetHeight() const { return ( bVisible ? nHeight : 0 ); } + sal_Int32 GetFirstLineOffset() const { return ( bVisible ? nFirstLineOffset : 0 ); } + void ResetHeight() { nHeight = 0; nFirstLineOffset = 0; } + + ContentNode* GetNode() const { return pNode; } + TextPortionList& GetTextPortions() { return aTextPortionList; } + const TextPortionList& GetTextPortions() const { return aTextPortionList; } + + sal_Int32 GetInvalidPosStart() const { return nInvalidPosStart; } + short GetInvalidDiff() const { return nInvalidDiff; } + + void CorrectValuesBehindLastFormattedLine( sal_Int32 nLastFormattedLine ); +#if OSL_DEBUG_LEVEL > 0 + static bool DbgCheckTextPortions(ParaPortion const&); +#endif +}; + + + +class ParaPortionList +{ + mutable sal_Int32 nLastCache; + std::vector<std::unique_ptr<ParaPortion>> maPortions; +public: + ParaPortionList(); + ~ParaPortionList(); + + void Reset(); + tools::Long GetYOffset(const ParaPortion* pPPortion) const; + sal_Int32 FindParagraph(tools::Long nYOffset) const; + + const ParaPortion* SafeGetObject(sal_Int32 nPos) const; + ParaPortion* SafeGetObject(sal_Int32 nPos); + + sal_Int32 GetPos(const ParaPortion* p) const; + ParaPortion* operator[](sal_Int32 nPos); + const ParaPortion* operator[](sal_Int32 nPos) const; + + std::unique_ptr<ParaPortion> Release(sal_Int32 nPos); + void Remove(sal_Int32 nPos); + void Insert(sal_Int32 nPos, std::unique_ptr<ParaPortion> p); + void Append(std::unique_ptr<ParaPortion> p); + sal_Int32 Count() const; + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + // temporary: + static void DbgCheck(ParaPortionList const&, EditDoc const& rDoc); +#endif +}; + + + +class EditSelection +{ +private: + EditPaM aStartPaM; + EditPaM aEndPaM; + +public: + EditSelection(); // No constructor and destructor + // are automatically executed correctly! + EditSelection( const EditPaM& rStartAndAnd ); + EditSelection( const EditPaM& rStart, const EditPaM& rEnd ); + + EditPaM& Min() { return aStartPaM; } + EditPaM& Max() { return aEndPaM; } + + const EditPaM& Min() const { return aStartPaM; } + const EditPaM& Max() const { return aEndPaM; } + + bool HasRange() const { return aStartPaM != aEndPaM; } + bool IsInvalid() const { return !aStartPaM || !aEndPaM; } + bool DbgIsBuggy( EditDoc const & rDoc ) const; + + void Adjust( const EditDoc& rNodes ); + + EditSelection& operator = ( const EditPaM& r ); + bool operator == ( const EditSelection& r ) const + { return ( aStartPaM == r.aStartPaM ) && ( aEndPaM == r.aEndPaM ); } + bool operator != ( const EditSelection& r ) const { return !( r == *this ); } +}; + + + +class DeletedNodeInfo +{ +private: + ContentNode* mpInvalidNode; + sal_Int32 nInvalidParagraph; + +public: + DeletedNodeInfo( ContentNode* pNode, sal_Int32 nPos ) + : mpInvalidNode(pNode) + , nInvalidParagraph(nPos) + { + } + + ContentNode* GetNode() const { return mpInvalidNode; } + sal_Int32 GetPosition() const { return nInvalidParagraph; } +}; + + + +class EditDoc +{ +private: + mutable sal_Int32 nLastCache; + std::vector<std::unique_ptr<ContentNode> > maContents; + + rtl::Reference<SfxItemPool> pItemPool; + Link<LinkParamNone*,void> aModifyHdl; + + SvxFont aDefFont; //faster than ever from the pool!! + sal_uInt16 nDefTab; + bool bIsVertical:1; + TextRotation mnRotation; + bool bIsFixedCellHeight:1; + + bool bModified:1; + bool bDisableAttributeExpanding:1; + +private: + void ImplDestroyContents(); + +public: + EditDoc( SfxItemPool* pItemPool ); + ~EditDoc(); + + void dumpAsXml(xmlTextWriterPtr pWriter) const; + void ClearSpellErrors(); + + bool IsModified() const { return bModified; } + void SetModified( bool b ); + + void DisableAttributeExpanding() { bDisableAttributeExpanding = true; } + + void SetModifyHdl( const Link<LinkParamNone*,void>& rLink ) { aModifyHdl = rLink; } + + void CreateDefFont( bool bUseStyles ); + const SvxFont& GetDefFont() const { return aDefFont; } + + void SetDefTab( sal_uInt16 nTab ) { nDefTab = nTab ? nTab : DEFTAB; } + sal_uInt16 GetDefTab() const { return nDefTab; } + + void SetVertical( bool bVertical ) { bIsVertical = bVertical; } + bool IsEffectivelyVertical() const; + bool IsTopToBottom() const; + bool GetVertical() const; + void SetRotation( TextRotation nRotation ) { mnRotation = nRotation; } + TextRotation GetRotation() const { return mnRotation; } + + void SetFixedCellHeight( bool bUseFixedCellHeight ) { bIsFixedCellHeight = bUseFixedCellHeight; } + bool IsFixedCellHeight() const { return bIsFixedCellHeight; } + + EditPaM Clear(); + EditPaM RemoveText(); + void RemoveChars( EditPaM aPaM, sal_Int32 nChars ); + EditPaM InsertText( EditPaM aPaM, const OUString& rStr ); + EditPaM InsertParaBreak( EditPaM aPaM, bool bKeepEndingAttribs ); + EditPaM InsertFeature( EditPaM aPaM, const SfxPoolItem& rItem ); + EditPaM ConnectParagraphs( ContentNode* pLeft, ContentNode* pRight ); + + OUString GetText( LineEnd eEnd ) const; + sal_Int32 GetTextLen() const; + + OUString GetParaAsString( sal_Int32 nNode ) const; + static OUString GetParaAsString(const ContentNode* pNode, sal_Int32 nStartPos = 0, sal_Int32 nEndPos = -1); + + EditPaM GetStartPaM() const; + EditPaM GetEndPaM() const; + + SfxItemPool& GetItemPool() { return *pItemPool; } + const SfxItemPool& GetItemPool() const { return *pItemPool; } + + void RemoveItemsFromPool(const ContentNode& rNode); + + void InsertAttrib( const SfxPoolItem& rItem, ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd ); + void InsertAttrib( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, const SfxPoolItem& rPoolItem ); + void InsertAttribInSelection( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, const SfxPoolItem& rPoolItem ); + bool RemoveAttribs( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, sal_uInt16 nWhich ); + bool RemoveAttribs( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, EditCharAttrib*& rpStarting, EditCharAttrib*& rpEnding, sal_uInt16 nWhich ); + static void FindAttribs( ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos, SfxItemSet& rCurSet ); + + sal_Int32 GetPos(const ContentNode* pNode) const; + const ContentNode* GetObject(sal_Int32 nPos) const; + ContentNode* GetObject(sal_Int32 nPos); + sal_Int32 Count() const; + const ContentNode* operator[](sal_Int32 nPos) const; + ContentNode* operator[](sal_Int32 nPos); + void Insert(sal_Int32 nPos, ContentNode* p); + /// deletes + void Remove(sal_Int32 nPos); + /// does not delete + void Release(sal_Int32 nPos); + + static OUString GetSepStr( LineEnd eEnd ); +}; + +inline EditCharAttrib* GetAttrib(CharAttribList::AttribsType& rAttribs, std::size_t nAttr) +{ + return (nAttr < rAttribs.size()) ? rAttribs[nAttr].get() : nullptr; +} + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG +void CheckOrderedList(const CharAttribList::AttribsType& rAttribs); +#endif + +class EditEngineItemPool final : public SfxItemPool +{ +private: + std::shared_ptr<DefItems> m_xDefItems; +public: + EditEngineItemPool(); +private: + virtual ~EditEngineItemPool() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/editeng.hxx b/editeng/inc/editeng.hxx new file mode 100644 index 000000000..5b831efa3 --- /dev/null +++ b/editeng/inc/editeng.hxx @@ -0,0 +1,28 @@ +/* -*- 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 + +#define MN_WORDLANGUAGE 998 +#define MN_PARALANGUAGE 999 +#define MN_ALTSTART 1000 +#define MN_AUTOSTART 2000 +#define MN_DICTSTART 3000 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/editxml.hxx b/editeng/inc/editxml.hxx new file mode 100644 index 000000000..650b7dfde --- /dev/null +++ b/editeng/inc/editxml.hxx @@ -0,0 +1,33 @@ +/* -*- 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 + +class EditEngine; +class SvStream; +struct ESelection; +class EditPaM; + +/** this function exports the selected content of an edit engine into a xml stream*/ +extern void SvxWriteXML(EditEngine& rEditEngine, SvStream& rStream, const ESelection& rSel); + +/** this function imports xml from the stream into the selected of an edit engine */ +extern EditPaM SvxReadXML(EditEngine& rEditEngine, SvStream& rStream, const ESelection& rSel); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/edtspell.hxx b/editeng/inc/edtspell.hxx new file mode 100644 index 000000000..a63c39569 --- /dev/null +++ b/editeng/inc/edtspell.hxx @@ -0,0 +1,144 @@ +/* -*- 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 <editeng/splwrap.hxx> +#include <editeng/svxacorr.hxx> +#include <editeng/svxenum.hxx> +#include <editeng/misspellrange.hxx> + +namespace com::sun::star::linguistic2 { + class XSpellChecker1; +} + + +class EditView; +class EditEngine; +class ContentNode; + +class EditSpellWrapper final : public SvxSpellWrapper +{ +private: + EditView* pEditView; + void CheckSpellTo(); + + virtual void SpellStart( SvxSpellArea eArea ) override; + virtual void SpellContinue() override; // Check area + virtual void ReplaceAll( const OUString &rNewText ) override; + virtual bool SpellMore() override; + +public: + EditSpellWrapper(weld::Widget* pWin, bool bIsStart, EditView* pView); +}; + +/** + * Keeps track of misspelled ranges in paragraph. + */ +class WrongList +{ + static constexpr size_t Valid = std::numeric_limits<size_t>::max(); + + std::vector<editeng::MisspellRange> maRanges; + size_t mnInvalidStart; + size_t mnInvalidEnd; + + bool DbgIsBuggy() const; + +public: + typedef std::vector<editeng::MisspellRange>::iterator iterator; + typedef std::vector<editeng::MisspellRange>::const_iterator const_iterator; + + WrongList(); + + const std::vector<editeng::MisspellRange>& GetRanges() const { return maRanges;} + void SetRanges( std::vector<editeng::MisspellRange>&& rRanges ); + + bool IsValid() const; + void SetValid(); + void SetInvalidRange( size_t nStart, size_t nEnd ); + void ResetInvalidRange( size_t nStart, size_t nEnd ); + + size_t GetInvalidStart() const { return mnInvalidStart; } + size_t GetInvalidEnd() const { return mnInvalidEnd; } + + void TextInserted( size_t nPos, size_t nLength, bool bPosIsSep ); + void TextDeleted( size_t nPos, size_t nLength ); + + void InsertWrong( size_t nStart, size_t nEnd ); + bool NextWrong( size_t& rnStart, size_t& rnEnd ) const; + bool HasWrong( size_t nStart, size_t nEnd ) const; + bool HasAnyWrong( size_t nStart, size_t nEnd ) const; + void ClearWrongs( size_t nStart, size_t nEnd, const ContentNode* pNode ); + void MarkWrongsInvalid(); + + WrongList* Clone() const; + + // #i102062# + bool operator==(const WrongList& rCompare) const; + + bool empty() const; + void push_back(const editeng::MisspellRange& rRange); + editeng::MisspellRange& back(); + const editeng::MisspellRange& back() const; + + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; +}; + +class EdtAutoCorrDoc final : public SvxAutoCorrDoc +{ + EditEngine* mpEditEngine; + ContentNode* pCurNode; + sal_Int32 nCursor; + + bool bAllowUndoAction; + bool bUndoAction; + + void ImplStartUndoAction(); + +public: + EdtAutoCorrDoc(EditEngine* pE, ContentNode* pCurNode, sal_Int32 nCrsr, sal_Unicode cIns); + virtual ~EdtAutoCorrDoc() override; + + virtual bool Delete( sal_Int32 nStt, sal_Int32 nEnd ) override; + virtual bool Insert( sal_Int32 nPos, const OUString& rTxt ) override; + virtual bool Replace( sal_Int32 nPos, const OUString& rTxt ) override; + virtual bool ReplaceRange( sal_Int32 nPos, sal_Int32 nLen, const OUString& rTxt ) override; + + virtual void SetAttr( sal_Int32 nStt, sal_Int32 nEnd, sal_uInt16 nSlotId, SfxPoolItem& ) override; + virtual bool SetINetAttr( sal_Int32 nStt, sal_Int32 nEnd, const OUString& rURL ) override; + + virtual OUString const* GetPrevPara(bool bAtNormalPos) override; + + virtual bool ChgAutoCorrWord( sal_Int32& rSttPos, sal_Int32 nEndPos, + SvxAutoCorrect& rACorrect, OUString* pPara ) override; + virtual bool TransliterateRTLWord( sal_Int32& rSttPos, sal_Int32 nEndPos, + bool bApply = false ) override; + + + virtual LanguageType GetLanguage( sal_Int32 nPos ) const override; + + sal_Int32 GetCursor() const { return nCursor; } + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/eerdll2.hxx b/editeng/inc/eerdll2.hxx new file mode 100644 index 000000000..76653e79c --- /dev/null +++ b/editeng/inc/eerdll2.hxx @@ -0,0 +1,67 @@ +/* -*- 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 <com/sun/star/linguistic2/XLanguageGuessing.hpp> +#include <editeng/forbiddencharacterstable.hxx> +#include <vcl/virdev.hxx> + +class SfxPoolItem; +class VirtualDevice; + +namespace editeng +{ + class SharedVclResources + { + private: + VclPtr<VirtualDevice> m_pVirDev; + public: + SharedVclResources(); + ~SharedVclResources(); + VclPtr<VirtualDevice> const & GetVirtualDevice() const; + }; +} + +class DefItems +{ +public: + DefItems(); + std::vector<SfxPoolItem*> & getDefaults() { return mvDefItems; } + ~DefItems(); +private: + std::vector<SfxPoolItem*> mvDefItems; +}; + +class GlobalEditData +{ +private: + css::uno::Reference< css::linguistic2::XLanguageGuessing > xLanguageGuesser; + std::weak_ptr<DefItems> m_xDefItems; + std::shared_ptr<SvxForbiddenCharactersTable> xForbiddenCharsTable; + +public: + std::shared_ptr<DefItems> GetDefItems(); + + std::shared_ptr<SvxForbiddenCharactersTable> const & GetForbiddenCharsTable(); + void SetForbiddenCharsTable(std::shared_ptr<SvxForbiddenCharactersTable> const & xForbiddenChars ) { xForbiddenCharsTable = xForbiddenChars; } + css::uno::Reference< css::linguistic2::XLanguageGuessing > const & GetLanguageGuesser(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/pch/precompiled_editeng.cxx b/editeng/inc/pch/precompiled_editeng.cxx new file mode 100644 index 000000000..87feb9c49 --- /dev/null +++ b/editeng/inc/pch/precompiled_editeng.cxx @@ -0,0 +1,12 @@ +/* -*- 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/. + */ + +#include "precompiled_editeng.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/pch/precompiled_editeng.hxx b/editeng/inc/pch/precompiled_editeng.hxx new file mode 100644 index 000000000..b37710e16 --- /dev/null +++ b/editeng/inc/pch/precompiled_editeng.hxx @@ -0,0 +1,325 @@ +/* -*- 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 has been autogenerated by update_pch.sh. It is possible to edit it + manually (such as when an include file has been moved/renamed/removed). All such + manual changes will be rewritten by the next run of update_pch.sh (which presumably + also fixes all possible problems, so it's usually better to use it). + + Generated on 2021-07-28 20:17:03 using: + ./bin/update_pch editeng editeng --cutoff=5 --exclude:system --include:module --exclude:local + + If after updating build fails, use the following command to locate conflicting headers: + ./bin/update_pch_bisect ./editeng/inc/pch/precompiled_editeng.hxx "make editeng.build" --find-conflicts +*/ + +#include <sal/config.h> +#if PCH_LEVEL >= 1 +#include <algorithm> +#include <array> +#include <assert.h> +#include <atomic> +#include <cassert> +#include <chrono> +#include <cmath> +#include <cstddef> +#include <cstdlib> +#include <cstring> +#include <deque> +#include <float.h> +#include <functional> +#include <initializer_list> +#include <iomanip> +#include <limits.h> +#include <limits> +#include <map> +#include <math.h> +#include <memory> +#include <new> +#include <numeric> +#include <optional> +#include <ostream> +#include <set> +#include <stddef.h> +#include <string.h> +#include <string> +#include <string_view> +#include <type_traits> +#include <unordered_map> +#include <utility> +#include <vector> +#include <boost/property_tree/json_parser.hpp> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/ptree_fwd.hpp> +#endif // PCH_LEVEL >= 1 +#if PCH_LEVEL >= 2 +#include <osl/diagnose.h> +#include <osl/doublecheckedlocking.h> +#include <osl/endian.h> +#include <osl/file.hxx> +#include <osl/getglobalmutex.hxx> +#include <osl/interlck.h> +#include <osl/mutex.h> +#include <osl/mutex.hxx> +#include <osl/thread.h> +#include <osl/time.h> +#include <rtl/alloc.h> +#include <rtl/character.hxx> +#include <rtl/instance.hxx> +#include <rtl/math.h> +#include <rtl/math.hxx> +#include <rtl/ref.hxx> +#include <rtl/strbuf.h> +#include <rtl/string.h> +#include <rtl/string.hxx> +#include <rtl/stringconcat.hxx> +#include <rtl/stringutils.hxx> +#include <rtl/tencinfo.h> +#include <rtl/textcvt.h> +#include <rtl/textenc.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.h> +#include <rtl/ustring.hxx> +#include <rtl/uuid.h> +#include <sal/backtrace.hxx> +#include <sal/log.hxx> +#include <sal/macros.h> +#include <sal/saldllapi.h> +#include <sal/types.h> +#include <sal/typesizes.h> +#include <vcl/BinaryDataContainer.hxx> +#include <vcl/GraphicExternalLink.hxx> +#include <vcl/Scanline.hxx> +#include <vcl/alpha.hxx> +#include <vcl/animate/Animation.hxx> +#include <vcl/animate/AnimationBitmap.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/bitmap/BitmapTypes.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/checksum.hxx> +#include <vcl/dllapi.h> +#include <vcl/errcode.hxx> +#include <vcl/font.hxx> +#include <vcl/gfxlink.hxx> +#include <vcl/graph.hxx> +#include <vcl/mapmod.hxx> +#include <vcl/metric.hxx> +#include <vcl/outdev.hxx> +#include <vcl/region.hxx> +#include <vcl/scopedbitmapaccess.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/task.hxx> +#include <vcl/timer.hxx> +#include <vcl/vclenum.hxx> +#include <vcl/vclptr.hxx> +#include <vcl/vectorgraphicdata.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> +#endif // PCH_LEVEL >= 2 +#if PCH_LEVEL >= 3 +#include <basegfx/basegfxdllapi.h> +#include <basegfx/color/bcolor.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/point/b2ipoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/range/basicrange.hxx> +#include <basegfx/tuple/Tuple2D.hxx> +#include <basegfx/tuple/Tuple3D.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/tuple/b2ituple.hxx> +#include <basegfx/tuple/b3dtuple.hxx> +#include <basegfx/utils/common.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/vector/b2enums.hxx> +#include <basegfx/vector/b2ivector.hxx> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleContext2.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> +#include <com/sun/star/accessibility/XAccessibleExtendedComponent.hpp> +#include <com/sun/star/awt/Key.hpp> +#include <com/sun/star/awt/KeyGroup.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/TransliterationModules.hpp> +#include <com/sun/star/i18n/TransliterationModulesExtra.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/EventObject.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/style/NumberingType.hpp> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Type.h> +#include <com/sun/star/uno/TypeClass.hdl> +#include <com/sun/star/uno/XAggregation.hpp> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/uno/XWeak.hpp> +#include <com/sun/star/uno/genfunc.h> +#include <com/sun/star/uno/genfunc.hxx> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/util/Time.hpp> +#include <com/sun/star/util/XCloneable.hpp> +#include <com/sun/star/xml/sax/XFastAttributeList.hpp> +#include <com/sun/star/xml/sax/XFastContextHandler.hpp> +#include <com/sun/star/xml/sax/XFastTokenHandler.hpp> +#include <comphelper/accessiblecomponenthelper.hxx> +#include <comphelper/accessiblecontexthelper.hxx> +#include <comphelper/comphelperdllapi.h> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/solarmutex.hxx> +#include <comphelper/string.hxx> +#include <comphelper/uno3.hxx> +#include <cppu/cppudllapi.h> +#include <cppu/unotype.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase2.hxx> +#include <cppuhelper/compbase_ex.hxx> +#include <cppuhelper/cppuhelperdllapi.h> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/implbase1.hxx> +#include <cppuhelper/implbase2.hxx> +#include <cppuhelper/implbase_ex.hxx> +#include <cppuhelper/implbase_ex_post.hxx> +#include <cppuhelper/implbase_ex_pre.hxx> +#include <cppuhelper/interfacecontainer.h> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <cppuhelper/weakagg.hxx> +#include <cppuhelper/weakref.hxx> +#include <i18nlangtag/lang.h> +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/i18nutildllapi.h> +#include <i18nutil/transliteration.hxx> +#include <libxml/xmlwriter.h> +#include <o3tl/cow_wrapper.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/span.hxx> +#include <o3tl/strong_int.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <o3tl/underlyingenumvalue.hxx> +#include <o3tl/unit_conversion.hxx> +#include <salhelper/salhelperdllapi.h> +#include <salhelper/simplereferenceobject.hxx> +#include <sax/fastattribs.hxx> +#include <sax/saxdllapi.h> +#include <sfx2/dllapi.h> +#include <sfx2/shell.hxx> +#include <sot/formats.hxx> +#include <sot/sotdllapi.h> +#include <svl/SfxBroadcaster.hxx> +#include <svl/cintitem.hxx> +#include <svl/eitem.hxx> +#include <svl/hint.hxx> +#include <svl/intitem.hxx> +#include <svl/itempool.hxx> +#include <svl/itemset.hxx> +#include <svl/lstner.hxx> +#include <svl/poolitem.hxx> +#include <svl/svldllapi.h> +#include <svl/typedwhich.hxx> +#include <svtools/svtdllapi.h> +#include <tools/UnitConversion.hxx> +#include <tools/color.hxx> +#include <tools/date.hxx> +#include <tools/datetime.hxx> +#include <tools/debug.hxx> +#include <tools/degree.hxx> +#include <tools/diagnose_ex.h> +#include <tools/fontenum.hxx> +#include <tools/gen.hxx> +#include <tools/link.hxx> +#include <tools/long.hxx> +#include <tools/mapunit.hxx> +#include <tools/ref.hxx> +#include <tools/solar.h> +#include <tools/stream.hxx> +#include <tools/time.hxx> +#include <tools/toolsdllapi.h> +#include <tools/urlobj.hxx> +#include <typelib/typeclass.h> +#include <typelib/typedescription.h> +#include <typelib/uik.h> +#include <uno/any2.h> +#include <uno/data.h> +#include <uno/sequence2.h> +#include <unotools/options.hxx> +#include <unotools/unotoolsdllapi.h> +#include <xmloff/dllapi.h> +#include <xmloff/families.hxx> +#include <xmloff/namespacemap.hxx> +#include <xmloff/xmltoken.hxx> +#endif // PCH_LEVEL >= 3 +#if PCH_LEVEL >= 4 +#include <editeng/adjustitem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editengdllapi.h> +#include <editeng/editstat.hxx> +#include <editeng/editview.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/forbiddencharacterstable.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/itemtype.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/memberids.h> +#include <editeng/numitem.hxx> +#include <editeng/outliner.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/postitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/svxenum.hxx> +#include <editeng/svxfont.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/unoedsrc.hxx> +#include <editeng/unoipset.hxx> +#include <editeng/unolingu.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/unotext.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/wrlmitem.hxx> +#endif // PCH_LEVEL >= 4 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/strings.hrc b/editeng/inc/strings.hrc new file mode 100644 index 000000000..b527e390d --- /dev/null +++ b/editeng/inc/strings.hrc @@ -0,0 +1,44 @@ +/* -*- 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/. + */ + +#pragma once + +#include <unotools/resmgr.hxx> + +#define NC_(Context, String) TranslateId(Context, reinterpret_cast<char const *>(u8##String)) + +const TranslateId RID_SVXITEMS_HORJUST[] = +{ + // enum SvxCellHorJustify ---------------------------------------------------- + NC_("RID_SVXITEMS_HORJUST_STANDARD", "Horizontal alignment default"), + NC_("RID_SVXITEMS_HORJUST_LEFT", "Align left"), + NC_("RID_SVXITEMS_HORJUST_CENTER", "Centered horizontally"), + NC_("RID_SVXITEMS_HORJUST_RIGHT", "Align right"), + NC_("RID_SVXITEMS_HORJUST_BLOCK", "Justify horizontally"), + NC_("RID_SVXITEMS_HORJUST_REPEAT", "Repeat alignment") +}; + +const TranslateId RID_SVXITEMS_VERJUST[] = +{ + // enum SvxCellVerJustify ---------------------------------------------------- + NC_("RID_SVXITEMS_VERJUST_STANDARD", "Vertical alignment default"), + NC_("RID_SVXITEMS_VERJUST_TOP", "Align to top"), + NC_("RID_SVXITEMS_VERJUST_CENTER", "Centered vertically"), + NC_("RID_SVXITEMS_VERJUST_BOTTOM", "Align to bottom"), + NC_("RID_SVXITEMS_HORJUST_BLOCK", "Justify vertically") +}; + +const TranslateId RID_SVXITEMS_JUSTMETHOD[] = +{ + // enum SvxCellJustifyMethod ---------------------------------------------------- + NC_("RID_SVXITEMS_JUSTMETHOD_AUTO", "Automatic Justify"), + NC_("RID_SVXITEMS_JUSTMETHOD_DISTRIBUTE", "Distributed Justify") +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/unomodel.hxx b/editeng/inc/unomodel.hxx new file mode 100644 index 000000000..cfe5b59ee --- /dev/null +++ b/editeng/inc/unomodel.hxx @@ -0,0 +1,68 @@ +/* -*- 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 <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/ucb/XAnyCompareFactory.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <cppuhelper/implbase4.hxx> + +class SvxSimpleUnoModel final : public cppu::WeakAggImplHelper4< + css::frame::XModel, + css::ucb::XAnyCompareFactory, + css::style::XStyleFamiliesSupplier, + css::lang::XMultiServiceFactory > +{ +public: + SvxSimpleUnoModel(); + + // XMultiServiceFactory + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstance( const OUString& aServiceSpecifier ) override; + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstanceWithArguments( const OUString& ServiceSpecifier, const css::uno::Sequence< css::uno::Any >& Arguments ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getAvailableServiceNames( ) override; + + // XStyleFamiliesSupplier + virtual css::uno::Reference< css::container::XNameAccess > SAL_CALL getStyleFamilies( ) override; + + // XAnyCompareFactory + virtual css::uno::Reference< css::ucb::XAnyCompare > SAL_CALL createAnyCompareByName( const OUString& PropertyName ) override; + + // XModel + virtual sal_Bool SAL_CALL attachResource( const OUString& aURL, const css::uno::Sequence< css::beans::PropertyValue >& aArgs ) override; + virtual OUString SAL_CALL getURL( ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getArgs( ) override; + virtual void SAL_CALL connectController( const css::uno::Reference< css::frame::XController >& xController ) override; + virtual void SAL_CALL disconnectController( const css::uno::Reference< css::frame::XController >& xController ) override; + virtual void SAL_CALL lockControllers( ) override; + virtual void SAL_CALL unlockControllers( ) override; + virtual sal_Bool SAL_CALL hasControllersLocked( ) override; + virtual css::uno::Reference< css::frame::XController > SAL_CALL getCurrentController( ) override; + virtual void SAL_CALL setCurrentController( const css::uno::Reference< css::frame::XController >& xController ) override; + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL getCurrentSelection( ) override; + + // XComponent + virtual void SAL_CALL dispose( ) override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/inc/unopracc.hxx b/editeng/inc/unopracc.hxx new file mode 100644 index 000000000..03fd268e7 --- /dev/null +++ b/editeng/inc/unopracc.hxx @@ -0,0 +1,56 @@ +/* -*- 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 <editeng/unotext.hxx> + +class SvxEditSource; + +/** Wraps SvxUnoTextRangeBase and provides us with the text properties + + Inherits from SvxUnoTextRangeBase and provides XPropertySet and + XMultiPropertySet interfaces. Just set the selection to the + required text range and return a reference to a XPropertySet. + */ +class SvxAccessibleTextPropertySet final : public SvxUnoTextRangeBase, + public css::lang::XTypeProvider, + public ::cppu::OWeakObject +{ +public: + SvxAccessibleTextPropertySet(const SvxEditSource*, const SvxItemPropertySet*); + virtual ~SvxAccessibleTextPropertySet() noexcept override; + + // XTextRange + virtual css::uno::Reference<css::text::XText> SAL_CALL getText() override; + + virtual css::uno::Any SAL_CALL queryInterface(const css::uno::Type& rType) override; + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + + // lang::XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString&) override; + + // lang::XTypeProvider + virtual css::uno::Sequence<css::uno::Type> SAL_CALL getTypes() override; + virtual css::uno::Sequence<sal_Int8> SAL_CALL getImplementationId() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/qa/items/borderline_test.cxx b/editeng/qa/items/borderline_test.cxx new file mode 100644 index 000000000..0499c7087 --- /dev/null +++ b/editeng/qa/items/borderline_test.cxx @@ -0,0 +1,164 @@ +/* -*- 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/. + */ + +#include <sal/types.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +#include <editeng/borderline.hxx> + +using namespace ::com::sun::star::table::BorderLineStyle; + +#define TEST_WIDTH tools::Long( 40 ) + +#define THINTHICKSG_IN_WIDTH tools::Long( 15 ) +#define THINTHICKSG_OUT_WIDTH tools::Long( 40 ) +#define THINTHICKSG_DIST_WIDTH tools::Long( 15 ) + +#define THINTHICKLG_IN_WIDTH tools::Long( 15 ) +#define THINTHICKLG_OUT_WIDTH tools::Long( 30 ) +#define THINTHICKLG_DIST_WIDTH tools::Long( 40 ) + +using namespace editeng; + +CPPUNIT_NS_BEGIN + +template<> struct assertion_traits<SvxBorderLineStyle> +{ + static bool equal( SvxBorderLineStyle x, SvxBorderLineStyle y ) + { + return x == y; + } + + static std::string toString( SvxBorderLineStyle x ) + { + OStringStream ost; + ost << static_cast<unsigned int>(x); + return ost.str(); + } +}; + +CPPUNIT_NS_END + +namespace { + +class BorderLineTest : public CppUnit::TestFixture +{ + public: + void testGuessWidthDouble(); + void testGuessWidthNoMatch(); + void testGuessWidthThinthickSmallgap(); + void testGuessWidthThinthickLargegap(); + void testGuessWidthNostyleDouble(); + void testGuessWidthNostyleSingle(); + + CPPUNIT_TEST_SUITE(BorderLineTest); + CPPUNIT_TEST(testGuessWidthDouble); + CPPUNIT_TEST(testGuessWidthNoMatch); + CPPUNIT_TEST(testGuessWidthThinthickSmallgap); + CPPUNIT_TEST(testGuessWidthThinthickLargegap); + CPPUNIT_TEST(testGuessWidthNostyleDouble); + CPPUNIT_TEST(testGuessWidthNostyleSingle); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(BorderLineTest); + +void BorderLineTest::testGuessWidthDouble() +{ + // Normal double case + SvxBorderLine line; + line.GuessLinesWidths( SvxBorderLineStyle::DOUBLE, TEST_WIDTH, TEST_WIDTH, TEST_WIDTH ); + CPPUNIT_ASSERT_EQUAL( SvxBorderLineStyle::DOUBLE, line.GetBorderLineStyle() ); + CPPUNIT_ASSERT_EQUAL( TEST_WIDTH, static_cast<tools::Long>(line.GetOutWidth()) ); + CPPUNIT_ASSERT_EQUAL( TEST_WIDTH, static_cast<tools::Long>(line.GetInWidth()) ); + CPPUNIT_ASSERT_EQUAL( TEST_WIDTH, static_cast<tools::Long>(line.GetDistance()) ); + CPPUNIT_ASSERT_EQUAL( 3*TEST_WIDTH, line.GetWidth() ); +} + +void BorderLineTest::testGuessWidthNoMatch() +{ + SvxBorderLine line; + line.GuessLinesWidths( SvxBorderLineStyle::DOUBLE, + TEST_WIDTH + 1, TEST_WIDTH + 2, TEST_WIDTH + 3 ); + CPPUNIT_ASSERT_EQUAL( SvxBorderLineStyle::DOUBLE, line.GetBorderLineStyle() ); + CPPUNIT_ASSERT_EQUAL( TEST_WIDTH+1, static_cast<tools::Long>(line.GetOutWidth()) ); + CPPUNIT_ASSERT_EQUAL( TEST_WIDTH+2, static_cast<tools::Long>(line.GetInWidth()) ); + CPPUNIT_ASSERT_EQUAL( TEST_WIDTH+3, static_cast<tools::Long>(line.GetDistance())); + CPPUNIT_ASSERT_EQUAL( tools::Long( (3 * TEST_WIDTH) + 6 ), line.GetWidth() ); +} + +void BorderLineTest::testGuessWidthThinthickSmallgap() +{ + SvxBorderLine line; + line.GuessLinesWidths( SvxBorderLineStyle::DOUBLE, + THINTHICKSG_OUT_WIDTH, + THINTHICKSG_IN_WIDTH, + THINTHICKSG_DIST_WIDTH ); + CPPUNIT_ASSERT_EQUAL( SvxBorderLineStyle::THINTHICK_SMALLGAP, line.GetBorderLineStyle() ); + CPPUNIT_ASSERT_EQUAL( THINTHICKSG_OUT_WIDTH, + static_cast<tools::Long>(line.GetOutWidth()) ); + CPPUNIT_ASSERT_EQUAL( THINTHICKSG_IN_WIDTH, + static_cast<tools::Long>(line.GetInWidth()) ); + CPPUNIT_ASSERT_EQUAL( THINTHICKSG_DIST_WIDTH, + static_cast<tools::Long>(line.GetDistance()) ); + CPPUNIT_ASSERT_EQUAL( THINTHICKSG_OUT_WIDTH + THINTHICKSG_IN_WIDTH + + THINTHICKSG_DIST_WIDTH, line.GetWidth() ); +} + +void BorderLineTest::testGuessWidthThinthickLargegap() +{ + SvxBorderLine line; + line.GuessLinesWidths( SvxBorderLineStyle::DOUBLE, + THINTHICKLG_OUT_WIDTH, + THINTHICKLG_IN_WIDTH, + THINTHICKLG_DIST_WIDTH ); + CPPUNIT_ASSERT_EQUAL( SvxBorderLineStyle::THINTHICK_LARGEGAP, line.GetBorderLineStyle() ); + CPPUNIT_ASSERT_EQUAL( THINTHICKLG_OUT_WIDTH, + static_cast<tools::Long>(line.GetOutWidth()) ); + CPPUNIT_ASSERT_EQUAL( THINTHICKLG_IN_WIDTH, + static_cast<tools::Long>(line.GetInWidth()) ); + CPPUNIT_ASSERT_EQUAL( THINTHICKLG_DIST_WIDTH, + static_cast<tools::Long>(line.GetDistance()) ); + CPPUNIT_ASSERT_EQUAL( THINTHICKLG_OUT_WIDTH + THINTHICKLG_IN_WIDTH + + THINTHICKLG_DIST_WIDTH, line.GetWidth() ); +} + +void BorderLineTest::testGuessWidthNostyleDouble() +{ + SvxBorderLine line; + line.GuessLinesWidths( SvxBorderLineStyle::NONE, + THINTHICKLG_OUT_WIDTH, + THINTHICKLG_IN_WIDTH, + THINTHICKLG_DIST_WIDTH ); + CPPUNIT_ASSERT_EQUAL( SvxBorderLineStyle::THINTHICK_LARGEGAP, line.GetBorderLineStyle() ); + CPPUNIT_ASSERT_EQUAL( THINTHICKLG_OUT_WIDTH, + static_cast<tools::Long>(line.GetOutWidth()) ); + CPPUNIT_ASSERT_EQUAL( THINTHICKLG_IN_WIDTH, + static_cast<tools::Long>(line.GetInWidth()) ); + CPPUNIT_ASSERT_EQUAL( THINTHICKLG_DIST_WIDTH, + static_cast<tools::Long>(line.GetDistance()) ); + CPPUNIT_ASSERT_EQUAL( THINTHICKLG_OUT_WIDTH + THINTHICKLG_IN_WIDTH + + THINTHICKLG_DIST_WIDTH, line.GetWidth() ); +} + +void BorderLineTest::testGuessWidthNostyleSingle() +{ + SvxBorderLine line; + line.GuessLinesWidths( SvxBorderLineStyle::NONE, TEST_WIDTH ); + CPPUNIT_ASSERT_EQUAL( SvxBorderLineStyle::SOLID, line.GetBorderLineStyle() ); + CPPUNIT_ASSERT_EQUAL( TEST_WIDTH, line.GetWidth() ); +} + +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/qa/lookuptree/lookuptree_test.cxx b/editeng/qa/lookuptree/lookuptree_test.cxx new file mode 100644 index 000000000..7d5e9647b --- /dev/null +++ b/editeng/qa/lookuptree/lookuptree_test.cxx @@ -0,0 +1,146 @@ +/* -*- 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/types.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <editeng/Trie.hxx> + +namespace { + +class LookupTreeTest : public CppUnit::TestFixture +{ +public: + void testTrie(); + void testTrieGetAllEntries(); + + CPPUNIT_TEST_SUITE(LookupTreeTest); + CPPUNIT_TEST(testTrie); + CPPUNIT_TEST(testTrieGetAllEntries); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(LookupTreeTest); + +void LookupTreeTest::testTrie() +{ + editeng::Trie trie; + std::vector<OUString> suggestions; + + trie.findSuggestions( OUString(), suggestions); + CPPUNIT_ASSERT_EQUAL( size_t(0), suggestions.size() ); + + trie.insert( OUString() ); + trie.findSuggestions( OUString(), suggestions); + CPPUNIT_ASSERT_EQUAL( size_t(0), suggestions.size() ); + + trie.findSuggestions( "a", suggestions); + CPPUNIT_ASSERT_EQUAL( size_t(0), suggestions.size() ); + + trie.insert( "abc" ); + trie.insert( "abcdefghijklmnopqrstuvwxyz" ); + trie.findSuggestions( "a", suggestions); + CPPUNIT_ASSERT_EQUAL( size_t(2), suggestions.size() ); + CPPUNIT_ASSERT_EQUAL( OUString("abc"), suggestions[0] ); + CPPUNIT_ASSERT_EQUAL( OUString("abcdefghijklmnopqrstuvwxyz"), suggestions[1] ); + suggestions.clear(); + + trie.findSuggestions( "abc", suggestions); + CPPUNIT_ASSERT_EQUAL( size_t(1), suggestions.size() ); + CPPUNIT_ASSERT_EQUAL( OUString("abcdefghijklmnopqrstuvwxyz"), suggestions[0] ); + suggestions.clear(); + + trie.findSuggestions( "abe", suggestions); + CPPUNIT_ASSERT_EQUAL( size_t(0), suggestions.size() ); + suggestions.clear(); + + trie.insert( "abe" ); + trie.findSuggestions( "", suggestions); + CPPUNIT_ASSERT_EQUAL( size_t(3), suggestions.size() ); + CPPUNIT_ASSERT_EQUAL( OUString("abc"), suggestions[0] ); + CPPUNIT_ASSERT_EQUAL( OUString("abcdefghijklmnopqrstuvwxyz"), suggestions[1] ); + CPPUNIT_ASSERT_EQUAL( OUString("abe"), suggestions[2] ); + suggestions.clear(); + + trie.insert( "H31l0" ); + trie.findSuggestions( "H", suggestions); + + CPPUNIT_ASSERT_EQUAL( size_t(1), suggestions.size() ); + CPPUNIT_ASSERT_EQUAL( OUString("H31l0"), suggestions[0] ); + suggestions.clear(); + + trie.insert( "H1" ); + trie.findSuggestions( "H", suggestions); + CPPUNIT_ASSERT_EQUAL( size_t(2), suggestions.size() ); + CPPUNIT_ASSERT_EQUAL( OUString("H31l0"), suggestions[0] ); + CPPUNIT_ASSERT_EQUAL( OUString("H1"), suggestions[1] ); + suggestions.clear(); + + trie.findSuggestions( "H3", suggestions); + CPPUNIT_ASSERT_EQUAL( size_t(1), suggestions.size() ); + CPPUNIT_ASSERT_EQUAL( OUString("H31l0"), suggestions[0] ); + suggestions.clear(); + + trie.insert( OStringToOUString( "H\xC3\xA4llo", RTL_TEXTENCODING_UTF8 ) ); + trie.findSuggestions( "H", suggestions ); + CPPUNIT_ASSERT_EQUAL( size_t(3), suggestions.size() ); + CPPUNIT_ASSERT_EQUAL( OUString("H31l0"), suggestions[0] ); + CPPUNIT_ASSERT_EQUAL( OUString("H1"), suggestions[1] ); + CPPUNIT_ASSERT_EQUAL( OStringToOUString( "H\xC3\xA4llo", RTL_TEXTENCODING_UTF8 ), suggestions[2] ); + suggestions.clear(); + + trie.findSuggestions( "H3", suggestions ); + CPPUNIT_ASSERT_EQUAL( size_t(1), suggestions.size() ); + CPPUNIT_ASSERT_EQUAL( OUString("H31l0"), suggestions[0] ); + suggestions.clear(); + + trie.findSuggestions( OStringToOUString("H\xC3\xA4", RTL_TEXTENCODING_UTF8), suggestions ); + CPPUNIT_ASSERT_EQUAL( size_t(1), suggestions.size() ); + CPPUNIT_ASSERT_EQUAL( OStringToOUString("H\xC3\xA4llo", RTL_TEXTENCODING_UTF8), suggestions[0] ); + suggestions.clear(); + + trie.findSuggestions( "", suggestions); + CPPUNIT_ASSERT_EQUAL( size_t(6), suggestions.size() ); + suggestions.clear(); +} + +void LookupTreeTest::testTrieGetAllEntries() +{ + editeng::Trie trie; + + CPPUNIT_ASSERT_EQUAL( size_t(0), trie.size() ); + + trie.insert("A"); + CPPUNIT_ASSERT_EQUAL( size_t(1), trie.size() ); + + trie.insert("B"); + trie.insert("C"); + CPPUNIT_ASSERT_EQUAL( size_t(3), trie.size() ); + + trie.insert("AA"); + trie.insert("AAA"); + CPPUNIT_ASSERT_EQUAL( size_t(5), trie.size() ); +} + +} // namespace end + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/qa/unit/core-test.cxx b/editeng/qa/unit/core-test.cxx new file mode 100644 index 000000000..2836c6ba8 --- /dev/null +++ b/editeng/qa/unit/core-test.cxx @@ -0,0 +1,1984 @@ +/* -*- 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/. + */ + +#include <config_fonts.h> + +#include <test/bootstrapfixture.hxx> + +#include <cppunit/extensions/HelperMacros.h> + +#include <editdoc.hxx> + +#include <sfx2/app.hxx> +#include <svl/itempool.hxx> +#include <editeng/editeng.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/svxacorr.hxx> +#include <editeng/unofield.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/section.hxx> +#include <editeng/editobj.hxx> +#include <editeng/flditem.hxx> +#include <editeng/udlnitem.hxx> +#include <svl/srchitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> + +#include <com/sun/star/text/textfield/Type.hpp> + +#include <memory> +#include <editeng/outliner.hxx> + +using namespace com::sun::star; + +namespace { + +class Test : public test::BootstrapFixture +{ +public: + Test(); + + virtual void setUp() override; + virtual void tearDown() override; + +#if HAVE_MORE_FONTS + /// Test text portions position when percentage line spacing is set + void testLineSpacing(); +#endif + + void testConstruction(); + + /// Test UNO service class that implements text field items. + void testUnoTextFields(); + + /// AutoCorrect tests + void testAutocorrect(); + + /// Test Copy/Paste with hyperlinks in text using Legacy Format + void testHyperlinkCopyPaste(); + + /// Test Copy/Paste using Legacy Format + void testCopyPaste(); + + /// Test Copy/Paste with selective selection over multiple paragraphs + void testMultiParaSelCopyPaste(); + + /// Test Copy/Paste with Tabs + void testTabsCopyPaste(); + + /// Test hyperlinks + void testHyperlinkSearch(); + + /// Test Copy/Paste with Bold/Italic text using Legacy Format + void testBoldItalicCopyPaste(); + + /// Test Copy/Paste with Underline text using Legacy Format + void testUnderlineCopyPaste(); + + /// Test Copy/Paste with multiple paragraphs + void testMultiParaCopyPaste(); + + /// Test Copy/Paste with multiple paragraphs having Bold/Italic text + void testParaBoldItalicCopyPaste(); + + void testParaStartCopyPaste(); + + void testSectionAttributes(); + + void testLargeParaCopyPaste(); + + void testTransliterate(); + + void testTdf147196(); + + void testTdf148148(); + + DECL_STATIC_LINK( Test, CalcFieldValueHdl, EditFieldInfo*, void ); + + CPPUNIT_TEST_SUITE(Test); +#if HAVE_MORE_FONTS + CPPUNIT_TEST(testLineSpacing); +#endif + CPPUNIT_TEST(testConstruction); + CPPUNIT_TEST(testUnoTextFields); + CPPUNIT_TEST(testAutocorrect); + CPPUNIT_TEST(testHyperlinkCopyPaste); + CPPUNIT_TEST(testCopyPaste); + CPPUNIT_TEST(testMultiParaSelCopyPaste); + CPPUNIT_TEST(testTabsCopyPaste); + CPPUNIT_TEST(testHyperlinkSearch); + CPPUNIT_TEST(testBoldItalicCopyPaste); + CPPUNIT_TEST(testUnderlineCopyPaste); + CPPUNIT_TEST(testMultiParaCopyPaste); + CPPUNIT_TEST(testParaBoldItalicCopyPaste); + CPPUNIT_TEST(testParaStartCopyPaste); + CPPUNIT_TEST(testSectionAttributes); + CPPUNIT_TEST(testLargeParaCopyPaste); + CPPUNIT_TEST(testTransliterate); + CPPUNIT_TEST(testTdf147196); + CPPUNIT_TEST(testTdf148148); + CPPUNIT_TEST_SUITE_END(); + +private: + rtl::Reference<EditEngineItemPool> mpItemPool; +}; + +Test::Test() {} + +void Test::setUp() +{ + test::BootstrapFixture::setUp(); + + mpItemPool = new EditEngineItemPool(); + + SfxApplication::GetOrCreate(); +} + +void Test::tearDown() +{ + mpItemPool.clear(); + test::BootstrapFixture::tearDown(); +} + +#if HAVE_MORE_FONTS +void Test::testLineSpacing() +{ + // Create EditEngine's instance + EditEngine aEditEngine(mpItemPool.get()); + + if(aEditEngine.GetRefDevice()->GetDPIY() != 96 + || aEditEngine.GetRefDevice()->GetDPIScaleFactor() != 1.0) + return; + + // Get EditDoc for current EditEngine's instance + EditDoc &rDoc = aEditEngine.GetEditDoc(); + + // Initially no text should be there + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), rDoc.GetTextLen()); + CPPUNIT_ASSERT_EQUAL(OUString(), rDoc.GetParaAsString(sal_Int32(0))); + + // Set initial text + OUString aText = "This is multi-line paragraph"; + + sal_Int32 aTextLen = aText.getLength(); + aEditEngine.SetText(aText); + + // Assert changes - text insertion + CPPUNIT_ASSERT_EQUAL(aTextLen, rDoc.GetTextLen()); + CPPUNIT_ASSERT_EQUAL(aText, rDoc.GetParaAsString(sal_Int32(0))); + + // Select all paragraphs + ESelection aSelection(0, 0, 0, aTextLen); + + auto doTest = [&](sal_uInt16 nSpace, sal_uInt16 nExpMaxAscent, sal_uInt32 nExpLineHeight) + { + std::unique_ptr<SfxItemSet> pSet(new SfxItemSet(aEditEngine.GetEmptyItemSet())); + SvxLineSpacingItem aLineSpacing(LINE_SPACE_DEFAULT_HEIGHT, EE_PARA_SBL); + aLineSpacing.SetPropLineSpace(nSpace); + pSet->Put(aLineSpacing); + + // Set font + SvxFontItem aFont(EE_CHAR_FONTINFO); + aFont.SetFamilyName("Liberation Sans"); + pSet->Put(aFont); + SvxFontHeightItem aFontSize(240, 100, EE_CHAR_FONTHEIGHT); + pSet->Put(aFontSize); + + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(3), pSet->Count()); + + aEditEngine.QuickSetAttribs(*pSet, aSelection); + + // Assert changes + ParaPortion* pParaPortion = aEditEngine.GetParaPortions()[0]; + ContentNode* const pNode = pParaPortion->GetNode(); + const SvxLineSpacingItem& rLSItem = pNode->GetContentAttribs().GetItem(EE_PARA_SBL); + CPPUNIT_ASSERT_EQUAL(SvxInterLineSpaceRule::Prop, rLSItem.GetInterLineSpaceRule()); + CPPUNIT_ASSERT_EQUAL(nSpace, rLSItem.GetPropLineSpace()); + + // Check the first line + ParagraphInfos aInfo = aEditEngine.GetParagraphInfos(0); + CPPUNIT_ASSERT_EQUAL(nExpMaxAscent, aInfo.nFirstLineMaxAscent); + CPPUNIT_ASSERT_EQUAL(nExpLineHeight, aEditEngine.GetLineHeight(0)); + }; + + // Test first case - 60% + doTest(60, 122, 153); + + // Force multiple lines + aEditEngine.SetPaperSize(Size(1000, 6000)); + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), aEditEngine.GetLineCount(0)); + + // Test second case - 150% + doTest(150, 337, 382); + + // Test lower Word limit - 6% (factor 0.06) + doTest(6, 12, 15); + + // Test upper Word limit - 13200% (factor 132) + doTest(13200, 33615, 33660); +} +#endif + +void Test::testConstruction() +{ + EditEngine aEngine(mpItemPool.get()); + + aEngine.SetText("I am Edit Engine."); +} + +bool includes(const uno::Sequence<OUString>& rSeq, std::u16string_view rVal) +{ + for (OUString const & s : rSeq) + if (s == rVal) + return true; + + return false; +} + +void Test::testUnoTextFields() +{ + { + // DATE + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::DATE)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.text.textfield.DateTime"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // URL + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::URL)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.text.textfield.URL"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // PAGE + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::PAGE)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.text.textfield.PageNumber"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // PAGES + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::PAGES)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.text.textfield.PageCount"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // TIME + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::TIME)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.text.textfield.DateTime"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // FILE + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::DOCINFO_TITLE)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.text.textfield.docinfo.Title"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // TABLE + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::TABLE)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.text.textfield.SheetName"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // EXTENDED TIME + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::EXTENDED_TIME)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.text.textfield.DateTime"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // EXTENDED FILE + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::EXTENDED_FILE)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.text.textfield.FileName"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // AUTHOR + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::AUTHOR)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.text.textfield.Author"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // MEASURE + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::MEASURE)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.text.textfield.Measure"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // PRESENTATION HEADER + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::PRESENTATION_HEADER)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.presentation.textfield.Header"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // PRESENTATION FOOTER + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::PRESENTATION_FOOTER)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.presentation.textfield.Footer"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } + + { + // PRESENTATION DATE TIME + rtl::Reference<SvxUnoTextField> xField(new SvxUnoTextField(text::textfield::Type::PRESENTATION_DATE_TIME)); + uno::Sequence<OUString> aSvcs = xField->getSupportedServiceNames(); + bool bGood = includes(aSvcs, u"com.sun.star.presentation.textfield.DateTime"); + CPPUNIT_ASSERT_MESSAGE("expected service is not present.", bGood); + } +} + +class TestAutoCorrDoc : public SvxAutoCorrDoc +{ +public: + /// just like the real thing, this dummy modifies the rText parameter :( + TestAutoCorrDoc(OUString &rText, LanguageType eLang) + : m_rText(rText) + , m_eLang(eLang) + { + } + OUString const& getResult() const + { + return m_rText; + } +private: + OUString & m_rText; + LanguageType m_eLang; + virtual bool Delete( sal_Int32 nStt, sal_Int32 nEnd ) override + { + //fprintf(stderr, "TestAutoCorrDoc::Delete\n"); + m_rText = m_rText.replaceAt(nStt, nEnd-nStt, u""); + return true; + } + virtual bool Insert( sal_Int32 nPos, const OUString& rTxt ) override + { + //fprintf(stderr, "TestAutoCorrDoc::Insert\n"); + m_rText = m_rText.replaceAt(nPos, 0, rTxt); + return true; + } + virtual bool Replace( sal_Int32 nPos, const OUString& rTxt ) override + { + //fprintf(stderr, "TestAutoCorrDoc::Replace\n"); + return ReplaceRange( nPos, rTxt.getLength(), rTxt ); + } + virtual bool ReplaceRange( sal_Int32 nPos, sal_Int32 nLen, const OUString& rTxt ) override + { + //fprintf(stderr, "TestAutoCorrDoc::ReplaceRange %d %d %s\n", nPos, nLen, OUStringToOString(rTxt, RTL_TEXTENCODING_UTF8).getStr()); + m_rText = m_rText.replaceAt(nPos, nLen, rTxt); + return true; + } + virtual void SetAttr( sal_Int32, sal_Int32, sal_uInt16, SfxPoolItem& ) override + { + //fprintf(stderr, "TestAutoCorrDoc::SetAttr\n"); + } + virtual bool SetINetAttr( sal_Int32, sal_Int32, const OUString& ) override + { + //fprintf(stderr, "TestAutoCorrDoc::SetINetAttr\n"); + return true; + } + virtual OUString const* GetPrevPara(bool) override + { + //fprintf(stderr, "TestAutoCorrDoc::GetPrevPara\n"); + return nullptr; + } + virtual bool ChgAutoCorrWord( sal_Int32& rSttPos, + sal_Int32 nEndPos, SvxAutoCorrect& rACorrect, + OUString* pPara ) override + { + //fprintf(stderr, "TestAutoCorrDoc::ChgAutoCorrWord\n"); + + if (m_rText.isEmpty()) + return false; + + LanguageTag aLanguageTag( m_eLang); + const SvxAutocorrWord* pFnd = rACorrect.SearchWordsInList( + m_rText, rSttPos, nEndPos, *this, aLanguageTag); + if (pFnd && pFnd->IsTextOnly()) + { + m_rText = m_rText.replaceAt(rSttPos, nEndPos, pFnd->GetLong()); + if( pPara ) + pPara->clear(); // =&pCurNode->GetString(); + return true; + } + + return false; + } + virtual bool TransliterateRTLWord( sal_Int32& /*rSttPos*/, + sal_Int32 /*nEndPos*/, bool /*bApply*/ ) override + { + return false; + } +}; + +//https://bugs.libreoffice.org/show_bug.cgi?id=55693 +//Two capitalized letters are not corrected if dash or slash are directly +//before the two letters +void Test::testAutocorrect() +{ + SvxAutoCorrect aAutoCorrect((OUString()), (OUString())); + + { + OUString sInput("TEst-TEst"); + sal_Unicode const cNextChar(' '); + bool bNbspRunNext = false; + + TestAutoCorrDoc aFoo(sInput, LANGUAGE_ENGLISH_US); + aAutoCorrect.DoAutoCorrect(aFoo, sInput, sInput.getLength(), cNextChar, true, bNbspRunNext); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("autocorrect", OUString("Test-Test "), aFoo.getResult()); + } + + { + OUString sInput("TEst/TEst"); + sal_Unicode const cNextChar(' '); + bool bNbspRunNext = false; + + TestAutoCorrDoc aFoo(sInput, LANGUAGE_ENGLISH_US); + aAutoCorrect.DoAutoCorrect(aFoo, sInput, sInput.getLength(), cNextChar, true, bNbspRunNext); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("autocorrect", OUString("Test/Test "), aFoo.getResult()); + } + + { + // test auto-bolding with '*' + OUString sInput("*foo"); + sal_Unicode const cNextChar('*'); + bool bNbspRunNext = false; + + TestAutoCorrDoc aFoo(sInput, LANGUAGE_ENGLISH_US); + aAutoCorrect.DoAutoCorrect(aFoo, sInput, sInput.getLength(), cNextChar, true, bNbspRunNext); + + CPPUNIT_ASSERT_EQUAL(OUString("foo"), aFoo.getResult()); + } + + { + OUString sInput("Test. test"); + sal_Unicode const cNextChar(' '); + bool bNbspRunNext = false; + + TestAutoCorrDoc aFoo(sInput, LANGUAGE_ENGLISH_US); + aAutoCorrect.DoAutoCorrect(aFoo, sInput, sInput.getLength(), cNextChar, true, bNbspRunNext); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("autocorrect", OUString("Test. Test "), aFoo.getResult()); + } + + // don't autocapitalize after a field mark + { + OUString sInput("Test. \x01 test"); + sal_Unicode const cNextChar(' '); + bool bNbspRunNext = false; + + TestAutoCorrDoc aFoo(sInput, LANGUAGE_ENGLISH_US); + aAutoCorrect.DoAutoCorrect(aFoo, sInput, sInput.getLength(), cNextChar, true, bNbspRunNext); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("autocorrect", OUString("Test. \x01 test "), aFoo.getResult()); + } + + // consider field contents as text for auto quotes + { + OUString sInput("T\x01"); + sal_Unicode const cNextChar('"'); + static constexpr OUStringLiteral sExpected = u"T\x01\u201d"; + bool bNbspRunNext = false; + + TestAutoCorrDoc aFoo(sInput, LANGUAGE_ENGLISH_US); + aAutoCorrect.SetAutoCorrFlag(ACFlags::ChgQuotes, true); + aAutoCorrect.DoAutoCorrect(aFoo, sInput, sInput.getLength(), cNextChar, true, bNbspRunNext); + fprintf(stderr, "text is %x\n", aFoo.getResult()[aFoo.getResult().getLength() - 1]); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("autocorrect", OUString(sExpected), aFoo.getResult()); + } + +} + +IMPL_STATIC_LINK( Test, CalcFieldValueHdl, EditFieldInfo*, pInfo, void ) +{ + if (!pInfo) + return; + + const SvxFieldItem& rField = pInfo->GetField(); + const SvxFieldData* pField = rField.GetField(); + if (const SvxURLField* pURLField = dynamic_cast<const SvxURLField*>(pField)) + { + // URLField + OUString aURL = pURLField->GetURL(); + switch ( pURLField->GetFormat() ) + { + case SvxURLFormat::AppDefault: + case SvxURLFormat::Repr: + { + pInfo->SetRepresentation( pURLField->GetRepresentation() ); + } + break; + + case SvxURLFormat::Url: + { + pInfo->SetRepresentation( aURL ); + } + break; + } + } + else + { + OSL_FAIL("Unknown Field"); + pInfo->SetRepresentation(OUString('?')); + } +} + +void Test::testHyperlinkCopyPaste() +{ + // Create Outliner instance + Outliner aOutliner(mpItemPool.get(), OutlinerMode +::TextObject); + aOutliner.SetCalcFieldValueHdl( LINK( nullptr, Test, CalcFieldValueHdl ) ); + + // Create EditEngine's instance + EditEngine& aEditEngine = const_cast<EditEngine&> (aOutliner.GetEditEngine()); + + // Get EditDoc for current EditEngine's instance + EditDoc &rDoc = aEditEngine.GetEditDoc(); + + // New instance must be empty - no initial text + CPPUNIT_ASSERT_EQUAL( sal_Int32(0), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Get corresponding Field Item for inserting URLs in text + // URL 1 + OUString aURL1 = "mailto:///user@example.com"; + OUString aRepres1 = "user@example.com"; + SvxURLField aURLField1( aURL1, aRepres1, SvxURLFormat::Repr ); + SvxFieldItem aField1( aURLField1, EE_FEATURE_FIELD ); + // URL 2 + OUString aURL2 = "mailto:///example@domain.com"; + OUString aRepres2 = "example@domain.com"; + SvxURLField aURLField2( aURL2, aRepres2, SvxURLFormat::Repr ); + SvxFieldItem aField2( aURLField2, EE_FEATURE_FIELD ); + + // Insert initial text + OUString aParaText = "sampletextfortestingfeaturefields"; + // Positions Ref .............*13....*20.......... + sal_Int32 aTextLen = aParaText.getLength(); + aEditEngine.SetText( aParaText ); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aParaText, rDoc.GetParaAsString(sal_Int32(0)) ); + + // Insert URL 1 + ContentNode *pNode = rDoc.GetObject(0); + EditSelection aSel1( EditPaM(pNode, 13), EditPaM(pNode, 13) ); + aEditEngine.InsertField( aSel1, aField1 ); + + // Assert Field Count + CPPUNIT_ASSERT_EQUAL( sal_uInt16(1), aEditEngine.GetFieldCount(0) ); + + // Insert URL 2 + EditSelection aSel2( EditPaM(pNode, 20 + 1), EditPaM(pNode, 20 + 1) ); + aEditEngine.InsertField( aSel2, aField2 ); + + // Assert Field Count + CPPUNIT_ASSERT_EQUAL( sal_uInt16(2), aEditEngine.GetFieldCount(0) ); + + // Assert URL Fields and text before copy + // Check text + CPPUNIT_ASSERT_EQUAL( aTextLen + aRepres1.getLength() + aRepres2.getLength(), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString("sampletextforuser@example.comtestingexample@domain.comfeaturefields"), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Check Field 1 + EFieldInfo aURLFieldInfo1 = aEditEngine.GetFieldInfo( sal_Int32(0), sal_uInt16(0) ); + CPPUNIT_ASSERT_EQUAL( sal_Int32(13), aURLFieldInfo1.aPosition.nIndex ); + CPPUNIT_ASSERT_EQUAL( sal_uInt16(EE_FEATURE_FIELD), aURLFieldInfo1.pFieldItem->Which() ); + SvxURLField* pURLField1 = dynamic_cast<SvxURLField*> ( const_cast<SvxFieldData*> (aURLFieldInfo1.pFieldItem->GetField()) ); + CPPUNIT_ASSERT(pURLField1); + CPPUNIT_ASSERT_EQUAL( aURL1, pURLField1->GetURL() ); + CPPUNIT_ASSERT_EQUAL( aRepres1, pURLField1->GetRepresentation() ); + + // Check Field 2 + EFieldInfo aURLFieldInfo2 = aEditEngine.GetFieldInfo( sal_Int32(0), sal_uInt16(1) ); + CPPUNIT_ASSERT_EQUAL( sal_Int32(21), aURLFieldInfo2.aPosition.nIndex ); + CPPUNIT_ASSERT_EQUAL( sal_uInt16(EE_FEATURE_FIELD), aURLFieldInfo2.pFieldItem->Which() ); + SvxURLField* pURLField2 = dynamic_cast<SvxURLField*> ( const_cast<SvxFieldData*> (aURLFieldInfo2.pFieldItem->GetField()) ); + CPPUNIT_ASSERT(pURLField2); + CPPUNIT_ASSERT_EQUAL( aURL2, pURLField2->GetURL() ); + CPPUNIT_ASSERT_EQUAL( aRepres2, pURLField2->GetRepresentation() ); + + // Copy text using legacy format + uno::Reference< datatransfer::XTransferable > xData = aEditEngine.CreateTransferable( ESelection(0,10,0,21) ); + + // Paste text at the end + aEditEngine.InsertText( xData, OUString(), rDoc.GetEndPaM(), true ); + + // Assert Changes ACP, ACP: after Copy/Paste + + // Check the fields count + CPPUNIT_ASSERT_EQUAL( sal_uInt16(3), aEditEngine.GetFieldCount(0) ); + + // Check the updated text length + CPPUNIT_ASSERT_EQUAL( aTextLen + 10 + aRepres1.getLength()*2 + aRepres2.getLength(), rDoc.GetTextLen() ); + + // Check the updated text contents + CPPUNIT_ASSERT_EQUAL( OUString("sampletextforuser@example.comtestingexample@domain.comfeaturefieldsforuser@example.comtesting"), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Check the Fields and their values + + // Field 1 + EFieldInfo aACPURLFieldInfo1 = aEditEngine.GetFieldInfo( sal_Int32(0), sal_uInt16(0) ); + CPPUNIT_ASSERT_EQUAL( sal_Int32(13), aACPURLFieldInfo1.aPosition.nIndex ); + CPPUNIT_ASSERT_EQUAL( sal_uInt16(EE_FEATURE_FIELD), aACPURLFieldInfo1.pFieldItem->Which() ); + SvxURLField* pACPURLField1 = dynamic_cast<SvxURLField*> ( const_cast<SvxFieldData*> (aACPURLFieldInfo1.pFieldItem->GetField()) ); + CPPUNIT_ASSERT(pACPURLField1); + CPPUNIT_ASSERT_EQUAL( aURL1, pACPURLField1->GetURL() ); + CPPUNIT_ASSERT_EQUAL( aRepres1, pACPURLField1->GetRepresentation() ); + + // Field 2 + EFieldInfo aACPURLFieldInfo2 = aEditEngine.GetFieldInfo( sal_Int32(0), sal_uInt16(1) ); + CPPUNIT_ASSERT_EQUAL( sal_Int32(21), aACPURLFieldInfo2.aPosition.nIndex ); + CPPUNIT_ASSERT_EQUAL( sal_uInt16(EE_FEATURE_FIELD), aACPURLFieldInfo2.pFieldItem->Which() ); + SvxURLField* pACPURLField2 = dynamic_cast<SvxURLField*> ( const_cast<SvxFieldData*> (aACPURLFieldInfo2.pFieldItem->GetField()) ); + CPPUNIT_ASSERT(pACPURLField2); + CPPUNIT_ASSERT_EQUAL( aURL2, pACPURLField2->GetURL() ); + CPPUNIT_ASSERT_EQUAL( aRepres2, pACPURLField2->GetRepresentation() ) ; + + // Field 3 + EFieldInfo aACPURLFieldInfo3 = aEditEngine.GetFieldInfo( sal_Int32(0), sal_uInt16(2) ); + CPPUNIT_ASSERT_EQUAL( sal_Int32(38), aACPURLFieldInfo3.aPosition.nIndex ); + CPPUNIT_ASSERT_EQUAL( sal_uInt16(EE_FEATURE_FIELD), aACPURLFieldInfo3.pFieldItem->Which() ); + SvxURLField* pACPURLField3 = dynamic_cast<SvxURLField*> ( const_cast<SvxFieldData*> (aACPURLFieldInfo3.pFieldItem->GetField()) ); + CPPUNIT_ASSERT(pACPURLField3); + CPPUNIT_ASSERT_EQUAL( aURL1, pACPURLField3->GetURL() ); + CPPUNIT_ASSERT_EQUAL( aRepres1, pACPURLField3->GetRepresentation() ); +} + +void Test::testCopyPaste() +{ + // Create EditEngine's instance + EditEngine aEditEngine( mpItemPool.get() ); + + // Get EditDoc for current EditEngine's instance + EditDoc &rDoc = aEditEngine.GetEditDoc(); + + // Initially no text should be there + CPPUNIT_ASSERT_EQUAL( sal_Int32(0), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Set initial text + OUString aText = "This is custom initial text"; + sal_Int32 aTextLen = aText.getLength(); + aEditEngine.SetText( aText ); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aText, rDoc.GetParaAsString(sal_Int32(0)) ); + + // Copy initial text using legacy format + uno::Reference< datatransfer::XTransferable > xData = aEditEngine.CreateTransferable( ESelection(0,0,0,aTextLen) ); + + // Paste text at the end + aEditEngine.InsertText( xData, OUString(), rDoc.GetEndPaM(), true ); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen + aTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(aText + aText), rDoc.GetParaAsString(sal_Int32(0)) ); +} + +void Test::testMultiParaSelCopyPaste() +{ + // Create EditEngine's instance + EditEngine aEditEngine( mpItemPool.get() ); + + // Get EditDoc for current EditEngine's instance + EditDoc &rDoc = aEditEngine.GetEditDoc(); + + // Initially no text should be there + CPPUNIT_ASSERT_EQUAL( sal_Int32(0), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Insert initial text + OUString aFirstPara = "This is first paragraph"; + // Selection Ref ........8.............. + OUString aSecondPara = "This is second paragraph"; + // Selection Ref .............14......... + OUString aThirdPara = "This is third paragraph"; + OUString aText = aFirstPara + "\n" + aSecondPara + "\n" + aThirdPara; + sal_Int32 aTextLen = aFirstPara.getLength() + aSecondPara.getLength() + aThirdPara.getLength(); + aEditEngine.SetText( aText ); + OUString aCopyText = "first paragraphThis is second"; + sal_Int32 aCopyTextLen = aCopyText.getLength(); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aFirstPara, rDoc.GetParaAsString(sal_Int32(0)) ); + CPPUNIT_ASSERT_EQUAL( aSecondPara, rDoc.GetParaAsString(sal_Int32(1)) ); + CPPUNIT_ASSERT_EQUAL( aThirdPara, rDoc.GetParaAsString(sal_Int32(2)) ); + + // Copy initial text using legacy format + uno::Reference< datatransfer::XTransferable > xData = aEditEngine.CreateTransferable( ESelection(0,8,1,14) ); + + // Paste text at the end + aEditEngine.InsertText( xData, OUString(), rDoc.GetEndPaM(), true ); + + // Assert changes + OUString aThirdParaAfterCopyPaste = aThirdPara + "first paragraph"; + CPPUNIT_ASSERT_EQUAL( aTextLen + aCopyTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aFirstPara, rDoc.GetParaAsString(sal_Int32(0)) ); + CPPUNIT_ASSERT_EQUAL( aSecondPara, rDoc.GetParaAsString(sal_Int32(1)) ); + CPPUNIT_ASSERT_EQUAL( aThirdParaAfterCopyPaste, rDoc.GetParaAsString(sal_Int32(2)) ); + CPPUNIT_ASSERT_EQUAL( OUString("This is second"), rDoc.GetParaAsString(sal_Int32(3)) ); +} + +void Test::testTabsCopyPaste() +{ + // Create EditEngine's instance + EditEngine aEditEngine( mpItemPool.get() ); + + // Get EditDoc for current EditEngine's instance + EditDoc &rDoc = aEditEngine.GetEditDoc(); + + // New instance must be empty - no initial text + CPPUNIT_ASSERT_EQUAL( sal_Int32(0), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Get corresponding Item for inserting tabs in the text + SfxVoidItem aTab( EE_FEATURE_TAB ); + + // Insert initial text + OUString aParaText = "sampletextfortestingtab"; + // Positions Ref ......*6...............*23 + sal_Int32 aTextLen = aParaText.getLength(); + aEditEngine.SetText( aParaText ); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aParaText, rDoc.GetParaAsString(sal_Int32(0)) ); + + // Insert tab 1 at desired position + ContentNode *pNode = rDoc.GetObject(0); + EditSelection aSel1( EditPaM(pNode, 6), EditPaM(pNode, 6) ); + aEditEngine.InsertFeature( aSel1, aTab ); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen + 1, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString("sample\ttextfortestingtab"), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Insert tab 2 at desired position + EditSelection aSel2( EditPaM(pNode, 23+1), EditPaM(pNode, 23+1) ); + aEditEngine.InsertFeature( aSel2, aTab ); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen + 2, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString("sample\ttextfortestingtab\t"), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Copy text using legacy format + uno::Reference< datatransfer::XTransferable > xData = aEditEngine.CreateTransferable( ESelection(0,6,0,aTextLen+2) ); + + // Paste text at the end + aEditEngine.InsertText( xData, OUString(), rDoc.GetEndPaM(), true ); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen + aTextLen - 6 + 4, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString("sample\ttextfortestingtab\t\ttextfortestingtab\t"), rDoc.GetParaAsString(sal_Int32(0)) ); +} + +class UrlEditEngine : public EditEngine +{ +public: + explicit UrlEditEngine(SfxItemPool *pPool) : EditEngine(pPool) {} + + virtual OUString CalcFieldValue( const SvxFieldItem&, sal_Int32, sal_Int32, std::optional<Color>&, std::optional<Color>& ) override + { + return "jim@bob.com"; // a sophisticated view of value: + } +}; + +// Odd accounting for hyperlink position & size etc. +// https://bugzilla.novell.com/show_bug.cgi?id=467459 +void Test::testHyperlinkSearch() +{ + UrlEditEngine aEngine(mpItemPool.get()); + EditDoc &rDoc = aEngine.GetEditDoc(); + + OUString aSampleText = "Please write email to . if you find a fish(not a dog)."; + aEngine.SetText(aSampleText); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("set text", aSampleText, rDoc.GetParaAsString(sal_Int32(0))); + + ContentNode *pNode = rDoc.GetObject(0); + EditSelection aSel(EditPaM(pNode, 22), EditPaM(pNode, 22)); + SvxURLField aURLField("mailto:///jim@bob.com", "jim@bob.com", + SvxURLFormat::Repr); + SvxFieldItem aField(aURLField, EE_FEATURE_FIELD); + + aEngine.InsertField(aSel, aField); + + OUString aContent = pNode->GetExpandedText(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("get text", OUString("Please write email to jim@bob.com. if you find a fish(not a dog)."), + aContent); + CPPUNIT_ASSERT_EQUAL_MESSAGE("wrong length", aContent.getLength(), rDoc.GetTextLen()); + + // Check expansion and positioning re-work + CPPUNIT_ASSERT_EQUAL_MESSAGE("wrong length", aContent.getLength(), + pNode->GetExpandedLen()); + for (sal_Int32 n = 0; n < aContent.getLength(); n++) + { + sal_Int32 nStart = n, nEnd = n; + pNode->UnExpandPositions(nStart,nEnd); + CPPUNIT_ASSERT_MESSAGE("out of bound start", nStart < pNode->Len()); + CPPUNIT_ASSERT_MESSAGE("out of bound end", nEnd <= pNode->Len()); + } + + static const struct { + sal_Int32 mnStart, mnEnd; + sal_Int32 mnNewStart, mnNewEnd; + } aTrickyOnes[] = { + { 0, 1, /* -> */ 0, 1 }, + { 21, 25, /* -> */ 21, 23 }, // the field is really just one char + { 25, 27, /* -> */ 22, 23 }, + { 50, 56, /* -> */ 40, 46 } + }; + for (size_t n = 0; n < SAL_N_ELEMENTS(aTrickyOnes); n++) + { + sal_Int32 nStart = aTrickyOnes[n].mnStart; + sal_Int32 nEnd = aTrickyOnes[n].mnEnd; + pNode->UnExpandPositions(nStart,nEnd); + + CPPUNIT_ASSERT_EQUAL_MESSAGE( + OString("in row " + OString::number(n)).getStr(), + aTrickyOnes[n].mnNewStart, nStart); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + OString("in row " + OString::number(n)).getStr(), + aTrickyOnes[n].mnNewEnd, nEnd); + } + + SvxSearchItem aItem(1); //SID_SEARCH_ITEM); + aItem.SetBackward(false); + aItem.SetSelection(false); + aItem.SetSearchString("fish"); + CPPUNIT_ASSERT_MESSAGE("no fish", aEngine.HasText(aItem)); + aItem.SetSearchString("dog"); + CPPUNIT_ASSERT_MESSAGE("no dog", aEngine.HasText(aItem)); +} + +bool hasBold(const editeng::Section& rSecAttr) +{ + return std::any_of(rSecAttr.maAttributes.begin(), rSecAttr.maAttributes.end(), + [](const SfxPoolItem* p) { + return p->Which() == EE_CHAR_WEIGHT + && static_cast<const SvxWeightItem*>(p)->GetWeight() == WEIGHT_BOLD; + }); +} + +bool hasItalic(const editeng::Section& rSecAttr) +{ + return std::any_of(rSecAttr.maAttributes.begin(), rSecAttr.maAttributes.end(), + [](const SfxPoolItem* p) { + return p->Which() == EE_CHAR_ITALIC + && static_cast<const SvxPostureItem*>(p)->GetPosture() == ITALIC_NORMAL; + }); +} + +void Test::testBoldItalicCopyPaste() +{ + // Create EditEngine's instance + EditEngine aEditEngine( mpItemPool.get() ); + + // Get EditDoc for current EditEngine's instance + EditDoc &rDoc = aEditEngine.GetEditDoc(); + + // New instance must be empty - no initial text + CPPUNIT_ASSERT_EQUAL( sal_Int32(0), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Get corresponding ItemSet for inserting Bold/Italic text + std::unique_ptr<SfxItemSet> pSet( new SfxItemSet(aEditEngine.GetEmptyItemSet()) ); + SvxWeightItem aBold( WEIGHT_BOLD, EE_CHAR_WEIGHT ); + SvxPostureItem aItalic( ITALIC_NORMAL, EE_CHAR_ITALIC ); + + // Insert initial text + OUString aParaText = "boldeditengineitalic"; + // Positions Ref ..*2....*8...*13.*17 + // Bold Ref ..[ BOLD ]...... + // Italic Ref ........[ ITALIC ].. + sal_Int32 aTextLen = aParaText.getLength(); + aEditEngine.SetText( aParaText ); + + // Assert changes - text insertion + CPPUNIT_ASSERT_EQUAL( aTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aParaText, rDoc.GetParaAsString(sal_Int32(0)) ); + + // Apply Bold to appropriate selection + pSet->Put(aBold); + CPPUNIT_ASSERT_EQUAL( static_cast<sal_uInt16>(1), pSet->Count() ); + aEditEngine.QuickSetAttribs( *pSet, ESelection(0,2,0,14) ); + + // Assert changes + std::unique_ptr<EditTextObject> pEditText1( aEditEngine.CreateTextObject() ); + std::vector<editeng::Section> aAttrs1; + pEditText1->GetAllSections( aAttrs1 ); + // There should be 3 sections - woBold - wBold - woBold (w - with, wo - without) + CPPUNIT_ASSERT_EQUAL( size_t(3), aAttrs1.size() ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[0].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[0].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs1[0].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[0].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[1].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs1[1].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 14, static_cast<int>(aAttrs1[1].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs1[1].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs1[1]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[2].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 14, static_cast<int>(aAttrs1[2].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 20, static_cast<int>(aAttrs1[2].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[2].maAttributes.size()) ); + + // Apply Italic to appropriate selection + pSet.reset( new SfxItemSet(aEditEngine.GetEmptyItemSet()) ); + pSet->Put(aItalic); + CPPUNIT_ASSERT_EQUAL( static_cast<sal_uInt16>(1), pSet->Count() ); + aEditEngine.QuickSetAttribs( *pSet, ESelection(0,8,0,18) ); + + // Assert changes + std::unique_ptr<EditTextObject> pEditText2( aEditEngine.CreateTextObject() ); + std::vector<editeng::Section> aAttrs2; + pEditText2->GetAllSections( aAttrs2 ); + // There should be 5 sections - woBold&woItalic - wBold&woItalic - wBold&wItalic - woBold&wItalic - woBold&woItalic (w - with, wo - without) + CPPUNIT_ASSERT_EQUAL( size_t(5), aAttrs2.size() ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[0].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[0].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs2[0].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[0].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[1].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs2[1].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 8, static_cast<int>(aAttrs2[1].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[1].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs2[1]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[2].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 8, static_cast<int>(aAttrs2[2].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 14, static_cast<int>(aAttrs2[2].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs2[2].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasBold(aAttrs2[2]) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasItalic(aAttrs2[2]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[3].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 14, static_cast<int>(aAttrs2[3].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs2[3].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[3].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be italic.", hasItalic(aAttrs2[3]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[4].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs2[4].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 20, static_cast<int>(aAttrs2[4].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[4].maAttributes.size()) ); + + // Copy text using legacy format + uno::Reference< datatransfer::XTransferable > xData = aEditEngine.CreateTransferable( ESelection(0,1,0,aTextLen-1) ); + + // Paste text at the end + aEditEngine.InsertText( xData, OUString(), rDoc.GetEndPaM(), true ); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen + aTextLen - 2, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(aParaText + "oldeditengineitali" ), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Check updated text for appropriate Bold/Italics + std::unique_ptr<EditTextObject> pEditText3( aEditEngine.CreateTextObject() ); + std::vector<editeng::Section> aAttrs3; + pEditText3->GetAllSections( aAttrs3 ); + // There should be 9 sections - woBold&woItalic - wBold&woItalic - wBold&wItalic - woBold&wItalic - woBold&woItalic - wBold&woItalic + // - wBold&wItalic - woBold&wItalic - woBold&woItalic(w - with, wo - without) + CPPUNIT_ASSERT_EQUAL( size_t(9), aAttrs3.size() ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[0].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[0].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[0].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[0].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[1].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[1].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 8, static_cast<int>(aAttrs3[1].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[1].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs3[1]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[2].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 8, static_cast<int>(aAttrs3[2].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 14, static_cast<int>(aAttrs3[2].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[2].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasBold(aAttrs3[2]) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasItalic(aAttrs3[2]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[3].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 14, static_cast<int>(aAttrs3[3].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs3[3].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[3].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be italic.", hasItalic(aAttrs3[3]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[4].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs3[4].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 21, static_cast<int>(aAttrs3[4].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[4].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[5].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 21, static_cast<int>(aAttrs3[5].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 27, static_cast<int>(aAttrs3[5].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[5].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs3[5]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[6].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 27, static_cast<int>(aAttrs3[6].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 33, static_cast<int>(aAttrs3[6].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[6].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasBold(aAttrs3[6]) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasItalic(aAttrs3[6]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[7].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 33, static_cast<int>(aAttrs3[7].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 37, static_cast<int>(aAttrs3[7].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[7].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be italic.", hasItalic(aAttrs3[7]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[8].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 37, static_cast<int>(aAttrs3[8].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 38, static_cast<int>(aAttrs3[8].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[8].maAttributes.size()) ); +} + +// Auxiliary function to test Underline text Copy/Paste using Legacy Format +bool hasUnderline(const editeng::Section& rSecAttr) +{ + return std::any_of(rSecAttr.maAttributes.begin(), rSecAttr.maAttributes.end(), + [](const SfxPoolItem* p) { + return p->Which() == EE_CHAR_UNDERLINE + && static_cast<const SvxUnderlineItem*>(p)->GetLineStyle() == LINESTYLE_SINGLE; + }); +} + +void Test::testUnderlineCopyPaste() +{ + // Create EditEngine's instance + EditEngine aEditEngine( mpItemPool.get() ); + + // Get EditDoc for current EditEngine's instance + EditDoc &rDoc = aEditEngine.GetEditDoc(); + + // New instance must be empty - no initial text + CPPUNIT_ASSERT_EQUAL( sal_Int32(0), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Get corresponding ItemSet for inserting Underline text + std::unique_ptr<SfxItemSet> pSet( new SfxItemSet(aEditEngine.GetEmptyItemSet()) ); + SvxUnderlineItem aULine( LINESTYLE_SINGLE, EE_CHAR_UNDERLINE ); + + // Insert initial text + OUString aParaText = "sampletextforunderline"; + // Positions Ref ......*6.........*17.. + // Underline Ref ......[UNDERLINE ].... + sal_Int32 aTextLen = aParaText.getLength(); + aEditEngine.SetText( aParaText ); + + // Apply Underline style + pSet->Put( aULine ); + CPPUNIT_ASSERT_EQUAL( static_cast<sal_uInt16>(1), pSet->Count() ); + aEditEngine.QuickSetAttribs( *pSet, ESelection(0,6,0,18) ); + + // Assert changes + std::unique_ptr<EditTextObject> pEditText1( aEditEngine.CreateTextObject() ); + std::vector<editeng::Section> aAttrs1; + pEditText1->GetAllSections( aAttrs1 ); + + // There should be 3 sections - woUnderline - wUnderline - woUnderline (w - with, wo - without) + CPPUNIT_ASSERT_EQUAL( size_t(3), aAttrs1.size() ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[0].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[0].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 6, static_cast<int>(aAttrs1[0].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[0].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[1].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 6, static_cast<int>(aAttrs1[1].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs1[1].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs1[1].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be underlined.", hasUnderline(aAttrs1[1]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[2].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs1[2].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 22, static_cast<int>(aAttrs1[2].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[2].maAttributes.size()) ); + + // Copy text using legacy format + uno::Reference< datatransfer::XTransferable > xData = aEditEngine.CreateTransferable( ESelection(0,6,0,aTextLen-4) ); + + // Paste text at the end + aEditEngine.InsertText( xData, OUString(), rDoc.GetEndPaM(), true ); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen + (OUString("textforunder")).getLength(), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(aParaText + "textforunder" ), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Check updated text for appropriate Underline + std::unique_ptr<EditTextObject> pEditText2( aEditEngine.CreateTextObject() ); + std::vector<editeng::Section> aAttrs2; + pEditText2->GetAllSections( aAttrs2 ); + + // There should be 4 sections - woUnderline - wUnderline - woUnderline - wUnderline (w - with, wo - without) + CPPUNIT_ASSERT_EQUAL( size_t(4), aAttrs2.size() ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[0].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[0].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 6, static_cast<int>(aAttrs2[0].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[0].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[1].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 6, static_cast<int>(aAttrs2[1].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs2[1].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[1].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be underlined.", hasUnderline(aAttrs2[1]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[2].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs2[2].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 22, static_cast<int>(aAttrs2[2].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[2].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[3].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 22, static_cast<int>(aAttrs2[3].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 34, static_cast<int>(aAttrs2[3].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[3].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be underlined.", hasUnderline(aAttrs2[3]) ); +} + +void Test::testMultiParaCopyPaste() +{ + // Create EditEngine's instance + EditEngine aEditEngine( mpItemPool.get() ); + + // Get EditDoc for current EditEngine's instance + EditDoc &rDoc = aEditEngine.GetEditDoc(); + + // Initially no text should be there + CPPUNIT_ASSERT_EQUAL( sal_Int32(0), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Insert initial text + OUString aFirstPara = "This is first paragraph"; + OUString aSecondPara = "This is second paragraph"; + OUString aThirdPara = "This is third paragraph"; + OUString aText = aFirstPara + "\n" + aSecondPara + "\n" + aThirdPara; + sal_Int32 aTextLen = aFirstPara.getLength() + aSecondPara.getLength() + aThirdPara.getLength(); + aEditEngine.SetText( aText ); + sal_Int32 aCopyTextLen = aFirstPara.getLength() + aSecondPara.getLength(); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aFirstPara, rDoc.GetParaAsString(sal_Int32(0)) ); + CPPUNIT_ASSERT_EQUAL( aSecondPara, rDoc.GetParaAsString(sal_Int32(1)) ); + CPPUNIT_ASSERT_EQUAL( aThirdPara, rDoc.GetParaAsString(sal_Int32(2)) ); + + // Copy initial text using legacy format + uno::Reference< datatransfer::XTransferable > xData = aEditEngine.CreateTransferable( ESelection(0,0,1,aSecondPara.getLength()) ); + + // Paste text at the end + aEditEngine.InsertText( xData, OUString(), rDoc.GetEndPaM(), true ); + + // Assert changes + OUString aThirdParaAfterCopyPaste = aThirdPara + aFirstPara; + CPPUNIT_ASSERT_EQUAL( aTextLen + aCopyTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aFirstPara, rDoc.GetParaAsString(sal_Int32(0)) ); + CPPUNIT_ASSERT_EQUAL( aSecondPara, rDoc.GetParaAsString(sal_Int32(1)) ); + CPPUNIT_ASSERT_EQUAL( aThirdParaAfterCopyPaste, rDoc.GetParaAsString(sal_Int32(2)) ); + CPPUNIT_ASSERT_EQUAL( aSecondPara, rDoc.GetParaAsString(sal_Int32(3)) ); +} + +void Test::testParaBoldItalicCopyPaste() +{ + // Create EditEngine's instance + EditEngine aEditEngine( mpItemPool.get() ); + + // Get EditDoc for current EditEngine's instance + EditDoc &rDoc = aEditEngine.GetEditDoc(); + + // Initially no text should be there + CPPUNIT_ASSERT_EQUAL( sal_Int32(0), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Get corresponding ItemSet for inserting Bold/Italic text + std::unique_ptr<SfxItemSet> pSet( new SfxItemSet(aEditEngine.GetEmptyItemSet()) ); + SvxWeightItem aBold( WEIGHT_BOLD, EE_CHAR_WEIGHT ); + SvxPostureItem aItalic( ITALIC_NORMAL, EE_CHAR_ITALIC ); + + // Insert initial text + OUString aFirstPara = "This is first paragraph"; + // Positions Ref .....*5.*8....*14*17... + // Bold Ref .....[ BOLD ]..... + // Italic Ref ..............[ ITA + // Copy Ref ........[ Copy Sel + OUString aSecondPara = "This is second paragraph"; + // Positions Ref .....*5.*8...*13..*18... + // Bold Ref .....[ BOLD ]..... + // Italic Ref LIC ]............... + // Copy Ref ection ].......... + OUString aThirdPara = "This is third paragraph"; + OUString aText = aFirstPara + "\n" + aSecondPara + "\n" + aThirdPara; + sal_Int32 aTextLen = aFirstPara.getLength() + aSecondPara.getLength() + aThirdPara.getLength(); + aEditEngine.SetText( aText ); + OUString aCopyText = "first paragraphThis is second"; + sal_Int32 aCopyTextLen = aCopyText.getLength(); + + // Assert changes - text insertion + CPPUNIT_ASSERT_EQUAL( aTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aFirstPara, rDoc.GetParaAsString(sal_Int32(0)) ); + CPPUNIT_ASSERT_EQUAL( aSecondPara, rDoc.GetParaAsString(sal_Int32(1)) ); + CPPUNIT_ASSERT_EQUAL( aThirdPara, rDoc.GetParaAsString(sal_Int32(2)) ); + + // Apply Bold to appropriate selections + pSet->Put(aBold); + CPPUNIT_ASSERT_EQUAL( static_cast<sal_uInt16>(1), pSet->Count() ); + aEditEngine.QuickSetAttribs( *pSet, ESelection(0,5,0,18) ); + aEditEngine.QuickSetAttribs( *pSet, ESelection(1,5,1,19) ); + + // Assert changes + std::unique_ptr<EditTextObject> pEditText1( aEditEngine.CreateTextObject() ); + std::vector<editeng::Section> aAttrs1; + pEditText1->GetAllSections( aAttrs1 ); + // There should be 7 sections - woB - wB - woB -woB -wB -woB -woB (w - with, wo - without, B - Bold, I - Italic) + CPPUNIT_ASSERT_EQUAL( size_t(7), aAttrs1.size() ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[0].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[0].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs1[0].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[0].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[1].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs1[1].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs1[1].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs1[1].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs1[1]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[2].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs1[2].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 23, static_cast<int>(aAttrs1[2].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[2].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs1[3].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[3].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs1[3].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[3].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs1[4].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs1[4].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 19, static_cast<int>(aAttrs1[4].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs1[4].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs1[4]) ); + + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs1[5].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 19, static_cast<int>(aAttrs1[5].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 24, static_cast<int>(aAttrs1[5].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[5].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs1[6].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[6].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 23, static_cast<int>(aAttrs1[6].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs1[6].maAttributes.size()) ); + + // Apply Italic to appropriate selection + pSet.reset( new SfxItemSet(aEditEngine.GetEmptyItemSet()) ); + pSet->Put(aItalic); + CPPUNIT_ASSERT_EQUAL( static_cast<sal_uInt16>(1), pSet->Count() ); + aEditEngine.QuickSetAttribs( *pSet, ESelection(0,14,1,9) ); + + // Assert changes + std::unique_ptr<EditTextObject> pEditText2( aEditEngine.CreateTextObject() ); + std::vector<editeng::Section> aAttrs2; + pEditText2->GetAllSections( aAttrs2 ); + // There should be 9 sections - woB&woI - wB&woI - wB&wI -woB&wI - woB&wI - wB&wI - wB&woI - woB&woI - woB&woI (w - with, wo - without, B - Bold, I - Italic) + CPPUNIT_ASSERT_EQUAL( size_t(9), aAttrs2.size() ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[0].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[0].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs2[0].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[0].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[1].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs2[1].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 14, static_cast<int>(aAttrs2[1].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[1].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs2[1]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[2].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 14, static_cast<int>(aAttrs2[2].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs2[2].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs2[2].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasBold(aAttrs2[2]) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasItalic(aAttrs2[2]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[3].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs2[3].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 23, static_cast<int>(aAttrs2[3].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[3].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be italic.", hasItalic(aAttrs2[3]) ); + + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[4].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[4].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs2[4].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[4].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be italic.", hasItalic(aAttrs2[4]) ); + + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[5].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs2[5].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 9, static_cast<int>(aAttrs2[5].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs2[5].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasBold(aAttrs2[5]) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasItalic(aAttrs2[5]) ); + + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[6].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 9, static_cast<int>(aAttrs2[6].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 19, static_cast<int>(aAttrs2[6].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[6].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs2[6]) ); + + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs2[7].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 19, static_cast<int>(aAttrs2[7].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 24, static_cast<int>(aAttrs2[7].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[7].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs2[8].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[8].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 23, static_cast<int>(aAttrs2[8].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs2[8].maAttributes.size()) ); + + // Copy text using legacy format + uno::Reference< datatransfer::XTransferable > xData = aEditEngine.CreateTransferable( ESelection(0,8,1,14) ); + + // Paste text at the end + aEditEngine.InsertText( xData, OUString(), rDoc.GetEndPaM(), true ); + + // Assert changes + OUString aThirdParaAfterCopyPaste = aThirdPara + "first paragraph"; + CPPUNIT_ASSERT_EQUAL( aTextLen + aCopyTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aFirstPara, rDoc.GetParaAsString(sal_Int32(0)) ); + CPPUNIT_ASSERT_EQUAL( aSecondPara, rDoc.GetParaAsString(sal_Int32(1)) ); + CPPUNIT_ASSERT_EQUAL( aThirdParaAfterCopyPaste, rDoc.GetParaAsString(sal_Int32(2)) ); + CPPUNIT_ASSERT_EQUAL( OUString("This is second"), rDoc.GetParaAsString(sal_Int32(3)) ); + + // Check updated text for appropriate Bold/Italics + std::unique_ptr<EditTextObject> pEditText3( aEditEngine.CreateTextObject() ); + std::vector<editeng::Section> aAttrs3; + pEditText3->GetAllSections( aAttrs3 ); + // There should be 15 sections - woB&woI - wB&woI - wB&wI -woB&wI - woB&wI - wB&wI - wB&woI - woB&woI - woB&woI + // - wB&woI - wB&wI - woB&wI - -woB&wI - wB&wI - wB&woI (w - with, wo - without, B - Bold, I - Italic) + CPPUNIT_ASSERT_EQUAL( size_t(15), aAttrs3.size() ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[0].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[0].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs3[0].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[0].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[1].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs3[1].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 14, static_cast<int>(aAttrs3[1].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[1].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs3[1]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[2].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 14, static_cast<int>(aAttrs3[2].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs3[2].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[2].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasBold(aAttrs3[2]) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasItalic(aAttrs3[2]) ); + + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[3].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 18, static_cast<int>(aAttrs3[3].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 23, static_cast<int>(aAttrs3[3].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[3].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be italic.", hasItalic(aAttrs3[3]) ); + + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[4].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[4].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs3[4].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[4].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be italic.", hasItalic(aAttrs3[4]) ); + + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[5].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs3[5].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 9, static_cast<int>(aAttrs3[5].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[5].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasBold(aAttrs3[5]) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasItalic(aAttrs3[5]) ); + + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[6].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 9, static_cast<int>(aAttrs3[6].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 19, static_cast<int>(aAttrs3[6].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[6].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs3[6]) ); + + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[7].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 19, static_cast<int>(aAttrs3[7].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 24, static_cast<int>(aAttrs3[7].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[7].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[8].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[8].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 23, static_cast<int>(aAttrs3[8].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[8].maAttributes.size()) ); + + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[9].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 23, static_cast<int>(aAttrs3[9].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 29, static_cast<int>(aAttrs3[9].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[9].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs3[9]) ); + + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[10].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 29, static_cast<int>(aAttrs3[10].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 33, static_cast<int>(aAttrs3[10].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[10].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasBold(aAttrs3[10]) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasItalic(aAttrs3[10]) ); + + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[11].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 33, static_cast<int>(aAttrs3[11].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 38, static_cast<int>(aAttrs3[11].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[11].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be italic.", hasItalic(aAttrs3[11]) ); + + CPPUNIT_ASSERT_EQUAL( 3, static_cast<int>(aAttrs3[12].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 0, static_cast<int>(aAttrs3[12].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs3[12].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[12].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be italic.", hasItalic(aAttrs3[12]) ); + + CPPUNIT_ASSERT_EQUAL( 3, static_cast<int>(aAttrs3[13].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 5, static_cast<int>(aAttrs3[13].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 9, static_cast<int>(aAttrs3[13].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 2, static_cast<int>(aAttrs3[13].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasBold(aAttrs3[13]) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold and italic.", hasItalic(aAttrs3[13]) ); + + CPPUNIT_ASSERT_EQUAL( 3, static_cast<int>(aAttrs3[14].mnParagraph) ); + CPPUNIT_ASSERT_EQUAL( 9, static_cast<int>(aAttrs3[14].mnStart) ); + CPPUNIT_ASSERT_EQUAL( 14, static_cast<int>(aAttrs3[14].mnEnd) ); + CPPUNIT_ASSERT_EQUAL( 1, static_cast<int>(aAttrs3[14].maAttributes.size()) ); + CPPUNIT_ASSERT_MESSAGE( "This section must be bold.", hasBold(aAttrs3[14]) ); +} + +void Test::testParaStartCopyPaste() +{ + // Create EditEngine's instance + EditEngine aEditEngine( mpItemPool.get() ); + + // Get EditDoc for current EditEngine's instance + EditDoc &rDoc = aEditEngine.GetEditDoc(); + + // Initially no text should be there + CPPUNIT_ASSERT_EQUAL( sal_Int32(0), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Insert initial text + OUString aFirstPara = "This is first paragraph"; + // Selection Ref ........8.............. + OUString aSecondPara = "This is second paragraph"; + // Selection Ref .............14......... + OUString aThirdPara = "This is third paragraph"; + OUString aText = aFirstPara + "\n" + aSecondPara + "\n" + aThirdPara; + sal_Int32 aTextLen = aFirstPara.getLength() + aSecondPara.getLength() + aThirdPara.getLength(); + aEditEngine.SetText( aText ); + OUString aCopyText = "first paragraphThis is second"; + sal_Int32 aCopyTextLen = aCopyText.getLength(); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aFirstPara, rDoc.GetParaAsString(sal_Int32(0)) ); + CPPUNIT_ASSERT_EQUAL( aSecondPara, rDoc.GetParaAsString(sal_Int32(1)) ); + CPPUNIT_ASSERT_EQUAL( aThirdPara, rDoc.GetParaAsString(sal_Int32(2)) ); + + // Copy initial text using legacy format + uno::Reference< datatransfer::XTransferable > xData = aEditEngine.CreateTransferable( ESelection(0,8,1,14) ); + + // Paste text at the start + aEditEngine.InsertText( xData, OUString(), rDoc.GetStartPaM(), true ); + + // Assert changes + OUString aSecondParaAfterCopyPaste = "This is second" + aFirstPara; + CPPUNIT_ASSERT_EQUAL( aTextLen + aCopyTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString("first paragraph"), rDoc.GetParaAsString(sal_Int32(0)) ); + CPPUNIT_ASSERT_EQUAL( aSecondParaAfterCopyPaste, rDoc.GetParaAsString(sal_Int32(1)) ); + CPPUNIT_ASSERT_EQUAL( aSecondPara, rDoc.GetParaAsString(sal_Int32(2)) ); + CPPUNIT_ASSERT_EQUAL( aThirdPara, rDoc.GetParaAsString(sal_Int32(3)) ); +} + +void Test::testSectionAttributes() +{ + EditEngine aEngine(mpItemPool.get()); + + std::unique_ptr<SfxItemSet> pSet(new SfxItemSet(aEngine.GetEmptyItemSet())); + SvxWeightItem aBold(WEIGHT_BOLD, EE_CHAR_WEIGHT); + SvxPostureItem aItalic(ITALIC_NORMAL, EE_CHAR_ITALIC); + + { + aEngine.SetText("aaabbbccc"); + pSet->Put(aBold); + CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be exactly one item.", static_cast<sal_uInt16>(1), pSet->Count()); + aEngine.QuickSetAttribs(*pSet, ESelection(0,0,0,6)); // 'aaabbb' - end point is not inclusive. + pSet.reset(new SfxItemSet(aEngine.GetEmptyItemSet())); + pSet->Put(aItalic); + CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be exactly one item.", static_cast<sal_uInt16>(1), pSet->Count()); + + aEngine.QuickSetAttribs(*pSet, ESelection(0,3,0,9)); // 'bbbccc' + std::unique_ptr<EditTextObject> pEditText(aEngine.CreateTextObject()); + CPPUNIT_ASSERT_MESSAGE("Failed to create text object.", pEditText); + std::vector<editeng::Section> aAttrs; + pEditText->GetAllSections(aAttrs); + + // Now, we should have a total of 3 sections. + CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be 3 sections.", static_cast<size_t>(3), aAttrs.size()); + + // First section should be 0-3 of paragraph 0, and it should only have boldness applied. + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[0].mnParagraph)); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[0].mnStart)); + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(aAttrs[0].mnEnd)); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aAttrs[0].maAttributes.size())); + CPPUNIT_ASSERT_MESSAGE("This section must be bold.", hasBold(aAttrs[0])); + + // Second section should be 3-6, and it should be both bold and italic. + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[1].mnParagraph)); + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(aAttrs[1].mnStart)); + CPPUNIT_ASSERT_EQUAL(6, static_cast<int>(aAttrs[1].mnEnd)); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(aAttrs[1].maAttributes.size())); + CPPUNIT_ASSERT_MESSAGE("This section must be bold and italic.", hasBold(aAttrs[1])); + CPPUNIT_ASSERT_MESSAGE("This section must be bold and italic.", hasItalic(aAttrs[1])); + + // Third section should be 6-9, and it should be only italic. + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[2].mnParagraph)); + CPPUNIT_ASSERT_EQUAL(6, static_cast<int>(aAttrs[2].mnStart)); + CPPUNIT_ASSERT_EQUAL(9, static_cast<int>(aAttrs[2].mnEnd)); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aAttrs[2].maAttributes.size())); + CPPUNIT_ASSERT_MESSAGE("This section must be italic.", hasItalic(aAttrs[2])); + } + + { + // Set text consisting of 5 paragraphs with the 2nd and 4th paragraphs + // being empty. + aEngine.Clear(); + aEngine.SetText("one\n\ntwo\n\nthree"); + sal_Int32 nParaCount = aEngine.GetParagraphCount(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(5), nParaCount); + + // Apply boldness to paragraphs 1, 3, 5 only. Leave 2 and 4 unformatted. + pSet.reset(new SfxItemSet(aEngine.GetEmptyItemSet())); + pSet->Put(aBold); + CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be exactly one item.", static_cast<sal_uInt16>(1), pSet->Count()); + aEngine.QuickSetAttribs(*pSet, ESelection(0,0,0,3)); + aEngine.QuickSetAttribs(*pSet, ESelection(2,0,2,3)); + aEngine.QuickSetAttribs(*pSet, ESelection(4,0,4,5)); + + std::unique_ptr<EditTextObject> pEditText(aEngine.CreateTextObject()); + CPPUNIT_ASSERT_MESSAGE("Failed to create text object.", pEditText); + std::vector<editeng::Section> aAttrs; + pEditText->GetAllSections(aAttrs); + CPPUNIT_ASSERT_EQUAL(size_t(5), aAttrs.size()); + + // 1st, 3rd and 5th sections should correspond with 1st, 3rd and 5th paragraphs. + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[0].mnParagraph)); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[0].mnStart)); + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(aAttrs[0].mnEnd)); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aAttrs[0].maAttributes.size())); + CPPUNIT_ASSERT_MESSAGE("This section must be bold.", hasBold(aAttrs[0])); + + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(aAttrs[2].mnParagraph)); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[2].mnStart)); + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(aAttrs[2].mnEnd)); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aAttrs[2].maAttributes.size())); + CPPUNIT_ASSERT_MESSAGE("This section must be bold.", hasBold(aAttrs[2])); + + CPPUNIT_ASSERT_EQUAL(4, static_cast<int>(aAttrs[4].mnParagraph)); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[4].mnStart)); + CPPUNIT_ASSERT_EQUAL(5, static_cast<int>(aAttrs[4].mnEnd)); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aAttrs[4].maAttributes.size())); + CPPUNIT_ASSERT_MESSAGE("This section must be bold.", hasBold(aAttrs[4])); + + // The 2nd and 4th paragraphs should be empty. + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aAttrs[1].mnParagraph)); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[1].mnStart)); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[1].mnEnd)); + CPPUNIT_ASSERT_MESSAGE("Attribute array should be empty.", aAttrs[1].maAttributes.empty()); + + CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(aAttrs[3].mnParagraph)); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[3].mnStart)); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[3].mnEnd)); + CPPUNIT_ASSERT_MESSAGE("Attribute array should be empty.", aAttrs[3].maAttributes.empty()); + } + + + { + aEngine.Clear(); + aEngine.SetText("one\ntwo"); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), aEngine.GetParagraphCount()); + + // embolden 2nd paragraph + pSet.reset(new SfxItemSet(aEngine.GetEmptyItemSet())); + pSet->Put(aBold); + aEngine.QuickSetAttribs(*pSet, ESelection(1,0,1,3)); + // disboldify 1st paragraph + SvxWeightItem aNotSoBold(WEIGHT_NORMAL, EE_CHAR_WEIGHT); + pSet->Put(aNotSoBold); + aEngine.QuickSetAttribs(*pSet, ESelection(0,0,0,3)); + + // now delete & join the paragraphs - this is fdo#85496 scenario + aEngine.QuickDelete(ESelection(0,0,1,3)); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aEngine.GetParagraphCount()); + + std::unique_ptr<EditTextObject> pEditText(aEngine.CreateTextObject()); + CPPUNIT_ASSERT_MESSAGE("Failed to create text object.", pEditText); + std::vector<editeng::Section> aAttrs; + pEditText->GetAllSections(aAttrs); + + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aAttrs.size()); + + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[0].mnParagraph)); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[0].mnStart)); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(aAttrs[0].mnEnd)); + std::set<sal_uInt16> whiches; + for (size_t i = 0; i < aAttrs[0].maAttributes.size(); ++i) + { + sal_uInt16 const nWhich(aAttrs[0].maAttributes[i]->Which()); + CPPUNIT_ASSERT_MESSAGE("duplicate item in text portion attributes", + whiches.insert(nWhich).second); + } + } +} + +void Test::testLargeParaCopyPaste() +{ + // Create EditEngine's instance + EditEngine aEditEngine( mpItemPool.get() ); + + // Get EditDoc for current EditEngine's instance + EditDoc &rDoc = aEditEngine.GetEditDoc(); + + // Initially no text should be there + CPPUNIT_ASSERT_EQUAL( sal_Int32(0), rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( OUString(), rDoc.GetParaAsString(sal_Int32(0)) ); + + // Insert initial text + OUString aFirstPara = "This is first paragraph"; + OUString aSecondPara = "This is second paragraph"; + OUString aThirdPara = "This is third paragraph"; + OUString aFourthPara = "This is fourth paragraph"; + OUString aFifthPara = "This is fifth paragraph"; + OUString aSixthPara = "This is sixth paragraph"; + //Positions Ref: ........*8............. + OUString aSeventhPara = "This is seventh paragraph"; + OUString aEighthPara = "This is eighth paragraph"; + //Positions Ref: .............*13 + OUString aNinthPara = "This is ninth paragraph"; + OUString aTenthPara = "This is tenth paragraph"; + OUString aText = aFirstPara + "\n" + aSecondPara + "\n" + aThirdPara + "\n" + + aFourthPara + "\n" + aFifthPara + "\n" + aSixthPara + "\n" + aSeventhPara + "\n" + + aEighthPara + "\n" + aNinthPara + "\n" + aTenthPara; + sal_Int32 aTextLen = aFirstPara.getLength() + aSecondPara.getLength() + aThirdPara.getLength() + + aFourthPara.getLength() + aFifthPara.getLength() + aSixthPara.getLength() + + aSeventhPara.getLength() + aEighthPara.getLength() + aNinthPara.getLength() + aTenthPara.getLength(); + aEditEngine.SetText( aText ); + OUString aCopyText = "sixth paragraphThis is seventh paragraphThis is eighth"; + sal_Int32 aCopyTextLen = aCopyText.getLength(); + + // Assert changes + CPPUNIT_ASSERT_EQUAL( aTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aFirstPara, rDoc.GetParaAsString(sal_Int32(0)) ); + CPPUNIT_ASSERT_EQUAL( aSecondPara, rDoc.GetParaAsString(sal_Int32(1)) ); + CPPUNIT_ASSERT_EQUAL( aThirdPara, rDoc.GetParaAsString(sal_Int32(2)) ); + CPPUNIT_ASSERT_EQUAL( aFourthPara, rDoc.GetParaAsString(sal_Int32(3)) ); + CPPUNIT_ASSERT_EQUAL( aFifthPara, rDoc.GetParaAsString(sal_Int32(4)) ); + CPPUNIT_ASSERT_EQUAL( aSixthPara, rDoc.GetParaAsString(sal_Int32(5)) ); + CPPUNIT_ASSERT_EQUAL( aSeventhPara, rDoc.GetParaAsString(sal_Int32(6)) ); + CPPUNIT_ASSERT_EQUAL( aEighthPara, rDoc.GetParaAsString(sal_Int32(7)) ); + CPPUNIT_ASSERT_EQUAL( aNinthPara, rDoc.GetParaAsString(sal_Int32(8)) ); + CPPUNIT_ASSERT_EQUAL( aTenthPara, rDoc.GetParaAsString(sal_Int32(9)) ); + + // Copy initial text using legacy format + uno::Reference< datatransfer::XTransferable > xData = aEditEngine.CreateTransferable( ESelection(5,8,7,14) ); + + // Paste text at the end of 4th Para + ContentNode* pLastNode = rDoc.GetObject(3); + aEditEngine.InsertText( xData, OUString(), EditPaM( pLastNode, pLastNode->Len() ), true ); + + // Assert changes + OUString aFourthParaAfterCopyPaste = aFourthPara + "sixth paragraph"; + CPPUNIT_ASSERT_EQUAL( aTextLen + aCopyTextLen, rDoc.GetTextLen() ); + CPPUNIT_ASSERT_EQUAL( aFirstPara, rDoc.GetParaAsString(sal_Int32(0)) ); + CPPUNIT_ASSERT_EQUAL( aSecondPara, rDoc.GetParaAsString(sal_Int32(1)) ); + CPPUNIT_ASSERT_EQUAL( aThirdPara, rDoc.GetParaAsString(sal_Int32(2)) ); + CPPUNIT_ASSERT_EQUAL( aFourthParaAfterCopyPaste, rDoc.GetParaAsString(sal_Int32(3)) ); + CPPUNIT_ASSERT_EQUAL( aSeventhPara, rDoc.GetParaAsString(sal_Int32(4)) ); + CPPUNIT_ASSERT_EQUAL( OUString("This is eighth"), rDoc.GetParaAsString(sal_Int32(5)) ); + CPPUNIT_ASSERT_EQUAL( aFifthPara, rDoc.GetParaAsString(sal_Int32(6)) ); + CPPUNIT_ASSERT_EQUAL( aSixthPara, rDoc.GetParaAsString(sal_Int32(7)) ); + CPPUNIT_ASSERT_EQUAL( aSeventhPara, rDoc.GetParaAsString(sal_Int32(8)) ); + CPPUNIT_ASSERT_EQUAL( aEighthPara, rDoc.GetParaAsString(sal_Int32(9)) ); + CPPUNIT_ASSERT_EQUAL( aNinthPara, rDoc.GetParaAsString(sal_Int32(10)) ); + CPPUNIT_ASSERT_EQUAL( aTenthPara, rDoc.GetParaAsString(sal_Int32(11)) ); +} + +OUString lcl_translitTest(EditEngine& aEditEngine, const OUString& text, const ESelection& esel, const TransliterationFlags nType) +{ + aEditEngine.SetText(text); + aEditEngine.TransliterateText(esel, nType); + return aEditEngine.GetText(); +} + + +void Test::testTransliterate() +{ + // Create EditEngine's instance + EditEngine editEng( mpItemPool.get() ); + + OUString sText("one (two) three"); + editEng.SetText(sText); + editEng.TransliterateText(ESelection(0, 0, 0, sText.getLength()), TransliterationFlags::TITLE_CASE); + CPPUNIT_ASSERT_EQUAL(OUString("One (Two) Three"), editEng.GetText()); + + using TF = TransliterationFlags; + const OUString sText2 = "Mary Jones met joe Smith. Time Passed."; + int selStart = 12; + int selEnd = 12; + ESelection esel(0, selStart, 0, selEnd); + + /* DocumentContentOperationsManager checks if the cursor is inside of a word before transliterating, + * but Edit Engine has no such check. Therefore, behavior is different between these two when the + * cursor is on a word boundary. */ + + /* No selection tests. Cursor between the ' ' and 'm' before 'met'. */ + CPPUNIT_ASSERT_EQUAL(OUString(""), editEng.GetText(esel)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary jones met joe smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones MET joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE)); + + /* No selection tests. Cursor between the 't' and the ' ' after 'met'. */ + selStart = 14; + selEnd = 14; + esel = ESelection(0, selStart, 0, selEnd); + CPPUNIT_ASSERT_EQUAL(OUString(""), editEng.GetText(esel)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary jones met joe smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE)); + + /* No selection tests. Cursor between the 'h' and the '.' after 'Smith'. */ + selStart = 24; + selEnd = 24; + esel = ESelection(0, selStart, 0, selEnd); + CPPUNIT_ASSERT_EQUAL(OUString(""), editEng.GetText(esel)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary jones met joe smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE)); + + /* No selection tests. Cursor between the 'm' and 'e' in 'met'. */ + selStart = 12; + selEnd = 12; + esel = ESelection(0, selStart, 0, selEnd); + CPPUNIT_ASSERT_EQUAL(OUString(""), editEng.GetText(esel)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary jones met joe smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones MET joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE)); + + /* Test behavior when there is a selection that does not cross a word boundary: "met" */ + selStart = 11; + selEnd = 14; + esel = ESelection(0, selStart, 0, selEnd); + CPPUNIT_ASSERT_EQUAL(OUString("met"), editEng.GetText(esel)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones MET joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE)); + + /* Test behavior when there is a selection that does not begin at a word boundary: "et" */ + selStart = 12; + selEnd = 14; + esel = ESelection(0, selStart, 0, selEnd); + CPPUNIT_ASSERT_EQUAL(OUString("et"), editEng.GetText(esel)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones mEt joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones mEt joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones mET joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE)); + + /* Test behavior when there is a selection that ends in the middle of a word */ + selStart = 11; + selEnd = 13; + esel = ESelection(0, selStart, 0, selEnd); + CPPUNIT_ASSERT_EQUAL(OUString("me"), editEng.GetText(esel)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones MEt joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE)); + + + /* Test behavior when there is a selection that crosses a word boundary: "nes met joe Sm" */ + selStart = 7; + selEnd = 21; + esel = ESelection(0, selStart, 0, selEnd); + CPPUNIT_ASSERT_EQUAL(OUString("nes met joe Sm"), editEng.GetText(esel)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary JoNes met joe smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary JoNes Met Joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary JoNES MET JOE SMith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE)); + + /* Test behavior when there is a selection that crosses a sentence boundary: "joe Smith. Time Passed." */ + selStart = 15; + selEnd = 38; + esel = ESelection(0, selStart, 0, selEnd); + editEng.SetText(sText2); + CPPUNIT_ASSERT_EQUAL(OUString("joe Smith. Time Passed."), editEng.GetText(esel)); + + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met Joe smith. Time passed."), lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met Joe Smith. Time Passed."), lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met JOE SMITH. TIME PASSED."), lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe smith. time passed."), lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE)); + + /* Test behavior when sentence ends with a capital that is not selected: "CURRENT IS EQUAL TO 10 A" */ + selStart = 0; + selEnd = 19; + esel = ESelection(0, selStart, 0, selEnd); + const OUString sText3("CURRENT IS EQUAL TO 10 A"); + editEng.SetText(sText3); + CPPUNIT_ASSERT_EQUAL(OUString("CURRENT IS EQUAL TO"), editEng.GetText(esel)); + + CPPUNIT_ASSERT_EQUAL(OUString("Current is equal to 10 A"), lcl_translitTest(editEng, sText3, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("Current Is Equal To 10 A"), lcl_translitTest(editEng, sText3, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("CURRENT IS EQUAL TO 10 A"), lcl_translitTest(editEng, sText3, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("current is equal to 10 A"), lcl_translitTest(editEng, sText3, esel, TF::UPPERCASE_LOWERCASE)); + +} + +void Test::testTdf147196() +{ + EditEngine editEng( mpItemPool.get() ); + editEng.SetText("2.2 Publication of information - CAA\nSection 4.2 of a CA\'s Certificate Policy and/or Certification Practice Statement SHALL state the CA\'s policy or practice on processing CAA Records for Fully Qualified Domain Names; that policy shall be consistent with these Requirements. \n\nIt shall clearly specify the set of Issuer Domain Names that the CA recognises in CAA \"issue\" or \"issuewild\" records as permitting it to issue. The CA SHALL log all actions taken, if any, consistent with its processing practice."); + editEng.TransliterateText(ESelection(0, 0, 3, 232), TransliterationFlags::TITLE_CASE); + CPPUNIT_ASSERT_EQUAL(OUString("2.2 Publication Of Information - Caa\nSection 4.2 Of A Ca\'s Certificate Policy And/Or Certification Practice Statement Shall State The Ca\'s Policy Or Practice On Processing Caa Records For Fully Qualified Domain Names; That Policy Shall Be Consistent With These Requirements. \n\nIt Shall Clearly Specify The Set Of Issuer Domain Names That The Ca Recognises In Caa \"Issue\" Or \"Issuewild\" Records As Permitting It To Issue. The Ca Shall Log All Actions Taken, If Any, Consistent With Its Processing Practice."), editEng.GetText()); +} + +void Test::testTdf148148() +{ + using TF = TransliterationFlags; + EditEngine editEng( mpItemPool.get() ); + + /* Test what happens when node contains text but selection does not contain any text */ + int selStart = 0; + int selEnd = 3; + ESelection esel(0, selStart, 0, selEnd); + const OUString sText1(" text"); + editEng.SetText(sText1); + CPPUNIT_ASSERT_EQUAL(OUString(" "), editEng.GetText(esel)); + + CPPUNIT_ASSERT_EQUAL(OUString(" text"), lcl_translitTest(editEng, sText1, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString(" text"), lcl_translitTest(editEng, sText1, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString(" text"), lcl_translitTest(editEng, sText1, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString(" text"), lcl_translitTest(editEng, sText1, esel, TF::UPPERCASE_LOWERCASE)); + + selStart = 4; + selEnd = 8; + esel = ESelection(0, selStart, 0, selEnd); + const OUString sText2("text "); + editEng.SetText(sText2); + CPPUNIT_ASSERT_EQUAL(OUString(" "), editEng.GetText(esel)); + + CPPUNIT_ASSERT_EQUAL(OUString("text "), lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("text "), lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("text "), lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("text "), lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE)); + + /* Test what happens when node contains only non-word text but selection does not contain any text */ + selStart = 0; + selEnd = 3; + esel = ESelection(0, selStart, 0, selEnd); + const OUString sText3(" -1"); + editEng.SetText(sText3); + CPPUNIT_ASSERT_EQUAL(OUString(" "), editEng.GetText(esel)); + + CPPUNIT_ASSERT_EQUAL(OUString(" -1"), lcl_translitTest(editEng, sText3, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString(" -1"), lcl_translitTest(editEng, sText3, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString(" -1"), lcl_translitTest(editEng, sText3, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString(" -1"), lcl_translitTest(editEng, sText3, esel, TF::UPPERCASE_LOWERCASE)); + + selStart = 2; + selEnd = 6; + esel = ESelection(0, selStart, 0, selEnd); + const OUString sText4("-1 "); + editEng.SetText(sText4); + CPPUNIT_ASSERT_EQUAL(OUString(" "), editEng.GetText(esel)); + + CPPUNIT_ASSERT_EQUAL(OUString("-1 "), lcl_translitTest(editEng, sText4, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("-1 "), lcl_translitTest(editEng, sText4, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("-1 "), lcl_translitTest(editEng, sText4, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("-1 "), lcl_translitTest(editEng, sText4, esel, TF::UPPERCASE_LOWERCASE)); + + /* Test what happens when node and selection contains only non-word text */ + selStart = 0; + selEnd = 5; + esel = ESelection(0, selStart, 0, selEnd); + const OUString sText5(" -1"); + editEng.SetText(sText3); + CPPUNIT_ASSERT_EQUAL(OUString(" -1"), editEng.GetText(esel)); + + CPPUNIT_ASSERT_EQUAL(OUString(" -1"), lcl_translitTest(editEng, sText5, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString(" -1"), lcl_translitTest(editEng, sText5, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString(" -1"), lcl_translitTest(editEng, sText5, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString(" -1"), lcl_translitTest(editEng, sText5, esel, TF::UPPERCASE_LOWERCASE)); + + selStart = 0; + selEnd = 5; + esel = ESelection(0, selStart, 0, selEnd); + const OUString sText6("-1 "); + editEng.SetText(sText4); + CPPUNIT_ASSERT_EQUAL(OUString("-1 "), editEng.GetText(esel)); + + CPPUNIT_ASSERT_EQUAL(OUString("-1 "), lcl_translitTest(editEng, sText6, esel, TF::SENTENCE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("-1 "), lcl_translitTest(editEng, sText6, esel, TF::TITLE_CASE)); + CPPUNIT_ASSERT_EQUAL(OUString("-1 "), lcl_translitTest(editEng, sText6, esel, TF::LOWERCASE_UPPERCASE)); + CPPUNIT_ASSERT_EQUAL(OUString("-1 "), lcl_translitTest(editEng, sText6, esel, TF::UPPERCASE_LOWERCASE)); + + +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); + +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleComponentBase.cxx b/editeng/source/accessibility/AccessibleComponentBase.cxx new file mode 100644 index 000000000..5e95afbd2 --- /dev/null +++ b/editeng/source/accessibility/AccessibleComponentBase.cxx @@ -0,0 +1,149 @@ +/* -*- 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 <editeng/AccessibleComponentBase.hxx> + +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> + +#include <tools/color.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +namespace accessibility { + +// internal + +AccessibleComponentBase::AccessibleComponentBase() +{ +} + + +AccessibleComponentBase::~AccessibleComponentBase() +{ +} + +// XAccessibleComponent + +sal_Bool SAL_CALL AccessibleComponentBase::containsPoint ( + const css::awt::Point& aPoint) +{ + awt::Size aSize (getSize()); + return (aPoint.X >= 0) + && (aPoint.X < aSize.Width) + && (aPoint.Y >= 0) + && (aPoint.Y < aSize.Height); +} + + +uno::Reference<XAccessible > SAL_CALL + AccessibleComponentBase::getAccessibleAtPoint ( + const awt::Point& /*aPoint*/) +{ + return uno::Reference<XAccessible>(); +} + + +awt::Rectangle SAL_CALL AccessibleComponentBase::getBounds() +{ + return awt::Rectangle(); +} + + +awt::Point SAL_CALL AccessibleComponentBase::getLocation() +{ + awt::Rectangle aBBox (getBounds()); + return awt::Point (aBBox.X, aBBox.Y); +} + + +awt::Point SAL_CALL AccessibleComponentBase::getLocationOnScreen() +{ + return awt::Point(); +} + + +css::awt::Size SAL_CALL AccessibleComponentBase::getSize() +{ + awt::Rectangle aBBox (getBounds()); + return awt::Size (aBBox.Width, aBBox.Height); +} + + +void SAL_CALL AccessibleComponentBase::grabFocus() +{ + uno::Reference<XAccessibleContext> xContext (this, uno::UNO_QUERY); + uno::Reference<XAccessibleSelection> xSelection ( + xContext->getAccessibleParent(), uno::UNO_QUERY); + if (xSelection.is()) + { + // Do a single selection on this object. + xSelection->clearAccessibleSelection(); + xSelection->selectAccessibleChild (xContext->getAccessibleIndexInParent()); + } +} + + +sal_Int32 SAL_CALL AccessibleComponentBase::getForeground() +{ + return sal_Int32(COL_BLACK); +} + + +sal_Int32 SAL_CALL AccessibleComponentBase::getBackground() +{ + return sal_Int32(COL_WHITE); +} + + +// XAccessibleExtendedComponent + +css::uno::Reference< css::awt::XFont > SAL_CALL + AccessibleComponentBase::getFont() +{ + return uno::Reference<awt::XFont>(); +} + + +OUString SAL_CALL AccessibleComponentBase::getTitledBorderText() +{ + return OUString(); +} + + +OUString SAL_CALL AccessibleComponentBase::getToolTipText() +{ + return OUString(); +} + +// XTypeProvider + +uno::Sequence<uno::Type> + AccessibleComponentBase::getTypes() +{ + static const uno::Sequence aTypeList { + cppu::UnoType<XAccessibleComponent>::get(), + cppu::UnoType<XAccessibleExtendedComponent>::get() }; + return aTypeList; +} + + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleContextBase.cxx b/editeng/source/accessibility/AccessibleContextBase.cxx new file mode 100644 index 000000000..4cd16d735 --- /dev/null +++ b/editeng/source/accessibility/AccessibleContextBase.cxx @@ -0,0 +1,527 @@ +/* -*- 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 <editeng/AccessibleContextBase.hxx> + +#include <com/sun/star/accessibility/XAccessibleEventListener.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/IllegalAccessibleComponentStateException.hpp> + +#include <unotools/accessiblestatesethelper.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <comphelper/accessibleeventnotifier.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> + +#include <utility> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +namespace accessibility { + +// internal + +AccessibleContextBase::AccessibleContextBase ( + uno::Reference<XAccessible> xParent, + const sal_Int16 aRole) + : WeakComponentImplHelper(m_aMutex), + mxParent(std::move(xParent)), + meDescriptionOrigin(NotSet), + meNameOrigin(NotSet), + mnClientId(0), + maRole(aRole) +{ + // Create the state set. + rtl::Reference<::utl::AccessibleStateSetHelper> pStateSet = new ::utl::AccessibleStateSetHelper (); + mxStateSet = pStateSet; + + // Set some states. Don't use the SetState method because no events + // shall be broadcasted (that is not yet initialized anyway). + pStateSet->AddState (AccessibleStateType::ENABLED); + pStateSet->AddState (AccessibleStateType::SENSITIVE); + pStateSet->AddState (AccessibleStateType::SHOWING); + pStateSet->AddState (AccessibleStateType::VISIBLE); + pStateSet->AddState (AccessibleStateType::FOCUSABLE); + pStateSet->AddState (AccessibleStateType::SELECTABLE); + + // Create the relation set. + mxRelationSet = new ::utl::AccessibleRelationSetHelper (); +} + +AccessibleContextBase::~AccessibleContextBase() +{ +} + +bool AccessibleContextBase::SetState (sal_Int16 aState) +{ + ::osl::ClearableMutexGuard aGuard (m_aMutex); + if ((mxStateSet != nullptr) && !mxStateSet->contains(aState)) + { + mxStateSet->AddState (aState); + // Clear the mutex guard so that it is not locked during calls to + // listeners. + aGuard.clear(); + + // Send event for all states except the DEFUNC state. + if (aState != AccessibleStateType::DEFUNC) + { + uno::Any aNewValue; + aNewValue <<= aState; + CommitChange( + AccessibleEventId::STATE_CHANGED, + aNewValue, + uno::Any()); + } + return true; + } + else + return false; +} + + +bool AccessibleContextBase::ResetState (sal_Int16 aState) +{ + ::osl::ClearableMutexGuard aGuard (m_aMutex); + if ((mxStateSet != nullptr) && mxStateSet->contains(aState)) + { + mxStateSet->RemoveState (aState); + // Clear the mutex guard so that it is not locked during calls to listeners. + aGuard.clear(); + + uno::Any aOldValue; + aOldValue <<= aState; + CommitChange( + AccessibleEventId::STATE_CHANGED, + uno::Any(), + aOldValue); + return true; + } + else + return false; +} + + +bool AccessibleContextBase::GetState (sal_Int16 aState) +{ + ::osl::MutexGuard aGuard (m_aMutex); + if (mxStateSet != nullptr) + return mxStateSet->contains(aState); + else + // If there is no state set then return false as a default value. + return false; +} + + +void AccessibleContextBase::SetRelationSet ( + const uno::Reference<XAccessibleRelationSet>& rxNewRelationSet) +{ + // Try to emit some meaningful events indicating differing relations in + // both sets. + typedef std::pair<short int,short int> RD; + const RD aRelationDescriptors[] = { + RD(AccessibleRelationType::CONTROLLED_BY, AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED), + RD(AccessibleRelationType::CONTROLLER_FOR, AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED), + RD(AccessibleRelationType::LABELED_BY, AccessibleEventId::LABELED_BY_RELATION_CHANGED), + RD(AccessibleRelationType::LABEL_FOR, AccessibleEventId::LABEL_FOR_RELATION_CHANGED), + RD(AccessibleRelationType::MEMBER_OF, AccessibleEventId::MEMBER_OF_RELATION_CHANGED), + RD(AccessibleRelationType::INVALID, -1), + }; + for (int i=0; aRelationDescriptors[i].first!=AccessibleRelationType::INVALID; i++) + if (mxRelationSet->containsRelation(aRelationDescriptors[i].first) + != rxNewRelationSet->containsRelation(aRelationDescriptors[i].first)) + CommitChange (aRelationDescriptors[i].second, uno::Any(), uno::Any()); + + mxRelationSet = rxNewRelationSet; +} + + +// XAccessible + +uno::Reference< XAccessibleContext> SAL_CALL + AccessibleContextBase::getAccessibleContext() +{ + return this; +} + + +// XAccessibleContext + +/** No children. +*/ +sal_Int32 SAL_CALL + AccessibleContextBase::getAccessibleChildCount() +{ + return 0; +} + + +/** Forward the request to the shape. Return the requested shape or throw + an exception for a wrong index. +*/ +uno::Reference<XAccessible> SAL_CALL + AccessibleContextBase::getAccessibleChild (sal_Int32 nIndex) +{ + ThrowIfDisposed (); + throw lang::IndexOutOfBoundsException ( + "no child with index " + OUString::number(nIndex), + nullptr); +} + + +uno::Reference<XAccessible> SAL_CALL + AccessibleContextBase::getAccessibleParent() +{ + ThrowIfDisposed (); + return mxParent; +} + + +sal_Int32 SAL_CALL + AccessibleContextBase::getAccessibleIndexInParent() +{ + ThrowIfDisposed (); + // 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()) + // Return -1 to indicate that this object's parent does not know about the + // object. + return -1; + + uno::Reference<XAccessibleContext> xParentContext ( + mxParent->getAccessibleContext()); + if (xParentContext.is()) + { + sal_Int32 nChildCount = xParentContext->getAccessibleChildCount(); + for (sal_Int32 i=0; i<nChildCount; i++) + { + uno::Reference<XAccessible> xChild (xParentContext->getAccessibleChild (i)); + if (xChild.is()) + { + uno::Reference<XAccessibleContext> xChildContext = xChild->getAccessibleContext(); + if (xChildContext == static_cast<XAccessibleContext*>(this)) + return i; + } + } + } + + // Return -1 to indicate that this object's parent does not know about the + // object. + return -1; +} + + +sal_Int16 SAL_CALL + AccessibleContextBase::getAccessibleRole() +{ + ThrowIfDisposed (); + return maRole; +} + + +OUString SAL_CALL + AccessibleContextBase::getAccessibleDescription() +{ + ThrowIfDisposed (); + + return msDescription; +} + + +OUString SAL_CALL + AccessibleContextBase::getAccessibleName() +{ + ThrowIfDisposed (); + + if (meNameOrigin == NotSet) + { + // Do not send an event because this is the first time it has been + // requested. + msName = CreateAccessibleName(); + meNameOrigin = AutomaticallyCreated; + } + + return msName; +} + + +/** Return a copy of the relation set. +*/ +uno::Reference<XAccessibleRelationSet> SAL_CALL + AccessibleContextBase::getAccessibleRelationSet() +{ + ThrowIfDisposed (); + + // Create a copy of the relation set and return it. + ::utl::AccessibleRelationSetHelper* pRelationSet = + static_cast< ::utl::AccessibleRelationSetHelper*>(mxRelationSet.get()); + if (pRelationSet != nullptr) + { + return uno::Reference<XAccessibleRelationSet> ( + new ::utl::AccessibleRelationSetHelper (*pRelationSet)); + } + else + return uno::Reference<XAccessibleRelationSet>(nullptr); +} + + +/** Return a copy of the state set. + Possible states are: + ENABLED + SHOWING + VISIBLE +*/ +uno::Reference<XAccessibleStateSet> SAL_CALL + AccessibleContextBase::getAccessibleStateSet() +{ + rtl::Reference<::utl::AccessibleStateSetHelper> pStateSet; + + if (rBHelper.bDisposed) + { + // We are already disposed! + // Create a new state set that has only set the DEFUNC state. + pStateSet = new ::utl::AccessibleStateSetHelper (); + pStateSet->AddState (AccessibleStateType::DEFUNC); + } + else + { + // Create a copy of the state set and return it. + pStateSet = mxStateSet; + + if (pStateSet != nullptr) + pStateSet = new ::utl::AccessibleStateSetHelper (*pStateSet); + } + + return pStateSet; +} + + +lang::Locale SAL_CALL + AccessibleContextBase::getLocale() +{ + ThrowIfDisposed (); + // Delegate request to parent. + if (mxParent.is()) + { + uno::Reference<XAccessibleContext> xParentContext ( + mxParent->getAccessibleContext()); + if (xParentContext.is()) + return xParentContext->getLocale (); + } + + // No locale and no parent. Therefore throw exception to indicate this + // cluelessness. + throw IllegalAccessibleComponentStateException (); +} + + +// XAccessibleEventListener + +void SAL_CALL AccessibleContextBase::addAccessibleEventListener ( + const uno::Reference<XAccessibleEventListener >& rxListener) +{ + if (!rxListener.is()) + return; + + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + uno::Reference<uno::XInterface> x (static_cast<lang::XComponent *>(this), uno::UNO_QUERY); + rxListener->disposing (lang::EventObject (x)); + } + else + { + if (!mnClientId) + mnClientId = comphelper::AccessibleEventNotifier::registerClient( ); + comphelper::AccessibleEventNotifier::addEventListener( mnClientId, rxListener ); + } +} + + +void SAL_CALL AccessibleContextBase::removeAccessibleEventListener ( + const uno::Reference<XAccessibleEventListener >& rxListener ) +{ + ThrowIfDisposed (); + if (!(rxListener.is() && mnClientId)) + return; + + sal_Int32 nListenerCount = comphelper::AccessibleEventNotifier::removeEventListener( mnClientId, rxListener ); + 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; + } +} + +// XServiceInfo +OUString SAL_CALL AccessibleContextBase::getImplementationName() +{ + return "AccessibleContextBase"; +} + +sal_Bool SAL_CALL AccessibleContextBase::supportsService (const OUString& sServiceName) +{ + return cppu::supportsService(this, sServiceName); +} + +uno::Sequence< OUString > SAL_CALL + AccessibleContextBase::getSupportedServiceNames() +{ + return { + "com.sun.star.accessibility.Accessible", + "com.sun.star.accessibility.AccessibleContext"}; +} + + +// XTypeProvider + +uno::Sequence<sal_Int8> SAL_CALL + AccessibleContextBase::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// internal + +void SAL_CALL AccessibleContextBase::disposing() +{ + SetState (AccessibleStateType::DEFUNC); + + ::osl::MutexGuard aGuard (m_aMutex); + + // Send a disposing to all listeners. + if ( mnClientId ) + { + comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( mnClientId, *this ); + mnClientId = 0; + } +} + + +void AccessibleContextBase::SetAccessibleDescription ( + const OUString& rDescription, + StringOrigin eDescriptionOrigin) +{ + if (!(eDescriptionOrigin < meDescriptionOrigin + || (eDescriptionOrigin == meDescriptionOrigin && msDescription != rDescription))) + return; + + uno::Any aOldValue, aNewValue; + aOldValue <<= msDescription; + aNewValue <<= rDescription; + + msDescription = rDescription; + meDescriptionOrigin = eDescriptionOrigin; + + CommitChange( + AccessibleEventId::DESCRIPTION_CHANGED, + aNewValue, + aOldValue); +} + + +void AccessibleContextBase::SetAccessibleName ( + const OUString& rName, + StringOrigin eNameOrigin) +{ + if (!(eNameOrigin < meNameOrigin + || (eNameOrigin == meNameOrigin && msName != rName))) + return; + + uno::Any aOldValue, aNewValue; + aOldValue <<= msName; + aNewValue <<= rName; + + msName = rName; + meNameOrigin = eNameOrigin; + + CommitChange( + AccessibleEventId::NAME_CHANGED, + aNewValue, + aOldValue); +} + + +OUString AccessibleContextBase::CreateAccessibleName() +{ + return "Empty Name"; +} + + +void AccessibleContextBase::CommitChange ( + sal_Int16 nEventId, + const uno::Any& rNewValue, + const uno::Any& rOldValue) +{ + // Do not call FireEvent and do not even create the event object when no + // listener has been registered yet. Creating the event object can + // otherwise lead to a crash. See issue 93419 for details. + if (mnClientId != 0) + { + AccessibleEventObject aEvent ( + static_cast<XAccessibleContext*>(this), + nEventId, + rNewValue, + rOldValue); + + FireEvent (aEvent); + } +} + + +void AccessibleContextBase::FireEvent (const AccessibleEventObject& aEvent) +{ + if (mnClientId) + comphelper::AccessibleEventNotifier::addEvent( mnClientId, aEvent ); +} + + +void AccessibleContextBase::ThrowIfDisposed() +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ("object has been already disposed", + static_cast<uno::XWeak*>(this)); + } +} + + +bool AccessibleContextBase::IsDisposed() const +{ + return (rBHelper.bDisposed || rBHelper.bInDispose); +} + + +void AccessibleContextBase::SetAccessibleRole( sal_Int16 _nRole ) +{ + maRole = _nRole; +} + + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleEditableTextPara.cxx b/editeng/source/accessibility/AccessibleEditableTextPara.cxx new file mode 100644 index 000000000..c6c6520cc --- /dev/null +++ b/editeng/source/accessibility/AccessibleEditableTextPara.cxx @@ -0,0 +1,2710 @@ +/* -*- 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 . + */ + + +// Global header + + +#include <algorithm> +#include <utility> +#include <vcl/svapp.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> +#include <editeng/flditem.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/i18n/Boundary.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <comphelper/accessibleeventnotifier.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <vcl/unohelp.hxx> +#include <vcl/settings.hxx> +#include <i18nlangtag/languagetag.hxx> + +#include <editeng/editeng.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/unoipset.hxx> +#include <editeng/outliner.hxx> +#include <editeng/unoedprx.hxx> +#include <editeng/unoedsrc.hxx> +#include <svl/eitem.hxx> + + +// Project-local header + + +#include <com/sun/star/beans/PropertyState.hpp> + +#include <unopracc.hxx> +#include <editeng/AccessibleEditableTextPara.hxx> +#include "AccessibleHyperlink.hxx" +#include "AccessibleImageBullet.hxx" + +#include <svtools/colorcfg.hxx> +#include <editeng/editrids.hrc> +#include <editeng/eerdll.hxx> +#include <editeng/numitem.hxx> +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::accessibility; + + +// AccessibleEditableTextPara implementation + + +namespace accessibility +{ + static const SvxItemPropertySet* ImplGetSvxCharAndParaPropertiesSet() + { + // PropertyMap for character and paragraph properties + static const SfxItemPropertyMapEntry aPropMap[] = + { + SVX_UNOEDIT_OUTLINER_PROPERTIES, + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_PARA_PROPERTIES, + SVX_UNOEDIT_NUMBERING_PROPERTY, + { u"TextUserDefinedAttributes", EE_CHAR_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + { u"ParaUserDefinedAttributes", EE_PARA_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static SvxItemPropertySet aPropSet( aPropMap, EditEngine::GetGlobalItemPool() ); + return &aPropSet; + } + + // #i27138# - add parameter <_pParaManager> + AccessibleEditableTextPara::AccessibleEditableTextPara( + uno::Reference< XAccessible > xParent, + const AccessibleParaManager* _pParaManager ) + : mnParagraphIndex( 0 ), + mnIndexInParent( 0 ), + mpEditSource( nullptr ), + maEEOffset( 0, 0 ), + mxParent(std::move( xParent )), + // well, that's strictly (UNO) exception safe, though not + // really robust. We rely on the fact that this member is + // constructed last, and that the constructor body catches + // exceptions, thus no chance for exceptions once the Id is + // fetched. Nevertheless, normally should employ RAII here... + mnNotifierClientId(::comphelper::AccessibleEventNotifier::registerClient()), + // #i27138# + mpParaManager( _pParaManager ) + { + + try + { + // Create the state set. + mxStateSet = new ::utl::AccessibleStateSetHelper (); + + // these are always on + mxStateSet->AddState( AccessibleStateType::MULTI_LINE ); + mxStateSet->AddState( AccessibleStateType::FOCUSABLE ); + mxStateSet->AddState( AccessibleStateType::VISIBLE ); + mxStateSet->AddState( AccessibleStateType::SHOWING ); + mxStateSet->AddState( AccessibleStateType::ENABLED ); + mxStateSet->AddState( AccessibleStateType::SENSITIVE ); + } + catch (const uno::Exception&) + { + } + } + + AccessibleEditableTextPara::~AccessibleEditableTextPara() + { + // sign off from event notifier + if( getNotifierClientId() != -1 ) + { + try + { + ::comphelper::AccessibleEventNotifier::revokeClient( getNotifierClientId() ); + } + catch (const uno::Exception&) + { + } + } + } + + OUString AccessibleEditableTextPara::implGetText() + { + return GetTextRange( 0, GetTextLen() ); + } + + css::lang::Locale AccessibleEditableTextPara::implGetLocale() + { + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getLocale: paragraph index value overflow"); + + // return locale of first character in the paragraph + return LanguageTag(GetTextForwarder().GetLanguage( GetParagraphIndex(), 0 )).getLocale(); + } + + void AccessibleEditableTextPara::implGetSelection( sal_Int32& nStartIndex, sal_Int32& nEndIndex ) + { + sal_Int32 nStart, nEnd; + + if( GetSelection( nStart, nEnd ) ) + { + nStartIndex = nStart; + nEndIndex = nEnd; + } + else + { + // #102234# No exception, just set to 'invalid' + nStartIndex = -1; + nEndIndex = -1; + } + } + + void AccessibleEditableTextPara::implGetParagraphBoundary( const OUString& rText, css::i18n::Boundary& rBoundary, sal_Int32 /*nIndex*/ ) + { + SAL_INFO( "editeng", "AccessibleEditableTextPara::implGetParagraphBoundary: only a base implementation, ignoring the index" ); + + rBoundary.startPos = 0; + rBoundary.endPos = rText.getLength(); + } + + void AccessibleEditableTextPara::implGetLineBoundary( const OUString&, css::i18n::Boundary& rBoundary, sal_Int32 nIndex ) + { + SvxTextForwarder& rCacheTF = GetTextForwarder(); + const sal_Int32 nParaIndex = GetParagraphIndex(); + + DBG_ASSERT(nParaIndex >= 0, + "AccessibleEditableTextPara::implGetLineBoundary: paragraph index value overflow"); + + const sal_Int32 nTextLen = rCacheTF.GetTextLen( nParaIndex ); + + CheckPosition(nIndex); + + rBoundary.startPos = rBoundary.endPos = -1; + + const sal_Int32 nLineCount=rCacheTF.GetLineCount( nParaIndex ); + + if( nIndex == nTextLen ) + { + // #i17014# Special-casing one-behind-the-end character + if( nLineCount <= 1 ) + rBoundary.startPos = 0; + else + rBoundary.startPos = nTextLen - rCacheTF.GetLineLen( nParaIndex, + nLineCount-1 ); + + rBoundary.endPos = nTextLen; + } + else + { + // normal line search + sal_Int32 nLine; + sal_Int32 nCurIndex; + for( nLine=0, nCurIndex=0; nLine<nLineCount; ++nLine ) + { + nCurIndex += rCacheTF.GetLineLen( nParaIndex, nLine); + + if( nCurIndex > nIndex ) + { + rBoundary.startPos = nCurIndex - rCacheTF.GetLineLen( nParaIndex, nLine); + rBoundary.endPos = nCurIndex; + break; + } + } + } + } + + + void AccessibleEditableTextPara::SetIndexInParent( sal_Int32 nIndex ) + { + mnIndexInParent = nIndex; + } + + + void AccessibleEditableTextPara::SetParagraphIndex( sal_Int32 nIndex ) + { + sal_Int32 nOldIndex = mnParagraphIndex; + + mnParagraphIndex = nIndex; + + auto aChild( maImageBullet.get() ); + if( aChild.is() ) + aChild->SetParagraphIndex(mnParagraphIndex); + + try + { + if( nOldIndex != nIndex ) + { + uno::Any aOldDesc; + uno::Any aOldName; + + try + { + aOldDesc <<= getAccessibleDescription(); + aOldName <<= getAccessibleName(); + } + catch (const uno::Exception&) // optional behaviour + { + } + // index and therefore description changed + FireEvent( AccessibleEventId::DESCRIPTION_CHANGED, uno::Any( getAccessibleDescription() ), aOldDesc ); + FireEvent( AccessibleEventId::NAME_CHANGED, uno::Any( getAccessibleName() ), aOldName ); + } + } + catch (const uno::Exception&) // optional behaviour + { + } + } + + + void AccessibleEditableTextPara::Dispose() + { + int nClientId( getNotifierClientId() ); + + // #108212# drop all references before notifying dispose + mxParent = nullptr; + mnNotifierClientId = -1; + mpEditSource = nullptr; + + // notify listeners + if( nClientId == -1 ) + return; + + try + { + uno::Reference < XAccessibleContext > xThis = getAccessibleContext(); + + // #106234# Delegate to EventNotifier + ::comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( nClientId, xThis ); + } + catch (const uno::Exception&) + { + } + } + + void AccessibleEditableTextPara::SetEditSource( SvxEditSourceAdapter* pEditSource ) + { + auto aChild( maImageBullet.get() ); + if( aChild.is() ) + aChild->SetEditSource(pEditSource); + + if( !pEditSource ) + { + // going defunc + UnSetState( AccessibleStateType::SHOWING ); + UnSetState( AccessibleStateType::VISIBLE ); + SetState( AccessibleStateType::INVALID ); + SetState( AccessibleStateType::DEFUNC ); + + Dispose(); + } + mpEditSource = pEditSource; + // #108900# Init last text content + try + { + TextChanged(); + } + catch (const uno::RuntimeException&) + { + } + } + + ESelection AccessibleEditableTextPara::MakeSelection( sal_Int32 nStartEEIndex, sal_Int32 nEndEEIndex ) + { + // check overflow + DBG_ASSERT(nStartEEIndex >= 0 && + nEndEEIndex >= 0 && + GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::MakeSelection: index value overflow"); + + sal_Int32 nParaIndex = GetParagraphIndex(); + return ESelection(nParaIndex, nStartEEIndex, nParaIndex, nEndEEIndex); + } + + ESelection AccessibleEditableTextPara::MakeSelection( sal_Int32 nEEIndex ) + { + return MakeSelection( nEEIndex, nEEIndex+1 ); + } + + ESelection AccessibleEditableTextPara::MakeCursor( sal_Int32 nEEIndex ) + { + return MakeSelection( nEEIndex, nEEIndex ); + } + + void AccessibleEditableTextPara::CheckIndex( sal_Int32 nIndex ) + { + if( nIndex < 0 || nIndex >= getCharacterCount() ) + throw lang::IndexOutOfBoundsException("AccessibleEditableTextPara: character index out of bounds", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > (this) ) ); // disambiguate hierarchy + } + + void AccessibleEditableTextPara::CheckPosition( sal_Int32 nIndex ) + { + if( nIndex < 0 || nIndex > getCharacterCount() ) + throw lang::IndexOutOfBoundsException("AccessibleEditableTextPara: character position out of bounds", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > (this) ) ); // disambiguate hierarchy + } + + void AccessibleEditableTextPara::CheckRange( sal_Int32 nStart, sal_Int32 nEnd ) + { + CheckPosition( nStart ); + CheckPosition( nEnd ); + } + + bool AccessibleEditableTextPara::GetSelection(sal_Int32 &nStartPos, sal_Int32 &nEndPos) + { + ESelection aSelection; + sal_Int32 nPara = GetParagraphIndex(); + if( !GetEditViewForwarder().GetSelection( aSelection ) ) + return false; + + if( aSelection.nStartPara < aSelection.nEndPara ) + { + if( aSelection.nStartPara > nPara || + aSelection.nEndPara < nPara ) + return false; + + if( nPara == aSelection.nStartPara ) + nStartPos = aSelection.nStartPos; + else + nStartPos = 0; + + if( nPara == aSelection.nEndPara ) + nEndPos = aSelection.nEndPos; + else + nEndPos = GetTextLen(); + } + else + { + if( aSelection.nStartPara < nPara || + aSelection.nEndPara > nPara ) + return false; + + if( nPara == aSelection.nStartPara ) + nStartPos = aSelection.nStartPos; + else + nStartPos = GetTextLen(); + + if( nPara == aSelection.nEndPara ) + nEndPos = aSelection.nEndPos; + else + nEndPos = 0; + } + + return true; + } + + OUString AccessibleEditableTextPara::GetTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + return GetTextForwarder().GetText( MakeSelection(nStartIndex, nEndIndex) ); + } + + sal_Int32 AccessibleEditableTextPara::GetTextLen() const + { + return GetTextForwarder().GetTextLen(GetParagraphIndex()); + } + + SvxEditSourceAdapter& AccessibleEditableTextPara::GetEditSource() const + { + if( !mpEditSource ) + throw uno::RuntimeException("No edit source, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleEditableTextPara* > (this) ) ) ); // disambiguate hierarchy + return *mpEditSource; + } + + SvxAccessibleTextAdapter& AccessibleEditableTextPara::GetTextForwarder() const + { + SvxEditSourceAdapter& rEditSource = GetEditSource(); + SvxAccessibleTextAdapter* pTextForwarder = rEditSource.GetTextForwarderAdapter(); + + if( !pTextForwarder ) + throw uno::RuntimeException("Unable to fetch text forwarder, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleEditableTextPara* > (this) ) ) ); // disambiguate hierarchy + + if( !pTextForwarder->IsValid() ) + throw uno::RuntimeException("Text forwarder is invalid, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleEditableTextPara* > (this) ) ) ); // disambiguate hierarchy + return *pTextForwarder; + } + + SvxViewForwarder& AccessibleEditableTextPara::GetViewForwarder() const + { + SvxEditSource& rEditSource = GetEditSource(); + SvxViewForwarder* pViewForwarder = rEditSource.GetViewForwarder(); + + if( !pViewForwarder ) + { + throw uno::RuntimeException("Unable to fetch view forwarder, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleEditableTextPara* > (this) ) ) ); // disambiguate hierarchy + } + + if( !pViewForwarder->IsValid() ) + throw uno::RuntimeException("View forwarder is invalid, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleEditableTextPara* > (this) ) ) ); // disambiguate hierarchy + return *pViewForwarder; + } + + SvxAccessibleTextEditViewAdapter& AccessibleEditableTextPara::GetEditViewForwarder( bool bCreate ) const + { + SvxEditSourceAdapter& rEditSource = GetEditSource(); + SvxAccessibleTextEditViewAdapter* pTextEditViewForwarder = rEditSource.GetEditViewForwarderAdapter( bCreate ); + + if( !pTextEditViewForwarder ) + { + if( bCreate ) + throw uno::RuntimeException("Unable to fetch view forwarder, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleEditableTextPara* > (this) ) ) ); // disambiguate hierarchy + else + throw uno::RuntimeException("No view forwarder, object not in edit mode", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleEditableTextPara* > (this) ) ) ); // disambiguate hierarchy + } + + if( pTextEditViewForwarder->IsValid() ) + return *pTextEditViewForwarder; + else + { + if( bCreate ) + throw uno::RuntimeException("View forwarder is invalid, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleEditableTextPara* > (this) ) ) ); // disambiguate hierarchy + else + throw uno::RuntimeException("View forwarder is invalid, object not in edit mode", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleEditableTextPara* > (this) ) ) ); // disambiguate hierarchy + } + } + + bool AccessibleEditableTextPara::HaveEditView() const + { + SvxEditSource& rEditSource = GetEditSource(); + SvxEditViewForwarder* pViewForwarder = rEditSource.GetEditViewForwarder(); + + if( !pViewForwarder ) + return false; + + if( !pViewForwarder->IsValid() ) + return false; + + return true; + } + + bool AccessibleEditableTextPara::HaveChildren() + { + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::HaveChildren: paragraph index value overflow"); + + return GetTextForwarder().HaveImageBullet( GetParagraphIndex() ); + } + + tools::Rectangle AccessibleEditableTextPara::LogicToPixel( const tools::Rectangle& rRect, const MapMode& rMapMode, SvxViewForwarder const & rForwarder ) + { + // convert to screen coordinates + return tools::Rectangle( rForwarder.LogicToPixel( rRect.TopLeft(), rMapMode ), + rForwarder.LogicToPixel( rRect.BottomRight(), rMapMode ) ); + } + + + void AccessibleEditableTextPara::SetEEOffset( const Point& rOffset ) + { + auto aChild( maImageBullet.get() ); + if( aChild.is() ) + aChild->SetEEOffset(rOffset); + + maEEOffset = rOffset; + } + + void AccessibleEditableTextPara::FireEvent(const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue) const + { + uno::Reference < XAccessibleContext > xThis( const_cast< AccessibleEditableTextPara* > (this)->getAccessibleContext() ); + + AccessibleEventObject aEvent(xThis, nEventId, rNewValue, rOldValue); + + // #106234# Delegate to EventNotifier + if( getNotifierClientId() != -1 ) + ::comphelper::AccessibleEventNotifier::addEvent( getNotifierClientId(), + aEvent ); + } + + void AccessibleEditableTextPara::SetState( const sal_Int16 nStateId ) + { + if( mxStateSet != nullptr && + !mxStateSet->contains(nStateId) ) + { + mxStateSet->AddState( nStateId ); + FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any( nStateId ) ); + } + } + + void AccessibleEditableTextPara::UnSetState( const sal_Int16 nStateId ) + { + if( mxStateSet != nullptr && + mxStateSet->contains(nStateId) ) + { + mxStateSet->RemoveState( nStateId ); + FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any(), uno::Any( nStateId ) ); + } + } + + void AccessibleEditableTextPara::TextChanged() + { + OUString aCurrentString( implGetText() ); + uno::Any aDeleted; + uno::Any aInserted; + if( OCommonAccessibleText::implInitTextChangedEvent( maLastTextString, aCurrentString, + aDeleted, aInserted) ) + { + FireEvent( AccessibleEventId::TEXT_CHANGED, aInserted, aDeleted ); + maLastTextString = aCurrentString; + } + } + + bool AccessibleEditableTextPara::GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, sal_Int32 nIndex ) + { + DBG_ASSERT(nIndex >= 0, + "AccessibleEditableTextPara::GetAttributeRun: index value overflow"); + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getLocale: paragraph index value overflow"); + + return GetTextForwarder().GetAttributeRun( nStartIndex, + nEndIndex, + GetParagraphIndex(), + nIndex ); + } + + uno::Any SAL_CALL AccessibleEditableTextPara::queryInterface (const uno::Type & rType) + { + uno::Any aRet; + + // must provide XAccessibleText by hand, since it comes publicly inherited by XAccessibleEditableText + if ( rType == cppu::UnoType<XAccessibleText>::get()) + { + uno::Reference< XAccessibleText > aAccText = static_cast< XAccessibleEditableText * >(this); + aRet <<= aAccText; + } + else if ( rType == cppu::UnoType<XAccessibleEditableText>::get()) + { + uno::Reference< XAccessibleEditableText > aAccEditText = this; + aRet <<= aAccEditText; + } + else if ( rType == cppu::UnoType<XAccessibleHypertext>::get()) + { + uno::Reference< XAccessibleHypertext > aAccHyperText = this; + aRet <<= aAccHyperText; + } + else + { + aRet = AccessibleTextParaInterfaceBase::queryInterface(rType); + } + + return aRet; + } + + // XAccessible + uno::Reference< XAccessibleContext > SAL_CALL AccessibleEditableTextPara::getAccessibleContext() + { + // We implement the XAccessibleContext interface in the same object + return uno::Reference< XAccessibleContext > ( this ); + } + + // XAccessibleContext + sal_Int32 SAL_CALL AccessibleEditableTextPara::getAccessibleChildCount() + { + SolarMutexGuard aGuard; + + return HaveChildren() ? 1 : 0; + } + + uno::Reference< XAccessible > SAL_CALL AccessibleEditableTextPara::getAccessibleChild( sal_Int32 i ) + { + SolarMutexGuard aGuard; + + if( !HaveChildren() ) + throw lang::IndexOutOfBoundsException("No children available", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > (this) ) ); // static_cast: disambiguate hierarchy + + if( i != 0 ) + throw lang::IndexOutOfBoundsException("Invalid child index", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > (this) ) ); // static_cast: disambiguate hierarchy + + auto aChild( maImageBullet.get() ); + + if( !aChild.is() ) + { + // there is no hard reference available, create object then + aChild = new AccessibleImageBullet(this); + + aChild->SetEditSource( &GetEditSource() ); + aChild->SetParagraphIndex( GetParagraphIndex() ); + aChild->SetIndexInParent( i ); + + maImageBullet = aChild; + } + + return aChild; + } + + uno::Reference< XAccessible > SAL_CALL AccessibleEditableTextPara::getAccessibleParent() + { + SAL_WARN_IF(!mxParent.is(), "editeng", "AccessibleEditableTextPara::getAccessibleParent: no frontend set, did somebody forgot to call AccessibleTextHelper::SetEventSource()?"); + + return mxParent; + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getAccessibleIndexInParent() + { + return mnIndexInParent; + } + + sal_Int16 SAL_CALL AccessibleEditableTextPara::getAccessibleRole() + { + return AccessibleRole::PARAGRAPH; + } + + OUString SAL_CALL AccessibleEditableTextPara::getAccessibleDescription() + { + SolarMutexGuard aGuard; + + // append first 40 characters from text, or first line, if shorter + // (writer takes first sentence here, but that's not supported + // from EditEngine) + // throws if defunc + OUString aLine; + + if( getCharacterCount() ) + aLine = getTextAtIndex(0, AccessibleTextType::LINE).SegmentText; + + // Get the string from the resource for the specified id. + OUString sStr(EditResId(RID_SVXSTR_A11Y_PARAGRAPH_DESCRIPTION)); + OUString sParaIndex = OUString::number(GetParagraphIndex()); + sStr = sStr.replaceFirst("$(ARG)", sParaIndex); + + if( aLine.getLength() > MaxDescriptionLen ) + { + OUString aCurrWord; + sal_Int32 i; + + // search backward from MaxDescriptionLen for previous word start + for( aCurrWord=getTextAtIndex(MaxDescriptionLen, AccessibleTextType::WORD).SegmentText, + i=MaxDescriptionLen, + aLine=OUString(); + i>=0; + --i ) + { + if( getTextAtIndex(i, AccessibleTextType::WORD).SegmentText != aCurrWord ) + { + if( i == 0 ) + // prevent completely empty string + aLine = getTextAtIndex(0, AccessibleTextType::WORD).SegmentText; + else + aLine = getTextRange(0, i); + } + } + } + + return sStr + aLine; + } + + OUString SAL_CALL AccessibleEditableTextPara::getAccessibleName() + { + //See tdf#101003 before implementing a body + return OUString(); + } + + uno::Reference< XAccessibleRelationSet > SAL_CALL AccessibleEditableTextPara::getAccessibleRelationSet() + { + // #i27138# - provide relations CONTENT_FLOWS_FROM + // and CONTENT_FLOWS_TO + if ( mpParaManager ) + { + rtl::Reference<utl::AccessibleRelationSetHelper> pAccRelSetHelper = + new utl::AccessibleRelationSetHelper(); + sal_Int32 nMyParaIndex( GetParagraphIndex() ); + // relation CONTENT_FLOWS_FROM + if ( nMyParaIndex > 0 && + mpParaManager->IsReferencable( nMyParaIndex - 1 ) ) + { + uno::Sequence<uno::Reference<XInterface> > aSequence + { static_cast<cppu::OWeakObject *>(mpParaManager->GetChild( nMyParaIndex - 1 ).first.get().get()) }; + AccessibleRelation aAccRel( AccessibleRelationType::CONTENT_FLOWS_FROM, + aSequence ); + pAccRelSetHelper->AddRelation( aAccRel ); + } + + // relation CONTENT_FLOWS_TO + if ( (nMyParaIndex + 1) < mpParaManager->GetNum() && + mpParaManager->IsReferencable( nMyParaIndex + 1 ) ) + { + uno::Sequence<uno::Reference<XInterface> > aSequence + { static_cast<cppu::OWeakObject *>(mpParaManager->GetChild( nMyParaIndex + 1 ).first.get().get()) }; + AccessibleRelation aAccRel( AccessibleRelationType::CONTENT_FLOWS_TO, + aSequence ); + pAccRelSetHelper->AddRelation( aAccRel ); + } + + return pAccRelSetHelper; + } + else + { + // no relations, therefore empty + return uno::Reference< XAccessibleRelationSet >(); + } + } + + static uno::Sequence< OUString > const & getAttributeNames() + { + static const uno::Sequence<OUString> aNames{ + "CharColor", + "CharContoured", + "CharEmphasis", + "CharEscapement", + "CharFontName", + "CharHeight", + "CharPosture", + "CharShadowed", + "CharStrikeout", + "CharCaseMap", + "CharUnderline", + "CharUnderlineColor", + "CharWeight", + "NumberingLevel", + "NumberingRules", + "ParaAdjust", + "ParaBottomMargin", + "ParaFirstLineIndent", + "ParaLeftMargin", + "ParaLineSpacing", + "ParaRightMargin", + "ParaTabStops"}; + + return aNames; + } + + namespace { + + struct IndexCompare + { + const PropertyValue* pValues; + explicit IndexCompare( const PropertyValue* pVals ) : pValues(pVals) {} + bool operator() ( sal_Int32 a, sal_Int32 b ) const + { + return pValues[a].Name < pValues[b].Name; + } + }; + + } +} + +namespace +{ + OUString GetFieldTypeNameFromField(EFieldInfo const &ree) + { + OUString strFldType; + sal_Int32 nFieldType = -1; + if (ree.pFieldItem) + { + // So we get a field, check its type now. + nFieldType = ree.pFieldItem->GetField()->GetClassId() ; + } + switch (nFieldType) + { + case text::textfield::Type::DATE: + { + const SvxDateField* pDateField = static_cast< const SvxDateField* >(ree.pFieldItem->GetField()); + if (pDateField) + { + if (pDateField->GetType() == SvxDateType::Fix) + strFldType = "date (fixed)"; + else if (pDateField->GetType() == SvxDateType::Var) + strFldType = "date (variable)"; + } + break; + } + case text::textfield::Type::PAGE: + strFldType = "page-number"; + break; + //support the sheet name & pages fields + case text::textfield::Type::PAGES: + strFldType = "page-count"; + break; + case text::textfield::Type::TABLE: + strFldType = "sheet-name"; + break; + //End + case text::textfield::Type::TIME: + strFldType = "time"; + break; + case text::textfield::Type::EXTENDED_TIME: + { + const SvxExtTimeField* pTimeField = static_cast< const SvxExtTimeField* >(ree.pFieldItem->GetField()); + if (pTimeField) + { + if (pTimeField->GetType() == SvxTimeType::Fix) + strFldType = "time (fixed)"; + else if (pTimeField->GetType() == SvxTimeType::Var) + strFldType = "time (variable)"; + } + break; + } + case text::textfield::Type::AUTHOR: + strFldType = "author"; + break; + case text::textfield::Type::EXTENDED_FILE: + case text::textfield::Type::DOCINFO_TITLE: + strFldType = "file name"; + break; + case text::textfield::Type::DOCINFO_CUSTOM: + strFldType = "custom document property"; + break; + default: + break; + } + return strFldType; + } +} + +namespace accessibility +{ + OUString AccessibleEditableTextPara::GetFieldTypeNameAtIndex(sal_Int32 nIndex) + { + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); + //For field object info + sal_Int32 nParaIndex = GetParagraphIndex(); + sal_Int32 nAllFieldLen = 0; + sal_Int32 nField = rCacheTF.GetFieldCount(nParaIndex); + for (sal_Int32 j = 0; j < nField; ++j) + { + EFieldInfo ree = rCacheTF.GetFieldInfo(nParaIndex, j); + sal_Int32 reeBegin = ree.aPosition.nIndex + nAllFieldLen; + sal_Int32 reeEnd = reeBegin + ree.aCurrentText.getLength(); + nAllFieldLen += (ree.aCurrentText.getLength() - 1); + if (nIndex < reeBegin) + break; + if (nIndex < reeEnd) + return GetFieldTypeNameFromField(ree); + } + return OUString(); + } + + uno::Reference< XAccessibleStateSet > SAL_CALL AccessibleEditableTextPara::getAccessibleStateSet() + { + SolarMutexGuard aGuard; + + // Create a copy of the state set and return it. + + if( !mxStateSet ) + return uno::Reference<XAccessibleStateSet>(); + uno::Reference<XAccessibleStateSet> xParentStates; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + xParentStates = xParentContext->getAccessibleStateSet(); + } + if (xParentStates.is() && xParentStates->contains(AccessibleStateType::EDITABLE) ) + { + mxStateSet->AddState(AccessibleStateType::EDITABLE); + } + return uno::Reference<XAccessibleStateSet>( new ::utl::AccessibleStateSetHelper (*mxStateSet) ); + } + + lang::Locale SAL_CALL AccessibleEditableTextPara::getLocale() + { + SolarMutexGuard aGuard; + + return implGetLocale(); + } + + void SAL_CALL AccessibleEditableTextPara::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { + if( getNotifierClientId() != -1 ) + ::comphelper::AccessibleEventNotifier::addEventListener( getNotifierClientId(), xListener ); + } + + void SAL_CALL AccessibleEditableTextPara::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { + if( getNotifierClientId() == -1 ) + return; + + const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), 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::TClientId nId( getNotifierClientId() ); + mnNotifierClientId = -1; + ::comphelper::AccessibleEventNotifier::revokeClient( nId ); + } + } + + // XAccessibleComponent + sal_Bool SAL_CALL AccessibleEditableTextPara::containsPoint( const awt::Point& aTmpPoint ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::contains: index value overflow"); + + awt::Rectangle aTmpRect = getBounds(); + tools::Rectangle aRect( Point(aTmpRect.X, aTmpRect.Y), Size(aTmpRect.Width, aTmpRect.Height) ); + Point aPoint( aTmpPoint.X, aTmpPoint.Y ); + + return aRect.Contains( aPoint ); + } + + uno::Reference< XAccessible > SAL_CALL AccessibleEditableTextPara::getAccessibleAtPoint( const awt::Point& _aPoint ) + { + SolarMutexGuard aGuard; + + if( HaveChildren() ) + { + // #103862# No longer need to make given position relative + Point aPoint( _aPoint.X, _aPoint.Y ); + + // respect EditEngine offset to surrounding shape/cell + aPoint -= GetEEOffset(); + + // convert to EditEngine coordinate system + SvxTextForwarder& rCacheTF = GetTextForwarder(); + Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) ); + + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(GetParagraphIndex()); + + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType == SVX_NUM_BITMAP ) + { + tools::Rectangle aRect = aBulletInfo.aBounds; + + if( aRect.Contains( aLogPoint ) ) + return getAccessibleChild(0); + } + } + + // no children at all, or none at given position + return uno::Reference< XAccessible >(); + } + + awt::Rectangle SAL_CALL AccessibleEditableTextPara::getBounds() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getBounds: index value overflow"); + + SvxTextForwarder& rCacheTF = GetTextForwarder(); + tools::Rectangle aRect = rCacheTF.GetParaBounds( GetParagraphIndex() ); + + // convert to screen coordinates + tools::Rectangle aScreenRect = AccessibleEditableTextPara::LogicToPixel( aRect, + rCacheTF.GetMapMode(), + GetViewForwarder() ); + + // offset from shape/cell + Point aOffset = GetEEOffset(); + + return awt::Rectangle( aScreenRect.Left() + aOffset.X(), + aScreenRect.Top() + aOffset.Y(), + aScreenRect.GetSize().Width(), + aScreenRect.GetSize().Height() ); + } + + awt::Point SAL_CALL AccessibleEditableTextPara::getLocation( ) + { + SolarMutexGuard aGuard; + + awt::Rectangle aRect = getBounds(); + + return awt::Point( aRect.X, aRect.Y ); + } + + awt::Point SAL_CALL AccessibleEditableTextPara::getLocationOnScreen( ) + { + SolarMutexGuard aGuard; + + // relate us to parent + uno::Reference< XAccessible > xParent = getAccessibleParent(); + if( xParent.is() ) + { + uno::Reference< XAccessibleComponent > xParentComponent( xParent, uno::UNO_QUERY ); + if( xParentComponent.is() ) + { + awt::Point aRefPoint = xParentComponent->getLocationOnScreen(); + awt::Point aPoint = getLocation(); + aPoint.X += aRefPoint.X; + aPoint.Y += aRefPoint.Y; + + return aPoint; + } + // #i88070# + // fallback to parent's <XAccessibleContext> instance + else + { + uno::Reference< XAccessibleContext > xParentContext = xParent->getAccessibleContext(); + if ( xParentContext.is() ) + { + uno::Reference< XAccessibleComponent > xParentContextComponent( xParentContext, uno::UNO_QUERY ); + if( xParentContextComponent.is() ) + { + awt::Point aRefPoint = xParentContextComponent->getLocationOnScreen(); + awt::Point aPoint = getLocation(); + aPoint.X += aRefPoint.X; + aPoint.Y += aRefPoint.Y; + + return aPoint; + } + } + } + } + + throw uno::RuntimeException("Cannot access parent", + uno::Reference< uno::XInterface > + ( static_cast< XAccessible* > (this) ) ); // disambiguate hierarchy + } + + awt::Size SAL_CALL AccessibleEditableTextPara::getSize( ) + { + SolarMutexGuard aGuard; + + awt::Rectangle aRect = getBounds(); + + return awt::Size( aRect.Width, aRect.Height ); + } + + void SAL_CALL AccessibleEditableTextPara::grabFocus( ) + { + // set cursor to this paragraph + setSelection(0,0); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getForeground( ) + { + // #104444# Added to XAccessibleComponent interface + svtools::ColorConfig aColorConfig; + Color nColor = aColorConfig.GetColorValue( svtools::FONTCOLOR ).nColor; + return static_cast<sal_Int32>(nColor); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getBackground( ) + { + // #104444# Added to XAccessibleComponent interface + Color aColor( Application::GetSettings().GetStyleSettings().GetWindowColor() ); + + // the background is transparent + aColor.SetAlpha(0); + + return static_cast<sal_Int32>( aColor ); + } + + // XAccessibleText + sal_Int32 SAL_CALL AccessibleEditableTextPara::getCaretPosition() + { + SolarMutexGuard aGuard; + + if( !HaveEditView() ) + return -1; + + ESelection aSelection; + if( GetEditViewForwarder().GetSelection( aSelection ) && + GetParagraphIndex() == aSelection.nEndPara ) + { + // caret is always nEndPara,nEndPos + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ) + { + sal_Int32 nBulletLen = aBulletInfo.aText.getLength(); + if( aSelection.nEndPos - nBulletLen >= 0 ) + return aSelection.nEndPos - nBulletLen; + } + return aSelection.nEndPos; + } + + // not within this paragraph + return -1; + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::setCaretPosition( sal_Int32 nIndex ) + { + return setSelection(nIndex, nIndex); + } + + sal_Unicode SAL_CALL AccessibleEditableTextPara::getCharacter( sal_Int32 nIndex ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getCharacter: index value overflow"); + + return OCommonAccessibleText::implGetCharacter( implGetText(), nIndex ); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleEditableTextPara::getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& rRequestedAttributes ) + { + SolarMutexGuard aGuard; + + //Skip the bullet range to ignore the bullet text + SvxTextForwarder& rCacheTF = GetTextForwarder(); + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(GetParagraphIndex()); + if (aBulletInfo.bVisible) + nIndex += aBulletInfo.aText.getLength(); + CheckIndex(nIndex); // may throw IndexOutOfBoundsException + + bool bSupplementalMode = false; + uno::Sequence< OUString > aPropertyNames = rRequestedAttributes; + if (!aPropertyNames.hasElements()) + { + bSupplementalMode = true; + aPropertyNames = getAttributeNames(); + } + + // get default attributes... + ::comphelper::SequenceAsHashMap aPropHashMap( getDefaultAttributes( aPropertyNames ) ); + + // ... and override them with the direct attributes from the specific position + const uno::Sequence< beans::PropertyValue > aRunAttribs( getRunAttributes( nIndex, aPropertyNames ) ); + for (auto const& rRunAttrib : aRunAttribs) + { + aPropHashMap[ rRunAttrib.Name ] = rRunAttrib.Value; //!! should not only be the value !! + } + + // get resulting sequence + uno::Sequence< beans::PropertyValue > aRes; + aPropHashMap >> aRes; + + // since SequenceAsHashMap ignores property handles and property state + // we have to restore the property state here (property handles are + // of no use to the accessibility API). + for (beans::PropertyValue & rRes : asNonConstRange(aRes)) + { + bool bIsDirectVal = false; + for (auto const& rRunAttrib : aRunAttribs) + { + bIsDirectVal = rRes.Name == rRunAttrib.Name; + if (bIsDirectVal) + break; + } + rRes.Handle = -1; + rRes.State = bIsDirectVal ? PropertyState_DIRECT_VALUE : PropertyState_DEFAULT_VALUE; + } + if( bSupplementalMode ) + { + _correctValues( aRes ); + // NumberingPrefix + sal_Int32 nRes = aRes.getLength(); + aRes.realloc( nRes + 1 ); + beans::PropertyValue &rRes = aRes.getArray()[nRes]; + rRes.Name = "NumberingPrefix"; + OUString numStr; + if (aBulletInfo.nType != SVX_NUM_CHAR_SPECIAL && aBulletInfo.nType != SVX_NUM_BITMAP) + numStr = aBulletInfo.aText; + rRes.Value <<= numStr; + rRes.Handle = -1; + rRes.State = PropertyState_DIRECT_VALUE; + //For field object. + OUString strFieldType = GetFieldTypeNameAtIndex(nIndex); + if (!strFieldType.isEmpty()) + { + nRes = aRes.getLength(); + aRes.realloc( nRes + 1 ); + beans::PropertyValue &rResField = aRes.getArray()[nRes]; + rResField.Name = "FieldType"; + rResField.Value <<= strFieldType.toAsciiLowerCase(); + rResField.Handle = -1; + rResField.State = PropertyState_DIRECT_VALUE; + } + //sort property values + // build sorted index array + sal_Int32 nLength = aRes.getLength(); + const beans::PropertyValue* pPairs = aRes.getConstArray(); + std::unique_ptr<sal_Int32[]> pIndices(new sal_Int32[nLength]); + sal_Int32 i = 0; + for( i = 0; i < nLength; i++ ) + pIndices[i] = i; + std::sort( &pIndices[0], &pIndices[nLength], IndexCompare(pPairs) ); + // create sorted sequences according to index array + uno::Sequence<beans::PropertyValue> aNewValues( nLength ); + beans::PropertyValue* pNewValues = aNewValues.getArray(); + for( i = 0; i < nLength; i++ ) + { + pNewValues[i] = pPairs[pIndices[i]]; + } + + return aNewValues; + } + return aRes; + } + + awt::Rectangle SAL_CALL AccessibleEditableTextPara::getCharacterBounds( sal_Int32 nIndex ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getCharacterBounds: index value overflow"); + + // #108900# Have position semantics now for nIndex, as + // one-past-the-end values are legal, too. + CheckPosition( nIndex ); + + SvxTextForwarder& rCacheTF = GetTextForwarder(); + tools::Rectangle aRect = rCacheTF.GetCharBounds(GetParagraphIndex(), nIndex); + + // convert to screen + tools::Rectangle aScreenRect = AccessibleEditableTextPara::LogicToPixel( aRect, + rCacheTF.GetMapMode(), + GetViewForwarder() ); + // #109864# offset from parent (paragraph), but in screen + // coordinates. This makes sure the internal text offset in + // the outline view forwarder gets cancelled out here + awt::Rectangle aParaRect( getBounds() ); + aScreenRect.Move( -aParaRect.X, -aParaRect.Y ); + + // offset from shape/cell + Point aOffset = GetEEOffset(); + + return awt::Rectangle( aScreenRect.Left() + aOffset.X(), + aScreenRect.Top() + aOffset.Y(), + aScreenRect.GetSize().Width(), + aScreenRect.GetSize().Height() ); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getCharacterCount() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getCharacterCount: index value overflow"); + + return implGetText().getLength(); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getIndexAtPoint( const awt::Point& rPoint ) + { + SolarMutexGuard aGuard; + + sal_Int32 nPara; + sal_Int32 nIndex; + + // offset from surrounding cell/shape + Point aOffset( GetEEOffset() ); + Point aPoint( rPoint.X - aOffset.X(), rPoint.Y - aOffset.Y() ); + + // convert to logical coordinates + SvxTextForwarder& rCacheTF = GetTextForwarder(); + Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) ); + + // re-offset to parent (paragraph) + tools::Rectangle aParaRect = rCacheTF.GetParaBounds( GetParagraphIndex() ); + aLogPoint.Move( aParaRect.Left(), aParaRect.Top() ); + + if( rCacheTF.GetIndexAtPoint( aLogPoint, nPara, nIndex ) && + GetParagraphIndex() == nPara ) + { + // #102259# Double-check if we're _really_ on the given character + try + { + awt::Rectangle aRect1( getCharacterBounds(nIndex) ); + tools::Rectangle aRect2( aRect1.X, aRect1.Y, + aRect1.Width + aRect1.X, aRect1.Height + aRect1.Y ); + if( aRect2.Contains( Point( rPoint.X, rPoint.Y ) ) ) + return nIndex; + else + return -1; + } + catch (const lang::IndexOutOfBoundsException&) + { + // #103927# Don't throw for invalid nIndex values + return -1; + } + } + else + { + // not within our paragraph + return -1; + } + } + + OUString SAL_CALL AccessibleEditableTextPara::getSelectedText() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getSelectedText: index value overflow"); + + if( !HaveEditView() ) + return OUString(); + + return OCommonAccessibleText::getSelectedText(); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getSelectionStart() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getSelectionStart: index value overflow"); + + if( !HaveEditView() ) + return -1; + + return OCommonAccessibleText::getSelectionStart(); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getSelectionEnd() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getSelectionEnd: index value overflow"); + + if( !HaveEditView() ) + return -1; + + return OCommonAccessibleText::getSelectionEnd(); + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::setSelection: paragraph index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + try + { + SvxEditViewForwarder& rCacheVF = GetEditViewForwarder( true ); + return rCacheVF.SetSelection( MakeSelection(nStartIndex, nEndIndex) ); + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + OUString SAL_CALL AccessibleEditableTextPara::getText() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getText: paragraph index value overflow"); + + return implGetText(); + } + + OUString SAL_CALL AccessibleEditableTextPara::getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getTextRange: paragraph index value overflow"); + + return OCommonAccessibleText::implGetTextRange(implGetText(), nStartIndex, nEndIndex); + } + + void AccessibleEditableTextPara::_correctValues( uno::Sequence< PropertyValue >& rValues) + { + SvxTextForwarder& rCacheTF = GetTextForwarder(); + sal_Int32 nRes = rValues.getLength(); + beans::PropertyValue *pRes = rValues.getArray(); + for (sal_Int32 i = 0; i < nRes; ++i) + { + beans::PropertyValue &rRes = pRes[i]; + // Char color + if (rRes.Name == "CharColor") + { + uno::Any &anyChar = rRes.Value; + Color crChar(ColorTransparency, static_cast<sal_uInt32>( reinterpret_cast<sal_uIntPtr>(anyChar.pReserved))); + if (COL_AUTO == crChar ) + { + uno::Reference< css::accessibility::XAccessibleComponent > xComponent(mxParent,uno::UNO_QUERY); + if (xComponent.is()) + { + uno::Reference< css::accessibility::XAccessibleContext > xContext(xComponent,uno::UNO_QUERY); + if (xContext->getAccessibleRole() == AccessibleRole::SHAPE + || xContext->getAccessibleRole() == AccessibleRole::TABLE_CELL) + { + anyChar <<= COL_BLACK; + } + else + { + Color cr(ColorTransparency, xComponent->getBackground()); + crChar = cr.IsDark() ? COL_WHITE : COL_BLACK; + anyChar <<= crChar; + } + } + } + continue; + } + // Underline + if (rRes.Name == "CharUnderline") + { + continue; + } + // Underline color && Mis-spell + if (rRes.Name == "CharUnderlineColor") + { + uno::Any &anyCharUnderLine = rRes.Value; + Color crCharUnderLine(ColorTransparency, static_cast<sal_uInt32>( reinterpret_cast<sal_uIntPtr>( anyCharUnderLine.pReserved))); + if (COL_AUTO == crCharUnderLine ) + { + uno::Reference< css::accessibility::XAccessibleComponent > xComponent(mxParent,uno::UNO_QUERY); + if (xComponent.is()) + { + uno::Reference< css::accessibility::XAccessibleContext > xContext(xComponent,uno::UNO_QUERY); + if (xContext->getAccessibleRole() == AccessibleRole::SHAPE + || xContext->getAccessibleRole() == AccessibleRole::TABLE_CELL) + { + anyCharUnderLine <<= COL_BLACK; + } + else + { + Color cr(ColorTransparency, xComponent->getBackground()); + crCharUnderLine = cr.IsDark() ? COL_WHITE : COL_BLACK; + anyCharUnderLine <<= crCharUnderLine; + } + } + } + continue; + } + // NumberingLevel + if (rRes.Name == "NumberingLevel") + { + const SvxNumBulletItem& rNumBullet = rCacheTF.GetParaAttribs(GetParagraphIndex()).Get(EE_PARA_NUMBULLET); + if(rNumBullet.GetNumRule().GetLevelCount()==0) + { + rRes.Value <<= sal_Int16(-1); + rRes.Handle = -1; + rRes.State = PropertyState_DIRECT_VALUE; + } + else + { +// SvxAccessibleTextPropertySet aPropSet( &GetEditSource(), +// ImplGetSvxCharAndParaPropertiesMap() ); + // MT IA2 TODO: Check if this is the correct replacement for ImplGetSvxCharAndParaPropertiesMap + rtl::Reference< SvxAccessibleTextPropertySet > xPropSet( new SvxAccessibleTextPropertySet( &GetEditSource(), ImplGetSvxTextPortionSvxPropertySet() ) ); + + xPropSet->SetSelection( MakeSelection( 0, GetTextLen() ) ); + rRes.Value = xPropSet->_getPropertyValue( rRes.Name, mnParagraphIndex ); + rRes.State = xPropSet->_getPropertyState( rRes.Name, mnParagraphIndex ); + rRes.Handle = -1; + } + continue; + } + // NumberingRules + if (rRes.Name == "NumberingRules") + { + SfxItemSet aAttribs = rCacheTF.GetParaAttribs(GetParagraphIndex()); + bool bVis = aAttribs.Get( EE_PARA_BULLETSTATE ).GetValue(); + if(bVis) + { + rRes.Value <<= sal_Int16(-1); + rRes.Handle = -1; + rRes.State = PropertyState_DIRECT_VALUE; + } + else + { + // MT IA2 TODO: Check if this is the correct replacement for ImplGetSvxCharAndParaPropertiesMap + rtl::Reference< SvxAccessibleTextPropertySet > xPropSet( new SvxAccessibleTextPropertySet( &GetEditSource(), ImplGetSvxTextPortionSvxPropertySet() ) ); + xPropSet->SetSelection( MakeSelection( 0, GetTextLen() ) ); + rRes.Value = xPropSet->_getPropertyValue( rRes.Name, mnParagraphIndex ); + rRes.State = xPropSet->_getPropertyState( rRes.Name, mnParagraphIndex ); + rRes.Handle = -1; + } + continue; + } + } + } + sal_Int32 AccessibleEditableTextPara::SkipField(sal_Int32 nIndex, bool bForward) + { + sal_Int32 nParaIndex = GetParagraphIndex(); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); + sal_Int32 nAllFieldLen = 0; + sal_Int32 nField = rCacheTF.GetFieldCount(nParaIndex), nFoundFieldIndex = -1; + sal_Int32 reeBegin=0, reeEnd=0; + for (sal_Int32 j = 0; j < nField; ++j) + { + EFieldInfo ree = rCacheTF.GetFieldInfo(nParaIndex, j); + reeBegin = ree.aPosition.nIndex + nAllFieldLen; + reeEnd = reeBegin + ree.aCurrentText.getLength(); + nAllFieldLen += (ree.aCurrentText.getLength() - 1); + if (nIndex < reeBegin) + break; + if (!ree.pFieldItem) + continue; + if (nIndex < reeEnd) + { + if (ree.pFieldItem->GetField()->GetClassId() != text::textfield::Type::URL) + { + nFoundFieldIndex = j; + break; + } + } + } + if( nFoundFieldIndex >= 0 ) + { + if( bForward ) + return reeEnd - 1; + else + return reeBegin; + } + return nIndex; + } + void AccessibleEditableTextPara::ExtendByField( css::accessibility::TextSegment& Segment ) + { + sal_Int32 nParaIndex = GetParagraphIndex(); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); + sal_Int32 nAllFieldLen = 0; + sal_Int32 nField = rCacheTF.GetFieldCount(nParaIndex), nFoundFieldIndex = -1; + sal_Int32 reeBegin=0, reeEnd=0; + for (sal_Int32 j = 0; j < nField; ++j) + { + EFieldInfo ree = rCacheTF.GetFieldInfo(nParaIndex, j); + reeBegin = ree.aPosition.nIndex + nAllFieldLen; + reeEnd = reeBegin + ree.aCurrentText.getLength(); + nAllFieldLen += (ree.aCurrentText.getLength() - 1); + if( reeBegin > Segment.SegmentEnd ) + { + break; + } + if (!ree.pFieldItem) + continue; + if( (Segment.SegmentEnd > reeBegin && Segment.SegmentEnd <= reeEnd) || + (Segment.SegmentStart >= reeBegin && Segment.SegmentStart < reeEnd) ) + { + if(ree.pFieldItem->GetField()->GetClassId() != text::textfield::Type::URL) + { + nFoundFieldIndex = j; + break; + } + } + } + if( nFoundFieldIndex < 0 ) + return; + + bool bExtend = false; + if( Segment.SegmentEnd < reeEnd ) + { + Segment.SegmentEnd = reeEnd; + bExtend = true; + } + if( Segment.SegmentStart > reeBegin ) + { + Segment.SegmentStart = reeBegin; + bExtend = true; + } + if( !bExtend ) + return; + + //If there is a bullet before the field, should add the bullet length into the segment. + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(nParaIndex); + sal_Int32 nBulletLen = aBulletInfo.aText.getLength(); + if (nBulletLen > 0) + { + Segment.SegmentEnd += nBulletLen; + if (nFoundFieldIndex > 0) + Segment.SegmentStart += nBulletLen; + Segment.SegmentText = GetTextRange(Segment.SegmentStart, Segment.SegmentEnd); + //After get the correct field name, should restore the offset value which don't contain the bullet. + Segment.SegmentEnd -= nBulletLen; + if (nFoundFieldIndex > 0) + Segment.SegmentStart -= nBulletLen; + } + else + Segment.SegmentText = GetTextRange(Segment.SegmentStart, Segment.SegmentEnd); + } + + css::accessibility::TextSegment SAL_CALL AccessibleEditableTextPara::getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getTextAtIndex: paragraph index value overflow"); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + switch( aTextType ) + { + case AccessibleTextType::CHARACTER: + case AccessibleTextType::WORD: + { + aResult = OCommonAccessibleText::getTextAtIndex( nIndex, aTextType ); + ExtendByField( aResult ); + break; + } + // Not yet handled by OCommonAccessibleText. Missing + // implGetAttributeRunBoundary() method there + case AccessibleTextType::ATTRIBUTE_RUN: + { + const sal_Int32 nTextLen = GetTextForwarder().GetTextLen( GetParagraphIndex() ); + + if( nIndex == nTextLen ) + { + // #i17014# Special-casing one-behind-the-end character + aResult.SegmentStart = aResult.SegmentEnd = nTextLen; + } + else + { + sal_Int32 nStartIndex, nEndIndex; + //For the bullet paragraph, the bullet string is ignored for IAText::attributes() function. + SvxTextForwarder& rCacheTF = GetTextForwarder(); + // MT IA2: Not used? sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(GetParagraphIndex()); + if (aBulletInfo.bVisible) + nIndex += aBulletInfo.aText.getLength(); + if (nIndex != 0 && nIndex >= getCharacterCount()) + nIndex = getCharacterCount()-1; + CheckPosition(nIndex); + if( GetAttributeRun(nStartIndex, nEndIndex, nIndex) ) + { + aResult.SegmentText = GetTextRange(nStartIndex, nEndIndex); + if (aBulletInfo.bVisible) + { + nStartIndex -= aBulletInfo.aText.getLength(); + nEndIndex -= aBulletInfo.aText.getLength(); + } + aResult.SegmentStart = nStartIndex; + aResult.SegmentEnd = nEndIndex; + } + } + break; + } + case AccessibleTextType::LINE: + { + SvxTextForwarder& rCacheTF = GetTextForwarder(); + sal_Int32 nParaIndex = GetParagraphIndex(); + CheckPosition(nIndex); + if (nIndex != 0 && nIndex == getCharacterCount()) + --nIndex; + sal_Int32 nLine, nLineCount=rCacheTF.GetLineCount( nParaIndex ); + sal_Int32 nCurIndex; + //the problem is that rCacheTF.GetLineLen() will include the bullet length. But for the bullet line, + //the text value doesn't contain the bullet characters. all of the bullet and numbering info are exposed + //by the IAText::attributes(). So here must do special support for bullet line. + sal_Int32 nBulletLen = 0; + for( nLine=0, nCurIndex=0; nLine<nLineCount; ++nLine ) + { + if (nLine == 0) + { + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo( nParaIndex ); + if (aBulletInfo.bVisible) + { + //in bullet or numbering; + nBulletLen = aBulletInfo.aText.getLength(); + } + } + sal_Int32 nLineLen = rCacheTF.GetLineLen(nParaIndex, nLine); + if (nLine == 0) + nCurIndex += nLineLen - nBulletLen; + else + nCurIndex += nLineLen; + if( nCurIndex > nIndex ) + { + if (nLine ==0) + { + aResult.SegmentStart = 0; + aResult.SegmentEnd = nCurIndex; + aResult.SegmentText = GetTextRange( aResult.SegmentStart, aResult.SegmentEnd + nBulletLen); + break; + } + else + { + aResult.SegmentStart = nCurIndex - nLineLen; + aResult.SegmentEnd = nCurIndex; + //aResult.SegmentText = GetTextRange( aResult.SegmentStart, aResult.SegmentEnd ); + aResult.SegmentText = GetTextRange( aResult.SegmentStart + nBulletLen, aResult.SegmentEnd + nBulletLen); + break; + } + } + } + break; + } + default: + aResult = OCommonAccessibleText::getTextAtIndex( nIndex, aTextType ); + break; + } /* end of switch( aTextType ) */ + + return aResult; + } + + css::accessibility::TextSegment SAL_CALL AccessibleEditableTextPara::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getTextBeforeIndex: paragraph index value overflow"); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + i18n::Boundary aBoundary; + switch( aTextType ) + { + // Not yet handled by OCommonAccessibleText. Missing + // implGetAttributeRunBoundary() method there + case AccessibleTextType::ATTRIBUTE_RUN: + { + const sal_Int32 nTextLen = GetTextForwarder().GetTextLen( GetParagraphIndex() ); + sal_Int32 nStartIndex, nEndIndex; + + if( nIndex == nTextLen ) + { + // #i17014# Special-casing one-behind-the-end character + if( nIndex > 0 && + GetAttributeRun(nStartIndex, nEndIndex, nIndex-1) ) + { + aResult.SegmentText = GetTextRange(nStartIndex, nEndIndex); + aResult.SegmentStart = nStartIndex; + aResult.SegmentEnd = nEndIndex; + } + } + else + { + if( GetAttributeRun(nStartIndex, nEndIndex, nIndex) ) + { + // already at the left border? If not, query + // one index further left + if( nStartIndex > 0 && + GetAttributeRun(nStartIndex, nEndIndex, nStartIndex-1) ) + { + aResult.SegmentText = GetTextRange(nStartIndex, nEndIndex); + aResult.SegmentStart = nStartIndex; + aResult.SegmentEnd = nEndIndex; + } + } + } + break; + } + case AccessibleTextType::LINE: + { + SvxTextForwarder& rCacheTF = GetTextForwarder(); + sal_Int32 nParaIndex = GetParagraphIndex(); + + CheckPosition(nIndex); + + sal_Int32 nLine, nLineCount=rCacheTF.GetLineCount( nParaIndex ); + //the problem is that rCacheTF.GetLineLen() will include the bullet length. But for the bullet line, + //the text value doesn't contain the bullet characters. all of the bullet and numbering info are exposed + //by the IAText::attributes(). So here must do special support for bullet line. + sal_Int32 nCurIndex=0, nLastIndex=0, nCurLineLen=0; + sal_Int32 nLastLineLen = 0, nBulletLen = 0; + // get the line before the line the index points into + for( nLine=0, nCurIndex=0; nLine<nLineCount; ++nLine ) + { + nLastIndex = nCurIndex; + if (nLine == 0) + { + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(nParaIndex); + if (aBulletInfo.bVisible) + { + //in bullet or numbering; + nBulletLen = aBulletInfo.aText.getLength(); + } + } + if (nLine == 1) + nLastLineLen = nCurLineLen - nBulletLen; + else + nLastLineLen = nCurLineLen; + nCurLineLen = rCacheTF.GetLineLen( nParaIndex, nLine); + //nCurIndex += nCurLineLen; + if (nLine == 0) + nCurIndex += nCurLineLen - nBulletLen; + else + nCurIndex += nCurLineLen; + + //if( nCurIndex > nIndex && + //nLastIndex > nCurLineLen ) + if (nCurIndex > nIndex) + { + if (nLine == 0) + { + break; + } + else if (nLine == 1) + { + aResult.SegmentStart = 0; + aResult.SegmentEnd = nLastIndex; + aResult.SegmentText = GetTextRange( aResult.SegmentStart, aResult.SegmentEnd + nBulletLen); + break; + } + else + { + //aResult.SegmentStart = nLastIndex - nCurLineLen; + aResult.SegmentStart = nLastIndex - nLastLineLen; + aResult.SegmentEnd = nLastIndex; + aResult.SegmentText = GetTextRange( aResult.SegmentStart + nBulletLen, aResult.SegmentEnd + nBulletLen); + break; + } + } + } + + break; + } + case AccessibleTextType::WORD: + { + nIndex = SkipField( nIndex, false); + OUString sText( implGetText() ); + sal_Int32 nLength = sText.getLength(); + + // get word at index + implGetWordBoundary( sText, aBoundary, nIndex ); + + + //sal_Int32 curWordStart = aBoundary.startPos; + //sal_Int32 preWordStart = curWordStart; + sal_Int32 curWordStart , preWordStart; + if( aBoundary.startPos == -1 || aBoundary.startPos > nIndex) + curWordStart = preWordStart = nIndex; + else + curWordStart = preWordStart = aBoundary.startPos; + + // get previous word + + bool bWord = false; + + //while ( preWordStart > 0 && aBoundary.startPos == curWordStart) + while ( (preWordStart >= 0 && !bWord ) || ( aBoundary.endPos > curWordStart ) ) + { + preWordStart--; + bWord = implGetWordBoundary( sText, aBoundary, preWordStart ); + } + if ( bWord && implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + ExtendByField( aResult ); + } + } + break; + case AccessibleTextType::CHARACTER: + { + nIndex = SkipField( nIndex, false); + aResult = OCommonAccessibleText::getTextBeforeIndex( nIndex, aTextType ); + ExtendByField( aResult ); + break; + } + default: + aResult = OCommonAccessibleText::getTextBeforeIndex( nIndex, aTextType ); + break; + } /* end of switch( aTextType ) */ + + return aResult; + } + + css::accessibility::TextSegment SAL_CALL AccessibleEditableTextPara::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getTextBehindIndex: paragraph index value overflow"); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + i18n::Boundary aBoundary; + switch( aTextType ) + { + case AccessibleTextType::ATTRIBUTE_RUN: + { + sal_Int32 nStartIndex, nEndIndex; + + if( GetAttributeRun(nStartIndex, nEndIndex, nIndex) ) + { + // already at the right border? + if( nEndIndex < GetTextLen() ) + { + if( GetAttributeRun(nStartIndex, nEndIndex, nEndIndex) ) + { + aResult.SegmentText = GetTextRange(nStartIndex, nEndIndex); + aResult.SegmentStart = nStartIndex; + aResult.SegmentEnd = nEndIndex; + } + } + } + break; + } + + case AccessibleTextType::LINE: + { + SvxTextForwarder& rCacheTF = GetTextForwarder(); + sal_Int32 nParaIndex = GetParagraphIndex(); + + CheckPosition(nIndex); + + sal_Int32 nLine, nLineCount = rCacheTF.GetLineCount( nParaIndex ); + sal_Int32 nCurIndex; + //the problem is that rCacheTF.GetLineLen() will include the bullet length. But for the bullet line, + //the text value doesn't contain the bullet characters. all of the bullet and numbering info are exposed + //by the IAText::attributes(). So here must do special support for bullet line. + sal_Int32 nBulletLen = 0; + // get the line after the line the index points into + for( nLine=0, nCurIndex=0; nLine<nLineCount; ++nLine ) + { + if (nLine == 0) + { + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(nParaIndex); + if (aBulletInfo.bVisible) + { + //in bullet or numbering; + nBulletLen = aBulletInfo.aText.getLength(); + } + } + sal_Int32 nLineLen = rCacheTF.GetLineLen( nParaIndex, nLine); + + if (nLine == 0) + nCurIndex += nLineLen - nBulletLen; + else + nCurIndex += nLineLen; + + if( nCurIndex > nIndex && + nLine < nLineCount-1 ) + { + aResult.SegmentStart = nCurIndex; + aResult.SegmentEnd = nCurIndex + rCacheTF.GetLineLen( nParaIndex, nLine+1); + aResult.SegmentText = GetTextRange( aResult.SegmentStart + nBulletLen, aResult.SegmentEnd + nBulletLen); + break; + } + } + + break; + } + case AccessibleTextType::WORD: + { + nIndex = SkipField( nIndex, true); + OUString sText( implGetText() ); + sal_Int32 nLength = sText.getLength(); + + // get word at index + bool bWord = implGetWordBoundary( sText, aBoundary, nIndex ); + + // real current world + sal_Int32 nextWord = nIndex; + //if( nIndex >= aBoundary.startPos && nIndex <= aBoundary.endPos ) + if( nIndex <= aBoundary.endPos ) + { + nextWord = aBoundary.endPos; + if (nextWord < sText.getLength() && sText[nextWord] == u' ') nextWord++; + bWord = implGetWordBoundary( sText, aBoundary, nextWord ); + } + + if ( bWord && implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + + // If the end position of aBoundary is inside a field, extend the result to the end of the field + + ExtendByField( aResult ); + } + } + break; + + case AccessibleTextType::CHARACTER: + { + nIndex = SkipField( nIndex, true); + aResult = OCommonAccessibleText::getTextBehindIndex( nIndex, aTextType ); + ExtendByField( aResult ); + break; + } + default: + aResult = OCommonAccessibleText::getTextBehindIndex( nIndex, aTextType ); + break; + } /* end of switch( aTextType ) */ + + return aResult; + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + try + { + SvxEditViewForwarder& rCacheVF = GetEditViewForwarder( true ); + GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + bool aRetVal; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::copyText: index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + //Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + // save current selection + ESelection aOldSelection; + + rCacheVF.GetSelection( aOldSelection ); + //rCacheVF.SetSelection( MakeSelection(nStartIndex, nEndIndex) ); + rCacheVF.SetSelection( MakeSelection(nStartIndex + nBulletLen, nEndIndex + nBulletLen) ); + aRetVal = rCacheVF.Copy(); + rCacheVF.SetSelection( aOldSelection ); // restore + + return aRetVal; + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::scrollSubstringTo( sal_Int32, sal_Int32, AccessibleScrollType ) + { + return false; + } + + // XAccessibleEditableText + sal_Bool SAL_CALL AccessibleEditableTextPara::cutText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + + SolarMutexGuard aGuard; + + try + { + SvxEditViewForwarder& rCacheVF = GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::cutText: index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + // Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + ESelection aSelection = MakeSelection (nStartIndex + nBulletLen, nEndIndex + nBulletLen); + //if( !rCacheTF.IsEditable( MakeSelection(nStartIndex, nEndIndex) ) ) + if( !rCacheTF.IsEditable( aSelection ) ) + return false; // non-editable area selected + + // don't save selection, might become invalid after cut! + //rCacheVF.SetSelection( MakeSelection(nStartIndex, nEndIndex) ); + rCacheVF.SetSelection( aSelection ); + + return rCacheVF.Cut(); + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::pasteText( sal_Int32 nIndex ) + { + + SolarMutexGuard aGuard; + + try + { + SvxEditViewForwarder& rCacheVF = GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::pasteText: index value overflow"); + + CheckPosition(nIndex); + + // Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + if( !rCacheTF.IsEditable( MakeSelection(nIndex + nBulletLen) ) ) + return false; // non-editable area selected + + // #104400# set empty selection (=> cursor) to given index + //rCacheVF.SetSelection( MakeCursor(nIndex) ); + rCacheVF.SetSelection( MakeCursor(nIndex + nBulletLen) ); + + return rCacheVF.Paste(); + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::deleteText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + + SolarMutexGuard aGuard; + + try + { + // #102710# Request edit view when doing changes + // AccessibleEmptyEditSource relies on this behaviour + GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::deleteText: index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + // Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + ESelection aSelection = MakeSelection (nStartIndex + nBulletLen, nEndIndex + nBulletLen); + + //if( !rCacheTF.IsEditable( MakeSelection(nStartIndex, nEndIndex) ) ) + if( !rCacheTF.IsEditable( aSelection ) ) + return false; // non-editable area selected + + //sal_Bool bRet = rCacheTF.Delete( MakeSelection(nStartIndex, nEndIndex) ); + bool bRet = rCacheTF.Delete( aSelection ); + + GetEditSource().UpdateData(); + + return bRet; + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::insertText( const OUString& sText, sal_Int32 nIndex ) + { + + SolarMutexGuard aGuard; + + try + { + // #102710# Request edit view when doing changes + // AccessibleEmptyEditSource relies on this behaviour + GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::insertText: index value overflow"); + + CheckPosition(nIndex); + + // Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + + if( !rCacheTF.IsEditable( MakeSelection(nIndex + nBulletLen) ) ) + return false; // non-editable area selected + + // #104400# insert given text at empty selection (=> cursor) + bool bRet = rCacheTF.InsertText( sText, MakeCursor(nIndex + nBulletLen) ); + + rCacheTF.QuickFormatDoc(); + GetEditSource().UpdateData(); + + return bRet; + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::replaceText( sal_Int32 nStartIndex, sal_Int32 nEndIndex, const OUString& sReplacement ) + { + + SolarMutexGuard aGuard; + + try + { + // #102710# Request edit view when doing changes + // AccessibleEmptyEditSource relies on this behaviour + GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::replaceText: index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + // Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + ESelection aSelection = MakeSelection (nStartIndex + nBulletLen, nEndIndex + nBulletLen); + + //if( !rCacheTF.IsEditable( MakeSelection(nStartIndex, nEndIndex) ) ) + if( !rCacheTF.IsEditable( aSelection ) ) + return false; // non-editable area selected + + // insert given text into given range => replace + //sal_Bool bRet = rCacheTF.InsertText( sReplacement, MakeSelection(nStartIndex, nEndIndex) ); + bool bRet = rCacheTF.InsertText( sReplacement, aSelection ); + + rCacheTF.QuickFormatDoc(); + GetEditSource().UpdateData(); + + return bRet; + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::setAttributes( sal_Int32 nStartIndex, sal_Int32 nEndIndex, const uno::Sequence< beans::PropertyValue >& aAttributeSet ) + { + + SolarMutexGuard aGuard; + + try + { + // #102710# Request edit view when doing changes + // AccessibleEmptyEditSource relies on this behaviour + GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + sal_Int32 nPara = GetParagraphIndex(); + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::setAttributes: index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + if( !rCacheTF.IsEditable( MakeSelection(nStartIndex, nEndIndex) ) ) + return false; // non-editable area selected + + // do the indices span the whole paragraph? Then use the outliner map + // TODO: hold it as a member? + rtl::Reference< SvxAccessibleTextPropertySet > xPropSet( new SvxAccessibleTextPropertySet( &GetEditSource(), + 0 == nStartIndex && + rCacheTF.GetTextLen(nPara) == nEndIndex ? + ImplGetSvxUnoOutlinerTextCursorSvxPropertySet() : + ImplGetSvxTextPortionSvxPropertySet() ) ); + + xPropSet->SetSelection( MakeSelection(nStartIndex, nEndIndex) ); + + // convert from PropertyValue to Any + for(const beans::PropertyValue& rProp : aAttributeSet) + { + try + { + xPropSet->setPropertyValue(rProp.Name, rProp.Value); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "dbaccess", "AccessibleEditableTextPara::setAttributes exception in setPropertyValue"); + } + } + + rCacheTF.QuickFormatDoc(); + GetEditSource().UpdateData(); + + return true; + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::setText( const OUString& sText ) + { + + SolarMutexGuard aGuard; + + return replaceText(0, getCharacterCount(), sText); + } + + // XAccessibleTextAttributes + uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleEditableTextPara::getDefaultAttributes( + const uno::Sequence< OUString >& rRequestedAttributes ) + { + SolarMutexGuard aGuard; + + GetTextForwarder(); + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getCharacterAttributes: index value overflow"); + + // get XPropertySetInfo for paragraph attributes and + // character attributes that span all the paragraphs text. + rtl::Reference< SvxAccessibleTextPropertySet > xPropSet( new SvxAccessibleTextPropertySet( &GetEditSource(), + ImplGetSvxCharAndParaPropertiesSet() ) ); + xPropSet->SetSelection( MakeSelection( 0, GetTextLen() ) ); + uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo(); + if (!xPropSetInfo.is()) + throw uno::RuntimeException("Cannot query XPropertySetInfo", + uno::Reference< uno::XInterface > + ( static_cast< XAccessible* > (this) ) ); // disambiguate hierarchy + + // build sequence of available properties to check + uno::Sequence< beans::Property > aProperties; + if (const sal_Int32 nLenReqAttr = rRequestedAttributes.getLength()) + { + aProperties.realloc( nLenReqAttr ); + beans::Property *pProperties = aProperties.getArray(); + sal_Int32 nCurLen = 0; + for (const OUString& rRequestedAttribute : rRequestedAttributes) + { + beans::Property aProp; + try + { + aProp = xPropSetInfo->getPropertyByName( rRequestedAttribute ); + } + catch (const beans::UnknownPropertyException&) + { + continue; + } + pProperties[ nCurLen++ ] = aProp; + } + aProperties.realloc( nCurLen ); + } + else + aProperties = xPropSetInfo->getProperties(); + + // build resulting sequence + uno::Sequence< beans::PropertyValue > aOutSequence( aProperties.getLength() ); + beans::PropertyValue* pOutSequence = aOutSequence.getArray(); + sal_Int32 nOutLen = 0; + for (const beans::Property& rProperty : std::as_const(aProperties)) + { + // calling implementation functions: + // _getPropertyState and _getPropertyValue (see below) to provide + // the proper paragraph number when retrieving paragraph attributes + PropertyState eState = xPropSet->_getPropertyState( rProperty.Name, mnParagraphIndex ); + if ( eState == PropertyState_AMBIGUOUS_VALUE ) + { + OSL_FAIL( "ambiguous property value encountered" ); + } + + //if (eState == PropertyState_DIRECT_VALUE) + // per definition all paragraph properties and all character + // properties spanning the whole paragraph should be returned + // and declared as default value + { + pOutSequence->Name = rProperty.Name; + pOutSequence->Handle = rProperty.Handle; + pOutSequence->Value = xPropSet->_getPropertyValue( rProperty.Name, mnParagraphIndex ); + pOutSequence->State = PropertyState_DEFAULT_VALUE; + + ++pOutSequence; + ++nOutLen; + } + } + aOutSequence.realloc( nOutLen ); + + return aOutSequence; + } + + + uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleEditableTextPara::getRunAttributes( + sal_Int32 nIndex, + const uno::Sequence< OUString >& rRequestedAttributes ) + { + + SolarMutexGuard aGuard; + + GetTextForwarder(); + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getCharacterAttributes: index value overflow"); + + if( getCharacterCount() > 0 ) + CheckIndex(nIndex); + else + CheckPosition(nIndex); + + rtl::Reference< SvxAccessibleTextPropertySet > xPropSet( new SvxAccessibleTextPropertySet( &GetEditSource(), + ImplGetSvxCharAndParaPropertiesSet() ) ); + xPropSet->SetSelection( MakeSelection( nIndex ) ); + uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo(); + if (!xPropSetInfo.is()) + throw uno::RuntimeException("Cannot query XPropertySetInfo", + uno::Reference< uno::XInterface > + ( static_cast< XAccessible* > (this) ) ); // disambiguate hierarchy + + // build sequence of available properties to check + uno::Sequence< beans::Property > aProperties; + if (const sal_Int32 nLenReqAttr = rRequestedAttributes.getLength()) + { + aProperties.realloc( nLenReqAttr ); + beans::Property *pProperties = aProperties.getArray(); + sal_Int32 nCurLen = 0; + for (const OUString& rRequestedAttribute : rRequestedAttributes) + { + beans::Property aProp; + try + { + aProp = xPropSetInfo->getPropertyByName( rRequestedAttribute ); + } + catch (const beans::UnknownPropertyException&) + { + continue; + } + pProperties[ nCurLen++ ] = aProp; + } + aProperties.realloc( nCurLen ); + } + else + aProperties = xPropSetInfo->getProperties(); + + // build resulting sequence + uno::Sequence< beans::PropertyValue > aOutSequence( aProperties.getLength() ); + beans::PropertyValue* pOutSequence = aOutSequence.getArray(); + sal_Int32 nOutLen = 0; + for (const beans::Property& rProperty : std::as_const(aProperties)) + { + // calling 'regular' functions that will operate on the selection + PropertyState eState = xPropSet->getPropertyState( rProperty.Name ); + if (eState == PropertyState_DIRECT_VALUE) + { + pOutSequence->Name = rProperty.Name; + pOutSequence->Handle = rProperty.Handle; + pOutSequence->Value = xPropSet->getPropertyValue( rProperty.Name ); + pOutSequence->State = eState; + + ++pOutSequence; + ++nOutLen; + } + } + aOutSequence.realloc( nOutLen ); + + return aOutSequence; + } + + // XAccessibleHypertext + ::sal_Int32 SAL_CALL AccessibleEditableTextPara::getHyperLinkCount( ) + { + SvxAccessibleTextAdapter& rT = GetTextForwarder(); + const sal_Int32 nPara = GetParagraphIndex(); + + sal_Int32 nHyperLinks = 0; + sal_Int32 nFields = rT.GetFieldCount( nPara ); + for (sal_Int32 n = 0; n < nFields; ++n) + { + EFieldInfo aField = rT.GetFieldInfo( nPara, n ); + if ( dynamic_cast<const SvxURLField* >(aField.pFieldItem->GetField() ) != nullptr) + nHyperLinks++; + } + return nHyperLinks; + } + + css::uno::Reference< css::accessibility::XAccessibleHyperlink > SAL_CALL AccessibleEditableTextPara::getHyperLink( ::sal_Int32 nLinkIndex ) + { + css::uno::Reference< css::accessibility::XAccessibleHyperlink > xRef; + + SvxAccessibleTextAdapter& rT = GetTextForwarder(); + const sal_Int32 nPara = GetParagraphIndex(); + + sal_Int32 nHyperLink = 0; + sal_Int32 nFields = rT.GetFieldCount( nPara ); + for (sal_Int32 n = 0; n < nFields; ++n) + { + EFieldInfo aField = rT.GetFieldInfo( nPara, n ); + if ( dynamic_cast<const SvxURLField* >(aField.pFieldItem->GetField()) != nullptr ) + { + if ( nHyperLink == nLinkIndex ) + { + sal_Int32 nEEStart = aField.aPosition.nIndex; + + // Translate EE Index to accessible index + sal_Int32 nStart = rT.CalcEditEngineIndex( nPara, nEEStart ); + sal_Int32 nEnd = nStart + aField.aCurrentText.getLength(); + xRef = new AccessibleHyperlink( rT, new SvxFieldItem( *aField.pFieldItem ), nStart, nEnd, aField.aCurrentText ); + break; + } + nHyperLink++; + } + } + + return xRef; + } + + ::sal_Int32 SAL_CALL AccessibleEditableTextPara::getHyperLinkIndex( ::sal_Int32 nCharIndex ) + { + const sal_Int32 nPara = GetParagraphIndex(); + SvxAccessibleTextAdapter& rT = GetTextForwarder(); + + const sal_Int32 nEEIndex = rT.CalcEditEngineIndex( nPara, nCharIndex ); + sal_Int32 nHLIndex = -1; //i123620 + sal_Int32 nHyperLink = 0; + sal_Int32 nFields = rT.GetFieldCount( nPara ); + for (sal_Int32 n = 0; n < nFields; ++n) + { + EFieldInfo aField = rT.GetFieldInfo( nPara, n ); + if ( dynamic_cast<const SvxURLField* >( aField.pFieldItem->GetField() ) != nullptr) + { + if ( aField.aPosition.nIndex == nEEIndex ) + { + nHLIndex = nHyperLink; + break; + } + nHyperLink++; + } + } + + return nHLIndex; + } + + // XAccessibleMultiLineText + sal_Int32 SAL_CALL AccessibleEditableTextPara::getLineNumberAtIndex( sal_Int32 nIndex ) + { + + sal_Int32 nRes = -1; + sal_Int32 nPara = GetParagraphIndex(); + + SvxTextForwarder &rCacheTF = GetTextForwarder(); + const bool bValidPara = 0 <= nPara && nPara < rCacheTF.GetParagraphCount(); + DBG_ASSERT( bValidPara, "getLineNumberAtIndex: current paragraph index out of range" ); + if (bValidPara) + { + // we explicitly allow for the index to point at the character right behind the text + if (0 > nIndex || nIndex > rCacheTF.GetTextLen( nPara )) + throw lang::IndexOutOfBoundsException(); + nRes = rCacheTF.GetLineNumberAtIndex( nPara, nIndex ); + } + return nRes; + } + + // XAccessibleMultiLineText + css::accessibility::TextSegment SAL_CALL AccessibleEditableTextPara::getTextAtLineNumber( sal_Int32 nLineNo ) + { + + css::accessibility::TextSegment aResult; + sal_Int32 nPara = GetParagraphIndex(); + SvxTextForwarder &rCacheTF = GetTextForwarder(); + const bool bValidPara = 0 <= nPara && nPara < rCacheTF.GetParagraphCount(); + DBG_ASSERT( bValidPara, "getTextAtLineNumber: current paragraph index out of range" ); + if (bValidPara) + { + if (0 > nLineNo || nLineNo >= rCacheTF.GetLineCount( nPara )) + throw lang::IndexOutOfBoundsException(); + sal_Int32 nStart = 0, nEnd = 0; + rCacheTF.GetLineBoundaries( nStart, nEnd, nPara, nLineNo ); + if (nStart >= 0 && nEnd >= 0) + { + try + { + aResult.SegmentText = getTextRange( nStart, nEnd ); + aResult.SegmentStart = nStart; + aResult.SegmentEnd = nEnd; + } + catch (const lang::IndexOutOfBoundsException&) + { + // this is not the exception that should be raised in this function ... + DBG_UNHANDLED_EXCEPTION("editeng"); + } + } + } + return aResult; + } + + // XAccessibleMultiLineText + css::accessibility::TextSegment SAL_CALL AccessibleEditableTextPara::getTextAtLineWithCaret( ) + { + + css::accessibility::TextSegment aResult; + try + { + aResult = getTextAtLineNumber( getNumberOfLineWithCaret() ); + } + catch (const lang::IndexOutOfBoundsException&) + { + // this one needs to be caught since this interface does not allow for it. + } + return aResult; + } + + // XAccessibleMultiLineText + sal_Int32 SAL_CALL AccessibleEditableTextPara::getNumberOfLineWithCaret( ) + { + + sal_Int32 nRes = -1; + try + { + nRes = getLineNumberAtIndex( getCaretPosition() ); + } + catch (const lang::IndexOutOfBoundsException&) + { + // this one needs to be caught since this interface does not allow for it. + } + return nRes; + } + + + // XServiceInfo + OUString SAL_CALL AccessibleEditableTextPara::getImplementationName() + { + + return "AccessibleEditableTextPara"; + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::supportsService (const OUString& sServiceName) + { + + return cppu::supportsService(this, sServiceName); + } + + uno::Sequence< OUString> SAL_CALL AccessibleEditableTextPara::getSupportedServiceNames() + { + // #105185# Using correct service now + return { OUString("com.sun.star.text.AccessibleParagraphView") }; + } + +} // end of namespace accessibility + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleHyperlink.cxx b/editeng/source/accessibility/AccessibleHyperlink.cxx new file mode 100644 index 000000000..25d9683fc --- /dev/null +++ b/editeng/source/accessibility/AccessibleHyperlink.cxx @@ -0,0 +1,128 @@ +/* -*- 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/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <comphelper/accessiblekeybindinghelper.hxx> + +#include "AccessibleHyperlink.hxx" +#include <editeng/unoedprx.hxx> +#include <editeng/flditem.hxx> +#include <vcl/keycodes.hxx> + +using namespace ::com::sun::star; + + +// AccessibleHyperlink implementation + + +namespace accessibility +{ + + AccessibleHyperlink::AccessibleHyperlink( SvxAccessibleTextAdapter& r, SvxFieldItem* p, sal_Int32 nStt, sal_Int32 nEnd, const OUString& rD ) + : rTA( r ) + { + pFld.reset( p ); + nStartIdx = nStt; + nEndIdx = nEnd; + aDescription = rD; + } + + AccessibleHyperlink::~AccessibleHyperlink() + { + } + + // XAccessibleAction + sal_Int32 SAL_CALL AccessibleHyperlink::getAccessibleActionCount() + { + return isValid() ? 1 : 0; + } + + sal_Bool SAL_CALL AccessibleHyperlink::doAccessibleAction( sal_Int32 nIndex ) + { + bool bRet = false; + if ( isValid() && ( nIndex == 0 ) ) + { + rTA.FieldClicked( *pFld ); + bRet = true; + } + return bRet; + } + + OUString SAL_CALL AccessibleHyperlink::getAccessibleActionDescription( sal_Int32 nIndex ) + { + OUString aDesc; + + if ( isValid() && ( nIndex == 0 ) ) + aDesc = aDescription; + + return aDesc; + } + + uno::Reference< css::accessibility::XAccessibleKeyBinding > SAL_CALL AccessibleHyperlink::getAccessibleActionKeyBinding( sal_Int32 nIndex ) + { + uno::Reference< css::accessibility::XAccessibleKeyBinding > xKeyBinding; + + if( isValid() && ( nIndex == 0 ) ) + { + rtl::Reference<::comphelper::OAccessibleKeyBindingHelper> pKeyBindingHelper = new ::comphelper::OAccessibleKeyBindingHelper(); + xKeyBinding = pKeyBindingHelper; + + awt::KeyStroke aKeyStroke; + aKeyStroke.Modifiers = 0; + aKeyStroke.KeyCode = KEY_RETURN; + aKeyStroke.KeyChar = 0; + aKeyStroke.KeyFunc = 0; + pKeyBindingHelper->AddKeyBinding( aKeyStroke ); + } + + return xKeyBinding; + } + + // XAccessibleHyperlink + uno::Any SAL_CALL AccessibleHyperlink::getAccessibleActionAnchor( sal_Int32 /*nIndex*/ ) + { + return uno::Any(); + } + + uno::Any SAL_CALL AccessibleHyperlink::getAccessibleActionObject( sal_Int32 /*nIndex*/ ) + { + return uno::Any(); + } + + sal_Int32 SAL_CALL AccessibleHyperlink::getStartIndex() + { + return nStartIdx; + } + + sal_Int32 SAL_CALL AccessibleHyperlink::getEndIndex() + { + return nEndIdx; + } + + sal_Bool SAL_CALL AccessibleHyperlink::isValid( ) + { + return rTA.IsValid(); + } + +} // end of namespace accessibility + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleHyperlink.hxx b/editeng/source/accessibility/AccessibleHyperlink.hxx new file mode 100644 index 000000000..7e4f36a6b --- /dev/null +++ b/editeng/source/accessibility/AccessibleHyperlink.hxx @@ -0,0 +1,64 @@ +/* -*- 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 <cppuhelper/implbase.hxx> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/accessibility/XAccessibleHyperlink.hpp> + +#include <memory> + +class SvxFieldItem; +class SvxAccessibleTextAdapter; + +namespace accessibility +{ + + class AccessibleHyperlink : public ::cppu::WeakImplHelper< css::accessibility::XAccessibleHyperlink > + { + private: + + SvxAccessibleTextAdapter& rTA; + std::unique_ptr<SvxFieldItem> pFld; + sal_Int32 nStartIdx, nEndIdx; // translated values + OUString aDescription; + + public: + AccessibleHyperlink( SvxAccessibleTextAdapter& r, SvxFieldItem* p, sal_Int32 nStt, sal_Int32 nEnd, const OUString& rD ); + virtual ~AccessibleHyperlink() override; + + // XAccessibleAction + virtual sal_Int32 SAL_CALL getAccessibleActionCount() override; + virtual sal_Bool SAL_CALL doAccessibleAction( sal_Int32 nIndex ) override; + virtual OUString SAL_CALL getAccessibleActionDescription( sal_Int32 nIndex ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleKeyBinding > SAL_CALL getAccessibleActionKeyBinding( sal_Int32 nIndex ) override; + + // XAccessibleHyperlink + virtual css::uno::Any SAL_CALL getAccessibleActionAnchor( sal_Int32 nIndex ) override; + virtual css::uno::Any SAL_CALL getAccessibleActionObject( sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getStartIndex() override; + virtual sal_Int32 SAL_CALL getEndIndex() override; + virtual sal_Bool SAL_CALL isValid() override; + }; + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleImageBullet.cxx b/editeng/source/accessibility/AccessibleImageBullet.cxx new file mode 100644 index 000000000..da157b919 --- /dev/null +++ b/editeng/source/accessibility/AccessibleImageBullet.cxx @@ -0,0 +1,530 @@ +/* -*- 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/gen.hxx> +#include <tools/debug.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <rtl/ustring.hxx> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <comphelper/accessibleeventnotifier.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <editeng/AccessibleEditableTextPara.hxx> +#include <editeng/eerdll.hxx> + +#include <editeng/editdata.hxx> +#include <editeng/outliner.hxx> +#include <editeng/editrids.hrc> +#include <editeng/unoedsrc.hxx> +#include <svtools/colorcfg.hxx> + +#include "AccessibleImageBullet.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +namespace accessibility +{ + + AccessibleImageBullet::AccessibleImageBullet ( uno::Reference< XAccessible > xParent ) : + mnParagraphIndex( 0 ), + mnIndexInParent( 0 ), + mpEditSource( nullptr ), + maEEOffset( 0, 0 ), + mxParent(std::move( xParent )), + // well, that's strictly (UNO) exception safe, though not + // really robust. We rely on the fact that this member is + // constructed last, and that the constructor body catches + // exceptions, thus no chance for exceptions once the Id is + // fetched. Nevertheless, normally should employ RAII here... + mnNotifierClientId(::comphelper::AccessibleEventNotifier::registerClient()) + { + try + { + // Create the state set. + mxStateSet = new ::utl::AccessibleStateSetHelper (); + + // these are always on + mxStateSet->AddState( AccessibleStateType::VISIBLE ); + mxStateSet->AddState( AccessibleStateType::SHOWING ); + mxStateSet->AddState( AccessibleStateType::ENABLED ); + mxStateSet->AddState( AccessibleStateType::SENSITIVE ); + } + catch( const uno::Exception& ) {} + } + + AccessibleImageBullet::~AccessibleImageBullet() + { + + // sign off from event notifier + if( getNotifierClientId() != -1 ) + { + try + { + ::comphelper::AccessibleEventNotifier::revokeClient( getNotifierClientId() ); + } + catch( const uno::Exception& ) {} + } + } + + uno::Reference< XAccessibleContext > SAL_CALL AccessibleImageBullet::getAccessibleContext( ) + { + + // We implement the XAccessibleContext interface in the same object + return uno::Reference< XAccessibleContext > ( this ); + } + + sal_Int32 SAL_CALL AccessibleImageBullet::getAccessibleChildCount() + { + + return 0; + } + + uno::Reference< XAccessible > SAL_CALL AccessibleImageBullet::getAccessibleChild( sal_Int32 ) + { + throw lang::IndexOutOfBoundsException("No children available", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > (this) ) ); // static_cast: disambiguate hierarchy + } + + uno::Reference< XAccessible > SAL_CALL AccessibleImageBullet::getAccessibleParent() + { + + return mxParent; + } + + sal_Int32 SAL_CALL AccessibleImageBullet::getAccessibleIndexInParent() + { + + return mnIndexInParent; + } + + sal_Int16 SAL_CALL AccessibleImageBullet::getAccessibleRole() + { + + return AccessibleRole::GRAPHIC; + } + + OUString SAL_CALL AccessibleImageBullet::getAccessibleDescription() + { + // Get the string from the resource for the specified id. + return EditResId(RID_SVXSTR_A11Y_IMAGEBULLET_DESCRIPTION); + } + + OUString SAL_CALL AccessibleImageBullet::getAccessibleName() + { + // Get the string from the resource for the specified id. + return EditResId(RID_SVXSTR_A11Y_IMAGEBULLET_NAME); + } + + uno::Reference< XAccessibleRelationSet > SAL_CALL AccessibleImageBullet::getAccessibleRelationSet() + { + + // no relations, therefore empty + return uno::Reference< XAccessibleRelationSet >(); + } + + uno::Reference< XAccessibleStateSet > SAL_CALL AccessibleImageBullet::getAccessibleStateSet() + { + + SolarMutexGuard aGuard; + + // Create a copy of the state set and return it. + + if( !mxStateSet ) + return uno::Reference<XAccessibleStateSet>(); + + return uno::Reference<XAccessibleStateSet>( new ::utl::AccessibleStateSetHelper (*mxStateSet) ); + } + + lang::Locale SAL_CALL AccessibleImageBullet::getLocale() + { + + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleImageBullet::getLocale: paragraph index value overflow"); + + // return locale of first character in the paragraph + return LanguageTag(GetTextForwarder().GetLanguage( GetParagraphIndex(), 0 )).getLocale(); + } + + void SAL_CALL AccessibleImageBullet::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { + + if( getNotifierClientId() != -1 ) + ::comphelper::AccessibleEventNotifier::addEventListener( getNotifierClientId(), xListener ); + } + + void SAL_CALL AccessibleImageBullet::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { + + if( getNotifierClientId() == -1 ) + return; + + const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), 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::TClientId nId( getNotifierClientId() ); + mnNotifierClientId = -1; + ::comphelper::AccessibleEventNotifier::revokeClient( nId ); + } + } + + sal_Bool SAL_CALL AccessibleImageBullet::containsPoint( const awt::Point& rPoint ) + { + + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::contains: index value overflow"); + + awt::Rectangle aTmpRect = implGetBounds(); + tools::Rectangle aRect( Point(aTmpRect.X, aTmpRect.Y), Size(aTmpRect.Width, aTmpRect.Height) ); + Point aPoint( rPoint.X, rPoint.Y ); + + return aRect.Contains( aPoint ); + } + + uno::Reference< XAccessible > SAL_CALL AccessibleImageBullet::getAccessibleAtPoint( const awt::Point& /*aPoint*/ ) + { + + // as we have no children, empty reference + return uno::Reference< XAccessible >(); + } + + awt::Rectangle SAL_CALL AccessibleImageBullet::getBounds( ) + { + SolarMutexGuard aGuard; + + return implGetBounds(); + } + awt::Rectangle AccessibleImageBullet::implGetBounds( ) + { + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::implGetBounds: index value overflow"); + + SvxTextForwarder& rCacheTF = GetTextForwarder(); + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo( GetParagraphIndex() ); + tools::Rectangle aParentRect = rCacheTF.GetParaBounds( GetParagraphIndex() ); + + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType == SVX_NUM_BITMAP ) + { + tools::Rectangle aRect = aBulletInfo.aBounds; + + // subtract paragraph position (bullet pos is absolute in EditEngine/Outliner) + aRect.Move( -aParentRect.Left(), -aParentRect.Top() ); + + // convert to screen coordinates + tools::Rectangle aScreenRect = AccessibleEditableTextPara::LogicToPixel( aRect, + rCacheTF.GetMapMode(), + GetViewForwarder() ); + + // offset from shape/cell + Point aOffset = maEEOffset; + + return awt::Rectangle( aScreenRect.Left() + aOffset.X(), + aScreenRect.Top() + aOffset.Y(), + aScreenRect.GetSize().Width(), + aScreenRect.GetSize().Height() ); + } + + return awt::Rectangle(); + } + + awt::Point SAL_CALL AccessibleImageBullet::getLocation( ) + { + + SolarMutexGuard aGuard; + + awt::Rectangle aRect = implGetBounds(); + + return awt::Point( aRect.X, aRect.Y ); + } + + awt::Point SAL_CALL AccessibleImageBullet::getLocationOnScreen( ) + { + + SolarMutexGuard aGuard; + + // relate us to parent + uno::Reference< XAccessible > xParent = getAccessibleParent(); + if( xParent.is() ) + { + uno::Reference< XAccessibleComponent > xParentComponent( xParent, uno::UNO_QUERY ); + if( xParentComponent.is() ) + { + awt::Point aRefPoint = xParentComponent->getLocationOnScreen(); + awt::Point aPoint = getLocation(); + aPoint.X += aRefPoint.X; + aPoint.Y += aRefPoint.Y; + + return aPoint; + } + } + + throw uno::RuntimeException("Cannot access parent", + uno::Reference< uno::XInterface > + ( static_cast< XAccessible* > (this) ) ); // disambiguate hierarchy + } + + awt::Size SAL_CALL AccessibleImageBullet::getSize( ) + { + + SolarMutexGuard aGuard; + + awt::Rectangle aRect = implGetBounds(); + + return awt::Size( aRect.Width, aRect.Height ); + } + + void SAL_CALL AccessibleImageBullet::grabFocus( ) + { + + throw uno::RuntimeException("Not focusable", + uno::Reference< uno::XInterface > + ( static_cast< XAccessible* > (this) ) ); // disambiguate hierarchy + } + + sal_Int32 SAL_CALL AccessibleImageBullet::getForeground( ) + { + + // #104444# Added to XAccessibleComponent interface + svtools::ColorConfig aColorConfig; + Color nColor = aColorConfig.GetColorValue( svtools::FONTCOLOR ).nColor; + return static_cast<sal_Int32>(nColor); + } + + sal_Int32 SAL_CALL AccessibleImageBullet::getBackground( ) + { + + // #104444# Added to XAccessibleComponent interface + Color aColor( Application::GetSettings().GetStyleSettings().GetWindowColor() ); + + // the background is transparent + aColor.SetAlpha(0); + + return static_cast<sal_Int32>( aColor ); + } + + OUString SAL_CALL AccessibleImageBullet::getImplementationName() + { + + return "AccessibleImageBullet"; + } + + sal_Bool SAL_CALL AccessibleImageBullet::supportsService (const OUString& sServiceName) + { + + return cppu::supportsService(this, sServiceName); + } + + uno::Sequence< OUString > SAL_CALL AccessibleImageBullet::getSupportedServiceNames() + { + return { "com.sun.star.accessibility.AccessibleContext" }; + } + + void AccessibleImageBullet::SetIndexInParent( sal_Int32 nIndex ) + { + + mnIndexInParent = nIndex; + } + + void AccessibleImageBullet::SetEEOffset( const Point& rOffset ) + { + + maEEOffset = rOffset; + } + + void AccessibleImageBullet::Dispose() + { + + int nClientId( getNotifierClientId() ); + + // #108212# drop all references before notifying dispose + mxParent = nullptr; + mnNotifierClientId = -1; + mpEditSource = nullptr; + + // notify listeners + if( nClientId != -1 ) + { + try + { + uno::Reference < XAccessibleContext > xThis = getAccessibleContext(); + + // #106234# Delegate to EventNotifier + ::comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( nClientId, xThis ); + } + catch( const uno::Exception& ) {} + } + } + + void AccessibleImageBullet::SetEditSource( SvxEditSource* pEditSource ) + { + + mpEditSource = pEditSource; + + if( !mpEditSource ) + { + // going defunc + UnSetState( AccessibleStateType::SHOWING ); + UnSetState( AccessibleStateType::VISIBLE ); + SetState( AccessibleStateType::INVALID ); + SetState( AccessibleStateType::DEFUNC ); + + Dispose(); + } + } + + void AccessibleImageBullet::FireEvent(const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue ) const + { + + uno::Reference < XAccessibleContext > xThis( const_cast< AccessibleImageBullet* > (this)->getAccessibleContext() ); + + AccessibleEventObject aEvent(xThis, nEventId, rNewValue, rOldValue); + + // #106234# Delegate to EventNotifier + ::comphelper::AccessibleEventNotifier::addEvent( getNotifierClientId(), + aEvent ); + } + + void AccessibleImageBullet::SetState( const sal_Int16 nStateId ) + { + if( mxStateSet != nullptr && + !mxStateSet->contains(nStateId) ) + { + mxStateSet->AddState( nStateId ); + FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any( nStateId ) ); + } + } + + void AccessibleImageBullet::UnSetState( const sal_Int16 nStateId ) + { + if( mxStateSet != nullptr && + mxStateSet->contains(nStateId) ) + { + mxStateSet->RemoveState( nStateId ); + FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any(), uno::Any( nStateId ) ); + } + } + + + void AccessibleImageBullet::SetParagraphIndex( sal_Int32 nIndex ) + { + + uno::Any aOldDesc; + uno::Any aOldName; + + try + { + aOldDesc <<= getAccessibleDescription(); + aOldName <<= getAccessibleName(); + } + catch( const uno::Exception& ) {} // optional behaviour + + sal_Int32 nOldIndex = mnParagraphIndex; + + mnParagraphIndex = nIndex; + + try + { + if( nOldIndex != nIndex ) + { + // index and therefore description changed + FireEvent( AccessibleEventId::DESCRIPTION_CHANGED, uno::Any( getAccessibleDescription() ), aOldDesc ); + FireEvent( AccessibleEventId::NAME_CHANGED, uno::Any( getAccessibleName() ), aOldName ); + } + } + catch( const uno::Exception& ) {} // optional behaviour + } + + + SvxEditSource& AccessibleImageBullet::GetEditSource() const + { + + if( !mpEditSource ) + throw uno::RuntimeException("No edit source, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleImageBullet* > (this) ) ) ); // disambiguate hierarchy + return *mpEditSource; + } + + SvxTextForwarder& AccessibleImageBullet::GetTextForwarder() const + { + + SvxEditSource& rEditSource = GetEditSource(); + SvxTextForwarder* pTextForwarder = rEditSource.GetTextForwarder(); + + if( !pTextForwarder ) + throw uno::RuntimeException("Unable to fetch text forwarder, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleImageBullet* > (this) ) ) ); // disambiguate hierarchy + + if( !pTextForwarder->IsValid() ) + throw uno::RuntimeException("Text forwarder is invalid, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleImageBullet* > (this) ) ) ); // disambiguate hierarchy + return *pTextForwarder; + } + + SvxViewForwarder& AccessibleImageBullet::GetViewForwarder() const + { + + SvxEditSource& rEditSource = GetEditSource(); + SvxViewForwarder* pViewForwarder = rEditSource.GetViewForwarder(); + + if( !pViewForwarder ) + { + throw uno::RuntimeException("Unable to fetch view forwarder, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleImageBullet* > (this) ) ) ); // disambiguate hierarchy + } + + if( !pViewForwarder->IsValid() ) + throw uno::RuntimeException("View forwarder is invalid, object is defunct", + uno::Reference< uno::XInterface > + ( static_cast< ::cppu::OWeakObject* > + ( const_cast< AccessibleImageBullet* > (this) ) ) ); // disambiguate hierarchy + return *pViewForwarder; + } + + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleImageBullet.hxx b/editeng/source/accessibility/AccessibleImageBullet.hxx new file mode 100644 index 000000000..85f1011fb --- /dev/null +++ b/editeng/source/accessibility/AccessibleImageBullet.hxx @@ -0,0 +1,202 @@ +/* -*- 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 <rtl/ref.hxx> +#include <tools/gen.hxx> +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> + +class SvxEditSource; +class SvxTextForwarder; +class SvxViewForwarder; +namespace utl { class AccessibleStateSetHelper; } + +namespace accessibility +{ + typedef ::cppu::WeakImplHelper< css::accessibility::XAccessible, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleEventBroadcaster, + css::lang::XServiceInfo > AccessibleImageBulletInterfaceBase; + + /** This class implements the image bullets for the EditEngine/Outliner UAA + */ + class AccessibleImageBullet final : public AccessibleImageBulletInterfaceBase + { + + public: + /// Create accessible object for given parent + AccessibleImageBullet ( css::uno::Reference< css::accessibility::XAccessible > xParent ); + + virtual ~AccessibleImageBullet () override; + + // XAccessible + virtual css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL getAccessibleContext( ) override; + + // XAccessibleContext + virtual sal_Int32 SAL_CALL getAccessibleChildCount() override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int32 i ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent() override; + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent() override; + virtual sal_Int16 SAL_CALL getAccessibleRole() override; + virtual OUString SAL_CALL getAccessibleDescription() override; + virtual OUString SAL_CALL getAccessibleName() override; + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL getAccessibleRelationSet() override; + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet > SAL_CALL getAccessibleStateSet() override; + virtual css::lang::Locale SAL_CALL getLocale() override; + + // 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; + + // 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; + + // 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; + + /** Set the current index in the accessibility parent + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + void SetIndexInParent( sal_Int32 nIndex ); + + /** Set the edit engine offset + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + void SetEEOffset( const Point& rOffset ); + + /** Set the EditEngine offset + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + void SetEditSource( SvxEditSource* pEditSource ); + + /** Dispose this object + + Notifies and deregisters the listeners, drops all references. + */ + void Dispose(); + + /** Set the current paragraph number + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + void SetParagraphIndex( sal_Int32 nIndex ); + + /** Query the current paragraph number (0 - nParas-1) + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + sal_Int32 GetParagraphIndex() const { return mnParagraphIndex; } + + /// Calls all Listener objects to tell them the change. Don't hold locks when calling this! + void FireEvent(const sal_Int16 nEventId, const css::uno::Any& rNewValue, const css::uno::Any& rOldValue = css::uno::Any() ) const; + + private: + AccessibleImageBullet( const AccessibleImageBullet& ) = delete; + AccessibleImageBullet& operator= ( const AccessibleImageBullet& ) = delete; + + // maintain state set and send STATE_CHANGE events + void SetState( const sal_Int16 nStateId ); + void UnSetState( const sal_Int16 nStateId ); + + SvxEditSource& GetEditSource() const; + + int getNotifierClientId() const { return mnNotifierClientId; } + + /** Query the SvxTextForwarder for EditEngine access. + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + SvxTextForwarder& GetTextForwarder() const; + + /** Query the SvxViewForwarder for EditEngine access. + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + SvxViewForwarder& GetViewForwarder() const; + + css::awt::Rectangle implGetBounds(); + + // the paragraph index in the edit engine (guarded by solar mutex) + sal_Int32 mnParagraphIndex; + + // our current index in the parent (guarded by solar mutex) + sal_Int32 mnIndexInParent; + + // the current edit source (guarded by solar mutex) + SvxEditSource* mpEditSource; + + // the offset of the underlying EditEngine from the shape/cell (guarded by solar mutex) + Point maEEOffset; + + // the current state set (updated from SetState/UnSetState and guarded by solar mutex) + rtl::Reference< utl::AccessibleStateSetHelper > mxStateSet; + + /// The shape we're the accessible for (unguarded) + css::uno::Reference< css::accessibility::XAccessible > mxParent; + + /// Our listeners (guarded by maMutex) + int mnNotifierClientId; + }; + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleParaManager.cxx b/editeng/source/accessibility/AccessibleParaManager.cxx new file mode 100644 index 000000000..f597bba6a --- /dev/null +++ b/editeng/source/accessibility/AccessibleParaManager.cxx @@ -0,0 +1,403 @@ +/* -*- 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 . + */ + + +// Global header + + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> + + +// Project-local header + + +#include <editeng/AccessibleParaManager.hxx> +#include <editeng/AccessibleEditableTextPara.hxx> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + + +namespace accessibility +{ + AccessibleParaManager::AccessibleParaManager() : + maChildren(1), + maEEOffset( 0, 0 ), + mnFocusedChild( -1 ), + mbActive( false ) + { + } + + AccessibleParaManager::~AccessibleParaManager() + { + // owner is responsible for possible child death + } + + void AccessibleParaManager::SetAdditionalChildStates( VectorOfStates&& rChildStates ) + { + maChildStates = std::move(rChildStates); + } + + void AccessibleParaManager::SetNum( sal_Int32 nNumParas ) + { + if( o3tl::make_unsigned(nNumParas) < maChildren.size() ) + Release( nNumParas, maChildren.size() ); + + maChildren.resize( nNumParas ); + + if( mnFocusedChild >= nNumParas ) + mnFocusedChild = -1; + } + + sal_Int32 AccessibleParaManager::GetNum() const + { + size_t nSize = maChildren.size(); + if (nSize > SAL_MAX_INT32) + { + SAL_WARN( "editeng", "AccessibleParaManager::GetNum - overflow " << nSize); + return SAL_MAX_INT32; + } + return static_cast<sal_Int32>(nSize); + } + + AccessibleParaManager::VectorOfChildren::iterator AccessibleParaManager::begin() + { + return maChildren.begin(); + } + + AccessibleParaManager::VectorOfChildren::iterator AccessibleParaManager::end() + { + return maChildren.end(); + } + + void AccessibleParaManager::FireEvent( sal_Int32 nPara, + const sal_Int16 nEventId ) const + { + DBG_ASSERT( 0 <= nPara && maChildren.size() > o3tl::make_unsigned(nPara), + "AccessibleParaManager::FireEvent: invalid index" ); + + if( 0 <= nPara && maChildren.size() > o3tl::make_unsigned(nPara) ) + { + auto aChild( GetChild( nPara ).first.get() ); + if( aChild.is() ) + aChild->FireEvent( nEventId ); + } + } + + bool AccessibleParaManager::IsReferencable( + rtl::Reference<AccessibleEditableTextPara> const & aChild) + { + return aChild.is(); + } + + bool AccessibleParaManager::IsReferencable( sal_Int32 nChild ) const + { + DBG_ASSERT( 0 <= nChild && maChildren.size() > o3tl::make_unsigned(nChild), + "AccessibleParaManager::IsReferencable: invalid index" ); + + if( 0 <= nChild && maChildren.size() > o3tl::make_unsigned(nChild) ) + { + // retrieve hard reference from weak one + return IsReferencable( GetChild( nChild ).first.get() ); + } + else + { + return false; + } + } + + AccessibleParaManager::WeakChild AccessibleParaManager::GetChild( sal_Int32 nParagraphIndex ) const + { + DBG_ASSERT( 0 <= nParagraphIndex && maChildren.size() > o3tl::make_unsigned(nParagraphIndex), + "AccessibleParaManager::GetChild: invalid index" ); + + if( 0 <= nParagraphIndex && maChildren.size() > o3tl::make_unsigned(nParagraphIndex) ) + { + return maChildren[ nParagraphIndex ]; + } + else + { + return WeakChild(); + } + } + + bool AccessibleParaManager::HasCreatedChild( sal_Int32 nParagraphIndex ) const + { + if( 0 <= nParagraphIndex && maChildren.size() > o3tl::make_unsigned(nParagraphIndex) ) + { + auto const & rChild = maChildren[ nParagraphIndex ]; + return rChild.second.Width != 0 || rChild.second.Height != 0; + } + else + return false; + } + + AccessibleParaManager::Child AccessibleParaManager::CreateChild( sal_Int32 nChild, + const uno::Reference< XAccessible >& xFrontEnd, + SvxEditSourceAdapter& rEditSource, + sal_Int32 nParagraphIndex ) + { + DBG_ASSERT( 0 <= nParagraphIndex && maChildren.size() > o3tl::make_unsigned(nParagraphIndex), + "AccessibleParaManager::CreateChild: invalid index" ); + + if( 0 <= nParagraphIndex && maChildren.size() > o3tl::make_unsigned(nParagraphIndex) ) + { + // retrieve hard reference from weak one + auto aChild( GetChild( nParagraphIndex ).first.get() ); + + if( !IsReferencable( nParagraphIndex ) ) + { + // there is no hard reference available, create object then + // #i27138# + aChild = new AccessibleEditableTextPara(xFrontEnd, this); + + InitChild( *aChild, rEditSource, nChild, nParagraphIndex ); + + maChildren[ nParagraphIndex ] = WeakChild( aChild, aChild->getBounds() ); + } + + return Child( aChild.get(), GetChild( nParagraphIndex ).second ); + } + else + { + return Child(); + } + } + + void AccessibleParaManager::SetEEOffset( const Point& rOffset ) + { + maEEOffset = rOffset; + + MemFunAdapter< const Point& > aAdapter( &::accessibility::AccessibleEditableTextPara::SetEEOffset, rOffset ); + std::for_each( begin(), end(), aAdapter ); + } + + void AccessibleParaManager::SetActive( bool bActive ) + { + mbActive = bActive; + + if( bActive ) + { + SetState( AccessibleStateType::ACTIVE ); + SetState( AccessibleStateType::EDITABLE ); + } + else + { + UnSetState( AccessibleStateType::ACTIVE ); + UnSetState( AccessibleStateType::EDITABLE ); + } + } + + void AccessibleParaManager::SetFocus( sal_Int32 nChild ) + { + if( mnFocusedChild != -1 ) + UnSetState( mnFocusedChild, AccessibleStateType::FOCUSED ); + + mnFocusedChild = nChild; + + if( mnFocusedChild != -1 ) + SetState( mnFocusedChild, AccessibleStateType::FOCUSED ); + } + + void AccessibleParaManager::InitChild( AccessibleEditableTextPara& rChild, + SvxEditSourceAdapter& rEditSource, + sal_Int32 nChild, + sal_Int32 nParagraphIndex ) const + { + rChild.SetEditSource( &rEditSource ); + rChild.SetIndexInParent( nChild ); + rChild.SetParagraphIndex( nParagraphIndex ); + + rChild.SetEEOffset( maEEOffset ); + + if( mbActive ) + { + rChild.SetState( AccessibleStateType::ACTIVE ); + rChild.SetState( AccessibleStateType::EDITABLE ); + } + + if( mnFocusedChild == nParagraphIndex ) + rChild.SetState( AccessibleStateType::FOCUSED ); + + // add states passed from outside + for( const auto& rState : maChildStates ) + rChild.SetState( rState ); + } + + void AccessibleParaManager::SetState( sal_Int32 nChild, const sal_Int16 nStateId ) + { + MemFunAdapter< const sal_Int16 > aFunc( &AccessibleEditableTextPara::SetState, + nStateId ); + aFunc( GetChild(nChild) ); + } + + void AccessibleParaManager::SetState( const sal_Int16 nStateId ) + { + std::for_each( begin(), end(), + MemFunAdapter< const sal_Int16 >( &AccessibleEditableTextPara::SetState, + nStateId ) ); + } + + void AccessibleParaManager::UnSetState( sal_Int32 nChild, const sal_Int16 nStateId ) + { + MemFunAdapter< const sal_Int16 > aFunc( &AccessibleEditableTextPara::UnSetState, + nStateId ); + aFunc( GetChild(nChild) ); + } + + void AccessibleParaManager::UnSetState( const sal_Int16 nStateId ) + { + std::for_each( begin(), end(), + MemFunAdapter< const sal_Int16 >( &AccessibleEditableTextPara::UnSetState, + nStateId ) ); + } + + namespace { + + // not generic yet, no arguments... + class AccessibleParaManager_DisposeChildren + { + public: + AccessibleParaManager_DisposeChildren() {} + void operator()( ::accessibility::AccessibleEditableTextPara& rPara ) + { + rPara.Dispose(); + } + }; + + } + + void AccessibleParaManager::Dispose() + { + AccessibleParaManager_DisposeChildren aFunctor; + + std::for_each( begin(), end(), + WeakChildAdapter< AccessibleParaManager_DisposeChildren > (aFunctor) ); + } + + namespace { + + // not generic yet, too many method arguments... + class StateChangeEvent + { + public: + StateChangeEvent( const sal_Int16 nEventId, + const uno::Any& rNewValue, + const uno::Any& rOldValue ) : + mnEventId( nEventId ), + mrNewValue( rNewValue ), + mrOldValue( rOldValue ) {} + void operator()( ::accessibility::AccessibleEditableTextPara const & rPara ) + { + rPara.FireEvent( mnEventId, mrNewValue, mrOldValue ); + } + + private: + const sal_Int16 mnEventId; + const uno::Any& mrNewValue; + const uno::Any& mrOldValue; + }; + + } + + void AccessibleParaManager::FireEvent( sal_Int32 nStartPara, + sal_Int32 nEndPara, + const sal_Int16 nEventId, + const uno::Any& rNewValue, + const uno::Any& rOldValue ) const + { + DBG_ASSERT( 0 <= nStartPara && 0 <= nEndPara && + maChildren.size() > o3tl::make_unsigned(nStartPara) && + maChildren.size() >= o3tl::make_unsigned(nEndPara) && + nEndPara >= nStartPara, "AccessibleParaManager::FireEvent: invalid index" ); + + + if( 0 <= nStartPara && 0 <= nEndPara && + maChildren.size() > o3tl::make_unsigned(nStartPara) && + maChildren.size() >= o3tl::make_unsigned(nEndPara) && + nEndPara >= nStartPara ) + { + VectorOfChildren::const_iterator front = maChildren.begin(); + VectorOfChildren::const_iterator back = front; + + std::advance( front, nStartPara ); + std::advance( back, nEndPara ); + + StateChangeEvent aFunctor( nEventId, rNewValue, rOldValue ); + + std::for_each( front, back, AccessibleParaManager::WeakChildAdapter< StateChangeEvent >( aFunctor ) ); + } + } + + namespace { + + class ReleaseChild + { + public: + AccessibleParaManager::WeakChild operator()( const AccessibleParaManager::WeakChild& rPara ) + { + AccessibleParaManager::ShutdownPara( rPara ); + + // clear reference + return AccessibleParaManager::WeakChild(); + } + }; + + } + + void AccessibleParaManager::Release( sal_Int32 nStartPara, sal_Int32 nEndPara ) + { + DBG_ASSERT( 0 <= nStartPara && 0 <= nEndPara && + maChildren.size() > o3tl::make_unsigned(nStartPara) && + maChildren.size() >= o3tl::make_unsigned(nEndPara), + "AccessibleParaManager::Release: invalid index" ); + + if( 0 <= nStartPara && 0 <= nEndPara && + maChildren.size() > o3tl::make_unsigned(nStartPara) && + maChildren.size() >= o3tl::make_unsigned(nEndPara) ) + { + VectorOfChildren::iterator front = maChildren.begin(); + VectorOfChildren::iterator back = front; + + std::advance( front, nStartPara ); + std::advance( back, nEndPara ); + + std::transform( front, back, front, ReleaseChild() ); + } + } + + void AccessibleParaManager::ShutdownPara( const WeakChild& rChild ) + { + auto aChild( rChild.first.get() ); + + if( IsReferencable( aChild ) ) + aChild->SetEditSource( nullptr ); + } + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleSelectionBase.cxx b/editeng/source/accessibility/AccessibleSelectionBase.cxx new file mode 100644 index 000000000..09975c9ec --- /dev/null +++ b/editeng/source/accessibility/AccessibleSelectionBase.cxx @@ -0,0 +1,91 @@ +/* -*- 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 <editeng/AccessibleSelectionBase.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +namespace accessibility +{ + + // - AccessibleSelectionBase - + + + AccessibleSelectionBase::AccessibleSelectionBase() + { + } + + + AccessibleSelectionBase::~AccessibleSelectionBase() + { + } + + + void SAL_CALL AccessibleSelectionBase::selectAccessibleChild( sal_Int32 nChildIndex ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + OCommonAccessibleSelection::selectAccessibleChild( nChildIndex ); + } + + + sal_Bool SAL_CALL AccessibleSelectionBase::isAccessibleChildSelected( sal_Int32 nChildIndex ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + return OCommonAccessibleSelection::isAccessibleChildSelected( nChildIndex ); + } + + + void SAL_CALL AccessibleSelectionBase::clearAccessibleSelection( ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + OCommonAccessibleSelection::clearAccessibleSelection(); + } + + + void SAL_CALL AccessibleSelectionBase::selectAllAccessibleChildren( ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + OCommonAccessibleSelection::selectAllAccessibleChildren(); + } + + + sal_Int32 SAL_CALL AccessibleSelectionBase::getSelectedAccessibleChildCount( ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + return OCommonAccessibleSelection::getSelectedAccessibleChildCount(); + } + + + uno::Reference< XAccessible > SAL_CALL AccessibleSelectionBase::getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + return OCommonAccessibleSelection::getSelectedAccessibleChild( nSelectedChildIndex ); + } + + + void SAL_CALL AccessibleSelectionBase::deselectAccessibleChild( sal_Int32 nSelectedChildIndex ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + OCommonAccessibleSelection::deselectAccessibleChild( nSelectedChildIndex ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleStaticTextBase.cxx b/editeng/source/accessibility/AccessibleStaticTextBase.cxx new file mode 100644 index 000000000..9e73d192b --- /dev/null +++ b/editeng/source/accessibility/AccessibleStaticTextBase.cxx @@ -0,0 +1,965 @@ +/* -*- 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 . + */ + + +// Global header + + +#include <utility> +#include <memory> +#include <vector> +#include <algorithm> +#include <rtl/ustrbuf.hxx> +#include <tools/debug.hxx> +#include <vcl/svapp.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/accessibility/AccessibleTextType.hpp> + + +// Project-local header + + +#include <editeng/editdata.hxx> +#include <editeng/unoedprx.hxx> +#include <editeng/AccessibleStaticTextBase.hxx> +#include <editeng/AccessibleEditableTextPara.hxx> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +/* TODO: + ===== + + - separate adapter functionality from AccessibleStaticText class + + - refactor common loops into templates, using mem_fun + + */ + +namespace accessibility +{ + typedef std::vector< beans::PropertyValue > PropertyValueVector; + + namespace { + + class PropertyValueEqualFunctor + { + const beans::PropertyValue& m_rPValue; + + public: + explicit PropertyValueEqualFunctor(const beans::PropertyValue& rPValue) + : m_rPValue(rPValue) + {} + bool operator() ( const beans::PropertyValue& rhs ) const + { + return ( m_rPValue.Name == rhs.Name && m_rPValue.Value == rhs.Value ); + } + }; + + } + + sal_Unicode const cNewLine(0x0a); + + + // Static Helper + + + static ESelection MakeSelection( sal_Int32 nStartPara, sal_Int32 nStartIndex, + sal_Int32 nEndPara, sal_Int32 nEndIndex ) + { + DBG_ASSERT(nStartPara >= 0 && + nStartIndex >= 0 && + nEndPara >= 0 && + nEndIndex >= 0, + "AccessibleStaticTextBase_Impl::MakeSelection: index value overflow"); + + return ESelection(nStartPara, nStartIndex, nEndPara, nEndIndex); + } + + + // AccessibleStaticTextBase_Impl declaration + + + /** AccessibleStaticTextBase_Impl + + This class implements the AccessibleStaticTextBase + functionality, mainly by forwarding the calls to an aggregated + AccessibleEditableTextPara. As this is a therefore non-trivial + adapter, factoring out the common functionality from + AccessibleEditableTextPara might be a profitable future task. + */ + class AccessibleStaticTextBase_Impl + { + friend class AccessibleStaticTextBase; + public: + + // receive pointer to our frontend class and view window + AccessibleStaticTextBase_Impl(); + + void SetEditSource( std::unique_ptr< SvxEditSource > && pEditSource ); + + void SetEventSource( const uno::Reference< XAccessible >& rInterface ) + { + + mxThis = rInterface; + } + + void SetOffset( const Point& ); + + void Dispose(); + + AccessibleEditableTextPara& GetParagraph( sal_Int32 nPara ) const; + sal_Int32 GetParagraphCount() const; + + EPosition Index2Internal( sal_Int32 nFlatIndex ) const + { + + return ImpCalcInternal( nFlatIndex, false ); + } + + EPosition Range2Internal( sal_Int32 nFlatIndex ) const + { + + return ImpCalcInternal( nFlatIndex, true ); + } + + sal_Int32 Internal2Index( EPosition nEEIndex ) const; + + void CorrectTextSegment( TextSegment& aTextSegment, + int nPara ) const; + + bool SetSelection( sal_Int32 nStartPara, sal_Int32 nStartIndex, + sal_Int32 nEndPara, sal_Int32 nEndIndex ); + bool CopyText( sal_Int32 nStartPara, sal_Int32 nStartIndex, + sal_Int32 nEndPara, sal_Int32 nEndIndex ); + + tools::Rectangle GetParagraphBoundingBox() const; + bool RemoveLineBreakCount( sal_Int32& rIndex ); + + private: + + EPosition ImpCalcInternal( sal_Int32 nFlatIndex, bool bExclusive ) const; + + // our frontend class (the one implementing the actual + // interface). That's not necessarily the one containing the impl + // pointer + uno::Reference< XAccessible > mxThis; + + // implements our functionality, we're just an adapter (guarded by solar mutex) + mutable rtl::Reference<AccessibleEditableTextPara> mxTextParagraph; + + // a wrapper for the text forwarders (guarded by solar mutex) + mutable SvxEditSourceAdapter maEditSource; + }; + + + // AccessibleStaticTextBase_Impl implementation + + + AccessibleStaticTextBase_Impl::AccessibleStaticTextBase_Impl() : + mxTextParagraph( new AccessibleEditableTextPara(nullptr) ) + { + + // TODO: this is still somewhat of a hack, all the more since + // now the maTextParagraph has an empty parent reference set + } + + void AccessibleStaticTextBase_Impl::SetEditSource( std::unique_ptr< SvxEditSource > && pEditSource ) + { + + maEditSource.SetEditSource( std::move(pEditSource) ); + if( mxTextParagraph.is() ) + mxTextParagraph->SetEditSource( &maEditSource ); + } + + void AccessibleStaticTextBase_Impl::SetOffset( const Point& rPoint ) + { + if( mxTextParagraph.is() ) + mxTextParagraph->SetEEOffset( rPoint ); + } + + void AccessibleStaticTextBase_Impl::Dispose() + { + + // we're the owner of the paragraph, so destroy it, too + if( mxTextParagraph.is() ) + mxTextParagraph->Dispose(); + + // drop references + mxThis = nullptr; + mxTextParagraph.clear(); + } + + AccessibleEditableTextPara& AccessibleStaticTextBase_Impl::GetParagraph( sal_Int32 nPara ) const + { + + if( !mxTextParagraph.is() ) + throw lang::DisposedException ("object has been already disposed", mxThis ); + + // TODO: Have a different method on AccessibleEditableTextPara + // that does not care about state changes + mxTextParagraph->SetParagraphIndex( nPara ); + + return *mxTextParagraph; + } + + sal_Int32 AccessibleStaticTextBase_Impl::GetParagraphCount() const + { + + if( !mxTextParagraph.is() ) + return 0; + else + return mxTextParagraph->GetTextForwarder().GetParagraphCount(); + } + + sal_Int32 AccessibleStaticTextBase_Impl::Internal2Index( EPosition nEEIndex ) const + { + // XXX checks for overflow and returns maximum if so + sal_Int32 aRes(0); + for(sal_Int32 i=0; i<nEEIndex.nPara; ++i) + { + sal_Int32 nCount = GetParagraph(i).getCharacterCount(); + if (SAL_MAX_INT32 - aRes > nCount) + return SAL_MAX_INT32; + aRes += nCount; + } + + if (SAL_MAX_INT32 - aRes > nEEIndex.nIndex) + return SAL_MAX_INT32; + return aRes + nEEIndex.nIndex; + } + + void AccessibleStaticTextBase_Impl::CorrectTextSegment( TextSegment& aTextSegment, + int nPara ) const + { + // Keep 'invalid' values at the TextSegment + if( aTextSegment.SegmentStart != -1 && + aTextSegment.SegmentEnd != -1 ) + { + // #112814# Correct TextSegment by paragraph offset + sal_Int32 nOffset(0); + int i; + for(i=0; i<nPara; ++i) + nOffset += GetParagraph(i).getCharacterCount(); + + aTextSegment.SegmentStart += nOffset; + aTextSegment.SegmentEnd += nOffset; + } + } + + EPosition AccessibleStaticTextBase_Impl::ImpCalcInternal( sal_Int32 nFlatIndex, bool bExclusive ) const + { + + if( nFlatIndex < 0 ) + throw lang::IndexOutOfBoundsException("AccessibleStaticTextBase_Impl::Index2Internal: character index out of bounds", + mxThis); + // gratuitously accepting larger indices here, AccessibleEditableTextPara will throw eventually + + sal_Int32 nCurrPara, nCurrIndex, nParas, nCurrCount; + for( nCurrPara=0, nParas=GetParagraphCount(), nCurrCount=0, nCurrIndex=0; nCurrPara<nParas; ++nCurrPara ) + { + nCurrCount = GetParagraph( nCurrPara ).getCharacterCount(); + nCurrIndex += nCurrCount; + if( nCurrIndex >= nFlatIndex ) + { + // check overflow + DBG_ASSERT(nCurrPara >= 0 && + nFlatIndex - nCurrIndex + nCurrCount >= 0, + "AccessibleStaticTextBase_Impl::Index2Internal: index value overflow"); + + return EPosition(nCurrPara, nFlatIndex - nCurrIndex + nCurrCount); + } + } + + // #102170# Allow one-past the end for ranges + if( bExclusive && nCurrIndex == nFlatIndex ) + { + // check overflow + DBG_ASSERT(nCurrPara > 0 && + nFlatIndex - nCurrIndex + nCurrCount >= 0, + "AccessibleStaticTextBase_Impl::Index2Internal: index value overflow"); + + return EPosition(nCurrPara-1, nFlatIndex - nCurrIndex + nCurrCount); + } + + // not found? Out of bounds + throw lang::IndexOutOfBoundsException("AccessibleStaticTextBase_Impl::Index2Internal: character index out of bounds", + mxThis); + } + + bool AccessibleStaticTextBase_Impl::SetSelection( sal_Int32 nStartPara, sal_Int32 nStartIndex, + sal_Int32 nEndPara, sal_Int32 nEndIndex ) + { + + if( !mxTextParagraph.is() ) + return false; + + try + { + SvxEditViewForwarder& rCacheVF = mxTextParagraph->GetEditViewForwarder( true ); + return rCacheVF.SetSelection( MakeSelection(nStartPara, nStartIndex, nEndPara, nEndIndex) ); + } + catch( const uno::RuntimeException& ) + { + return false; + } + } + + bool AccessibleStaticTextBase_Impl::CopyText( sal_Int32 nStartPara, sal_Int32 nStartIndex, + sal_Int32 nEndPara, sal_Int32 nEndIndex ) + { + + if( !mxTextParagraph.is() ) + return false; + + try + { + SvxEditViewForwarder& rCacheVF = mxTextParagraph->GetEditViewForwarder( true ); + mxTextParagraph->GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + bool aRetVal; + + // save current selection + ESelection aOldSelection; + + rCacheVF.GetSelection( aOldSelection ); + rCacheVF.SetSelection( MakeSelection(nStartPara, nStartIndex, nEndPara, nEndIndex) ); + aRetVal = rCacheVF.Copy(); + rCacheVF.SetSelection( aOldSelection ); // restore + + return aRetVal; + } + catch( const uno::RuntimeException& ) + { + return false; + } + } + + tools::Rectangle AccessibleStaticTextBase_Impl::GetParagraphBoundingBox() const + { + tools::Rectangle aRect; + if( mxTextParagraph.is() ) + { + awt::Rectangle aAwtRect = mxTextParagraph->getBounds(); + aRect = tools::Rectangle( Point( aAwtRect.X, aAwtRect.Y ), Size( aAwtRect.Width, aAwtRect.Height ) ); + } + else + { + aRect.SetEmpty(); + } + return aRect; + } + //the input argument is the index(including "\n" ) in the string. + //the function will calculate the actual index(not including "\n") in the string. + //and return true if the index is just at a "\n" + bool AccessibleStaticTextBase_Impl::RemoveLineBreakCount( sal_Int32& rIndex ) + { + // get the total char number inside the cell. + sal_Int32 i, nCount, nParas; + for( i=0, nCount=0, nParas=GetParagraphCount(); i<nParas; ++i ) + nCount += GetParagraph(i).getCharacterCount(); + nCount = nCount + (nParas-1); + if( nCount == 0 && rIndex == 0) return false; + + + sal_Int32 nCurrPara, nCurrCount; + sal_Int32 nLineBreakPos = 0, nLineBreakCount = 0; + sal_Int32 nParaCount = GetParagraphCount(); + for ( nCurrCount = 0, nCurrPara = 0; nCurrPara < nParaCount; nCurrPara++ ) + { + nCurrCount += GetParagraph( nCurrPara ).getCharacterCount(); + nLineBreakPos = nCurrCount++; + if ( rIndex == nLineBreakPos ) + { + rIndex -= (++nLineBreakCount);//(++nLineBreakCount); + if ( rIndex < 0) + { + rIndex = 0; + } + //if the index is at the last position of the last paragraph + //there is no "\n" , so we should increase rIndex by 1 and return false. + if ( (nCurrPara+1) == nParaCount ) + { + rIndex++; + return false; + } + else + { + return true; + } + } + else if ( rIndex < nLineBreakPos ) + { + rIndex -= nLineBreakCount; + return false; + } + else + { + nLineBreakCount++; + } + } + return false; + } + + + // AccessibleStaticTextBase implementation + + AccessibleStaticTextBase::AccessibleStaticTextBase( std::unique_ptr< SvxEditSource > && pEditSource ) : + mpImpl( new AccessibleStaticTextBase_Impl() ) + { + SolarMutexGuard aGuard; + + SetEditSource( std::move(pEditSource) ); + } + + AccessibleStaticTextBase::~AccessibleStaticTextBase() + { + } + + void AccessibleStaticTextBase::SetEditSource( std::unique_ptr< SvxEditSource > && pEditSource ) + { + // precondition: solar mutex locked + DBG_TESTSOLARMUTEX(); + + mpImpl->SetEditSource( std::move(pEditSource) ); + } + + void AccessibleStaticTextBase::SetEventSource( const uno::Reference< XAccessible >& rInterface ) + { + mpImpl->SetEventSource( rInterface ); + + } + + void AccessibleStaticTextBase::SetOffset( const Point& rPoint ) + { + // precondition: solar mutex locked + DBG_TESTSOLARMUTEX(); + + mpImpl->SetOffset( rPoint ); + } + + void AccessibleStaticTextBase::Dispose() + { + mpImpl->Dispose(); + + } + + // XAccessibleContext + sal_Int32 AccessibleStaticTextBase::getAccessibleChildCount() + { + // no children at all + return 0; + } + + uno::Reference< XAccessible > AccessibleStaticTextBase::getAccessibleChild( sal_Int32 /*i*/ ) + { + // no children at all + return uno::Reference< XAccessible >(); + } + + uno::Reference< XAccessible > AccessibleStaticTextBase::getAccessibleAtPoint( const awt::Point& /*_aPoint*/ ) + { + // no children at all + return uno::Reference< XAccessible >(); + } + + // XAccessibleText + sal_Int32 SAL_CALL AccessibleStaticTextBase::getCaretPosition() + { + SolarMutexGuard aGuard; + + sal_Int32 i, nPos, nParas; + for( i=0, nPos=-1, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i ) + { + if( (nPos=mpImpl->GetParagraph(i).getCaretPosition()) != -1 ) + return nPos; + } + + return nPos; + } + + sal_Bool SAL_CALL AccessibleStaticTextBase::setCaretPosition( sal_Int32 nIndex ) + { + return setSelection(nIndex, nIndex); + } + + sal_Unicode SAL_CALL AccessibleStaticTextBase::getCharacter( sal_Int32 nIndex ) + { + SolarMutexGuard aGuard; + + EPosition aPos( mpImpl->Index2Internal(nIndex) ); + + return mpImpl->GetParagraph( aPos.nPara ).getCharacter( aPos.nIndex ); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleStaticTextBase::getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes ) + { + SolarMutexGuard aGuard; + + //get the actual index without "\n" + mpImpl->RemoveLineBreakCount( nIndex ); + + EPosition aPos( mpImpl->Index2Internal(nIndex) ); + + return mpImpl->GetParagraph( aPos.nPara ).getCharacterAttributes( aPos.nIndex, aRequestedAttributes ); + } + + awt::Rectangle SAL_CALL AccessibleStaticTextBase::getCharacterBounds( sal_Int32 nIndex ) + { + SolarMutexGuard aGuard; + + // #108900# Allow ranges for nIndex, as one-past-the-end + // values are now legal, too. + EPosition aPos( mpImpl->Range2Internal(nIndex) ); + + // #i70916# Text in spread sheet cells return the wrong extents + AccessibleEditableTextPara& rPara = mpImpl->GetParagraph( aPos.nPara ); + awt::Rectangle aParaBounds( rPara.getBounds() ); + awt::Rectangle aBounds( rPara.getCharacterBounds( aPos.nIndex ) ); + aBounds.X += aParaBounds.X; + aBounds.Y += aParaBounds.Y; + + return aBounds; + } + + sal_Int32 SAL_CALL AccessibleStaticTextBase::getCharacterCount() + { + SolarMutexGuard aGuard; + + sal_Int32 i, nCount, nParas; + for( i=0, nCount=0, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i ) + nCount += mpImpl->GetParagraph(i).getCharacterCount(); + //count on the number of "\n" which equals number of paragraphs decrease 1. + nCount = nCount + (nParas-1); + return nCount; + } + + sal_Int32 SAL_CALL AccessibleStaticTextBase::getIndexAtPoint( const awt::Point& rPoint ) + { + SolarMutexGuard aGuard; + + const sal_Int32 nParas( mpImpl->GetParagraphCount() ); + sal_Int32 nIndex; + int i; + for( i=0; i<nParas; ++i ) + { + // TODO: maybe exploit the fact that paragraphs are + // ordered vertically for early exit + + // #i70916# Text in spread sheet cells return the wrong extents + AccessibleEditableTextPara& rPara = mpImpl->GetParagraph( i ); + awt::Rectangle aParaBounds( rPara.getBounds() ); + awt::Point aPoint( rPoint ); + aPoint.X -= aParaBounds.X; + aPoint.Y -= aParaBounds.Y; + + // #112814# Use correct index offset + if ( ( nIndex = rPara.getIndexAtPoint( aPoint ) ) != -1 ) + return mpImpl->Internal2Index(EPosition(i, nIndex)); + } + + return -1; + } + + OUString SAL_CALL AccessibleStaticTextBase::getSelectedText() + { + SolarMutexGuard aGuard; + + sal_Int32 nStart( getSelectionStart() ); + sal_Int32 nEnd( getSelectionEnd() ); + + // #104481# Return the empty string for 'no selection' + if( nStart < 0 || nEnd < 0 ) + return OUString(); + + return getTextRange( nStart, nEnd ); + } + + sal_Int32 SAL_CALL AccessibleStaticTextBase::getSelectionStart() + { + SolarMutexGuard aGuard; + + sal_Int32 i, nPos, nParas; + for( i=0, nPos=-1, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i ) + { + if( (nPos=mpImpl->GetParagraph(i).getSelectionStart()) != -1 ) + return nPos; + } + + return nPos; + } + + sal_Int32 SAL_CALL AccessibleStaticTextBase::getSelectionEnd() + { + SolarMutexGuard aGuard; + + sal_Int32 i, nPos, nParas; + for( i=0, nPos=-1, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i ) + { + if( (nPos=mpImpl->GetParagraph(i).getSelectionEnd()) != -1 ) + return nPos; + } + + return nPos; + } + + sal_Bool SAL_CALL AccessibleStaticTextBase::setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + EPosition aStartIndex( mpImpl->Range2Internal(nStartIndex) ); + EPosition aEndIndex( mpImpl->Range2Internal(nEndIndex) ); + + return mpImpl->SetSelection( aStartIndex.nPara, aStartIndex.nIndex, + aEndIndex.nPara, aEndIndex.nIndex ); + } + + OUString SAL_CALL AccessibleStaticTextBase::getText() + { + SolarMutexGuard aGuard; + + sal_Int32 i, nParas; + OUStringBuffer aRes; + for( i=0, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i ) + aRes.append(mpImpl->GetParagraph(i).getText()); + + return aRes.makeStringAndClear(); + } + + OUString SAL_CALL AccessibleStaticTextBase::getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + if( nStartIndex > nEndIndex ) + std::swap(nStartIndex, nEndIndex); + //if startindex equals endindex we will get nothing. So return an empty string directly. + if ( nStartIndex == nEndIndex ) + { + return OUString(); + } + bool bStart = mpImpl->RemoveLineBreakCount( nStartIndex ); + //if the start index is just at a "\n", we need to begin from the next char + if ( bStart ) + { + nStartIndex++; + } + //we need to find out whether the previous position of the current endindex is at "\n" or not + //if yes we need to mark it and add "\n" at the end of the result + sal_Int32 nTemp = nEndIndex - 1; + bool bEnd = mpImpl->RemoveLineBreakCount( nTemp ); + bool bTemp = mpImpl->RemoveLineBreakCount( nEndIndex ); + //if the below condition is true it indicates an empty paragraph with just a "\n" + //so we need to set one "\n" flag to avoid duplication. + if ( bStart && bEnd && ( nStartIndex == nEndIndex) ) + { + bEnd = false; + } + //if the current endindex is at a "\n", we need to increase endindex by 1 to make sure + //the char before "\n" is included. Because string returned by this function will not include + //the char at the endindex. + if ( bTemp ) + { + nEndIndex++; + } + OUStringBuffer aRes; + EPosition aStartIndex( mpImpl->Range2Internal(nStartIndex) ); + EPosition aEndIndex( mpImpl->Range2Internal(nEndIndex) ); + + // #102170# Special case: start and end paragraph are identical + if( aStartIndex.nPara == aEndIndex.nPara ) + { + //we don't return the string directly now for that we have to do some further process for "\n" + aRes = mpImpl->GetParagraph( aStartIndex.nPara ).getTextRange( aStartIndex.nIndex, aEndIndex.nIndex ); + } + else + { + sal_Int32 i( aStartIndex.nPara ); + aRes = mpImpl->GetParagraph(i).getTextRange( aStartIndex.nIndex, + mpImpl->GetParagraph(i).getCharacterCount()/*-1*/); + ++i; + + // paragraphs inbetween are fully included + for( ; i<aEndIndex.nPara; ++i ) + { + aRes.append(cNewLine); + aRes.append(mpImpl->GetParagraph(i).getText()); + } + + if( i<=aEndIndex.nPara ) + { + //if the below condition is matched it means that endindex is at mid of the last paragraph + //we need to add a "\n" before we add the last part of the string. + if ( !bEnd && aEndIndex.nIndex ) + { + aRes.append(cNewLine); + } + aRes.append(mpImpl->GetParagraph(i).getTextRange( 0, aEndIndex.nIndex )); + } + } + //According to the flag we marked before, we have to add "\n" at the beginning + //or at the end of the result string. + if ( bStart ) + { + aRes.insert(0, OUStringChar(cNewLine)); + } + if ( bEnd ) + { + aRes.append(OUStringChar(cNewLine)); + } + return aRes.makeStringAndClear(); + } + + css::accessibility::TextSegment SAL_CALL AccessibleStaticTextBase::getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + bool bLineBreak = mpImpl->RemoveLineBreakCount( nIndex ); + EPosition aPos( mpImpl->Range2Internal(nIndex) ); + + css::accessibility::TextSegment aResult; + + if( AccessibleTextType::PARAGRAPH == aTextType ) + { + // #106393# Special casing one behind last paragraph is + // not necessary, since then, we return the content and + // boundary of that last paragraph. Range2Internal is + // tolerant against that, and returns the last paragraph + // in aPos.nPara. + + // retrieve full text of the paragraph + aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara ).getText(); + + // #112814# Adapt the start index with the paragraph offset + aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara, 0 ) ); + aResult.SegmentEnd = aResult.SegmentStart + aResult.SegmentText.getLength(); + } + else if ( AccessibleTextType::ATTRIBUTE_RUN == aTextType ) + { + SvxAccessibleTextAdapter& rTextForwarder = mpImpl->GetParagraph( aPos.nIndex ).GetTextForwarder(); + sal_Int32 nStartIndex, nEndIndex; + if ( rTextForwarder.GetAttributeRun( nStartIndex, nEndIndex, aPos.nPara, aPos.nIndex, true ) ) + { + aResult.SegmentText = getTextRange( nStartIndex, nEndIndex ); + aResult.SegmentStart = nStartIndex; + aResult.SegmentEnd = nEndIndex; + } + } + else + { + // No special handling required, forward to wrapped class + aResult = mpImpl->GetParagraph( aPos.nPara ).getTextAtIndex( aPos.nIndex, aTextType ); + + // #112814# Adapt the start index with the paragraph offset + mpImpl->CorrectTextSegment( aResult, aPos.nPara ); + if ( bLineBreak ) + { + aResult.SegmentText = OUString(cNewLine); + } + } + + return aResult; + } + + css::accessibility::TextSegment SAL_CALL AccessibleStaticTextBase::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + sal_Int32 nOldIdx = nIndex; + bool bLineBreak = mpImpl->RemoveLineBreakCount( nIndex ); + EPosition aPos( mpImpl->Range2Internal(nIndex) ); + + css::accessibility::TextSegment aResult; + + if( AccessibleTextType::PARAGRAPH == aTextType ) + { + if( aPos.nIndex == mpImpl->GetParagraph( aPos.nPara ).getCharacterCount() ) + { + // #103589# Special casing one behind the last paragraph + aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara ).getText(); + + // #112814# Adapt the start index with the paragraph offset + aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara, 0 ) ); + } + else if( aPos.nPara > 0 ) + { + aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara - 1 ).getText(); + + // #112814# Adapt the start index with the paragraph offset + aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara - 1, 0 ) ); + } + + aResult.SegmentEnd = aResult.SegmentStart + aResult.SegmentText.getLength(); + } + else + { + // No special handling required, forward to wrapped class + aResult = mpImpl->GetParagraph( aPos.nPara ).getTextBeforeIndex( aPos.nIndex, aTextType ); + + // #112814# Adapt the start index with the paragraph offset + mpImpl->CorrectTextSegment( aResult, aPos.nPara ); + if ( bLineBreak && (nOldIdx-1) >= 0) + { + aResult = getTextAtIndex( nOldIdx-1, aTextType ); + } + } + + return aResult; + } + + css::accessibility::TextSegment SAL_CALL AccessibleStaticTextBase::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + sal_Int32 nTemp = nIndex+1; + bool bLineBreak = mpImpl->RemoveLineBreakCount( nTemp ); + mpImpl->RemoveLineBreakCount( nIndex ); + EPosition aPos( mpImpl->Range2Internal(nIndex) ); + + css::accessibility::TextSegment aResult; + + if( AccessibleTextType::PARAGRAPH == aTextType ) + { + // Special casing one behind the last paragraph is not + // necessary, this case is invalid here for + // getTextBehindIndex + if( aPos.nPara + 1 < mpImpl->GetParagraphCount() ) + { + aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara + 1 ).getText(); + + // #112814# Adapt the start index with the paragraph offset + aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara + 1, 0 ) ); + aResult.SegmentEnd = aResult.SegmentStart + aResult.SegmentText.getLength(); + } + } + else + { + // No special handling required, forward to wrapped class + aResult = mpImpl->GetParagraph( aPos.nPara ).getTextBehindIndex( aPos.nIndex, aTextType ); + + // #112814# Adapt the start index with the paragraph offset + mpImpl->CorrectTextSegment( aResult, aPos.nPara ); + if ( bLineBreak ) + { + aResult.SegmentText = OUStringChar(cNewLine) + aResult.SegmentText; + } + } + + return aResult; + } + + sal_Bool SAL_CALL AccessibleStaticTextBase::copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + if( nStartIndex > nEndIndex ) + std::swap(nStartIndex, nEndIndex); + + EPosition aStartIndex( mpImpl->Range2Internal(nStartIndex) ); + EPosition aEndIndex( mpImpl->Range2Internal(nEndIndex) ); + + return mpImpl->CopyText( aStartIndex.nPara, aStartIndex.nIndex, + aEndIndex.nPara, aEndIndex.nIndex ); + } + + sal_Bool SAL_CALL AccessibleStaticTextBase::scrollSubstringTo( sal_Int32, sal_Int32, AccessibleScrollType ) + { + return false; + } + + // XAccessibleTextAttributes + uno::Sequence< beans::PropertyValue > AccessibleStaticTextBase::getDefaultAttributes( const uno::Sequence< OUString >& RequestedAttributes ) + { + // get the intersection of the default attributes of all paragraphs + + SolarMutexGuard aGuard; + + PropertyValueVector aDefAttrVec( + comphelper::sequenceToContainer<PropertyValueVector>(mpImpl->GetParagraph( 0 ).getDefaultAttributes( RequestedAttributes )) ); + + const sal_Int32 nParaCount = mpImpl->GetParagraphCount(); + for ( sal_Int32 nPara = 1; nPara < nParaCount; ++nPara ) + { + uno::Sequence< beans::PropertyValue > aSeq = mpImpl->GetParagraph( nPara ).getDefaultAttributes( RequestedAttributes ); + PropertyValueVector aIntersectionVec; + + for ( const auto& rDefAttr : aDefAttrVec ) + { + const beans::PropertyValue* pItr = aSeq.getConstArray(); + const beans::PropertyValue* pEnd = pItr + aSeq.getLength(); + const beans::PropertyValue* pFind = std::find_if( pItr, pEnd, PropertyValueEqualFunctor(rDefAttr) ); + if ( pFind != pEnd ) + { + aIntersectionVec.push_back( *pFind ); + } + } + + aDefAttrVec.swap( aIntersectionVec ); + + if ( aDefAttrVec.empty() ) + { + break; + } + } + + return comphelper::containerToSequence(aDefAttrVec); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleStaticTextBase::getRunAttributes( sal_Int32 nIndex, const uno::Sequence< OUString >& RequestedAttributes ) + { + // get those default attributes of the paragraph, which are not part + // of the intersection of all paragraphs and add them to the run attributes + + SolarMutexGuard aGuard; + + EPosition aPos( mpImpl->Index2Internal( nIndex ) ); + AccessibleEditableTextPara& rPara = mpImpl->GetParagraph( aPos.nPara ); + uno::Sequence< beans::PropertyValue > aDefAttrSeq = rPara.getDefaultAttributes( RequestedAttributes ); + uno::Sequence< beans::PropertyValue > aRunAttrSeq = rPara.getRunAttributes( aPos.nIndex, RequestedAttributes ); + uno::Sequence< beans::PropertyValue > aIntersectionSeq = getDefaultAttributes( RequestedAttributes ); + PropertyValueVector aDiffVec; + + const beans::PropertyValue* pDefAttr = aDefAttrSeq.getConstArray(); + const sal_Int32 nLength = aDefAttrSeq.getLength(); + for ( sal_Int32 i = 0; i < nLength; ++i ) + { + const beans::PropertyValue* pItr = aIntersectionSeq.getConstArray(); + const beans::PropertyValue* pEnd = pItr + aIntersectionSeq.getLength(); + bool bNone = std::none_of( pItr, pEnd, PropertyValueEqualFunctor( pDefAttr[i] ) ); + if ( bNone && pDefAttr[i].Handle != 0) + { + aDiffVec.push_back( pDefAttr[i] ); + } + } + + return ::comphelper::concatSequences( aRunAttrSeq, comphelper::containerToSequence(aDiffVec) ); + } + + tools::Rectangle AccessibleStaticTextBase::GetParagraphBoundingBox() const + { + return mpImpl->GetParagraphBoundingBox(); + } + +} // end of namespace accessibility + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleStringWrap.cxx b/editeng/source/accessibility/AccessibleStringWrap.cxx new file mode 100644 index 000000000..519a1feb7 --- /dev/null +++ b/editeng/source/accessibility/AccessibleStringWrap.cxx @@ -0,0 +1,91 @@ +/* -*- 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 <algorithm> +#include <cstdlib> + +#include <tools/debug.hxx> +#include <utility> +#include <vcl/outdev.hxx> + +#include <editeng/svxfont.hxx> +#include <AccessibleStringWrap.hxx> + + +// AccessibleStringWrap implementation + + +AccessibleStringWrap::AccessibleStringWrap( OutputDevice& rDev, SvxFont& rFont, OUString aText ) : + mrDev( rDev ), + mrFont( rFont ), + maText(std::move( aText )) +{ +} + +void AccessibleStringWrap::GetCharacterBounds( sal_Int32 nIndex, tools::Rectangle& rRect ) +{ + DBG_ASSERT(nIndex >= 0, + "SvxAccessibleStringWrap::GetCharacterBounds: index value overflow"); + + mrFont.SetPhysFont(mrDev); + + // #108900# Handle virtual position one-past-the end of the string + if( nIndex >= maText.getLength() ) + { + // create a caret bounding rect that has the height of the + // current font and is one pixel wide. + rRect.SetLeft( mrDev.GetTextWidth(maText) ); + rRect.SetTop( 0 ); + rRect.SetSize( Size(mrDev.GetTextHeight(), 1) ); + } + else + { + sal_Int32 aXArray[2]; + mrDev.GetCaretPositions( maText, aXArray, nIndex, 1 ); + rRect.SetLeft( 0 ); + rRect.SetTop( 0 ); + rRect.SetSize( Size(mrDev.GetTextHeight(), std::abs(aXArray[0] - aXArray[1])) ); + rRect.Move( std::min(aXArray[0], aXArray[1]), 0 ); + } + + if( mrFont.IsVertical() ) + { + // #101701# Rotate to vertical + rRect = tools::Rectangle( Point(-rRect.Top(), rRect.Left()), + Point(-rRect.Bottom(), rRect.Right())); + } +} + +sal_Int32 AccessibleStringWrap::GetIndexAtPoint( const Point& rPoint ) +{ + // search for character bounding box containing given point + tools::Rectangle aRect; + sal_Int32 i, nLen = maText.getLength(); + for( i=0; i<nLen; ++i ) + { + GetCharacterBounds(i, aRect); + if( aRect.Contains(rPoint) ) + return i; + } + + return -1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editattr.cxx b/editeng/source/editeng/editattr.cxx new file mode 100644 index 000000000..5d7da53de --- /dev/null +++ b/editeng/source/editeng/editattr.cxx @@ -0,0 +1,451 @@ +/* -*- 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/outdev.hxx> + +#include <svl/grabbagitem.hxx> +#include <libxml/xmlwriter.h> +#include <editeng/svxfont.hxx> +#include <editeng/flditem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/cmapitem.hxx> + +#include <editattr.hxx> + + + +EditCharAttrib::EditCharAttrib( const SfxPoolItem& rAttr, sal_Int32 nS, sal_Int32 nE ) : + nStart(nS), nEnd(nE), bFeature(false), bEdge(false) +{ + pItem = &rAttr; + + assert((rAttr.Which() >= EE_ITEMS_START) && (rAttr.Which() <= EE_ITEMS_END)); + assert((rAttr.Which() < EE_FEATURE_START) || (rAttr.Which() > EE_FEATURE_END) || (nE == (nS+1))); +} + +EditCharAttrib::~EditCharAttrib() +{ +} + +void EditCharAttrib::SetFont( SvxFont&, OutputDevice* ) +{ +} + +void EditCharAttrib::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("EditCharAttrib")); + (void)xmlTextWriterWriteFormatAttribute( + pWriter, BAD_CAST("nStart"), "%" SAL_PRIdINT32, nStart); + (void)xmlTextWriterWriteFormatAttribute( + pWriter, BAD_CAST("nEnd"), "%" SAL_PRIdINT32, nEnd); + pItem->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + + + +EditCharAttribFont::EditCharAttribFont( const SvxFontItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_FONTINFO || rAttr.Which() == EE_CHAR_FONTINFO_CJK || rAttr.Which() == EE_CHAR_FONTINFO_CTL); +} + +void EditCharAttribFont::SetFont( SvxFont& rFont, OutputDevice* ) +{ + const SvxFontItem& rAttr = static_cast<const SvxFontItem&>(*GetItem()); + + rFont.SetFamilyName( rAttr.GetFamilyName() ); + rFont.SetFamily( rAttr.GetFamily() ); + rFont.SetPitch( rAttr.GetPitch() ); + rFont.SetCharSet( rAttr.GetCharSet() ); +} + + + +EditCharAttribItalic::EditCharAttribItalic( const SvxPostureItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_ITALIC || rAttr.Which() == EE_CHAR_ITALIC_CJK || rAttr.Which() == EE_CHAR_ITALIC_CTL); +} + +void EditCharAttribItalic::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetItalic( static_cast<const SvxPostureItem*>(GetItem())->GetPosture() ); +} + + + +EditCharAttribWeight::EditCharAttribWeight( const SvxWeightItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_WEIGHT || rAttr.Which() == EE_CHAR_WEIGHT_CJK || rAttr.Which() == EE_CHAR_WEIGHT_CTL); +} + +void EditCharAttribWeight::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetWeight( static_cast<const SvxWeightItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribUnderline::EditCharAttribUnderline( const SvxUnderlineItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_UNDERLINE); +} + +void EditCharAttribUnderline::SetFont( SvxFont& rFont, OutputDevice* pOutDev ) +{ + rFont.SetUnderline( static_cast<const SvxUnderlineItem*>(GetItem())->GetValue() ); + + if ( pOutDev ) + pOutDev->SetTextLineColor( static_cast<const SvxUnderlineItem*>(GetItem())->GetColor() ); + +} + + + +EditCharAttribOverline::EditCharAttribOverline( const SvxOverlineItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_OVERLINE); +} + +void EditCharAttribOverline::SetFont( SvxFont& rFont, OutputDevice* pOutDev ) +{ + rFont.SetOverline( static_cast<const SvxOverlineItem*>(GetItem())->GetValue() ); + if ( pOutDev ) + pOutDev->SetOverlineColor( static_cast<const SvxOverlineItem*>(GetItem())->GetColor() ); +} + + + +EditCharAttribFontHeight::EditCharAttribFontHeight( const SvxFontHeightItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_FONTHEIGHT || rAttr.Which() == EE_CHAR_FONTHEIGHT_CJK || rAttr.Which() == EE_CHAR_FONTHEIGHT_CTL); +} + +void EditCharAttribFontHeight::SetFont( SvxFont& rFont, OutputDevice* ) +{ + // Property is ignored + rFont.SetFontSize( Size( rFont.GetFontSize().Width(), static_cast<const SvxFontHeightItem*>(GetItem())->GetHeight() ) ); +} + + + +EditCharAttribFontWidth::EditCharAttribFontWidth( const SvxCharScaleWidthItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_FONTWIDTH); +} + +void EditCharAttribFontWidth::SetFont( SvxFont& /*rFont*/, OutputDevice* ) +{ + // must be calculated outside, because f(device)... +} + + + +EditCharAttribStrikeout::EditCharAttribStrikeout( const SvxCrossedOutItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_STRIKEOUT); +} + +void EditCharAttribStrikeout::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetStrikeout( static_cast<const SvxCrossedOutItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribCaseMap::EditCharAttribCaseMap( const SvxCaseMapItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_CASEMAP); +} + +void EditCharAttribCaseMap::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetCaseMap( static_cast<const SvxCaseMapItem*>(GetItem())->GetCaseMap() ); +} + + + +EditCharAttribColor::EditCharAttribColor( const SvxColorItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_COLOR); +} + +void EditCharAttribColor::SetFont( SvxFont& rFont, OutputDevice* ) +{ + Color aColor = static_cast<const SvxColorItem*>(GetItem())->GetValue(); + rFont.SetColor( aColor); +} + + +EditCharAttribBackgroundColor::EditCharAttribBackgroundColor( + const SvxColorItem& rAttr, + sal_Int32 _nStart, + sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_BKGCOLOR); +} + +void EditCharAttribBackgroundColor::SetFont( SvxFont& rFont, OutputDevice* ) +{ + Color aColor = static_cast<const SvxColorItem*>(GetItem())->GetValue(); + rFont.SetTransparent(false); + rFont.SetFillColor(aColor); +} + +EditCharAttribLanguage::EditCharAttribLanguage( const SvxLanguageItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert((rAttr.Which() == EE_CHAR_LANGUAGE) || (rAttr.Which() == EE_CHAR_LANGUAGE_CJK) || (rAttr.Which() == EE_CHAR_LANGUAGE_CTL)); +} + +void EditCharAttribLanguage::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetLanguage( static_cast<const SvxLanguageItem*>(GetItem())->GetLanguage() ); +} + + + +EditCharAttribShadow::EditCharAttribShadow( const SvxShadowedItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_SHADOW); +} + +void EditCharAttribShadow::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetShadow( static_cast<const SvxShadowedItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribEscapement::EditCharAttribEscapement( const SvxEscapementItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_ESCAPEMENT); +} + +void EditCharAttribEscapement::SetFont( SvxFont& rFont, OutputDevice* pOutDev ) +{ + sal_uInt16 const nProp = static_cast<const SvxEscapementItem*>(GetItem())->GetProportionalHeight(); + rFont.SetPropr( static_cast<sal_uInt8>(nProp) ); + + short nEsc = static_cast<const SvxEscapementItem*>(GetItem())->GetEsc(); + rFont.SetNonAutoEscapement( nEsc, pOutDev ); +} + + + +EditCharAttribOutline::EditCharAttribOutline( const SvxContourItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_OUTLINE); +} + +void EditCharAttribOutline::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetOutline( static_cast<const SvxContourItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribTab::EditCharAttribTab( const SfxVoidItem& rAttr, sal_Int32 nPos ) + : EditCharAttrib( rAttr, nPos, nPos+1 ) +{ + SetFeature( true ); +} + +void EditCharAttribTab::SetFont( SvxFont&, OutputDevice* ) +{ +} + + + +EditCharAttribLineBreak::EditCharAttribLineBreak( const SfxVoidItem& rAttr, sal_Int32 nPos ) + : EditCharAttrib( rAttr, nPos, nPos+1 ) +{ + SetFeature( true ); +} + +void EditCharAttribLineBreak::SetFont( SvxFont&, OutputDevice* ) +{ +} + + + +EditCharAttribField::EditCharAttribField( const SvxFieldItem& rAttr, sal_Int32 nPos ) + : EditCharAttrib( rAttr, nPos, nPos+1 ) +{ + SetFeature( true ); // !!! +} + +void EditCharAttribField::SetFont( SvxFont& rFont, OutputDevice* ) +{ + if ( mxFldColor ) + { + rFont.SetFillColor( *mxFldColor ); + rFont.SetTransparent( false ); + } + if ( mxTxtColor ) + rFont.SetColor( *mxTxtColor ); +} + + +void EditCharAttribField::SetFieldValue(const OUString& rVal) +{ + aFieldValue = rVal; +} + +void EditCharAttribField::Reset() +{ + aFieldValue.clear(); + mxTxtColor.reset(); + mxFldColor.reset(); +} + +EditCharAttribField::EditCharAttribField( const EditCharAttribField& rAttr ) + : EditCharAttrib( *rAttr.GetItem(), rAttr.GetStart(), rAttr.GetEnd() ), + aFieldValue( rAttr.aFieldValue ) +{ + // Use this constructor only for temporary Objects, Item is not pooled. + mxTxtColor = rAttr.mxTxtColor; + mxFldColor = rAttr.mxFldColor; +} + +EditCharAttribField::~EditCharAttribField() +{ + Reset(); +} + +bool EditCharAttribField::operator == ( const EditCharAttribField& rAttr ) const +{ + if ( aFieldValue != rAttr.aFieldValue ) + return false; + + if ( ( mxTxtColor && !rAttr.mxTxtColor ) || ( !mxTxtColor && rAttr.mxTxtColor ) ) + return false; + if ( ( mxTxtColor && rAttr.mxTxtColor ) && ( *mxTxtColor != *rAttr.mxTxtColor ) ) + return false; + + if ( ( mxFldColor && !rAttr.mxFldColor ) || ( !mxFldColor && rAttr.mxFldColor ) ) + return false; + if ( ( mxFldColor && rAttr.mxFldColor ) && ( *mxFldColor != *rAttr.mxFldColor ) ) + return false; + + return true; +} + + + +EditCharAttribPairKerning::EditCharAttribPairKerning( const SvxAutoKernItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) +: EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_PAIRKERNING); +} + +void EditCharAttribPairKerning::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetKerning( static_cast<const SvxAutoKernItem*>(GetItem())->GetValue() ? FontKerning::FontSpecific : FontKerning::NONE ); +} + + + +EditCharAttribKerning::EditCharAttribKerning( const SvxKerningItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) +: EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_KERNING); +} + +void EditCharAttribKerning::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetFixKerning( static_cast<const SvxKerningItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribWordLineMode::EditCharAttribWordLineMode( const SvxWordLineModeItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) +: EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_WLM); +} + +void EditCharAttribWordLineMode::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetWordLineMode( static_cast<const SvxWordLineModeItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribEmphasisMark::EditCharAttribEmphasisMark( const SvxEmphasisMarkItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_EMPHASISMARK); +} + +void EditCharAttribEmphasisMark::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetEmphasisMark( static_cast<const SvxEmphasisMarkItem*>(GetItem())->GetEmphasisMark() ); +} + + + +EditCharAttribRelief::EditCharAttribRelief( const SvxCharReliefItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_RELIEF); +} + +void EditCharAttribRelief::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetRelief( static_cast<const SvxCharReliefItem*>(GetItem())->GetValue() ); +} + + +EditCharAttribGrabBag::EditCharAttribGrabBag( const SfxGrabBagItem& rAttr, sal_Int32 _nStart, sal_Int32 _nEnd ) + : EditCharAttrib( rAttr, _nStart, _nEnd ) +{ + assert(rAttr.Which() == EE_CHAR_GRABBAG); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editdata.cxx b/editeng/source/editeng/editdata.cxx new file mode 100644 index 000000000..f272a9afd --- /dev/null +++ b/editeng/source/editeng/editdata.cxx @@ -0,0 +1,16 @@ +/* -*- 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/. + */ + +#include <editeng/editdata.hxx> + +#include <limits> + +const size_t EE_APPEND = std::numeric_limits<size_t>::max(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editdbg.cxx b/editeng/source/editeng/editdbg.cxx new file mode 100644 index 000000000..e3c8f5a2b --- /dev/null +++ b/editeng/source/editeng/editdbg.cxx @@ -0,0 +1,520 @@ +/* -*- 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 <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> + +#include <editeng/lspcitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/numitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/frmdiritem.hxx> + +#include "impedit.hxx" +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editdoc.hxx> + +#include <rtl/strbuf.hxx> +#include <osl/diagnose.h> + +#if defined( DBG_UTIL ) || ( OSL_DEBUG_LEVEL > 1 ) + +static OString DbgOutItem(const SfxItemPool& rPool, const SfxPoolItem& rItem) +{ + OStringBuffer aDebStr; + switch ( rItem.Which() ) + { + case EE_PARA_WRITINGDIR: + aDebStr.append("WritingDir="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxFrameDirectionItem&>(rItem).GetValue())); + break; + case EE_PARA_OUTLLRSPACE: + case EE_PARA_LRSPACE: + aDebStr.append("FI="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxLRSpaceItem&>(rItem).GetTextFirstLineOffset())); + aDebStr.append(", LI="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxLRSpaceItem&>(rItem).GetTextLeft())); + aDebStr.append(", RI="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxLRSpaceItem&>(rItem).GetRight())); + break; + case EE_PARA_NUMBULLET: + aDebStr.append("NumItem "); + for ( sal_uInt16 nLevel = 0; nLevel < 3; nLevel++ ) + { + aDebStr.append("Level"); + aDebStr.append(static_cast<sal_Int32>(nLevel)); + aDebStr.append('='); + const SvxNumberFormat* pFmt = static_cast<const SvxNumBulletItem&>(rItem).GetNumRule().Get( nLevel ); + if ( pFmt ) + { + aDebStr.append('('); + aDebStr.append(pFmt->GetFirstLineOffset()); + aDebStr.append(','); + aDebStr.append(pFmt->GetAbsLSpace()); + aDebStr.append(','); + if ( pFmt->GetNumberingType() == SVX_NUM_BITMAP ) + aDebStr.append("Bitmap"); + else if( pFmt->GetNumberingType() != SVX_NUM_CHAR_SPECIAL ) + aDebStr.append("Number"); + else + { + aDebStr.append("Char=["); + aDebStr.append(static_cast<sal_Int32>(pFmt->GetBulletChar())); + aDebStr.append(']'); + } + aDebStr.append(") "); + } + } + break; + case EE_PARA_BULLETSTATE: + aDebStr.append("ShowBullet="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SfxBoolItem&>(rItem).GetValue())); + break; + case EE_PARA_HYPHENATE: + aDebStr.append("Hyphenate="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SfxBoolItem&>(rItem).GetValue())); + break; + case EE_PARA_OUTLLEVEL: + aDebStr.append("Level="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SfxInt16Item&>(rItem).GetValue())); + break; + case EE_PARA_ULSPACE: + aDebStr.append("SB="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxULSpaceItem&>(rItem).GetUpper())); + aDebStr.append(", SA="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxULSpaceItem&>(rItem).GetLower())); + break; + case EE_PARA_SBL: + aDebStr.append("SBL="); + if ( static_cast<const SvxLineSpacingItem&>(rItem).GetLineSpaceRule() == SvxLineSpaceRule::Min ) + { + aDebStr.append("Min: "); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxLineSpacingItem&>(rItem).GetInterLineSpace())); + } + else if ( static_cast<const SvxLineSpacingItem&>(rItem).GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) + { + aDebStr.append("Prop: "); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxLineSpacingItem&>(rItem).GetPropLineSpace())); + } + else + aDebStr.append("Unsupported Type!"); + break; + case EE_PARA_JUST: + aDebStr.append("SvxAdust="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxAdjustItem&>(rItem).GetAdjust())); + break; + case EE_PARA_TABS: + { + aDebStr.append("Tabs: "); + const SvxTabStopItem& rTabs = static_cast<const SvxTabStopItem&>(rItem); + aDebStr.append(static_cast<sal_Int32>(rTabs.Count())); + if ( rTabs.Count() ) + { + aDebStr.append("( "); + for (sal_uInt16 i = 0; i < rTabs.Count(); ++i) + { + const SvxTabStop& rTab = rTabs[i]; + aDebStr.append(rTab.GetTabPos()); + aDebStr.append(' '); + } + aDebStr.append(')'); + } + } + break; + case EE_CHAR_LANGUAGE: + case EE_CHAR_LANGUAGE_CJK: + case EE_CHAR_LANGUAGE_CTL: + aDebStr.append("Language="); + aDebStr.append(static_cast<sal_Int32>(static_cast<sal_uInt16>(static_cast<const SvxLanguageItem&>(rItem).GetLanguage()))); + break; + case EE_CHAR_COLOR: + { + aDebStr.append("Color= "); + Color aColor( static_cast<const SvxColorItem&>(rItem).GetValue() ); + aDebStr.append(static_cast<sal_Int32>(aColor.GetRed())); + aDebStr.append(", "); + aDebStr.append(static_cast<sal_Int32>(aColor.GetGreen())); + aDebStr.append(", "); + aDebStr.append(static_cast<sal_Int32>(aColor.GetBlue())); + } + break; + case EE_CHAR_BKGCOLOR: + { + aDebStr.append("FillColor= "); + Color aColor( static_cast<const SvxColorItem&>(rItem).GetValue() ); + aDebStr.append(static_cast<sal_Int32>(aColor.GetRed())); + aDebStr.append(", "); + aDebStr.append(static_cast<sal_Int32>(aColor.GetGreen())); + aDebStr.append(", "); + aDebStr.append(static_cast<sal_Int32>(aColor.GetBlue())); + } + break; + case EE_CHAR_FONTINFO: + case EE_CHAR_FONTINFO_CJK: + case EE_CHAR_FONTINFO_CTL: + { + aDebStr.append("Font="); + aDebStr.append(OUStringToOString(static_cast<const SvxFontItem&>(rItem).GetFamilyName(), RTL_TEXTENCODING_ASCII_US)); + aDebStr.append(" (CharSet: "); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxFontItem&>(rItem).GetCharSet())); + aDebStr.append(')'); + } + break; + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + aDebStr.append("Groesse="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxFontHeightItem&>(rItem).GetHeight())); + Size aSz( 0, static_cast<const SvxFontHeightItem&>(rItem).GetHeight() ); + MapUnit eUnit = rPool.GetMetric( rItem.Which() ); + MapMode aItemMapMode(eUnit); + MapMode aPntMap( MapUnit::MapPoint ); + aSz = OutputDevice::LogicToLogic( aSz, aItemMapMode, aPntMap ); + aDebStr.append(" Points="); + aDebStr.append(static_cast<sal_Int32>(aSz.Height())); + } + break; + case EE_CHAR_FONTWIDTH: + { + aDebStr.append("Breite="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxCharScaleWidthItem&>(rItem).GetValue())); + aDebStr.append('%'); + } + break; + case EE_CHAR_WEIGHT: + case EE_CHAR_WEIGHT_CJK: + case EE_CHAR_WEIGHT_CTL: + aDebStr.append("FontWeight="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxWeightItem&>(rItem).GetWeight())); + break; + case EE_CHAR_UNDERLINE: + aDebStr.append("FontUnderline="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxUnderlineItem&>(rItem).GetLineStyle())); + break; + case EE_CHAR_OVERLINE: + aDebStr.append("FontOverline="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxOverlineItem&>(rItem).GetLineStyle())); + break; + case EE_CHAR_EMPHASISMARK: + aDebStr.append("FontUnderline="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxEmphasisMarkItem&>(rItem).GetEmphasisMark())); + break; + case EE_CHAR_RELIEF: + aDebStr.append("FontRelief="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxCharReliefItem&>(rItem).GetValue())); + break; + case EE_CHAR_STRIKEOUT: + aDebStr.append("FontStrikeout="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxCrossedOutItem&>(rItem).GetStrikeout())); + break; + case EE_CHAR_ITALIC: + case EE_CHAR_ITALIC_CJK: + case EE_CHAR_ITALIC_CTL: + aDebStr.append("FontPosture="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxPostureItem&>(rItem).GetPosture())); + break; + case EE_CHAR_OUTLINE: + aDebStr.append("FontOutline="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxContourItem&>(rItem).GetValue())); + break; + case EE_CHAR_SHADOW: + aDebStr.append("FontShadowed="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxShadowedItem&>(rItem).GetValue())); + break; + case EE_CHAR_ESCAPEMENT: + aDebStr.append("Escape="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxEscapementItem&>(rItem).GetEsc())); + aDebStr.append(", "); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxEscapementItem&>(rItem).GetProportionalHeight())); + break; + case EE_CHAR_PAIRKERNING: + aDebStr.append("PairKerning="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxAutoKernItem&>(rItem).GetValue())); + break; + case EE_CHAR_KERNING: + { + aDebStr.append("Kerning="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxKerningItem&>(rItem).GetValue())); + Size aSz( 0, static_cast<short>(static_cast<const SvxKerningItem&>(rItem).GetValue()) ); + MapUnit eUnit = rPool.GetMetric( rItem.Which() ); + MapMode aItemMapMode(eUnit); + MapMode aPntMap( MapUnit::MapPoint ); + aSz = OutputDevice::LogicToLogic( aSz, aItemMapMode, aPntMap ); + aDebStr.append(" Points="); + aDebStr.append(static_cast<sal_Int32>(aSz.Height())); + } + break; + case EE_CHAR_WLM: + aDebStr.append("WordLineMode="); + aDebStr.append(static_cast<sal_Int32>(static_cast<const SvxWordLineModeItem&>(rItem).GetValue())); + break; + case EE_CHAR_XMLATTRIBS: + aDebStr.append("XMLAttribs=..."); + break; + } + return aDebStr.makeStringAndClear(); +} + +static void DbgOutItemSet(FILE* fp, const SfxItemSet& rSet, bool bSearchInParent, bool bShowALL) +{ + for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + fprintf( fp, "\nWhich: %i\t", nWhich ); + if ( rSet.GetItemState( nWhich, bSearchInParent ) == SfxItemState::DEFAULT ) + fprintf( fp, "ITEM_OFF " ); + else if ( rSet.GetItemState( nWhich, bSearchInParent ) == SfxItemState::DONTCARE ) + fprintf( fp, "ITEM_DC " ); + else if ( rSet.GetItemState( nWhich, bSearchInParent ) == SfxItemState::SET ) + fprintf( fp, "ITEM_ON *" ); + + if ( !bShowALL && ( rSet.GetItemState( nWhich, bSearchInParent ) != SfxItemState::SET ) ) + continue; + + const SfxPoolItem& rItem = rSet.Get( nWhich, bSearchInParent ); + OString aDebStr = DbgOutItem( *rSet.GetPool(), rItem ); + fprintf( fp, "%s", aDebStr.getStr() ); + } +} + +void EditEngine::DumpData(const EditEngine* pEE, bool bInfoBox) +{ + if (!pEE) + return; + + FILE* fp = fopen( "editenginedump.log", "w" ); + if ( fp == nullptr ) + { + OSL_FAIL( "Log file could not be created!" ); + return; + } + + const SfxItemPool& rPool = *pEE->GetEmptyItemSet().GetPool(); + + fprintf( fp, "================================================================================" ); + fprintf( fp, "\n================== Document ================================================" ); + fprintf( fp, "\n================================================================================" ); + for ( sal_Int32 nPortion = 0; nPortion < pEE->pImpEditEngine->GetParaPortions().Count(); nPortion++) + { + ParaPortion* pPPortion = pEE->pImpEditEngine->GetParaPortions()[nPortion]; + fprintf( fp, "\nParagraph %" SAL_PRIdINT32 ": Length = %" SAL_PRIdINT32 ", Invalid = %i\nText = '%s'", + nPortion, pPPortion->GetNode()->Len(), pPPortion->IsInvalid(), + OUStringToOString(pPPortion->GetNode()->GetString(), RTL_TEXTENCODING_UTF8).getStr() ); + fprintf( fp, "\nVorlage:" ); + SfxStyleSheet* pStyle = pPPortion->GetNode()->GetStyleSheet(); + if ( pStyle ) + fprintf( fp, " %s", OUStringToOString( pStyle->GetName(), RTL_TEXTENCODING_UTF8).getStr() ); + fprintf( fp, "\nParagraph attribute:" ); + DbgOutItemSet( fp, pPPortion->GetNode()->GetContentAttribs().GetItems(), false, false ); + + fprintf( fp, "\nCharacter attribute:" ); + bool bZeroAttr = false; + for ( sal_Int32 z = 0; z < pPPortion->GetNode()->GetCharAttribs().Count(); ++z ) + { + const std::unique_ptr<EditCharAttrib>& rAttr = pPPortion->GetNode()->GetCharAttribs().GetAttribs()[z]; + OStringBuffer aCharAttribs; + aCharAttribs.append("\nA"); + aCharAttribs.append(nPortion); + aCharAttribs.append(": "); + aCharAttribs.append(static_cast<sal_Int32>(rAttr->GetItem()->Which())); + aCharAttribs.append('\t'); + aCharAttribs.append(rAttr->GetStart()); + aCharAttribs.append('\t'); + aCharAttribs.append(rAttr->GetEnd()); + if ( rAttr->IsEmpty() ) + bZeroAttr = true; + fprintf(fp, "%s => ", aCharAttribs.getStr()); + + OString aDebStr = DbgOutItem( rPool, *rAttr->GetItem() ); + fprintf( fp, "%s", aDebStr.getStr() ); + } + if ( bZeroAttr ) + fprintf( fp, "\nNULL-Attribute!" ); + + const sal_Int32 nTextPortions = pPPortion->GetTextPortions().Count(); + OStringBuffer aPortionStr("\nText portions: #"); + aPortionStr.append(nTextPortions); + aPortionStr.append(" \nA"); + aPortionStr.append(nPortion); + aPortionStr.append(": Paragraph Length = "); + aPortionStr.append(pPPortion->GetNode()->Len()); + aPortionStr.append("\nA"); + aPortionStr.append(nPortion); + aPortionStr.append(": "); + sal_Int32 n = 0; + for ( sal_Int32 z = 0; z < nTextPortions; ++z ) + { + TextPortion& rPortion = pPPortion->GetTextPortions()[z]; + aPortionStr.append(' '); + aPortionStr.append(rPortion.GetLen()); + aPortionStr.append('('); + aPortionStr.append(static_cast<sal_Int32>(rPortion.GetSize().Width())); + aPortionStr.append(')'); + aPortionStr.append('['); + aPortionStr.append(static_cast<sal_Int32>(rPortion.GetKind())); + aPortionStr.append(']'); + aPortionStr.append(';'); + n += rPortion.GetLen(); + } + aPortionStr.append("\nA"); + aPortionStr.append(nPortion); + aPortionStr.append(": Total length: "); + aPortionStr.append(n); + if ( pPPortion->GetNode()->Len() != n ) + aPortionStr.append(" => Error !!!"); + fprintf(fp, "%s", aPortionStr.getStr()); + + fprintf( fp, "\n\nLines:" ); + // First the content ... + for ( sal_Int32 nLine = 0; nLine < pPPortion->GetLines().Count(); nLine++ ) + { + EditLine& rLine = pPPortion->GetLines()[nLine]; + + OString aLine(OUStringToOString(pPPortion->GetNode()->Copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()), RTL_TEXTENCODING_ASCII_US)); + fprintf( fp, "\nLine %" SAL_PRIdINT32 "\t>%s<", nLine, aLine.getStr() ); + } + // then the internal data ... + for ( sal_Int32 nLine = 0; nLine < pPPortion->GetLines().Count(); nLine++ ) + { + EditLine& rLine = pPPortion->GetLines()[nLine]; + fprintf( fp, "\nLine %" SAL_PRIdINT32 ":\tStart: %" SAL_PRIdINT32 ",\tEnd: %" SAL_PRIdINT32, nLine, rLine.GetStart(), rLine.GetEnd() ); + fprintf( fp, "\t\tPortions: %" SAL_PRIdINT32 " - %" SAL_PRIdINT32 ".\tHight: %i, Ascent=%i", rLine.GetStartPortion(), rLine.GetEndPortion(), rLine.GetHeight(), rLine.GetMaxAscent() ); + } + + fprintf( fp, "\n-----------------------------------------------------------------------------" ); + } + + if ( pEE->pImpEditEngine->GetStyleSheetPool() ) + { + SfxStyleSheetIterator aIter( pEE->pImpEditEngine->GetStyleSheetPool(), SfxStyleFamily::All ); + sal_uInt16 nStyles = aIter.Count(); + fprintf( fp, "\n\n================================================================================" ); + fprintf( fp, "\n================== Stylesheets =============================================" ); + fprintf( fp, "\n================================================================================" ); + fprintf( fp, "\n#Template: %" SAL_PRIuUINT32 "\n", sal_uInt32(nStyles) ); + SfxStyleSheetBase* pStyle = aIter.First(); + while ( pStyle ) + { + fprintf( fp, "\nTemplate: %s", OUStringToOString( pStyle->GetName(), RTL_TEXTENCODING_ASCII_US ).getStr() ); + fprintf( fp, "\nParent: %s", OUStringToOString( pStyle->GetParent(), RTL_TEXTENCODING_ASCII_US ).getStr() ); + fprintf( fp, "\nFollow: %s", OUStringToOString( pStyle->GetFollow(), RTL_TEXTENCODING_ASCII_US ).getStr() ); + DbgOutItemSet( fp, pStyle->GetItemSet(), false, false ); + fprintf( fp, "\n----------------------------------" ); + + pStyle = aIter.Next(); + } + } + + fprintf( fp, "\n\n================================================================================" ); + fprintf( fp, "\n================== Defaults ================================================" ); + fprintf( fp, "\n================================================================================" ); + DbgOutItemSet( fp, pEE->pImpEditEngine->GetEmptyItemSet(), true, true ); + + fprintf( fp, "\n\n================================================================================" ); + fprintf( fp, "\n================== EditEngine & Views ======================================" ); + fprintf( fp, "\n================================================================================" ); + fprintf( fp, "\nControl: %x", unsigned( pEE->GetControlWord() ) ); + fprintf( fp, "\nRefMapMode: %i", int( pEE->pImpEditEngine->pRefDev->GetMapMode().GetMapUnit() ) ); + fprintf( fp, "\nPaperSize: %" SAL_PRIdINT64 " x %" SAL_PRIdINT64, sal_Int64(pEE->GetPaperSize().Width()), sal_Int64(pEE->GetPaperSize().Height()) ); + fprintf( fp, "\nMaxAutoPaperSize: %" SAL_PRIdINT64 " x %" SAL_PRIdINT64, sal_Int64(pEE->GetMaxAutoPaperSize().Width()), sal_Int64(pEE->GetMaxAutoPaperSize().Height()) ); + fprintf( fp, "\nMinAutoPaperSize: %" SAL_PRIdINT64 " x %" SAL_PRIdINT64 , sal_Int64(pEE->GetMinAutoPaperSize().Width()), sal_Int64(pEE->GetMinAutoPaperSize().Height()) ); + fprintf( fp, "\nCalculateLayout: %i", pEE->IsUpdateLayout() ); + fprintf( fp, "\nNumber of Views: %" SAL_PRI_SIZET "i", pEE->GetViewCount() ); + for ( size_t nView = 0; nView < pEE->GetViewCount(); nView++ ) + { + EditView* pV = pEE->GetView( nView ); + DBG_ASSERT( pV, "View not found!" ); + fprintf( fp, "\nView %zu: Focus=%i", nView, pV->GetWindow()->HasFocus() ); + tools::Rectangle aR( pV->GetOutputArea() ); + fprintf( fp, "\n OutputArea: nX=%" SAL_PRIdINT64 ", nY=%" SAL_PRIdINT64 ", dX=%" SAL_PRIdINT64 ", dY=%" SAL_PRIdINT64 ", MapMode = %i", + sal_Int64(aR.Left()), sal_Int64(aR.Top()), sal_Int64(aR.GetSize().Width()), sal_Int64(aR.GetSize().Height()) , int( pV->GetWindow()->GetMapMode().GetMapUnit() ) ); + aR = pV->GetVisArea(); + fprintf( fp, "\n VisArea: nX=%" SAL_PRIdINT64 ", nY=%" SAL_PRIdINT64 ", dX=%" SAL_PRIdINT64 ", dY=%" SAL_PRIdINT64, + sal_Int64(aR.Left()), sal_Int64(aR.Top()), sal_Int64(aR.GetSize().Width()), sal_Int64(aR.GetSize().Height()) ); + ESelection aSel = pV->GetSelection(); + fprintf( fp, "\n Selection: Start=%" SAL_PRIdINT32 ",%" SAL_PRIdINT32 ", End=%" SAL_PRIdINT32 ",%" SAL_PRIdINT32, aSel.nStartPara, aSel.nStartPos, aSel.nEndPara, aSel.nEndPos ); + } + if ( pEE->GetActiveView() ) + { + fprintf( fp, "\n\n================================================================================" ); + fprintf( fp, "\n================== Current View ===========================================" ); + fprintf( fp, "\n================================================================================" ); + DbgOutItemSet( fp, pEE->GetActiveView()->GetAttribs(), true, false ); + } + fclose( fp ); + if ( bInfoBox ) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Info, VclButtonsType::Ok, + "Dumped editenginedump.log!" )); + xInfoBox->run(); + } +} +#endif + +#if OSL_DEBUG_LEVEL > 0 +bool ParaPortion::DbgCheckTextPortions(ParaPortion const& rPara) +{ + // check, if Portion length ok: + sal_uInt16 nXLen = 0; + for (sal_Int32 nPortion = 0; nPortion < rPara.aTextPortionList.Count(); nPortion++) + { + nXLen = nXLen + rPara.aTextPortionList[nPortion].GetLen(); + } + return nXLen == rPara.pNode->Len(); +} +#endif + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG +void CheckOrderedList(const CharAttribList::AttribsType& rAttribs) +{ + sal_Int32 nPrev = 0; + for (const std::unique_ptr<EditCharAttrib>& rAttr : rAttribs) + { + sal_Int32 const nCur = rAttr->GetStart(); + assert(nCur >= nPrev); + nPrev = nCur; + } +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editdoc.cxx b/editeng/source/editeng/editdoc.cxx new file mode 100644 index 000000000..41a8cf20f --- /dev/null +++ b/editeng/source/editeng/editdoc.cxx @@ -0,0 +1,3039 @@ +/* -*- 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 <editeng/tstpitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/editids.hrc> +#include <editeng/editdata.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lspcitem.hxx> + +#include <editdoc.hxx> +#include <editeng/eerdll.hxx> +#include <eerdll2.hxx> +#include "impedit.hxx" + +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include <svl/grabbagitem.hxx> +#include <tools/debug.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <libxml/xmlwriter.h> + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <limits> +#include <memory> +#include <set> +#include <string_view> +#include <utility> + +using namespace ::com::sun::star; + + +sal_uInt16 GetScriptItemId( sal_uInt16 nItemId, SvtScriptType nScriptType ) +{ + sal_uInt16 nId = nItemId; + + if ( ( nScriptType == SvtScriptType::ASIAN ) || + ( nScriptType == SvtScriptType::COMPLEX ) ) + { + switch ( nItemId ) + { + case EE_CHAR_LANGUAGE: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_LANGUAGE_CJK : EE_CHAR_LANGUAGE_CTL; + break; + case EE_CHAR_FONTINFO: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_FONTINFO_CJK : EE_CHAR_FONTINFO_CTL; + break; + case EE_CHAR_FONTHEIGHT: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_FONTHEIGHT_CJK : EE_CHAR_FONTHEIGHT_CTL; + break; + case EE_CHAR_WEIGHT: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_WEIGHT_CJK : EE_CHAR_WEIGHT_CTL; + break; + case EE_CHAR_ITALIC: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_ITALIC_CJK : EE_CHAR_ITALIC_CTL; + break; + } + } + + return nId; +} + +bool IsScriptItemValid( sal_uInt16 nItemId, short nScriptType ) +{ + bool bValid = true; + + switch ( nItemId ) + { + case EE_CHAR_LANGUAGE: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_LANGUAGE_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_LANGUAGE_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_FONTINFO: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_FONTINFO_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_FONTINFO_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_FONTHEIGHT: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_FONTHEIGHT_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_FONTHEIGHT_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_WEIGHT: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_WEIGHT_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_WEIGHT_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_ITALIC: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_ITALIC_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_ITALIC_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + } + + return bValid; +} + +const SfxItemInfo aItemInfos[EDITITEMCOUNT] = { + { SID_ATTR_FRAMEDIRECTION, true }, // EE_PARA_WRITINGDIR + { 0, true }, // EE_PARA_XMLATTRIBS + { SID_ATTR_PARA_HANGPUNCTUATION, true }, // EE_PARA_HANGINGPUNCTUATION + { SID_ATTR_PARA_FORBIDDEN_RULES, true }, // EE_PARA_FORBIDDENRULES + { SID_ATTR_PARA_SCRIPTSPACE, true }, // EE_PARA_ASIANCJKSPACING + { SID_ATTR_NUMBERING_RULE, true }, // EE_PARA_NUMBULL + { 0, true }, // EE_PARA_HYPHENATE + { 0, true }, // EE_PARA_HYPHENATE_NO_CAPS + { 0, true }, // EE_PARA_HYPHENATE_NO_LAST_WORD + { 0, true }, // EE_PARA_BULLETSTATE + { 0, true }, // EE_PARA_OUTLLRSPACE + { SID_ATTR_PARA_OUTLLEVEL, true }, // EE_PARA_OUTLLEVEL + { SID_ATTR_PARA_BULLET, true }, // EE_PARA_BULLET + { SID_ATTR_LRSPACE, true }, // EE_PARA_LRSPACE + { SID_ATTR_ULSPACE, true }, // EE_PARA_ULSPACE + { SID_ATTR_PARA_LINESPACE, true }, // EE_PARA_SBL + { SID_ATTR_PARA_ADJUST, true }, // EE_PARA_JUST + { SID_ATTR_TABSTOP, true }, // EE_PARA_TABS + { SID_ATTR_ALIGN_HOR_JUSTIFY_METHOD, true }, // EE_PARA_JUST_METHOD + { SID_ATTR_ALIGN_VER_JUSTIFY, true }, // EE_PARA_VER_JUST + { SID_ATTR_CHAR_COLOR, true }, // EE_CHAR_COLOR + { SID_ATTR_CHAR_FONT, true }, // EE_CHAR_FONTINFO + { SID_ATTR_CHAR_FONTHEIGHT, true }, // EE_CHAR_FONTHEIGHT + { SID_ATTR_CHAR_SCALEWIDTH, true }, // EE_CHAR_FONTWIDTH + { SID_ATTR_CHAR_WEIGHT, true }, // EE_CHAR_WEIGHT + { SID_ATTR_CHAR_UNDERLINE, true }, // EE_CHAR_UNDERLINE + { SID_ATTR_CHAR_STRIKEOUT, true }, // EE_CHAR_STRIKEOUT + { SID_ATTR_CHAR_POSTURE, true }, // EE_CHAR_ITALIC + { SID_ATTR_CHAR_CONTOUR, true }, // EE_CHAR_OUTLINE + { SID_ATTR_CHAR_SHADOWED, true }, // EE_CHAR_SHADOW + { SID_ATTR_CHAR_ESCAPEMENT, true }, // EE_CHAR_ESCAPEMENT + { SID_ATTR_CHAR_AUTOKERN, true }, // EE_CHAR_PAIRKERNING + { SID_ATTR_CHAR_KERNING, true }, // EE_CHAR_KERNING + { SID_ATTR_CHAR_WORDLINEMODE, true }, // EE_CHAR_WLM + { SID_ATTR_CHAR_LANGUAGE, true }, // EE_CHAR_LANGUAGE + { SID_ATTR_CHAR_CJK_LANGUAGE, true }, // EE_CHAR_LANGUAGE_CJK + { SID_ATTR_CHAR_CTL_LANGUAGE, true }, // EE_CHAR_LANGUAGE_CTL + { SID_ATTR_CHAR_CJK_FONT, true }, // EE_CHAR_FONTINFO_CJK + { SID_ATTR_CHAR_CTL_FONT, true }, // EE_CHAR_FONTINFO_CTL + { SID_ATTR_CHAR_CJK_FONTHEIGHT, true }, // EE_CHAR_FONTHEIGHT_CJK + { SID_ATTR_CHAR_CTL_FONTHEIGHT, true }, // EE_CHAR_FONTHEIGHT_CTL + { SID_ATTR_CHAR_CJK_WEIGHT, true }, // EE_CHAR_WEIGHT_CJK + { SID_ATTR_CHAR_CTL_WEIGHT, true }, // EE_CHAR_WEIGHT_CTL + { SID_ATTR_CHAR_CJK_POSTURE, true }, // EE_CHAR_ITALIC_CJK + { SID_ATTR_CHAR_CTL_POSTURE, true }, // EE_CHAR_ITALIC_CTL + { SID_ATTR_CHAR_EMPHASISMARK, true }, // EE_CHAR_EMPHASISMARK + { SID_ATTR_CHAR_RELIEF, true }, // EE_CHAR_RELIEF + { 0, true }, // EE_CHAR_RUBI_DUMMY + { 0, true }, // EE_CHAR_XMLATTRIBS + { SID_ATTR_CHAR_OVERLINE, true }, // EE_CHAR_OVERLINE + { SID_ATTR_CHAR_CASEMAP, true }, // EE_CHAR_CASEMAP + { SID_ATTR_CHAR_GRABBAG, true }, // EE_CHAR_GRABBAG + { SID_ATTR_CHAR_BACK_COLOR, true }, // EE_CHAR_BKGCOLOR + { 0, true }, // EE_FEATURE_TAB + { 0, true }, // EE_FEATURE_LINEBR + { SID_ATTR_CHAR_CHARSETCOLOR, true }, // EE_FEATURE_NOTCONV + { SID_FIELD, false }, // EE_FEATURE_FIELD +}; + +EditCharAttrib* MakeCharAttrib( SfxItemPool& rPool, const SfxPoolItem& rAttr, sal_Int32 nS, sal_Int32 nE ) +{ + // Create a new attribute in the pool + const SfxPoolItem& rNew = rPool.Put( rAttr ); + + EditCharAttrib* pNew = nullptr; + switch( rNew.Which() ) + { + case EE_CHAR_LANGUAGE: + case EE_CHAR_LANGUAGE_CJK: + case EE_CHAR_LANGUAGE_CTL: + { + pNew = new EditCharAttribLanguage( static_cast<const SvxLanguageItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_COLOR: + { + pNew = new EditCharAttribColor( static_cast<const SvxColorItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_FONTINFO: + case EE_CHAR_FONTINFO_CJK: + case EE_CHAR_FONTINFO_CTL: + { + pNew = new EditCharAttribFont( static_cast<const SvxFontItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + pNew = new EditCharAttribFontHeight( static_cast<const SvxFontHeightItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_FONTWIDTH: + { + pNew = new EditCharAttribFontWidth( static_cast<const SvxCharScaleWidthItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_WEIGHT: + case EE_CHAR_WEIGHT_CJK: + case EE_CHAR_WEIGHT_CTL: + { + pNew = new EditCharAttribWeight( static_cast<const SvxWeightItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_UNDERLINE: + { + pNew = new EditCharAttribUnderline( static_cast<const SvxUnderlineItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_OVERLINE: + { + pNew = new EditCharAttribOverline( static_cast<const SvxOverlineItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_EMPHASISMARK: + { + pNew = new EditCharAttribEmphasisMark( static_cast<const SvxEmphasisMarkItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_RELIEF: + { + pNew = new EditCharAttribRelief( static_cast<const SvxCharReliefItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_STRIKEOUT: + { + pNew = new EditCharAttribStrikeout( static_cast<const SvxCrossedOutItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_ITALIC: + case EE_CHAR_ITALIC_CJK: + case EE_CHAR_ITALIC_CTL: + { + pNew = new EditCharAttribItalic( static_cast<const SvxPostureItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_OUTLINE: + { + pNew = new EditCharAttribOutline( static_cast<const SvxContourItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_SHADOW: + { + pNew = new EditCharAttribShadow( static_cast<const SvxShadowedItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_ESCAPEMENT: + { + pNew = new EditCharAttribEscapement( static_cast<const SvxEscapementItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_PAIRKERNING: + { + pNew = new EditCharAttribPairKerning( static_cast<const SvxAutoKernItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_KERNING: + { + pNew = new EditCharAttribKerning( static_cast<const SvxKerningItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_WLM: + { + pNew = new EditCharAttribWordLineMode( static_cast<const SvxWordLineModeItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_XMLATTRIBS: + { + pNew = new EditCharAttrib( rNew, nS, nE ); // Attribute is only for holding XML information... + } + break; + case EE_CHAR_CASEMAP: + { + pNew = new EditCharAttribCaseMap( static_cast<const SvxCaseMapItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_GRABBAG: + { + pNew = new EditCharAttribGrabBag( static_cast<const SfxGrabBagItem&>(rNew), nS, nE ); + } + break; + case EE_FEATURE_TAB: + { + pNew = new EditCharAttribTab( static_cast<const SfxVoidItem&>(rNew), nS ); + } + break; + case EE_FEATURE_LINEBR: + { + pNew = new EditCharAttribLineBreak( static_cast<const SfxVoidItem&>(rNew), nS ); + } + break; + case EE_FEATURE_FIELD: + { + pNew = new EditCharAttribField( static_cast<const SvxFieldItem&>(rNew), nS ); + } + break; + case EE_CHAR_BKGCOLOR: + { + pNew = new EditCharAttribBackgroundColor( static_cast<const SvxColorItem&>(rNew), nS, nE ); + } + break; + default: + { + OSL_FAIL( "Invalid Attribute!" ); + } + } + return pNew; +} + +TextPortionList::TextPortionList() +{ +} + +TextPortionList::~TextPortionList() +{ + Reset(); +} + +void TextPortionList::Reset() +{ + maPortions.clear(); +} + +void TextPortionList::DeleteFromPortion(sal_Int32 nDelFrom) +{ + assert((nDelFrom < static_cast<sal_Int32>(maPortions.size())) || ((nDelFrom == 0) && maPortions.empty())); + PortionsType::iterator it = maPortions.begin(); + std::advance(it, nDelFrom); + maPortions.erase(it, maPortions.end()); +} + +sal_Int32 TextPortionList::Count() const +{ + return static_cast<sal_Int32>(maPortions.size()); +} + +const TextPortion& TextPortionList::operator[](sal_Int32 nPos) const +{ + return *maPortions[nPos]; +} + +TextPortion& TextPortionList::operator[](sal_Int32 nPos) +{ + return *maPortions[nPos]; +} + +void TextPortionList::Append(TextPortion* p) +{ + maPortions.push_back(std::unique_ptr<TextPortion>(p)); +} + +void TextPortionList::Insert(sal_Int32 nPos, TextPortion* p) +{ + maPortions.insert(maPortions.begin()+nPos, std::unique_ptr<TextPortion>(p)); +} + +void TextPortionList::Remove(sal_Int32 nPos) +{ + maPortions.erase(maPortions.begin()+nPos); +} + +namespace { + +class FindTextPortionByAddress +{ + const TextPortion* mp; +public: + explicit FindTextPortionByAddress(const TextPortion* p) : mp(p) {} + bool operator() (const std::unique_ptr<TextPortion>& v) const + { + return v.get() == mp; + } +}; + +} + +sal_Int32 TextPortionList::GetPos(const TextPortion* p) const +{ + PortionsType::const_iterator it = + std::find_if(maPortions.begin(), maPortions.end(), FindTextPortionByAddress(p)); + + if (it == maPortions.end()) + return std::numeric_limits<sal_Int32>::max(); // not found. + + return std::distance(maPortions.begin(), it); +} + +sal_Int32 TextPortionList::FindPortion( + sal_Int32 nCharPos, sal_Int32& nPortionStart, bool bPreferStartingPortion) const +{ + // When nCharPos at portion limit, the left portion is found + sal_Int32 nTmpPos = 0; + sal_Int32 n = maPortions.size(); + for (sal_Int32 i = 0; i < n; ++i) + { + const TextPortion& rPortion = *maPortions[i]; + nTmpPos = nTmpPos + rPortion.GetLen(); + if ( nTmpPos >= nCharPos ) + { + // take this one if we don't prefer the starting portion, or if it's the last one + if ( ( nTmpPos != nCharPos ) || !bPreferStartingPortion || ( i == n-1 ) ) + { + nPortionStart = nTmpPos - rPortion.GetLen(); + return i; + } + } + } + OSL_FAIL( "FindPortion: Not found!" ); + return n - 1; +} + +sal_Int32 TextPortionList::GetStartPos(sal_Int32 nPortion) +{ + sal_Int32 nPos = 0; + for (sal_Int32 i = 0; i < nPortion; ++i) + { + const TextPortion& rPortion = *maPortions[i]; + nPos = nPos + rPortion.GetLen(); + } + return nPos; +} + +ExtraPortionInfo::ExtraPortionInfo() +: nOrgWidth(0) +, nWidthFullCompression(0) +, nPortionOffsetX(0) +, nMaxCompression100thPercent(0) +, nAsianCompressionTypes(AsianCompressionFlags::Normal) +, bFirstCharIsRightPunktuation(false) +, bCompressed(false) +{ +} + +ExtraPortionInfo::~ExtraPortionInfo() +{ +} + +void ExtraPortionInfo::SaveOrgDXArray( const sal_Int32* pDXArray, sal_Int32 nLen ) +{ + if (pDXArray) + { + pOrgDXArray.reset(new sal_Int32[nLen]); + memcpy( pOrgDXArray.get(), pDXArray, nLen * sizeof(sal_Int32) ); + } + else + pOrgDXArray.reset(); +} + +ParaPortion::ParaPortion( ContentNode* pN ) : + pNode(pN), + nHeight(0), + nInvalidPosStart(0), + nFirstLineOffset(0), + nBulletX(0), + nInvalidDiff(0), + bInvalid(true), + bSimple(false), + bVisible(true), + bForceRepaint(false) +{ +} + +ParaPortion::~ParaPortion() +{ +} + +void ParaPortion::MarkInvalid( sal_Int32 nStart, sal_Int32 nDiff ) +{ + if ( !bInvalid ) + { +// nInvalidPosEnd = nStart; // ??? => CreateLines + nInvalidPosStart = ( nDiff >= 0 ) ? nStart : ( nStart + nDiff ); + nInvalidDiff = nDiff; + } + else + { + // Simple tap in succession + if ( ( nDiff > 0 ) && ( nInvalidDiff > 0 ) && + ( ( nInvalidPosStart+nInvalidDiff ) == nStart ) ) + { + nInvalidDiff = nInvalidDiff + nDiff; + } + // Simple delete in succession + else if ( ( nDiff < 0 ) && ( nInvalidDiff < 0 ) && ( nInvalidPosStart == nStart ) ) + { + nInvalidPosStart = nInvalidPosStart + nDiff; + nInvalidDiff = nInvalidDiff + nDiff; + } + else + { +// nInvalidPosEnd = pNode->Len(); + DBG_ASSERT( ( nDiff >= 0 ) || ( (nStart+nDiff) >= 0 ), "MarkInvalid: Diff out of Range" ); + nInvalidPosStart = std::min( nInvalidPosStart, ( nDiff < 0 ? nStart+nDiff : nDiff ) ); + nInvalidDiff = 0; + bSimple = false; + } + } + bInvalid = true; + aScriptInfos.clear(); + aWritingDirectionInfos.clear(); +} + +void ParaPortion::MarkSelectionInvalid( sal_Int32 nStart ) +{ + if ( !bInvalid ) + { + nInvalidPosStart = nStart; + } + else + { + nInvalidPosStart = std::min( nInvalidPosStart, nStart ); + } + nInvalidDiff = 0; + bInvalid = true; + bSimple = false; + aScriptInfos.clear(); + aWritingDirectionInfos.clear(); +} + +sal_Int32 ParaPortion::GetLineNumber( sal_Int32 nIndex ) const +{ + SAL_WARN_IF( !aLineList.Count(), "editeng", "Empty ParaPortion in GetLine!" ); + DBG_ASSERT( bVisible, "Why GetLine() on an invisible paragraph?" ); + + for ( sal_Int32 nLine = 0; nLine < aLineList.Count(); nLine++ ) + { + if ( aLineList[nLine].IsIn( nIndex ) ) + return nLine; + } + + // Then it should be at the end of the last line! + DBG_ASSERT( nIndex == aLineList[ aLineList.Count() - 1 ].GetEnd(), "Index dead wrong!" ); + return (aLineList.Count()-1); +} + +void ParaPortion::SetVisible( bool bMakeVisible ) +{ + bVisible = bMakeVisible; +} + +void ParaPortion::CorrectValuesBehindLastFormattedLine( sal_Int32 nLastFormattedLine ) +{ + sal_Int32 nLines = aLineList.Count(); + DBG_ASSERT( nLines, "CorrectPortionNumbersFromLine: Empty Portion?" ); + if ( nLastFormattedLine < ( nLines - 1 ) ) + { + const EditLine& rLastFormatted = aLineList[ nLastFormattedLine ]; + const EditLine& rUnformatted = aLineList[ nLastFormattedLine+1 ]; + sal_Int32 nPortionDiff = rUnformatted.GetStartPortion() - rLastFormatted.GetEndPortion(); + sal_Int32 nTextDiff = rUnformatted.GetStart() - rLastFormatted.GetEnd(); + nTextDiff++; // LastFormatted->GetEnd() was included => 1 deducted too much! + + // The first unformatted must begin exactly one Portion behind the last + // of the formatted: + // If the modified line was split into one portion, can + // nLastEnd > nNextStart! + int nPDiff = -( nPortionDiff-1 ); + int nTDiff = -( nTextDiff-1 ); + if ( nPDiff || nTDiff ) + { + for ( sal_Int32 nL = nLastFormattedLine+1; nL < nLines; nL++ ) + { + EditLine& rLine = aLineList[ nL ]; + + rLine.GetStartPortion() = rLine.GetStartPortion() + nPDiff; + rLine.GetEndPortion() = rLine.GetEndPortion() + nPDiff; + + rLine.GetStart() = rLine.GetStart() + nTDiff; + rLine.GetEnd() = rLine.GetEnd() + nTDiff; + + rLine.SetValid(); + } + } + } + DBG_ASSERT( aLineList[ aLineList.Count()-1 ].GetEnd() == pNode->Len(), "CorrectLines: The end is not right!" ); +} + +// Shared reverse lookup acceleration pieces ... + +namespace { + +template<typename Array, typename Val> +sal_Int32 FastGetPos(const Array& rArray, const Val* p, sal_Int32& rLastPos) +{ + sal_Int32 nArrayLen = rArray.size(); + + // Through certain filter code-paths we do a lot of appends, which in + // turn call GetPos - creating some N^2 nightmares. If we have a + // non-trivially large list, do a few checks from the end first. + if (rLastPos > 16 && nArrayLen > 16) + { + sal_Int32 nEnd; + if (rLastPos > nArrayLen - 2) + nEnd = nArrayLen; + else + nEnd = rLastPos + 2; + + for (sal_Int32 nIdx = rLastPos - 2; nIdx < nEnd; ++nIdx) + { + if (rArray.at(nIdx).get() == p) + { + rLastPos = nIdx; + return nIdx; + } + } + } + // The world's lamest linear search from svarray... + for (sal_Int32 nIdx = 0; nIdx < nArrayLen; ++nIdx) + if (rArray.at(nIdx).get() == p) + { + rLastPos = nIdx; + return rLastPos; + } + + // XXX "not found" condition for sal_Int32 indexes + return EE_PARA_NOT_FOUND; +} + +} + +ParaPortionList::ParaPortionList() : nLastCache( 0 ) +{ +} + +ParaPortionList::~ParaPortionList() +{ +} + +sal_Int32 ParaPortionList::GetPos(const ParaPortion* p) const +{ + return FastGetPos(maPortions, p, nLastCache); +} + +ParaPortion* ParaPortionList::operator [](sal_Int32 nPos) +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maPortions.size() ? maPortions[nPos].get() : nullptr; +} + +const ParaPortion* ParaPortionList::operator [](sal_Int32 nPos) const +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maPortions.size() ? maPortions[nPos].get() : nullptr; +} + +std::unique_ptr<ParaPortion> ParaPortionList::Release(sal_Int32 nPos) +{ + if (nPos < 0 || maPortions.size() <= o3tl::make_unsigned(nPos)) + { + SAL_WARN( "editeng", "ParaPortionList::Release - out of bounds pos " << nPos); + return nullptr; + } + std::unique_ptr<ParaPortion> p = std::move(maPortions[nPos]); + maPortions.erase(maPortions.begin()+nPos); + return p; +} + +void ParaPortionList::Remove(sal_Int32 nPos) +{ + if (nPos < 0 || maPortions.size() <= o3tl::make_unsigned(nPos)) + { + SAL_WARN( "editeng", "ParaPortionList::Remove - out of bounds pos " << nPos); + return; + } + maPortions.erase(maPortions.begin()+nPos); +} + +void ParaPortionList::Insert(sal_Int32 nPos, std::unique_ptr<ParaPortion> p) +{ + if (nPos < 0 || maPortions.size() < o3tl::make_unsigned(nPos)) + { + SAL_WARN( "editeng", "ParaPortionList::Insert - out of bounds pos " << nPos); + return; + } + maPortions.insert(maPortions.begin()+nPos, std::move(p)); +} + +void ParaPortionList::Append(std::unique_ptr<ParaPortion> p) +{ + maPortions.push_back(std::move(p)); +} + +sal_Int32 ParaPortionList::Count() const +{ + size_t nSize = maPortions.size(); + if (nSize > SAL_MAX_INT32) + { + SAL_WARN( "editeng", "ParaPortionList::Count - overflow " << nSize); + return SAL_MAX_INT32; + } + return nSize; +} + +void ParaPortionList::Reset() +{ + maPortions.clear(); +} + +tools::Long ParaPortionList::GetYOffset(const ParaPortion* pPPortion) const +{ + tools::Long nHeight = 0; + for (const auto & rPortion : maPortions) + { + const ParaPortion* pTmpPortion = rPortion.get(); + if ( pTmpPortion == pPPortion ) + return nHeight; + nHeight += pTmpPortion->GetHeight(); + } + OSL_FAIL( "GetYOffset: Portion not found" ); + return nHeight; +} + +sal_Int32 ParaPortionList::FindParagraph(tools::Long nYOffset) const +{ + tools::Long nY = 0; + for (size_t i = 0, n = maPortions.size(); i < n; ++i) + { + nY += maPortions[i]->GetHeight(); // should also be correct even in bVisible! + if ( nY > nYOffset ) + return i <= SAL_MAX_INT32 ? static_cast<sal_Int32>(i) : SAL_MAX_INT32; + } + return EE_PARA_NOT_FOUND; +} + +const ParaPortion* ParaPortionList::SafeGetObject(sal_Int32 nPos) const +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maPortions.size() ? maPortions[nPos].get() : nullptr; +} + +ParaPortion* ParaPortionList::SafeGetObject(sal_Int32 nPos) +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maPortions.size() ? maPortions[nPos].get() : nullptr; +} + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG +void +ParaPortionList::DbgCheck(ParaPortionList const& rParas, EditDoc const& rDoc) +{ + assert(rParas.Count() == rDoc.Count()); + for (sal_Int32 i = 0; i < rParas.Count(); ++i) + { + assert(rParas.SafeGetObject(i) != nullptr); + assert(rParas.SafeGetObject(i)->GetNode() != nullptr); + assert(rParas.SafeGetObject(i)->GetNode() == rDoc.GetObject(i)); + } +} +#endif + +ContentAttribsInfo::ContentAttribsInfo( SfxItemSet aParaAttribs ) : + aPrevParaAttribs(std::move( aParaAttribs)) +{ +} + +void ContentAttribsInfo::RemoveAllCharAttribsFromPool(SfxItemPool& rPool) const +{ + for (const std::unique_ptr<EditCharAttrib>& rAttrib : aPrevCharAttribs) + rPool.Remove(*rAttrib->GetItem()); +} + +void ContentAttribsInfo::AppendCharAttrib(EditCharAttrib* pNew) +{ + aPrevCharAttribs.push_back(std::unique_ptr<EditCharAttrib>(pNew)); +} + +void ConvertItem( std::unique_ptr<SfxPoolItem>& rPoolItem, MapUnit eSourceUnit, MapUnit eDestUnit ) +{ + DBG_ASSERT( eSourceUnit != eDestUnit, "ConvertItem - Why?!" ); + + switch ( rPoolItem->Which() ) + { + case EE_PARA_LRSPACE: + { + assert(dynamic_cast<const SvxLRSpaceItem *>(rPoolItem.get()) != nullptr); + SvxLRSpaceItem& rItem = static_cast<SvxLRSpaceItem&>(*rPoolItem); + rItem.SetTextFirstLineOffset( sal::static_int_cast< short >( OutputDevice::LogicToLogic( rItem.GetTextFirstLineOffset(), eSourceUnit, eDestUnit ) ) ); + rItem.SetTextLeft( OutputDevice::LogicToLogic( rItem.GetTextLeft(), eSourceUnit, eDestUnit ) ); + rItem.SetRight( OutputDevice::LogicToLogic( rItem.GetRight(), eSourceUnit, eDestUnit ) ); + } + break; + case EE_PARA_ULSPACE: + { + assert(dynamic_cast<const SvxULSpaceItem *>(rPoolItem.get()) != nullptr); + SvxULSpaceItem& rItem = static_cast<SvxULSpaceItem&>(*rPoolItem); + rItem.SetUpper( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetUpper(), eSourceUnit, eDestUnit ) ) ); + rItem.SetLower( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetLower(), eSourceUnit, eDestUnit ) ) ); + } + break; + case EE_PARA_SBL: + { + assert(dynamic_cast<const SvxLineSpacingItem *>(rPoolItem.get()) != nullptr); + SvxLineSpacingItem& rItem = static_cast<SvxLineSpacingItem&>(*rPoolItem); + // SetLineHeight changes also eLineSpace! + if ( rItem.GetLineSpaceRule() == SvxLineSpaceRule::Min ) + rItem.SetLineHeight( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetLineHeight(), eSourceUnit, eDestUnit ) ) ); + } + break; + case EE_PARA_TABS: + { + assert(dynamic_cast<const SvxTabStopItem *>(rPoolItem.get()) != nullptr); + SvxTabStopItem& rItem = static_cast<SvxTabStopItem&>(*rPoolItem); + SvxTabStopItem* pNewItem(new SvxTabStopItem(EE_PARA_TABS)); + for ( sal_uInt16 i = 0; i < rItem.Count(); i++ ) + { + const SvxTabStop& rTab = rItem[i]; + SvxTabStop aNewStop( OutputDevice::LogicToLogic( rTab.GetTabPos(), eSourceUnit, eDestUnit ), rTab.GetAdjustment(), rTab.GetDecimal(), rTab.GetFill() ); + pNewItem->Insert( aNewStop ); + } + rPoolItem.reset(pNewItem); + } + break; + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + assert(dynamic_cast<const SvxFontHeightItem *>(rPoolItem.get()) != nullptr); + SvxFontHeightItem& rItem = static_cast<SvxFontHeightItem&>(*rPoolItem); + rItem.SetHeight( OutputDevice::LogicToLogic( rItem.GetHeight(), eSourceUnit, eDestUnit ) ); + } + break; + } +} + +void ConvertAndPutItems( SfxItemSet& rDest, const SfxItemSet& rSource, const MapUnit* pSourceUnit, const MapUnit* pDestUnit ) +{ + const SfxItemPool* pSourcePool = rSource.GetPool(); + const SfxItemPool* pDestPool = rDest.GetPool(); + + for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + // If possible go through SlotID ... + + sal_uInt16 nSourceWhich = nWhich; + sal_uInt16 nSlot = pDestPool->GetTrueSlotId( nWhich ); + if ( nSlot ) + { + sal_uInt16 nW = pSourcePool->GetTrueWhich( nSlot ); + if ( nW ) + nSourceWhich = nW; + } + + if ( rSource.GetItemState( nSourceWhich, false ) == SfxItemState::SET ) + { + MapUnit eSourceUnit = pSourceUnit ? *pSourceUnit : pSourcePool->GetMetric( nSourceWhich ); + MapUnit eDestUnit = pDestUnit ? *pDestUnit : pDestPool->GetMetric( nWhich ); + if ( eSourceUnit != eDestUnit ) + { + std::unique_ptr<SfxPoolItem> pItem(rSource.Get( nSourceWhich ).Clone()); + ConvertItem( pItem, eSourceUnit, eDestUnit ); + pItem->SetWhich(nWhich); + rDest.Put( std::move(pItem) ); + } + else + { + rDest.Put( rSource.Get( nSourceWhich ).CloneSetWhich(nWhich) ); + } + } + } +} + +EditLine::EditLine() : + nTxtWidth(0), + nStartPosX(0), + nStart(0), + nEnd(0), + nStartPortion(0), // to be able to tell the difference between a line + // without Portions from one with the Portion number 0 + nEndPortion(0), + nHeight(0), + nTxtHeight(0), + nMaxAscent(0), + bHangingPunctuation(false), + bInvalid(true) +{ +} + +EditLine::EditLine( const EditLine& r ) : + nTxtWidth(0), + nStartPosX(0), + nStart(r.nStart), + nEnd(r.nEnd), + nStartPortion(r.nStartPortion), + nEndPortion(r.nEndPortion), + nHeight(0), + nTxtHeight(0), + nMaxAscent(0), + bHangingPunctuation(r.bHangingPunctuation), + bInvalid(true) +{ +} + +EditLine::~EditLine() +{ +} + + +EditLine* EditLine::Clone() const +{ + EditLine* pL = new EditLine; + pL->aPositions = aPositions; + pL->nStartPosX = nStartPosX; + pL->nStart = nStart; + pL->nEnd = nEnd; + pL->nStartPortion = nStartPortion; + pL->nEndPortion = nEndPortion; + pL->nHeight = nHeight; + pL->nTxtWidth = nTxtWidth; + pL->nTxtHeight = nTxtHeight; + pL->nMaxAscent = nMaxAscent; + + return pL; +} + +bool operator == ( const EditLine& r1, const EditLine& r2 ) +{ + if ( r1.nStart != r2.nStart ) + return false; + + if ( r1.nEnd != r2.nEnd ) + return false; + + if ( r1.nStartPortion != r2.nStartPortion ) + return false; + + if ( r1.nEndPortion != r2.nEndPortion ) + return false; + + return true; +} + +EditLine& EditLine::operator = ( const EditLine& r ) +{ + nEnd = r.nEnd; + nStart = r.nStart; + nEndPortion = r.nEndPortion; + nStartPortion = r.nStartPortion; + return *this; +} + + +void EditLine::SetHeight( sal_uInt16 nH, sal_uInt16 nTxtH ) +{ + nHeight = nH; + nTxtHeight = ( nTxtH ? nTxtH : nH ); +} + +void EditLine::SetStartPosX( sal_Int32 start ) +{ + if (start > 0) + nStartPosX = start; + else + nStartPosX = 0; +} + +Size EditLine::CalcTextSize( ParaPortion& rParaPortion ) +{ + Size aSz; + Size aTmpSz; + + DBG_ASSERT( rParaPortion.GetTextPortions().Count(), "GetTextSize before CreatePortions !" ); + + for ( sal_Int32 n = nStartPortion; n <= nEndPortion; n++ ) + { + TextPortion& rPortion = rParaPortion.GetTextPortions()[n]; + switch ( rPortion.GetKind() ) + { + case PortionKind::TEXT: + case PortionKind::FIELD: + case PortionKind::HYPHENATOR: + { + aTmpSz = rPortion.GetSize(); + aSz.AdjustWidth(aTmpSz.Width() ); + if ( aSz.Height() < aTmpSz.Height() ) + aSz.setHeight( aTmpSz.Height() ); + } + break; + case PortionKind::TAB: + { + aSz.AdjustWidth(rPortion.GetSize().Width() ); + } + break; + case PortionKind::LINEBREAK: break; + } + } + + SetHeight( static_cast<sal_uInt16>(aSz.Height()) ); + return aSz; +} + +EditLineList::EditLineList() +{ +} + +EditLineList::~EditLineList() +{ + Reset(); +} + +void EditLineList::Reset() +{ + maLines.clear(); +} + +void EditLineList::DeleteFromLine(sal_Int32 nDelFrom) +{ + assert(nDelFrom <= (static_cast<sal_Int32>(maLines.size()) - 1)); + LinesType::iterator it = maLines.begin(); + std::advance(it, nDelFrom); + maLines.erase(it, maLines.end()); +} + +sal_Int32 EditLineList::FindLine(sal_Int32 nChar, bool bInclEnd) +{ + sal_Int32 n = maLines.size(); + for (sal_Int32 i = 0; i < n; ++i) + { + const EditLine& rLine = *maLines[i]; + if ( (bInclEnd && (rLine.GetEnd() >= nChar)) || + (rLine.GetEnd() > nChar) ) + { + return i; + } + } + + DBG_ASSERT( !bInclEnd, "Line not found: FindLine" ); + return n - 1; +} + +sal_Int32 EditLineList::Count() const +{ + return maLines.size(); +} + +const EditLine& EditLineList::operator[](sal_Int32 nPos) const +{ + return *maLines[nPos]; +} + +EditLine& EditLineList::operator[](sal_Int32 nPos) +{ + return *maLines[nPos]; +} + +void EditLineList::Append(EditLine* p) +{ + maLines.push_back(std::unique_ptr<EditLine>(p)); +} + +void EditLineList::Insert(sal_Int32 nPos, EditLine* p) +{ + maLines.insert(maLines.begin()+nPos, std::unique_ptr<EditLine>(p)); +} + +EditPaM::EditPaM() : pNode(nullptr), nIndex(0) {} +EditPaM::EditPaM(ContentNode* p, sal_Int32 n) : pNode(p), nIndex(n) {} + + +void EditPaM::SetNode(ContentNode* p) +{ + pNode = p; +} + +bool EditPaM::DbgIsBuggy( EditDoc const & rDoc ) const +{ + return !pNode || + rDoc.GetPos( pNode ) >= rDoc.Count() || + nIndex > pNode->Len(); +} + +bool EditSelection::DbgIsBuggy( EditDoc const & rDoc ) const +{ + return aStartPaM.DbgIsBuggy( rDoc ) || aEndPaM.DbgIsBuggy( rDoc ); +} + +EditSelection::EditSelection() +{ +} + +EditSelection::EditSelection( const EditPaM& rStartAndAnd ) : + aStartPaM(rStartAndAnd), + aEndPaM(rStartAndAnd) +{ +} + +EditSelection::EditSelection( const EditPaM& rStart, const EditPaM& rEnd ) : + aStartPaM(rStart), + aEndPaM(rEnd) +{ +} + +EditSelection& EditSelection::operator = ( const EditPaM& rPaM ) +{ + aStartPaM = rPaM; + aEndPaM = rPaM; + return *this; +} + +void EditSelection::Adjust( const EditDoc& rNodes ) +{ + DBG_ASSERT( aStartPaM.GetIndex() <= aStartPaM.GetNode()->Len(), "Index out of range in Adjust(1)" ); + DBG_ASSERT( aEndPaM.GetIndex() <= aEndPaM.GetNode()->Len(), "Index out of range in Adjust(2)" ); + + const ContentNode* pStartNode = aStartPaM.GetNode(); + const ContentNode* pEndNode = aEndPaM.GetNode(); + + sal_Int32 nStartNode = rNodes.GetPos( pStartNode ); + sal_Int32 nEndNode = rNodes.GetPos( pEndNode ); + + DBG_ASSERT( nStartNode != SAL_MAX_INT32, "Node out of range in Adjust(1)" ); + DBG_ASSERT( nEndNode != SAL_MAX_INT32, "Node out of range in Adjust(2)" ); + + const bool bSwap = ( nStartNode > nEndNode ) || + ( ( nStartNode == nEndNode ) && + ( aStartPaM.GetIndex() > aEndPaM.GetIndex() ) ); + + if ( bSwap ) + { + EditPaM aTmpPaM( aStartPaM ); + aStartPaM = aEndPaM; + aEndPaM = aTmpPaM; + } +} + +bool operator == ( const EditPaM& r1, const EditPaM& r2 ) +{ + return ( r1.GetNode() == r2.GetNode() ) && + ( r1.GetIndex() == r2.GetIndex() ); +} + +bool operator != ( const EditPaM& r1, const EditPaM& r2 ) +{ + return !( r1 == r2 ); +} + +ContentNode::ContentNode( SfxItemPool& rPool ) : aContentAttribs( rPool ) +{ +} + +ContentNode::ContentNode( const OUString& rStr, const ContentAttribs& rContentAttribs ) : + maString(rStr), aContentAttribs(rContentAttribs) +{ +} + +ContentNode::~ContentNode() +{ +} + +void ContentNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew, SfxItemPool& rItemPool ) +{ + if ( !nNew ) + return; + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif + + // Since features are treated differently than normal character attributes, + // but can also affect the order of the start list. // In every if ..., in the next (n) opportunities due to bFeature or + // an existing special case, must (n-1) opportunities be provided with + // bResort. The most likely possibility receives no bResort, so that is + // not sorted anew when all attributes are the same. + bool bResort = false; + bool bExpandedEmptyAtIndexNull = false; + + std::size_t nAttr = 0; + CharAttribList::AttribsType& rAttribs = aCharAttribList.GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr); + while ( pAttrib ) + { + if ( pAttrib->GetEnd() >= nIndex ) + { + // Move all attributes behind the insertion point... + if ( pAttrib->GetStart() > nIndex ) + { + pAttrib->MoveForward( nNew ); + } + // 0: Expand empty attribute, if at insertion point + else if ( pAttrib->IsEmpty() ) + { + // Do not check Index, an empty one could only be there + // When later checking it anyhow: + // Special case: Start == 0; AbsLen == 1, nNew = 1 + // => Expand, because of paragraph break! + // Start <= nIndex, End >= nIndex => Start=End=nIndex! +// if ( pAttrib->GetStart() == nIndex ) + pAttrib->Expand( nNew ); + bResort = true; + if ( pAttrib->GetStart() == 0 ) + bExpandedEmptyAtIndexNull = true; + } + // 1: Attribute starts before, goes to index ... + else if ( pAttrib->GetEnd() == nIndex ) // Start must be before + { + // Only expand when there is no feature + // and if not in exclude list! + // Otherwise, a UL will go on until a new ULDB, expanding both +// if ( !pAttrib->IsFeature() && !rExclList.FindAttrib( pAttrib->Which() ) ) + if ( !pAttrib->IsFeature() && !aCharAttribList.FindEmptyAttrib( pAttrib->Which(), nIndex ) ) + { + if ( !pAttrib->IsEdge() ) + pAttrib->Expand( nNew ); + } + else + bResort = true; + } + // 2: Attribute starts before, goes past the Index... + else if ( ( pAttrib->GetStart() < nIndex ) && ( pAttrib->GetEnd() > nIndex ) ) + { + DBG_ASSERT( !pAttrib->IsFeature(), "Large Feature?!" ); + pAttrib->Expand( nNew ); + } + // 3: Attribute starts on index... + else if ( pAttrib->GetStart() == nIndex ) + { + if ( pAttrib->IsFeature() ) + { + pAttrib->MoveForward( nNew ); + bResort = true; + } + else + { + bool bExpand = false; + if ( nIndex == 0 ) + { + bExpand = true; + if( bExpandedEmptyAtIndexNull ) + { + // Check if this kind of attribute was empty and expanded here... + sal_uInt16 nW = pAttrib->GetItem()->Which(); + for ( std::size_t nA = 0; nA < nAttr; nA++ ) + { + const EditCharAttrib& r = *aCharAttribList.GetAttribs()[nA]; + if ( ( r.GetStart() == 0 ) && ( r.GetItem()->Which() == nW ) ) + { + bExpand = false; + break; + } + } + + } + } + if ( bExpand ) + { + pAttrib->Expand( nNew ); + bResort = true; + } + else + { + pAttrib->MoveForward( nNew ); + } + } + } + } + + if ( pAttrib->IsEdge() ) + pAttrib->SetEdge(false); + + DBG_ASSERT( !pAttrib->IsFeature() || ( pAttrib->GetLen() == 1 ), "Expand: FeaturesLen != 1" ); + + DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Expand: Attribute distorted!" ); + DBG_ASSERT( ( pAttrib->GetEnd() <= Len() ), "Expand: Attribute larger than paragraph!" ); + if ( pAttrib->IsEmpty() ) + { + OSL_FAIL( "Empty Attribute after ExpandAttribs?" ); + bResort = true; + rItemPool.Remove( *pAttrib->GetItem() ); + rAttribs.erase(rAttribs.begin()+nAttr); + } + else + { + ++nAttr; + } + pAttrib = GetAttrib(rAttribs, nAttr); + } + + if ( bResort ) + aCharAttribList.ResortAttribs(); + + if (mpWrongList) + { + bool bSep = ( maString[ nIndex ] == ' ' ) || IsFeature( nIndex ); + mpWrongList->TextInserted( nIndex, nNew, bSep ); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif +} + +void ContentNode::CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDeleted, SfxItemPool& rItemPool ) +{ + if ( !nDeleted ) + return; + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif + + // Since features are treated differently than normal character attributes, + // but can also affect the order of the start list + bool bResort = false; + sal_Int32 nEndChanges = nIndex+nDeleted; + + std::size_t nAttr = 0; + CharAttribList::AttribsType& rAttribs = aCharAttribList.GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr); + while ( pAttrib ) + { + bool bDelAttr = false; + if ( pAttrib->GetEnd() >= nIndex ) + { + // Move all Attribute behind the insert point... + if ( pAttrib->GetStart() >= nEndChanges ) + { + pAttrib->MoveBackward( nDeleted ); + } + // 1. Delete Internal attributes... + else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() <= nEndChanges ) ) + { + // Special case: Attribute covers the area exactly + // => keep as empty Attribute. + if ( !pAttrib->IsFeature() && ( pAttrib->GetStart() == nIndex ) && ( pAttrib->GetEnd() == nEndChanges ) ) + { + pAttrib->GetEnd() = nIndex; // empty + bResort = true; + } + else + bDelAttr = true; + } + // 2. Attribute starts earlier, ends inside or behind it ... + else if ( ( pAttrib->GetStart() <= nIndex ) && ( pAttrib->GetEnd() > nIndex ) ) + { + DBG_ASSERT( !pAttrib->IsFeature(), "Collapsing Feature!" ); + if ( pAttrib->GetEnd() <= nEndChanges ) // ends inside + pAttrib->GetEnd() = nIndex; + else + pAttrib->Collaps( nDeleted ); // ends behind + } + // 3. Attribute starts inside, ending behind ... + else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() > nEndChanges ) ) + { + // Features not allowed to expand! + if ( pAttrib->IsFeature() ) + { + pAttrib->MoveBackward( nDeleted ); + bResort = true; + } + else + { + pAttrib->GetStart() = nEndChanges; + pAttrib->MoveBackward( nDeleted ); + } + } + } + DBG_ASSERT( !pAttrib->IsFeature() || ( pAttrib->GetLen() == 1 ), "Expand: FeaturesLen != 1" ); + + DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Collapse: Attribute distorted!" ); + DBG_ASSERT( ( pAttrib->GetEnd() <= Len()) || bDelAttr, "Collapse: Attribute larger than paragraph!" ); + if ( bDelAttr ) + { + bResort = true; + rItemPool.Remove( *pAttrib->GetItem() ); + rAttribs.erase(rAttribs.begin()+nAttr); + } + else + { + if ( pAttrib->IsEmpty() ) + aCharAttribList.SetHasEmptyAttribs(true); + nAttr++; + } + + pAttrib = GetAttrib(rAttribs, nAttr); + } + + if ( bResort ) + aCharAttribList.ResortAttribs(); + + if (mpWrongList) + mpWrongList->TextDeleted(nIndex, nDeleted); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif +} + +void ContentNode::CopyAndCutAttribs( ContentNode* pPrevNode, SfxItemPool& rPool, bool bKeepEndingAttribs ) +{ + assert(pPrevNode); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pPrevNode->aCharAttribList); +#endif + + sal_Int32 nCut = pPrevNode->Len(); + + std::size_t nAttr = 0; + CharAttribList::AttribsType& rPrevAttribs = pPrevNode->GetCharAttribs().GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rPrevAttribs, nAttr); + while ( pAttrib ) + { + if ( pAttrib->GetEnd() < nCut ) + { + // remain unchanged... + nAttr++; + } + else if ( pAttrib->GetEnd() == nCut ) + { + // must be copied as an empty attributes. + if ( bKeepEndingAttribs && !pAttrib->IsFeature() && !aCharAttribList.FindAttrib( pAttrib->GetItem()->Which(), 0 ) ) + { + EditCharAttrib* pNewAttrib = MakeCharAttrib( rPool, *(pAttrib->GetItem()), 0, 0 ); + assert(pNewAttrib); + aCharAttribList.InsertAttrib( pNewAttrib ); + } + nAttr++; + } + else if ( pAttrib->IsInside( nCut ) || ( !nCut && !pAttrib->GetStart() && !pAttrib->IsFeature() ) ) + { + // If cut is done right at the front then the attribute must be + // kept! Has to be copied and changed. + EditCharAttrib* pNewAttrib = MakeCharAttrib( rPool, *(pAttrib->GetItem()), 0, pAttrib->GetEnd()-nCut ); + assert(pNewAttrib); + aCharAttribList.InsertAttrib( pNewAttrib ); + pAttrib->GetEnd() = nCut; + nAttr++; + } + else + { + // Move all attributes in the current node (this) + CharAttribList::AttribsType::iterator it = rPrevAttribs.begin() + nAttr; + aCharAttribList.InsertAttrib(it->release()); + rPrevAttribs.erase(it); + pAttrib->MoveBackward( nCut ); + } + pAttrib = GetAttrib(rPrevAttribs, nAttr); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pPrevNode->aCharAttribList); +#endif +} + +void ContentNode::AppendAttribs( ContentNode* pNextNode ) +{ + assert(pNextNode); + + sal_Int32 nNewStart = maString.getLength(); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pNextNode->aCharAttribList); +#endif + + std::size_t nAttr = 0; + CharAttribList::AttribsType& rNextAttribs = pNextNode->GetCharAttribs().GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rNextAttribs, nAttr); + while ( pAttrib ) + { + // Move all attributes in the current node (this) + bool bMelted = false; + if ( ( pAttrib->GetStart() == 0 ) && ( !pAttrib->IsFeature() ) ) + { + // Attributes can possibly be summarized as: + std::size_t nTmpAttr = 0; + EditCharAttrib* pTmpAttrib = GetAttrib( aCharAttribList.GetAttribs(), nTmpAttr ); + while ( !bMelted && pTmpAttrib ) + { + ++nTmpAttr; + if ( pTmpAttrib->GetEnd() == nNewStart ) + { + if (pTmpAttrib->Which() == pAttrib->Which()) + { + // prevent adding 2 0-length attributes at same position + if ((*(pTmpAttrib->GetItem()) == *(pAttrib->GetItem())) + || (0 == pAttrib->GetLen())) + { + pTmpAttrib->GetEnd() = + pTmpAttrib->GetEnd() + pAttrib->GetLen(); + rNextAttribs.erase(rNextAttribs.begin()+nAttr); + // Unsubscribe from the pool?! + bMelted = true; + } + else if (0 == pTmpAttrib->GetLen()) + { + --nTmpAttr; // to cancel earlier increment... + aCharAttribList.Remove(nTmpAttr); + } + } + } + pTmpAttrib = GetAttrib( aCharAttribList.GetAttribs(), nTmpAttr ); + } + } + + if ( !bMelted ) + { + pAttrib->GetStart() = pAttrib->GetStart() + nNewStart; + pAttrib->GetEnd() = pAttrib->GetEnd() + nNewStart; + CharAttribList::AttribsType::iterator it = rNextAttribs.begin() + nAttr; + aCharAttribList.InsertAttrib(it->release()); + rNextAttribs.erase(it); + } + pAttrib = GetAttrib(rNextAttribs, nAttr); + } + // For the Attributes that just moved over: + rNextAttribs.clear(); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pNextNode->aCharAttribList); +#endif +} + +void ContentNode::CreateDefFont() +{ + // First use the information from the style ... + SfxStyleSheet* pS = aContentAttribs.GetStyleSheet(); + if ( pS ) + CreateFont( GetCharAttribs().GetDefFont(), pS->GetItemSet() ); + + // ... then iron out the hard paragraph formatting... + CreateFont( GetCharAttribs().GetDefFont(), + GetContentAttribs().GetItems(), pS == nullptr ); +} + +void ContentNode::SetStyleSheet( SfxStyleSheet* pS, const SvxFont& rFontFromStyle ) +{ + aContentAttribs.SetStyleSheet( pS ); + + + // First use the information from the style ... + GetCharAttribs().GetDefFont() = rFontFromStyle; + // ... then iron out the hard paragraph formatting... + CreateFont( GetCharAttribs().GetDefFont(), + GetContentAttribs().GetItems(), pS == nullptr ); +} + +void ContentNode::SetStyleSheet( SfxStyleSheet* pS, bool bRecalcFont ) +{ + aContentAttribs.SetStyleSheet( pS ); + if ( bRecalcFont ) + CreateDefFont(); +} + +bool ContentNode::IsFeature( sal_Int32 nPos ) const +{ + return maString[nPos] == CH_FEATURE; +} + +sal_Int32 ContentNode::Len() const +{ + return maString.getLength(); +} + +sal_Int32 ContentNode::GetExpandedLen() const +{ + sal_Int32 nLen = maString.getLength(); + + // Fields can be longer than the placeholder in the Node + const CharAttribList::AttribsType& rAttrs = GetCharAttribs().GetAttribs(); + for (sal_Int32 nAttr = rAttrs.size(); nAttr; ) + { + const EditCharAttrib& rAttr = *rAttrs[--nAttr]; + if (rAttr.Which() == EE_FEATURE_FIELD) + { + nLen += static_cast<const EditCharAttribField&>(rAttr).GetFieldValue().getLength(); + --nLen; // Standalone, to avoid corner cases when previous getLength() returns 0 + } + } + + return nLen; +} + +OUString ContentNode::GetExpandedText(sal_Int32 nStartPos, sal_Int32 nEndPos) const +{ + if ( nEndPos < 0 || nEndPos > Len() ) + nEndPos = Len(); + + DBG_ASSERT( nStartPos <= nEndPos, "Start and End reversed?" ); + + sal_Int32 nIndex = nStartPos; + OUStringBuffer aStr(256); + const EditCharAttrib* pNextFeature = GetCharAttribs().FindFeature( nIndex ); + while ( nIndex < nEndPos ) + { + sal_Int32 nEnd = nEndPos; + if ( pNextFeature && ( pNextFeature->GetStart() < nEnd ) ) + nEnd = pNextFeature->GetStart(); + else + pNextFeature = nullptr; // Feature does not interest the below + + DBG_ASSERT( nEnd >= nIndex, "End in front of the index?" ); + //!! beware of sub string length of -1 + if (nEnd > nIndex) + aStr.append( GetString().subView(nIndex, nEnd - nIndex) ); + + if ( pNextFeature ) + { + switch ( pNextFeature->GetItem()->Which() ) + { + case EE_FEATURE_TAB: aStr.append( "\t" ); + break; + case EE_FEATURE_LINEBR: aStr.append( "\x0A" ); + break; + case EE_FEATURE_FIELD: + aStr.append( static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue() ); + break; + default: OSL_FAIL( "What feature?" ); + } + pNextFeature = GetCharAttribs().FindFeature( ++nEnd ); + } + nIndex = nEnd; + } + return aStr.makeStringAndClear(); +} + +void ContentNode::UnExpandPosition( sal_Int32 &rPos, bool bBiasStart ) +{ + sal_Int32 nOffset = 0; + + const CharAttribList::AttribsType& rAttrs = GetCharAttribs().GetAttribs(); + for (size_t nAttr = 0; nAttr < rAttrs.size(); ++nAttr ) + { + const EditCharAttrib& rAttr = *rAttrs[nAttr]; + assert (!(nAttr < rAttrs.size() - 1) || + rAttrs[nAttr]->GetStart() <= rAttrs[nAttr + 1]->GetStart()); + + nOffset = rAttr.GetStart(); + + if (nOffset >= rPos) // happens after the position + return; + + if (rAttr.Which() == EE_FEATURE_FIELD) + { + sal_Int32 nChunk = static_cast<const EditCharAttribField&>(rAttr).GetFieldValue().getLength(); + nChunk--; // Character representing the field in the string + + if (nOffset + nChunk >= rPos) // we're inside the field + { + if (bBiasStart) + rPos = rAttr.GetStart(); + else + rPos = rAttr.GetEnd(); + return; + } + // Adjust for the position + rPos -= nChunk; + } + } + assert (rPos <= Len()); +} + +/* + * Fields are represented by a single character in the underlying string + * and/or selection, however, they can be expanded to the full value of + * the field. When we're dealing with selection / offsets however we need + * to deal in character positions inside the real (unexpanded) string. + * This method maps us back to character offsets. + */ +void ContentNode::UnExpandPositions( sal_Int32 &rStartPos, sal_Int32 &rEndPos ) +{ + UnExpandPosition( rStartPos, true ); + UnExpandPosition( rEndPos, false ); +} + +void ContentNode::SetChar(sal_Int32 nPos, sal_Unicode c) +{ + maString = maString.replaceAt(nPos, 1, rtl::OUStringChar(c)); +} + +void ContentNode::Insert(std::u16string_view rStr, sal_Int32 nPos) +{ + maString = maString.replaceAt(nPos, 0, rStr); +} + +void ContentNode::Append(std::u16string_view rStr) +{ + maString += rStr; +} + +void ContentNode::Erase(sal_Int32 nPos) +{ + maString = maString.copy(0, nPos); +} + +void ContentNode::Erase(sal_Int32 nPos, sal_Int32 nCount) +{ + maString = maString.replaceAt(nPos, nCount, u""); +} + +OUString ContentNode::Copy(sal_Int32 nPos) const +{ + return maString.copy(nPos); +} + +OUString ContentNode::Copy(sal_Int32 nPos, sal_Int32 nCount) const +{ + return maString.copy(nPos, nCount); +} + +sal_Unicode ContentNode::GetChar(sal_Int32 nPos) const +{ + return maString[nPos]; +} + +void ContentNode::EnsureWrongList() +{ + if (!mpWrongList) + CreateWrongList(); +} + +WrongList* ContentNode::GetWrongList() +{ + return mpWrongList.get(); +} + +const WrongList* ContentNode::GetWrongList() const +{ + return mpWrongList.get(); +} + +void ContentNode::SetWrongList( WrongList* p ) +{ + mpWrongList.reset(p); +} + +void ContentNode::CreateWrongList() +{ + SAL_WARN_IF( mpWrongList && !mpWrongList->empty(), "editeng", "WrongList already exist!"); + if (!mpWrongList || !mpWrongList->empty()) + mpWrongList.reset(new WrongList); +} + +void ContentNode::DestroyWrongList() +{ + mpWrongList.reset(); +} + +void ContentNode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ContentNode")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("maString"), BAD_CAST(maString.toUtf8().getStr())); + aContentAttribs.dumpAsXml(pWriter); + aCharAttribList.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + + +ContentAttribs::ContentAttribs( SfxItemPool& rPool ) +: pStyle(nullptr) +, aAttribSet( rPool ) +{ +} + + +SvxTabStop ContentAttribs::FindTabStop( sal_Int32 nCurPos, sal_uInt16 nDefTab ) +{ + const SvxTabStopItem& rTabs = GetItem( EE_PARA_TABS ); + for ( sal_uInt16 i = 0; i < rTabs.Count(); i++ ) + { + const SvxTabStop& rTab = rTabs[i]; + if ( rTab.GetTabPos() > nCurPos ) + return rTab; + } + + // Determine DefTab ... + SvxTabStop aTabStop; + const sal_Int32 x = nCurPos / nDefTab + 1; + aTabStop.GetTabPos() = nDefTab * x; + return aTabStop; +} + +void ContentAttribs::SetStyleSheet( SfxStyleSheet* pS ) +{ + bool bStyleChanged = ( pStyle != pS ); + pStyle = pS; + // Only when other style sheet, not when current style sheet modified + if ( !(pStyle && bStyleChanged) ) + return; + + // Selectively remove the attributes from the paragraph formatting + // which are specified in the style, so that the attributes of the + // style can have an affect. + const SfxItemSet& rStyleAttribs = pStyle->GetItemSet(); + for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + // Don't change bullet on/off + if ( ( nWhich != EE_PARA_BULLETSTATE ) && ( rStyleAttribs.GetItemState( nWhich ) == SfxItemState::SET ) ) + aAttribSet.ClearItem( nWhich ); + } +} + +const SfxPoolItem& ContentAttribs::GetItem( sal_uInt16 nWhich ) const +{ + // Hard paragraph attributes take precedence! + const SfxItemSet* pTakeFrom = &aAttribSet; + if ( pStyle && ( aAttribSet.GetItemState( nWhich, false ) != SfxItemState::SET ) ) + pTakeFrom = &pStyle->GetItemSet(); + + return pTakeFrom->Get( nWhich ); +} + +bool ContentAttribs::HasItem( sal_uInt16 nWhich ) const +{ + bool bHasItem = false; + if ( aAttribSet.GetItemState( nWhich, false ) == SfxItemState::SET ) + bHasItem = true; + else if ( pStyle && pStyle->GetItemSet().GetItemState( nWhich ) == SfxItemState::SET ) + bHasItem = true; + + return bHasItem; +} + +void ContentAttribs::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ContentAttribs")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("style"), "%s", pStyle->GetName().toUtf8().getStr()); + aAttribSet.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + + +ItemList::ItemList() : CurrentItem( 0 ) +{ +} + +const SfxPoolItem* ItemList::First() +{ + CurrentItem = 0; + return aItemPool.empty() ? nullptr : aItemPool[ 0 ]; +} + +const SfxPoolItem* ItemList::Next() +{ + if ( CurrentItem + 1 < static_cast<sal_Int32>(aItemPool.size()) ) + { + ++CurrentItem; + return aItemPool[ CurrentItem ]; + } + return nullptr; +} + +void ItemList::Insert( const SfxPoolItem* pItem ) +{ + aItemPool.push_back( pItem ); + CurrentItem = aItemPool.size() - 1; +} + + +EditDoc::EditDoc( SfxItemPool* pPool ) : + nLastCache(0), + pItemPool(pPool ? pPool : new EditEngineItemPool()), + nDefTab(DEFTAB), + bIsVertical(false), + mnRotation(TextRotation::NONE), + bIsFixedCellHeight(false), + bModified(false), + bDisableAttributeExpanding(false) +{ + // Don't create an empty node, Clear() will be called in EditEngine-CTOR +}; + +EditDoc::~EditDoc() +{ + ImplDestroyContents(); +} + +namespace { + +class RemoveEachItemFromPool +{ + EditDoc& mrDoc; +public: + explicit RemoveEachItemFromPool(EditDoc& rDoc) : mrDoc(rDoc) {} + void operator() (const std::unique_ptr<ContentNode>& rNode) + { + mrDoc.RemoveItemsFromPool(*rNode); + } +}; + +struct ClearSpellErrorsHandler +{ + void operator() (std::unique_ptr<ContentNode> const & rNode) + { + rNode->DestroyWrongList(); + } +}; + +} + +void EditDoc::ImplDestroyContents() +{ + std::for_each(maContents.begin(), maContents.end(), RemoveEachItemFromPool(*this)); + maContents.clear(); +} + +void EditDoc::RemoveItemsFromPool(const ContentNode& rNode) +{ + for (sal_Int32 nAttr = 0; nAttr < rNode.GetCharAttribs().Count(); ++nAttr) + { + const EditCharAttrib& rAttr = *rNode.GetCharAttribs().GetAttribs()[nAttr]; + GetItemPool().Remove(*rAttr.GetItem()); + } +} + +void CreateFont( SvxFont& rFont, const SfxItemSet& rSet, bool bSearchInParent, SvtScriptType nScriptType ) +{ + vcl::Font aPrevFont( rFont ); + rFont.SetAlignment( ALIGN_BASELINE ); + rFont.SetTransparent( true ); + + sal_uInt16 nWhich_FontInfo = GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ); + sal_uInt16 nWhich_Language = GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ); + sal_uInt16 nWhich_FontHeight = GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ); + sal_uInt16 nWhich_Weight = GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ); + sal_uInt16 nWhich_Italic = GetScriptItemId( EE_CHAR_ITALIC, nScriptType ); + + if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontInfo ) == SfxItemState::SET ) ) + { + const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(rSet.Get( nWhich_FontInfo )); + rFont.SetFamilyName( rFontItem.GetFamilyName() ); + rFont.SetFamily( rFontItem.GetFamily() ); + rFont.SetPitch( rFontItem.GetPitch() ); + rFont.SetCharSet( rFontItem.GetCharSet() ); + } + if ( bSearchInParent || ( rSet.GetItemState( nWhich_Language ) == SfxItemState::SET ) ) + rFont.SetLanguage( static_cast<const SvxLanguageItem&>(rSet.Get( nWhich_Language )).GetLanguage() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_COLOR ) == SfxItemState::SET ) ) + rFont.SetColor( rSet.Get( EE_CHAR_COLOR ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_BKGCOLOR ) == SfxItemState::SET ) ) + rFont.SetFillColor( rSet.Get( EE_CHAR_BKGCOLOR ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontHeight ) == SfxItemState::SET ) ) + rFont.SetFontSize( Size( rFont.GetFontSize().Width(), static_cast<const SvxFontHeightItem&>(rSet.Get( nWhich_FontHeight ) ).GetHeight() ) ); + if ( bSearchInParent || ( rSet.GetItemState( nWhich_Weight ) == SfxItemState::SET ) ) + rFont.SetWeight( static_cast<const SvxWeightItem&>(rSet.Get( nWhich_Weight )).GetWeight() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_UNDERLINE ) == SfxItemState::SET ) ) + rFont.SetUnderline( rSet.Get( EE_CHAR_UNDERLINE ).GetLineStyle() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OVERLINE ) == SfxItemState::SET ) ) + rFont.SetOverline( rSet.Get( EE_CHAR_OVERLINE ).GetLineStyle() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_STRIKEOUT ) == SfxItemState::SET ) ) + rFont.SetStrikeout( rSet.Get( EE_CHAR_STRIKEOUT ).GetStrikeout() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_CASEMAP ) == SfxItemState::SET ) ) + rFont.SetCaseMap( rSet.Get( EE_CHAR_CASEMAP ).GetCaseMap() ); + if ( bSearchInParent || ( rSet.GetItemState( nWhich_Italic ) == SfxItemState::SET ) ) + rFont.SetItalic( static_cast<const SvxPostureItem&>(rSet.Get( nWhich_Italic )).GetPosture() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OUTLINE ) == SfxItemState::SET ) ) + rFont.SetOutline( rSet.Get( EE_CHAR_OUTLINE ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_SHADOW ) == SfxItemState::SET ) ) + rFont.SetShadow( rSet.Get( EE_CHAR_SHADOW ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_ESCAPEMENT ) == SfxItemState::SET ) ) + { + const SvxEscapementItem& rEsc = rSet.Get( EE_CHAR_ESCAPEMENT ); + + sal_uInt16 const nProp = rEsc.GetProportionalHeight(); + rFont.SetPropr( static_cast<sal_uInt8>(nProp) ); + + short nEsc = rEsc.GetEsc(); + rFont.SetNonAutoEscapement( nEsc ); + } + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_PAIRKERNING ) == SfxItemState::SET ) ) + rFont.SetKerning( rSet.Get( EE_CHAR_PAIRKERNING ).GetValue() ? FontKerning::FontSpecific : FontKerning::NONE ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_KERNING ) == SfxItemState::SET ) ) + rFont.SetFixKerning( rSet.Get( EE_CHAR_KERNING ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_WLM ) == SfxItemState::SET ) ) + rFont.SetWordLineMode( rSet.Get( EE_CHAR_WLM ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_EMPHASISMARK ) == SfxItemState::SET ) ) + rFont.SetEmphasisMark( rSet.Get( EE_CHAR_EMPHASISMARK ).GetEmphasisMark() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_RELIEF ) == SfxItemState::SET ) ) + rFont.SetRelief( rSet.Get( EE_CHAR_RELIEF ).GetValue() ); + + // Operator == compares the individual members of the font if the impl pointer is + // not equal. If all members are the same, this assignment makes + // sure that both also point to the same internal instance of the font. + // To avoid this assignment, you would need to check in + // every if statement above whether or not the new value differs from the + // old value before making an assignment. + if ( rFont == aPrevFont ) + rFont = aPrevFont; // => The same ImpPointer for IsSameInstance +} + +void EditDoc::CreateDefFont( bool bUseStyles ) +{ + SfxItemSetFixed<EE_PARA_START, EE_CHAR_END> aTmpSet( GetItemPool() ); + CreateFont( aDefFont, aTmpSet ); + aDefFont.SetVertical( IsEffectivelyVertical() ); + aDefFont.SetOrientation( Degree10(IsEffectivelyVertical() ? (IsTopToBottom() ? 2700 : 900) : 0) ); + + for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ ) + { + ContentNode* pNode = GetObject( nNode ); + pNode->GetCharAttribs().GetDefFont() = aDefFont; + if ( bUseStyles ) + pNode->CreateDefFont(); + } +} + +bool EditDoc::IsEffectivelyVertical() const +{ + return (bIsVertical && mnRotation == TextRotation::NONE) || + (!bIsVertical && mnRotation != TextRotation::NONE); +} + +bool EditDoc::IsTopToBottom() const +{ + return (bIsVertical && mnRotation == TextRotation::NONE) || + (!bIsVertical && mnRotation == TextRotation::TOPTOBOTTOM); +} + +bool EditDoc::GetVertical() const +{ + return bIsVertical; +} + +sal_Int32 EditDoc::GetPos(const ContentNode* p) const +{ + return FastGetPos(maContents, p, nLastCache); +} + +const ContentNode* EditDoc::GetObject(sal_Int32 nPos) const +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maContents.size() ? maContents[nPos].get() : nullptr; +} + +ContentNode* EditDoc::GetObject(sal_Int32 nPos) +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maContents.size() ? maContents[nPos].get() : nullptr; +} + +const ContentNode* EditDoc::operator[](sal_Int32 nPos) const +{ + return GetObject(nPos); +} + +ContentNode* EditDoc::operator[](sal_Int32 nPos) +{ + return GetObject(nPos); +} + +void EditDoc::Insert(sal_Int32 nPos, ContentNode* p) +{ + if (nPos < 0 || nPos == SAL_MAX_INT32) + { + SAL_WARN( "editeng", "EditDoc::Insert - overflow pos " << nPos); + return; + } + maContents.insert(maContents.begin()+nPos, std::unique_ptr<ContentNode>(p)); +} + +void EditDoc::Remove(sal_Int32 nPos) +{ + if (nPos < 0 || o3tl::make_unsigned(nPos) >= maContents.size()) + { + SAL_WARN( "editeng", "EditDoc::Remove - out of bounds pos " << nPos); + return; + } + maContents.erase(maContents.begin() + nPos); +} + +void EditDoc::Release(sal_Int32 nPos) +{ + if (nPos < 0 || o3tl::make_unsigned(nPos) >= maContents.size()) + { + SAL_WARN( "editeng", "EditDoc::Release - out of bounds pos " << nPos); + return; + } + (void)maContents[nPos].release(); + maContents.erase(maContents.begin() + nPos); +} + +sal_Int32 EditDoc::Count() const +{ + size_t nSize = maContents.size(); + if (nSize > SAL_MAX_INT32) + { + SAL_WARN( "editeng", "EditDoc::Count - overflow " << nSize); + return SAL_MAX_INT32; + } + return nSize; +} + +OUString EditDoc::GetSepStr( LineEnd eEnd ) +{ + if ( eEnd == LINEEND_CR ) + return "\015"; // 0x0d + if ( eEnd == LINEEND_LF ) + return "\012"; // 0x0a + return "\015\012"; // 0x0d, 0x0a +} + +OUString EditDoc::GetText( LineEnd eEnd ) const +{ + const sal_Int32 nNodes = Count(); + if (nNodes == 0) + return OUString(); + + const OUString aSep = EditDoc::GetSepStr( eEnd ); + const sal_Int32 nSepSize = aSep.getLength(); + const sal_Int32 nLen = GetTextLen() + (nNodes - 1)*nSepSize; + + OUStringBuffer aBuffer(nLen + 16); // leave some slack + + for ( sal_Int32 nNode = 0; nNode < nNodes; nNode++ ) + { + if ( nSepSize && nNode>0 ) + { + aBuffer.append(aSep); + } + aBuffer.append(GetParaAsString( GetObject(nNode) )); + } + + return aBuffer.makeStringAndClear(); +} + +OUString EditDoc::GetParaAsString( sal_Int32 nNode ) const +{ + return GetParaAsString( GetObject( nNode ) ); +} + +OUString EditDoc::GetParaAsString( + const ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos) +{ + return pNode->GetExpandedText(nStartPos, nEndPos); +} + +EditPaM EditDoc::GetStartPaM() const +{ + ContentNode* p = const_cast<ContentNode*>(GetObject(0)); + return EditPaM(p, 0); +} + +EditPaM EditDoc::GetEndPaM() const +{ + ContentNode* pLastNode = const_cast<ContentNode*>(GetObject(Count()-1)); + return EditPaM( pLastNode, pLastNode->Len() ); +} + +sal_Int32 EditDoc::GetTextLen() const +{ + sal_Int32 nLen = 0; + for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ ) + { + const ContentNode* pNode = GetObject( nNode ); + nLen += pNode->GetExpandedLen(); + } + return nLen; +} + +EditPaM EditDoc::Clear() +{ + ImplDestroyContents(); + + ContentNode* pNode = new ContentNode( GetItemPool() ); + Insert(0, pNode); + + CreateDefFont(false); + + SetModified(false); + + return EditPaM( pNode, 0 ); +} + +void EditDoc::ClearSpellErrors() +{ + std::for_each(maContents.begin(), maContents.end(), ClearSpellErrorsHandler()); +} + +void EditDoc::SetModified( bool b ) +{ + bModified = b; + if ( bModified ) + { + aModifyHdl.Call( nullptr ); + } +} + +EditPaM EditDoc::RemoveText() +{ + // Keep the old ItemSet, to keep the chart Font. + ContentNode* pPrevFirstNode = GetObject(0); + SfxStyleSheet* pPrevStyle = pPrevFirstNode->GetStyleSheet(); + SfxItemSet aPrevSet( pPrevFirstNode->GetContentAttribs().GetItems() ); + vcl::Font aPrevFont( pPrevFirstNode->GetCharAttribs().GetDefFont() ); + + ImplDestroyContents(); + + ContentNode* pNode = new ContentNode( GetItemPool() ); + Insert(0, pNode); + + pNode->SetStyleSheet(pPrevStyle, false); + pNode->GetContentAttribs().GetItems().Set( aPrevSet ); + pNode->GetCharAttribs().GetDefFont() = aPrevFont; + + SetModified(true); + + return EditPaM( pNode, 0 ); +} + +EditPaM EditDoc::InsertText( EditPaM aPaM, const OUString& rStr ) +{ + DBG_ASSERT( rStr.indexOf( 0x0A ) == -1, "EditDoc::InsertText: Newlines prohibited in paragraph!" ); + DBG_ASSERT( rStr.indexOf( 0x0D ) == -1, "EditDoc::InsertText: Newlines prohibited in paragraph!" ); + DBG_ASSERT( rStr.indexOf( '\t' ) == -1, "EditDoc::InsertText: Newlines prohibited in paragraph!" ); + assert(aPaM.GetNode()); + + aPaM.GetNode()->Insert( rStr, aPaM.GetIndex() ); + aPaM.GetNode()->ExpandAttribs( aPaM.GetIndex(), rStr.getLength(), GetItemPool() ); + aPaM.SetIndex( aPaM.GetIndex() + rStr.getLength() ); + + SetModified( true ); + + return aPaM; +} + +EditPaM EditDoc::InsertParaBreak( EditPaM aPaM, bool bKeepEndingAttribs ) +{ + assert(aPaM.GetNode()); + ContentNode* pCurNode = aPaM.GetNode(); + sal_Int32 nPos = GetPos( pCurNode ); + OUString aStr = aPaM.GetNode()->Copy( aPaM.GetIndex() ); + aPaM.GetNode()->Erase( aPaM.GetIndex() ); + + // the paragraph attributes... + ContentAttribs aContentAttribs( aPaM.GetNode()->GetContentAttribs() ); + + // for a new paragraph we like to have the bullet/numbering visible by default + aContentAttribs.GetItems().Put( SfxBoolItem( EE_PARA_BULLETSTATE, true) ); + + // ContentNode constructor copies also the paragraph attributes + ContentNode* pNode = new ContentNode( aStr, std::move(aContentAttribs) ); + + // Copy the Default Font + pNode->GetCharAttribs().GetDefFont() = aPaM.GetNode()->GetCharAttribs().GetDefFont(); + SfxStyleSheet* pStyle = aPaM.GetNode()->GetStyleSheet(); + if ( pStyle ) + { + OUString aFollow( pStyle->GetFollow() ); + if ( !aFollow.isEmpty() && ( aFollow != pStyle->GetName() ) ) + { + SfxStyleSheetBase* pNext = pStyle->GetPool()->Find( aFollow, pStyle->GetFamily() ); + pNode->SetStyleSheet( static_cast<SfxStyleSheet*>(pNext) ); + } + } + + // Character attributes may need to be copied or trimmed: + pNode->CopyAndCutAttribs( aPaM.GetNode(), GetItemPool(), bKeepEndingAttribs ); + + Insert(nPos+1, pNode); + + SetModified(true); + + aPaM.SetNode( pNode ); + aPaM.SetIndex( 0 ); + return aPaM; +} + +EditPaM EditDoc::InsertFeature( EditPaM aPaM, const SfxPoolItem& rItem ) +{ + assert(aPaM.GetNode()); + + aPaM.GetNode()->Insert( rtl::OUStringChar(CH_FEATURE), aPaM.GetIndex() ); + aPaM.GetNode()->ExpandAttribs( aPaM.GetIndex(), 1, GetItemPool() ); + + // Create a feature-attribute for the feature... + EditCharAttrib* pAttrib = MakeCharAttrib( GetItemPool(), rItem, aPaM.GetIndex(), aPaM.GetIndex()+1 ); + assert(pAttrib); + aPaM.GetNode()->GetCharAttribs().InsertAttrib( pAttrib ); + + SetModified( true ); + + aPaM.SetIndex( aPaM.GetIndex() + 1 ); + return aPaM; +} + +EditPaM EditDoc::ConnectParagraphs( ContentNode* pLeft, ContentNode* pRight ) +{ + const EditPaM aPaM( pLeft, pLeft->Len() ); + + // First the attributes, otherwise nLen will not be correct! + pLeft->AppendAttribs( pRight ); + // then the Text... + pLeft->Append(pRight->GetString()); + + // the one to the right disappears. + RemoveItemsFromPool(*pRight); + sal_Int32 nRight = GetPos( pRight ); + Remove( nRight ); + + SetModified(true); + + return aPaM; +} + +void EditDoc::RemoveChars( EditPaM aPaM, sal_Int32 nChars ) +{ + // Maybe remove Features! + aPaM.GetNode()->Erase( aPaM.GetIndex(), nChars ); + aPaM.GetNode()->CollapseAttribs( aPaM.GetIndex(), nChars, GetItemPool() ); + + SetModified( true ); +} + +void EditDoc::InsertAttribInSelection( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, const SfxPoolItem& rPoolItem ) +{ + assert(pNode); + DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribute too large!" ); + + // for Optimization: + // This ends at the beginning of the selection => can be expanded + EditCharAttrib* pEndingAttrib = nullptr; + // This starts at the end of the selection => can be expanded + EditCharAttrib* pStartingAttrib = nullptr; + + DBG_ASSERT( nStart <= nEnd, "Small miscalculations in InsertAttribInSelection" ); + + RemoveAttribs( pNode, nStart, nEnd, pStartingAttrib, pEndingAttrib, rPoolItem.Which() ); + + // tdf#132288 By default inserting an attribute beside another that is of + // the same type expands the original instead of inserting another. But the + // spell check dialog doesn't want that behaviour + if (bDisableAttributeExpanding) + { + pStartingAttrib = nullptr; + pEndingAttrib = nullptr; + } + + if ( pStartingAttrib && pEndingAttrib && + ( *(pStartingAttrib->GetItem()) == rPoolItem ) && + ( *(pEndingAttrib->GetItem()) == rPoolItem ) ) + { + // Will become a large Attribute. + pEndingAttrib->GetEnd() = pStartingAttrib->GetEnd(); + GetItemPool().Remove( *(pStartingAttrib->GetItem()) ); + pNode->GetCharAttribs().Remove(pStartingAttrib); + } + else if ( pStartingAttrib && ( *(pStartingAttrib->GetItem()) == rPoolItem ) ) + pStartingAttrib->GetStart() = nStart; + else if ( pEndingAttrib && ( *(pEndingAttrib->GetItem()) == rPoolItem ) ) + pEndingAttrib->GetEnd() = nEnd; + else + InsertAttrib( rPoolItem, pNode, nStart, nEnd ); + + if ( pStartingAttrib ) + pNode->GetCharAttribs().ResortAttribs(); + + SetModified(true); +} + +bool EditDoc::RemoveAttribs( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, sal_uInt16 nWhich ) +{ + EditCharAttrib* pStarting; + EditCharAttrib* pEnding; + return RemoveAttribs( pNode, nStart, nEnd, pStarting, pEnding, nWhich ); +} + +bool EditDoc::RemoveAttribs( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, EditCharAttrib*& rpStarting, EditCharAttrib*& rpEnding, sal_uInt16 nWhich ) +{ + + assert(pNode); + DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribute too large!" ); + + // This ends at the beginning of the selection => can be expanded + rpEnding = nullptr; + // This starts at the end of the selection => can be expanded + rpStarting = nullptr; + + bool bChanged = false; + bool bNeedsSorting = false; + + DBG_ASSERT( nStart <= nEnd, "Small miscalculations in InsertAttribInSelection" ); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(pNode->GetCharAttribs()); +#endif + + // iterate over the attributes ... + std::size_t nAttr = 0; + CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); + EditCharAttrib* pAttr = GetAttrib(rAttribs, nAttr); + while ( pAttr ) + { + bool bRemoveAttrib = false; + sal_uInt16 nAttrWhich = pAttr->Which(); + if ( ( nAttrWhich < EE_FEATURE_START ) && ( !nWhich || ( nAttrWhich == nWhich ) ) ) + { + // Attribute starts in Selection + if ( ( pAttr->GetStart() >= nStart ) && ( pAttr->GetStart() <= nEnd ) ) + { + bChanged = true; + if ( pAttr->GetEnd() > nEnd ) + { + bNeedsSorting = true; + pAttr->GetStart() = nEnd; // then it starts after this + rpStarting = pAttr; + if ( nWhich ) + break; // There can be no further attributes here + } + else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) ) + { + // Delete feature only if on the exact spot + bRemoveAttrib = true; + } + } + + // Attribute ends in Selection + else if ( ( pAttr->GetEnd() >= nStart ) && ( pAttr->GetEnd() <= nEnd ) ) + { + bChanged = true; + if ( ( pAttr->GetStart() < nStart ) && !pAttr->IsFeature() ) + { + pAttr->GetEnd() = nStart; // then it ends here + rpEnding = pAttr; + } + else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) ) + { + // Delete feature only if on the exact spot + bRemoveAttrib = true; + } + } + // Attribute overlaps the selection + else if ( ( pAttr->GetStart() <= nStart ) && ( pAttr->GetEnd() >= nEnd ) ) + { + bChanged = true; + if ( pAttr->GetStart() == nStart ) + { + bNeedsSorting = true; + pAttr->GetStart() = nEnd; + rpStarting = pAttr; + if ( nWhich ) + break; // There can be further attributes! + } + else if ( pAttr->GetEnd() == nEnd ) + { + pAttr->GetEnd() = nStart; + rpEnding = pAttr; + if ( nWhich ) + break; // There can be further attributes! + } + else // Attribute must be split ... + { + bNeedsSorting = true; + sal_Int32 nOldEnd = pAttr->GetEnd(); + pAttr->GetEnd() = nStart; + rpEnding = pAttr; + InsertAttrib( *pAttr->GetItem(), pNode, nEnd, nOldEnd ); + if ( nWhich ) + break; // There can be further attributes! + } + } + } + if ( bRemoveAttrib ) + { + DBG_ASSERT( ( pAttr != rpStarting ) && ( pAttr != rpEnding ), "Delete and retain the same attribute?" ); + DBG_ASSERT( !pAttr->IsFeature(), "RemoveAttribs: Remove a feature?!" ); + GetItemPool().Remove( *pAttr->GetItem() ); + rAttribs.erase(rAttribs.begin()+nAttr); + } + else + { + nAttr++; + } + pAttr = GetAttrib(rAttribs, nAttr); + } + + if ( bChanged ) + { + // char attributes need to be sorted by start again + if (bNeedsSorting) + pNode->GetCharAttribs().ResortAttribs(); + SetModified(true); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(pNode->GetCharAttribs()); +#endif + + return bChanged; +} + +void EditDoc::InsertAttrib( const SfxPoolItem& rPoolItem, ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd ) +{ + // This method no longer checks whether a corresponding attribute already + // exists at this place! + EditCharAttrib* pAttrib = MakeCharAttrib( GetItemPool(), rPoolItem, nStart, nEnd ); + assert(pAttrib); + pNode->GetCharAttribs().InsertAttrib( pAttrib ); + + SetModified( true ); +} + +void EditDoc::InsertAttrib( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, const SfxPoolItem& rPoolItem ) +{ + if ( nStart != nEnd ) + { + InsertAttribInSelection( pNode, nStart, nEnd, rPoolItem ); + } + else + { + // Check whether already a new attribute with WhichId exists at this place: + CharAttribList& rAttrList = pNode->GetCharAttribs(); + EditCharAttrib* pAttr = rAttrList.FindEmptyAttrib( rPoolItem.Which(), nStart ); + if ( pAttr ) + { + // Remove attribute... + rAttrList.Remove(pAttr); + } + + // check whether 'the same' attribute exist at this place. + pAttr = rAttrList.FindAttrib( rPoolItem.Which(), nStart ); + if ( pAttr ) + { + if ( pAttr->IsInside( nStart ) ) // split + { + // check again if really splitting, or return ! + sal_Int32 nOldEnd = pAttr->GetEnd(); + pAttr->GetEnd() = nStart; + EditCharAttrib* pNew = MakeCharAttrib( GetItemPool(), *(pAttr->GetItem()), nStart, nOldEnd ); + rAttrList.InsertAttrib(pNew); + } + else if ( pAttr->GetEnd() == nStart ) + { + DBG_ASSERT( !pAttr->IsEmpty(), "Still an empty attribute?" ); + // Check if exactly the same attribute + if ( *(pAttr->GetItem()) == rPoolItem ) + return; + } + } + InsertAttrib( rPoolItem, pNode, nStart, nStart ); + } + + SetModified( true ); +} + +void EditDoc::FindAttribs( ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos, SfxItemSet& rCurSet ) +{ + assert(pNode); + DBG_ASSERT( nStartPos <= nEndPos, "Invalid region!" ); + + std::size_t nAttr = 0; + EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + // No Selection... + if ( nStartPos == nEndPos ) + { + while ( pAttr && ( pAttr->GetStart() <= nEndPos) ) + { + const SfxPoolItem* pItem = nullptr; + // Attribute is about... + if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() > nStartPos ) ) + pItem = pAttr->GetItem(); + // Attribute ending here is not empty + else if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() == nStartPos ) ) + { + if ( !pNode->GetCharAttribs().FindEmptyAttrib( pAttr->GetItem()->Which(), nStartPos ) ) + pItem = pAttr->GetItem(); + } + // Attribute ending here is empty + else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() == nStartPos ) ) + { + pItem = pAttr->GetItem(); + } + // Attribute starts here + else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() > nStartPos ) ) + { + if ( nStartPos == 0 ) // special case + pItem = pAttr->GetItem(); + } + + if ( pItem ) + { + sal_uInt16 nWhich = pItem->Which(); + if ( rCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT ) + { + rCurSet.Put( *pItem ); + } + else if ( rCurSet.GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = rCurSet.Get( nWhich ); + if ( rItem != *pItem ) + { + rCurSet.InvalidateItem( nWhich ); + } + } + } + nAttr++; + pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + } + } + else // Selection + { + while ( pAttr && ( pAttr->GetStart() < nEndPos) ) + { + const SfxPoolItem* pItem = nullptr; + // Attribute is about... + if ( ( pAttr->GetStart() <= nStartPos ) && ( pAttr->GetEnd() >= nEndPos ) ) + pItem = pAttr->GetItem(); + // Attribute starts right in the middle ... + else if ( pAttr->GetStart() >= nStartPos ) + { + // !!! pItem = pAttr->GetItem(); + // PItem is simply not enough, since one for example in case + // of Shadow, would never find an unequal item, since such a + // item represents its presence by absence! + // If (...) + // It needs to be examined on exactly the same attribute at the + // break point, which is quite expensive. + // Since optimization is done when inserting the attributes + // this case does not appear so fast... + // So based on the need for speed: + rCurSet.InvalidateItem( pAttr->GetItem()->Which() ); + + } + // Attribute ends in the middle of it ... + else if ( pAttr->GetEnd() > nStartPos ) + { + rCurSet.InvalidateItem( pAttr->GetItem()->Which() ); + } + + if ( pItem ) + { + sal_uInt16 nWhich = pItem->Which(); + if ( rCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT ) + { + rCurSet.Put( *pItem ); + } + else if ( rCurSet.GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = rCurSet.Get( nWhich ); + if ( rItem != *pItem ) + { + rCurSet.InvalidateItem( nWhich ); + } + } + } + nAttr++; + pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + } + } +} + +void EditDoc::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + bool bOwns = false; + if (!pWriter) + { + pWriter = xmlNewTextWriterFilename("editdoc.xml", 0); + xmlTextWriterSetIndent(pWriter,1); + (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); + (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr); + bOwns = true; + } + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("EditDoc")); + for (auto const & i : maContents) + { + i->dumpAsXml(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + + if (bOwns) + { + (void)xmlTextWriterEndDocument(pWriter); + xmlFreeTextWriter(pWriter); + } +} + + +namespace { + +struct LessByStart +{ + bool operator() (const std::unique_ptr<EditCharAttrib>& left, const std::unique_ptr<EditCharAttrib>& right) const + { + return left->GetStart() < right->GetStart(); + } +}; + +} + +CharAttribList::CharAttribList() +: bHasEmptyAttribs(false) +{ +} + +CharAttribList::~CharAttribList() +{ +} + +void CharAttribList::InsertAttrib( EditCharAttrib* pAttrib ) +{ +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// optimize: binary search? ! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // Maybe just simply iterate backwards: + // The most common and critical case: Attributes are already sorted + // (InsertTextObject!) binary search would not be optimal here. + // => Would bring something! + + const sal_Int32 nStart = pAttrib->GetStart(); // may be better for Comp.Opt. + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif + + if ( pAttrib->IsEmpty() ) + bHasEmptyAttribs = true; + + bool bInsert(true); + for (sal_Int32 i = 0, n = aAttribs.size(); i < n; ++i) + { + const EditCharAttrib& rCurAttrib = *aAttribs[i]; + if (rCurAttrib.GetStart() > nStart) + { + aAttribs.insert(aAttribs.begin()+i, std::unique_ptr<EditCharAttrib>(pAttrib)); + bInsert = false; + break; + } + } + + if (bInsert) aAttribs.push_back(std::unique_ptr<EditCharAttrib>(pAttrib)); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif +} + +void CharAttribList::ResortAttribs() +{ + std::sort(aAttribs.begin(), aAttribs.end(), LessByStart()); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif +} + +void CharAttribList::OptimizeRanges( SfxItemPool& rItemPool ) +{ +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif + for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAttribs.size()); ++i) + { + EditCharAttrib& rAttr = *aAttribs[i]; + for (sal_Int32 nNext = i+1; nNext < static_cast<sal_Int32>(aAttribs.size()); ++nNext) + { + EditCharAttrib& rNext = *aAttribs[nNext]; + if (!rAttr.IsFeature() && rNext.GetStart() == rAttr.GetEnd() && rNext.Which() == rAttr.Which()) + { + if (*rNext.GetItem() == *rAttr.GetItem()) + { + rAttr.GetEnd() = rNext.GetEnd(); + rItemPool.Remove(*rNext.GetItem()); + aAttribs.erase(aAttribs.begin()+nNext); + } + break; // only 1 attr with same which can start here. + } + else if (rNext.GetStart() > rAttr.GetEnd()) + { + break; + } + } + } +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif +} + +sal_Int32 CharAttribList::Count() const +{ + return aAttribs.size(); +} + +const EditCharAttrib* CharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos ) const +{ + // Backwards, if one ends where the next starts. + // => The starting one is the valid one ... + AttribsType::const_reverse_iterator it = std::find_if(aAttribs.rbegin(), aAttribs.rend(), + [&nWhich, &nPos](const AttribsType::value_type& rxAttr) { + return rxAttr->Which() == nWhich && rxAttr->IsIn(nPos); }); + if (it != aAttribs.rend()) + { + const EditCharAttrib& rAttr = **it; + return &rAttr; + } + return nullptr; +} + +EditCharAttrib* CharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos ) +{ + // Backwards, if one ends where the next starts. + // => The starting one is the valid one ... + AttribsType::reverse_iterator it = std::find_if(aAttribs.rbegin(), aAttribs.rend(), + [&nWhich, &nPos](AttribsType::value_type& rxAttr) { + return rxAttr->Which() == nWhich && rxAttr->IsIn(nPos); }); + if (it != aAttribs.rend()) + { + EditCharAttrib& rAttr = **it; + return &rAttr; + } + return nullptr; +} + +const EditCharAttrib* CharAttribList::FindNextAttrib( sal_uInt16 nWhich, sal_Int32 nFromPos ) const +{ + assert(nWhich); + for (auto const& attrib : aAttribs) + { + const EditCharAttrib& rAttr = *attrib; + if (rAttr.GetStart() >= nFromPos && rAttr.Which() == nWhich) + return &rAttr; + } + return nullptr; +} + +bool CharAttribList::HasAttrib( sal_Int32 nStartPos, sal_Int32 nEndPos ) const +{ + return std::any_of(aAttribs.rbegin(), aAttribs.rend(), + [&nStartPos, &nEndPos](const AttribsType::value_type& rxAttr) { + return rxAttr->GetStart() < nEndPos && rxAttr->GetEnd() > nStartPos; }); +} + + +namespace { + +class FindByAddress +{ + const EditCharAttrib* mpAttr; +public: + explicit FindByAddress(const EditCharAttrib* p) : mpAttr(p) {} + bool operator() (const std::unique_ptr<EditCharAttrib>& r) const + { + return r.get() == mpAttr; + } +}; + +} + +void CharAttribList::Remove(const EditCharAttrib* p) +{ + AttribsType::iterator it = std::find_if(aAttribs.begin(), aAttribs.end(), FindByAddress(p)); + if (it != aAttribs.end()) + aAttribs.erase(it); +} + +void CharAttribList::Remove(sal_Int32 nPos) +{ + if (nPos >= static_cast<sal_Int32>(aAttribs.size())) + return; + + aAttribs.erase(aAttribs.begin()+nPos); +} + +void CharAttribList::SetHasEmptyAttribs(bool b) +{ + bHasEmptyAttribs = b; +} + +bool CharAttribList::HasBoundingAttrib( sal_Int32 nBound ) const +{ + // Backwards, if one ends where the next starts. + // => The starting one is the valid one ... + AttribsType::const_reverse_iterator it = aAttribs.rbegin(), itEnd = aAttribs.rend(); + for (; it != itEnd; ++it) + { + const EditCharAttrib& rAttr = **it; + if (rAttr.GetEnd() < nBound) + return false; + + if (rAttr.GetStart() == nBound || rAttr.GetEnd() == nBound) + return true; + } + return false; +} + +EditCharAttrib* CharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos ) +{ + if ( !bHasEmptyAttribs ) + return nullptr; + + for (const std::unique_ptr<EditCharAttrib>& rAttr : aAttribs) + { + if (rAttr->GetStart() == nPos && rAttr->GetEnd() == nPos && rAttr->Which() == nWhich) + return rAttr.get(); + } + return nullptr; +} + +namespace { + +class FindByStartPos +{ + sal_Int32 mnPos; +public: + explicit FindByStartPos(sal_Int32 nPos) : mnPos(nPos) {} + bool operator() (const std::unique_ptr<EditCharAttrib>& r) const + { + return r->GetStart() >= mnPos; + } +}; + +} + +const EditCharAttrib* CharAttribList::FindFeature( sal_Int32 nPos ) const +{ + // First, find the first attribute that starts at or after specified position. + AttribsType::const_iterator it = + std::find_if(aAttribs.begin(), aAttribs.end(), FindByStartPos(nPos)); + + if (it == aAttribs.end()) + // All attributes are before the specified position. + return nullptr; + + // And find the first attribute with feature. + it = std::find_if(it, aAttribs.end(), [](const std::unique_ptr<EditCharAttrib>& aAttrib) { return aAttrib->IsFeature(); } ); + return it == aAttribs.end() ? nullptr : it->get(); +} + +namespace { + +class RemoveEmptyAttrItem +{ + SfxItemPool& mrItemPool; +public: + explicit RemoveEmptyAttrItem(SfxItemPool& rPool) : mrItemPool(rPool) {} + void operator() (const std::unique_ptr<EditCharAttrib>& r) + { + if (r->IsEmpty()) + mrItemPool.Remove(*r->GetItem()); + } +}; + +} + +void CharAttribList::DeleteEmptyAttribs( SfxItemPool& rItemPool ) +{ + std::for_each(aAttribs.begin(), aAttribs.end(), RemoveEmptyAttrItem(rItemPool)); + aAttribs.erase( std::remove_if(aAttribs.begin(), aAttribs.end(), [](const std::unique_ptr<EditCharAttrib>& aAttrib) { return aAttrib->IsEmpty(); } ), aAttribs.end() ); + bHasEmptyAttribs = false; +} + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG +void CharAttribList::DbgCheckAttribs(CharAttribList const& rAttribs) +{ + std::set<std::pair<sal_Int32, sal_uInt16>> zero_set; + for (const std::unique_ptr<EditCharAttrib>& rAttr : rAttribs.aAttribs) + { + assert(rAttr->GetStart() <= rAttr->GetEnd()); + assert(!rAttr->IsFeature() || rAttr->GetLen() == 1); + if (0 == rAttr->GetLen()) + { + // not sure if 0-length attributes allowed at all in non-empty para? + assert(zero_set.insert(std::make_pair(rAttr->GetStart(), rAttr->Which())).second && "duplicate 0-length attribute detected"); + } + } + CheckOrderedList(rAttribs.GetAttribs()); +} +#endif + +void CharAttribList::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("CharAttribList")); + for (auto const & i : aAttribs) { + i->dumpAsXml(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); +} + +EditEngineItemPool::EditEngineItemPool() + : SfxItemPool( "EditEngineItemPool", EE_ITEMS_START, EE_ITEMS_END, + aItemInfos, nullptr ) +{ + m_xDefItems = EditDLL::Get().GetGlobalData()->GetDefItems(); + SetDefaults(&m_xDefItems->getDefaults()); +} + +EditEngineItemPool::~EditEngineItemPool() +{ + ClearDefaults(); + SetSecondaryPool(nullptr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editeng.cxx b/editeng/source/editeng/editeng.cxx new file mode 100644 index 000000000..1b8b446c3 --- /dev/null +++ b/editeng/source/editeng/editeng.cxx @@ -0,0 +1,2907 @@ +/* -*- 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 <utility> + +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <config_global.h> +#include <o3tl/safeint.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> + +#include <tools/stream.hxx> + +#include <editeng/svxfont.hxx> +#include "impedit.hxx" +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/editstat.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/editrids.hrc> +#include <editeng/flditem.hxx> +#include <editeng/txtrange.hxx> +#include <editeng/cmapitem.hxx> + +#include <editeng/autokernitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> + +#include <sot/exchange.hxx> +#include <sot/formats.hxx> + +#include <editeng/numitem.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include <vcl/help.hxx> +#include <vcl/transfer.hxx> +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <com/sun/star/frame/Desktop.hpp> + +#if OSL_DEBUG_LEVEL > 1 +#include <editeng/frmdiritem.hxx> +#endif +#include <basegfx/polygon/b2dpolygon.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::linguistic2; + + +#if (OSL_DEBUG_LEVEL > 1) || defined ( DBG_UTIL ) +static bool bDebugPaint = false; +#endif + + +static rtl::Reference<SfxItemPool> pGlobalPool; + +EditEngine::EditEngine( SfxItemPool* pItemPool ) +{ + pImpEditEngine.reset( new ImpEditEngine( this, pItemPool ) ); +} + +EditEngine::~EditEngine() +{ +} + +void EditEngine::EnableUndo( bool bEnable ) +{ + pImpEditEngine->EnableUndo( bEnable ); +} + +bool EditEngine::IsUndoEnabled() const +{ + return pImpEditEngine->IsUndoEnabled(); +} + +bool EditEngine::IsInUndo() const +{ + return pImpEditEngine->IsInUndo(); +} + +SfxUndoManager& EditEngine::GetUndoManager() +{ + return pImpEditEngine->GetUndoManager(); +} + +SfxUndoManager* EditEngine::SetUndoManager(SfxUndoManager* pNew) +{ + return pImpEditEngine->SetUndoManager(pNew); +} + +void EditEngine::UndoActionStart( sal_uInt16 nId ) +{ + DBG_ASSERT( !pImpEditEngine->IsInUndo(), "Calling UndoActionStart in Undomode!" ); + if ( !pImpEditEngine->IsInUndo() ) + pImpEditEngine->UndoActionStart( nId ); +} + +void EditEngine::UndoActionStart(sal_uInt16 nId, const ESelection& rSel) +{ + pImpEditEngine->UndoActionStart(nId, rSel); +} + +void EditEngine::UndoActionEnd() +{ + DBG_ASSERT( !pImpEditEngine->IsInUndo(), "Calling UndoActionEnd in Undomode!" ); + if ( !pImpEditEngine->IsInUndo() ) + pImpEditEngine->UndoActionEnd(); +} + +bool EditEngine::HasTriedMergeOnLastAddUndo() const +{ + return pImpEditEngine->mbLastTryMerge; +} + +void EditEngine::SetRefDevice( OutputDevice* pRefDev ) +{ + pImpEditEngine->SetRefDevice( pRefDev ); +} + +OutputDevice* EditEngine::GetRefDevice() const +{ + return pImpEditEngine->GetRefDevice(); +} + +void EditEngine::SetRefMapMode( const MapMode& rMapMode ) +{ + pImpEditEngine->SetRefMapMode( rMapMode ); +} + +MapMode const & EditEngine::GetRefMapMode() const +{ + return pImpEditEngine->GetRefMapMode(); +} + +void EditEngine::SetBackgroundColor( const Color& rColor ) +{ + pImpEditEngine->SetBackgroundColor( rColor ); +} + +Color const & EditEngine::GetBackgroundColor() const +{ + return pImpEditEngine->GetBackgroundColor(); +} + +Color EditEngine::GetAutoColor() const +{ + return pImpEditEngine->GetAutoColor(); +} + +void EditEngine::EnableAutoColor( bool b ) +{ + pImpEditEngine->EnableAutoColor( b ); +} + +void EditEngine::ForceAutoColor( bool b ) +{ + pImpEditEngine->ForceAutoColor( b ); +} + +bool EditEngine::IsForceAutoColor() const +{ + return pImpEditEngine->IsForceAutoColor(); +} + +const SfxItemSet& EditEngine::GetEmptyItemSet() const +{ + return pImpEditEngine->GetEmptyItemSet(); +} + +void EditEngine::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect ) +{ + Draw( rOutDev, rOutRect, Point( 0, 0 ) ); +} + +void EditEngine::Draw( OutputDevice& rOutDev, const Point& rStartPos, Degree10 nOrientation ) +{ + // Create with 2 points, as with positive points it will end up with + // LONGMAX as Size, Bottom and Right in the range > LONGMAX. + tools::Rectangle aBigRect( -0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF ); + if( rOutDev.GetConnectMetaFile() ) + rOutDev.Push(); + Point aStartPos( rStartPos ); + if ( IsEffectivelyVertical() ) + { + aStartPos.AdjustX(GetPaperSize().Width() ); + aStartPos = Rotate( aStartPos, nOrientation, rStartPos ); + } + pImpEditEngine->Paint(rOutDev, aBigRect, aStartPos, false, nOrientation); + if( rOutDev.GetConnectMetaFile() ) + rOutDev.Pop(); +} + +void EditEngine::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect, const Point& rStartDocPos ) +{ + Draw( rOutDev, rOutRect, rStartDocPos, true ); +} + +void EditEngine::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect, const Point& rStartDocPos, bool bClip ) +{ +#if defined( DBG_UTIL ) || (OSL_DEBUG_LEVEL > 1) + if ( bDebugPaint ) + DumpData(this, false); +#endif + + // Align to the pixel boundary, so that it becomes exactly the same + // as Paint () + tools::Rectangle aOutRect( rOutDev.LogicToPixel( rOutRect ) ); + aOutRect = rOutDev.PixelToLogic( aOutRect ); + + Point aStartPos; + if ( !IsEffectivelyVertical() ) + { + aStartPos.setX( aOutRect.Left() - rStartDocPos.X() ); + aStartPos.setY( aOutRect.Top() - rStartDocPos.Y() ); + } + else + { + aStartPos.setX( aOutRect.Right() + rStartDocPos.Y() ); + aStartPos.setY( aOutRect.Top() - rStartDocPos.X() ); + } + + bool bClipRegion = rOutDev.IsClipRegion(); + bool bMetafile = rOutDev.GetConnectMetaFile(); + vcl::Region aOldRegion = rOutDev.GetClipRegion(); + + // If one existed => intersection! + // Use Push/pop for creating the Meta file + if ( bMetafile ) + rOutDev.Push(); + + // Always use the Intersect method, it is a must for Metafile! + if ( bClip ) + { + // Clip only if necessary... + if ( rStartDocPos.X() || rStartDocPos.Y() || + ( rOutRect.GetHeight() < static_cast<tools::Long>(GetTextHeight()) ) || + ( rOutRect.GetWidth() < static_cast<tools::Long>(CalcTextWidth()) ) ) + { + // Some printer drivers cause problems if characters graze the + // ClipRegion, therefore rather add a pixel more ... + tools::Rectangle aClipRect( aOutRect ); + if ( rOutDev.GetOutDevType() == OUTDEV_PRINTER ) + { + Size aPixSz( 1, 0 ); + aPixSz = rOutDev.PixelToLogic( aPixSz ); + aClipRect.AdjustRight(aPixSz.Width() ); + aClipRect.AdjustBottom(aPixSz.Width() ); + } + rOutDev.IntersectClipRegion( aClipRect ); + } + } + + pImpEditEngine->Paint( rOutDev, aOutRect, aStartPos ); + + if ( bMetafile ) + rOutDev.Pop(); + else if ( bClipRegion ) + rOutDev.SetClipRegion( aOldRegion ); + else + rOutDev.SetClipRegion(); +} + +void EditEngine::InsertView(EditView* pEditView, size_t nIndex) +{ + + if ( nIndex > pImpEditEngine->GetEditViews().size() ) + nIndex = pImpEditEngine->GetEditViews().size(); + + ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews(); + rViews.insert(rViews.begin()+nIndex, pEditView); + + EditSelection aStartSel = pImpEditEngine->GetEditDoc().GetStartPaM(); + pEditView->pImpEditView->SetEditSelection( aStartSel ); + if ( !pImpEditEngine->GetActiveView() ) + pImpEditEngine->SetActiveView( pEditView ); + + pEditView->pImpEditView->AddDragAndDropListeners(); +} + +EditView* EditEngine::RemoveView( EditView* pView ) +{ + + pView->HideCursor(); + EditView* pRemoved = nullptr; + ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews(); + ImpEditEngine::ViewsType::iterator it = std::find(rViews.begin(), rViews.end(), pView); + + DBG_ASSERT( it != rViews.end(), "RemoveView with invalid index" ); + if (it != rViews.end()) + { + pRemoved = *it; + rViews.erase(it); + if ( pImpEditEngine->GetActiveView() == pView ) + { + pImpEditEngine->SetActiveView( nullptr ); + pImpEditEngine->GetSelEngine().SetCurView( nullptr ); + } + pView->pImpEditView->RemoveDragAndDropListeners(); + + } + return pRemoved; +} + +void EditEngine::RemoveView(size_t nIndex) +{ + ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews(); + if (nIndex >= rViews.size()) + return; + + EditView* pView = rViews[nIndex]; + if ( pView ) + RemoveView( pView ); +} + +EditView* EditEngine::GetView(size_t nIndex) const +{ + return pImpEditEngine->GetEditViews()[nIndex]; +} + +size_t EditEngine::GetViewCount() const +{ + return pImpEditEngine->GetEditViews().size(); +} + +bool EditEngine::HasView( EditView* pView ) const +{ + ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews(); + return std::find(rViews.begin(), rViews.end(), pView) != rViews.end(); +} + +EditView* EditEngine::GetActiveView() const +{ + return pImpEditEngine->GetActiveView(); +} + +void EditEngine::SetActiveView(EditView* pView) +{ + pImpEditEngine->SetActiveView(pView); +} + +void EditEngine::SetDefTab( sal_uInt16 nDefTab ) +{ + pImpEditEngine->GetEditDoc().SetDefTab( nDefTab ); + if ( pImpEditEngine->IsFormatted() ) + { + pImpEditEngine->FormatFullDoc(); + pImpEditEngine->UpdateViews(); + } +} + +void EditEngine::SetPaperSize( const Size& rNewSize ) +{ + + Size aOldSize( pImpEditEngine->GetPaperSize() ); + pImpEditEngine->SetValidPaperSize( rNewSize ); + Size aNewSize( pImpEditEngine->GetPaperSize() ); + + bool bAutoPageSize = pImpEditEngine->GetStatus().AutoPageSize(); + if ( !(bAutoPageSize || ( aNewSize.Width() != aOldSize.Width() )) ) + return; + + for (EditView* pView : pImpEditEngine->aEditViews) + { + if ( bAutoPageSize ) + pView->pImpEditView->RecalcOutputArea(); + else if ( pView->pImpEditView->DoAutoSize() ) + { + pView->pImpEditView->ResetOutputArea( tools::Rectangle( + pView->pImpEditView->GetOutputArea().TopLeft(), aNewSize ) ); + } + } + + if ( bAutoPageSize || pImpEditEngine->IsFormatted() ) + { + // Changing the width has no effect for AutoPageSize, as this is + // determined by the text width. + // Optimization first after Vobis delivery was enabled ... + pImpEditEngine->FormatFullDoc(); + + pImpEditEngine->UpdateViews( pImpEditEngine->GetActiveView() ); + + if ( pImpEditEngine->IsUpdateLayout() && pImpEditEngine->GetActiveView() ) + pImpEditEngine->pActiveView->ShowCursor( false, false ); + } +} + +const Size& EditEngine::GetPaperSize() const +{ + return pImpEditEngine->GetPaperSize(); +} + +void EditEngine::SetVertical(bool bVertical) +{ + pImpEditEngine->SetVertical(bVertical); +} + +void EditEngine::SetRotation(TextRotation nRotation) +{ + pImpEditEngine->SetRotation(nRotation); +} + +TextRotation EditEngine::GetRotation() const +{ + return pImpEditEngine->GetRotation(); +} + +bool EditEngine::IsEffectivelyVertical() const +{ + return pImpEditEngine->IsEffectivelyVertical(); +} + +bool EditEngine::IsTopToBottom() const +{ + return pImpEditEngine->IsTopToBottom(); +} + +bool EditEngine::GetVertical() const +{ + return pImpEditEngine->GetVertical(); +} + +void EditEngine::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing) +{ + pImpEditEngine->SetTextColumns(nColumns, nSpacing); +} + +void EditEngine::SetFixedCellHeight( bool bUseFixedCellHeight ) +{ + pImpEditEngine->SetFixedCellHeight( bUseFixedCellHeight ); +} + +void EditEngine::SetDefaultHorizontalTextDirection( EEHorizontalTextDirection eHTextDir ) +{ + pImpEditEngine->SetDefaultHorizontalTextDirection( eHTextDir ); +} + +EEHorizontalTextDirection EditEngine::GetDefaultHorizontalTextDirection() const +{ + return pImpEditEngine->GetDefaultHorizontalTextDirection(); +} + +SvtScriptType EditEngine::GetScriptType( const ESelection& rSelection ) const +{ + EditSelection aSel( pImpEditEngine->CreateSel( rSelection ) ); + return pImpEditEngine->GetItemScriptType( aSel ); +} + +editeng::LanguageSpan EditEngine::GetLanguage(const EditPaM& rPaM) const +{ + return pImpEditEngine->GetLanguage(rPaM); +} + +editeng::LanguageSpan EditEngine::GetLanguage( sal_Int32 nPara, sal_Int32 nPos ) const +{ + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + DBG_ASSERT( pNode, "GetLanguage - nPara is invalid!" ); + return pNode ? pImpEditEngine->GetLanguage( EditPaM( pNode, nPos ) ) : editeng::LanguageSpan{}; +} + + +void EditEngine::TransliterateText( const ESelection& rSelection, TransliterationFlags nTransliterationMode ) +{ + pImpEditEngine->TransliterateText( pImpEditEngine->CreateSel( rSelection ), nTransliterationMode ); +} + +EditSelection EditEngine::TransliterateText(const EditSelection& rSelection, TransliterationFlags nTransliterationMode) +{ + return pImpEditEngine->TransliterateText(rSelection, nTransliterationMode); +} + +void EditEngine::SetAsianCompressionMode( CharCompressType n ) +{ + pImpEditEngine->SetAsianCompressionMode( n ); +} + +void EditEngine::SetKernAsianPunctuation( bool b ) +{ + pImpEditEngine->SetKernAsianPunctuation( b ); +} + +void EditEngine::SetAddExtLeading( bool b ) +{ + pImpEditEngine->SetAddExtLeading( b ); +} + +void EditEngine::SetPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon ) +{ + SetPolygon( rPolyPolygon, nullptr ); +} + +void EditEngine::SetPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DPolyPolygon* pLinePolyPolygon) +{ + bool bSimple(false); + + if(pLinePolyPolygon && 1 == rPolyPolygon.count()) + { + if(rPolyPolygon.getB2DPolygon(0).isClosed()) + { + // open polygon + bSimple = true; + } + } + + TextRanger* pRanger = new TextRanger( rPolyPolygon, pLinePolyPolygon, 30, 2, 2, bSimple, true ); + pImpEditEngine->SetTextRanger( std::unique_ptr<TextRanger>(pRanger) ); + pImpEditEngine->SetPaperSize( pRanger->GetBoundRect().GetSize() ); +} + +void EditEngine::ClearPolygon() +{ + pImpEditEngine->SetTextRanger( nullptr ); +} + +const Size& EditEngine::GetMinAutoPaperSize() const +{ + return pImpEditEngine->GetMinAutoPaperSize(); +} + +void EditEngine::SetMinAutoPaperSize( const Size& rSz ) +{ + pImpEditEngine->SetMinAutoPaperSize( rSz ); +} + +const Size& EditEngine::GetMaxAutoPaperSize() const +{ + return pImpEditEngine->GetMaxAutoPaperSize(); +} + +void EditEngine::SetMaxAutoPaperSize( const Size& rSz ) +{ + pImpEditEngine->SetMaxAutoPaperSize( rSz ); +} + +void EditEngine::SetMinColumnWrapHeight(tools::Long nVal) +{ + pImpEditEngine->SetMinColumnWrapHeight(nVal); +} + +OUString EditEngine::GetText( LineEnd eEnd ) const +{ + return pImpEditEngine->GetEditDoc().GetText( eEnd ); +} + +OUString EditEngine::GetText( const ESelection& rESelection ) const +{ + EditSelection aSel( pImpEditEngine->CreateSel( rESelection ) ); + return pImpEditEngine->GetSelected( aSel ); +} + +sal_Int32 EditEngine::GetTextLen() const +{ + return pImpEditEngine->GetEditDoc().GetTextLen(); +} + +sal_Int32 EditEngine::GetParagraphCount() const +{ + return pImpEditEngine->aEditDoc.Count(); +} + +sal_Int32 EditEngine::GetLineCount( sal_Int32 nParagraph ) const +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + return pImpEditEngine->GetLineCount( nParagraph ); +} + +sal_Int32 EditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + return pImpEditEngine->GetLineLen( nParagraph, nLine ); +} + +void EditEngine::GetLineBoundaries( /*out*/sal_Int32& rStart, /*out*/sal_Int32& rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + return pImpEditEngine->GetLineBoundaries( rStart, rEnd, nParagraph, nLine ); +} + +sal_Int32 EditEngine::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + return pImpEditEngine->GetLineNumberAtIndex( nPara, nIndex ); +} + +sal_uInt32 EditEngine::GetLineHeight( sal_Int32 nParagraph ) +{ + // If someone calls GetLineHeight() with an empty Engine. + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + return pImpEditEngine->GetLineHeight( nParagraph, 0 ); +} + +tools::Rectangle EditEngine::GetParaBounds( sal_Int32 nPara ) +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + Point aPnt = GetDocPosTopLeft( nPara ); + + if( IsEffectivelyVertical() ) + { + sal_Int32 nTextHeight = pImpEditEngine->GetTextHeight(); + sal_Int32 nParaWidth = pImpEditEngine->CalcParaWidth( nPara, true ); + sal_Int32 nParaHeight = pImpEditEngine->GetParaHeight( nPara ); + + return tools::Rectangle( nTextHeight - aPnt.Y() - nParaHeight, 0, nTextHeight - aPnt.Y(), nParaWidth ); + } + else + { + sal_Int32 nParaWidth = pImpEditEngine->CalcParaWidth( nPara, true ); + sal_Int32 nParaHeight = pImpEditEngine->GetParaHeight( nPara ); + + return tools::Rectangle( 0, aPnt.Y(), nParaWidth, aPnt.Y() + nParaHeight ); + } +} + +sal_uInt32 EditEngine::GetTextHeight( sal_Int32 nParagraph ) const +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + sal_uInt32 nHeight = pImpEditEngine->GetParaHeight( nParagraph ); + return nHeight; +} + +OUString EditEngine::GetWord( sal_Int32 nPara, sal_Int32 nIndex ) +{ + ESelection aESel( nPara, nIndex, nPara, nIndex ); + EditSelection aSel( pImpEditEngine->CreateSel( aESel ) ); + aSel = pImpEditEngine->SelectWord( aSel ); + return pImpEditEngine->GetSelected( aSel ); +} + +ESelection EditEngine::GetWord( const ESelection& rSelection, sal_uInt16 nWordType ) const +{ + // ImpEditEngine-Iteration-Methods should be const! + EditEngine* pE = const_cast<EditEngine*>(this); + + EditSelection aSel( pE->pImpEditEngine->CreateSel( rSelection ) ); + aSel = pE->pImpEditEngine->SelectWord( aSel, nWordType ); + return pE->pImpEditEngine->CreateESel( aSel ); +} + +void EditEngine::CursorMoved(const ContentNode* pPrevNode) +{ + pImpEditEngine->CursorMoved(pPrevNode); +} + +void EditEngine::CheckIdleFormatter() +{ + pImpEditEngine->CheckIdleFormatter(); +} + +bool EditEngine::IsIdleFormatterActive() const +{ + return pImpEditEngine->aIdleFormatter.IsActive(); +} + +ParaPortion* EditEngine::FindParaPortion(ContentNode const * pNode) +{ + return pImpEditEngine->FindParaPortion(pNode); +} + +const ParaPortion* EditEngine::FindParaPortion(ContentNode const * pNode) const +{ + return pImpEditEngine->FindParaPortion(pNode); +} + +const ParaPortion* EditEngine::GetPrevVisPortion(const ParaPortion* pCurPortion) const +{ + return pImpEditEngine->GetPrevVisPortion(pCurPortion); +} + +SvtScriptType EditEngine::GetScriptType(const EditSelection& rSel) const +{ + return pImpEditEngine->GetItemScriptType(rSel); +} + +void EditEngine::RemoveParaPortion(sal_Int32 nNode) +{ + pImpEditEngine->GetParaPortions().Remove(nNode); +} + +void EditEngine::SetCallParaInsertedOrDeleted(bool b) +{ + pImpEditEngine->SetCallParaInsertedOrDeleted(b); +} + +bool EditEngine::IsCallParaInsertedOrDeleted() const +{ + return pImpEditEngine->IsCallParaInsertedOrDeleted(); +} + +void EditEngine::AppendDeletedNodeInfo(DeletedNodeInfo* pInfo) +{ + pImpEditEngine->aDeletedNodes.push_back(std::unique_ptr<DeletedNodeInfo>(pInfo)); +} + +void EditEngine::UpdateSelections() +{ + pImpEditEngine->UpdateSelections(); +} + +void EditEngine::InsertContent(ContentNode* pNode, sal_Int32 nPos) +{ + pImpEditEngine->InsertContent(pNode, nPos); +} + +EditPaM EditEngine::SplitContent(sal_Int32 nNode, sal_Int32 nSepPos) +{ + return pImpEditEngine->SplitContent(nNode, nSepPos); +} + +EditPaM EditEngine::ConnectContents(sal_Int32 nLeftNode, bool bBackward) +{ + return pImpEditEngine->ConnectContents(nLeftNode, bBackward); +} + +void EditEngine::InsertFeature(const EditSelection& rEditSelection, const SfxPoolItem& rItem) +{ + pImpEditEngine->ImpInsertFeature(rEditSelection, rItem); +} + +EditSelection EditEngine::MoveParagraphs(const Range& rParagraphs, sal_Int32 nNewPos) +{ + return pImpEditEngine->MoveParagraphs(rParagraphs, nNewPos, nullptr); +} + +void EditEngine::RemoveCharAttribs(sal_Int32 nPara, sal_uInt16 nWhich, bool bRemoveFeatures) +{ + pImpEditEngine->RemoveCharAttribs(nPara, nWhich, bRemoveFeatures); +} + +void EditEngine::RemoveCharAttribs(const EditSelection& rSel, bool bRemoveParaAttribs, sal_uInt16 nWhich) +{ + const EERemoveParaAttribsMode eMode = bRemoveParaAttribs? + EERemoveParaAttribsMode::RemoveAll : + EERemoveParaAttribsMode::RemoveCharItems; + pImpEditEngine->RemoveCharAttribs(rSel, eMode, nWhich); +} + +void EditEngine::RemoveCharAttribs(const EditSelection& rSel, EERemoveParaAttribsMode eMode, sal_uInt16 nWhich) +{ + pImpEditEngine->RemoveCharAttribs(rSel, eMode, nWhich); +} + +EditEngine::ViewsType& EditEngine::GetEditViews() +{ + return pImpEditEngine->GetEditViews(); +} + +const EditEngine::ViewsType& EditEngine::GetEditViews() const +{ + return pImpEditEngine->GetEditViews(); +} + +void EditEngine::SetUndoMode(bool b) +{ + pImpEditEngine->SetUndoMode(b); +} + +void EditEngine::FormatAndLayout(EditView* pCurView, bool bCalledFromUndo) +{ + pImpEditEngine->FormatAndLayout(pCurView, bCalledFromUndo); +} + +void EditEngine::Undo(EditView* pView) +{ + pImpEditEngine->Undo(pView); +} + +void EditEngine::Redo(EditView* pView) +{ + pImpEditEngine->Redo(pView); +} + +uno::Reference<datatransfer::XTransferable> EditEngine::CreateTransferable(const EditSelection& rSelection) +{ + return pImpEditEngine->CreateTransferable(rSelection); +} + +void EditEngine::ParaAttribsToCharAttribs(ContentNode* pNode) +{ + pImpEditEngine->ParaAttribsToCharAttribs(pNode); +} + +EditPaM EditEngine::CreateEditPaM(const EPaM& rEPaM) +{ + return pImpEditEngine->CreateEditPaM(rEPaM); +} + +EditPaM EditEngine::ConnectParagraphs( + ContentNode* pLeft, ContentNode* pRight, bool bBackward) +{ + return pImpEditEngine->ImpConnectParagraphs(pLeft, pRight, bBackward); +} + +EditPaM EditEngine::InsertField(const EditSelection& rEditSelection, const SvxFieldItem& rFld) +{ + return pImpEditEngine->InsertField(rEditSelection, rFld); +} + +EditPaM EditEngine::InsertText(const EditSelection& aCurEditSelection, const OUString& rStr) +{ + return pImpEditEngine->InsertText(aCurEditSelection, rStr); +} + +EditSelection EditEngine::InsertText(const EditTextObject& rTextObject, const EditSelection& rSel) +{ + return pImpEditEngine->InsertText(rTextObject, rSel); +} + +EditSelection EditEngine::InsertText( + uno::Reference<datatransfer::XTransferable > const & rxDataObj, + const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial) +{ + return pImpEditEngine->PasteText(rxDataObj, rBaseURL, rPaM, bUseSpecial); +} + +EditPaM EditEngine::EndOfWord(const EditPaM& rPaM) +{ + return pImpEditEngine->EndOfWord(rPaM); +} + +EditPaM EditEngine::GetPaM(const Point& aDocPos, bool bSmart) +{ + return pImpEditEngine->GetPaM(aDocPos, bSmart); +} + +EditSelection EditEngine::SelectWord( + const EditSelection& rCurSelection, sal_Int16 nWordType) +{ + return pImpEditEngine->SelectWord(rCurSelection, nWordType); +} + +tools::Long EditEngine::GetXPos( + const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart) const +{ + return pImpEditEngine->GetXPos(pParaPortion, pLine, nIndex, bPreferPortionStart); +} + +Range EditEngine::GetLineXPosStartEnd( + const ParaPortion* pParaPortion, const EditLine* pLine) const +{ + return pImpEditEngine->GetLineXPosStartEnd(pParaPortion, pLine); +} + +bool EditEngine::IsFormatted() const +{ + return pImpEditEngine->IsFormatted(); +} + +EditPaM EditEngine::CursorLeft(const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode) +{ + return pImpEditEngine->CursorLeft(rPaM, nCharacterIteratorMode); +} + +EditPaM EditEngine::CursorRight(const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode) +{ + return pImpEditEngine->CursorRight(rPaM, nCharacterIteratorMode); +} + +InternalEditStatus& EditEngine::GetInternalEditStatus() +{ + return pImpEditEngine->GetStatus(); +} + +EditDoc& EditEngine::GetEditDoc() +{ + return pImpEditEngine->GetEditDoc(); +} + +const EditDoc& EditEngine::GetEditDoc() const +{ + return pImpEditEngine->GetEditDoc(); +} + +void EditEngine::dumpAsXmlEditDoc(xmlTextWriterPtr pWriter) const +{ + pImpEditEngine->GetEditDoc().dumpAsXml(pWriter); +} + +ParaPortionList& EditEngine::GetParaPortions() +{ + return pImpEditEngine->GetParaPortions(); +} + +const ParaPortionList& EditEngine::GetParaPortions() const +{ + return pImpEditEngine->GetParaPortions(); +} + +void EditEngine::SeekCursor(ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont) +{ + pImpEditEngine->SeekCursor(pNode, nPos, rFont); +} + +EditPaM EditEngine::DeleteSelection(const EditSelection& rSel) +{ + return pImpEditEngine->ImpDeleteSelection(rSel); +} + +ESelection EditEngine::CreateESelection(const EditSelection& rSel) const +{ + return pImpEditEngine->CreateESel(rSel); +} + +EditSelection EditEngine::CreateSelection(const ESelection& rSel) +{ + return pImpEditEngine->CreateSel(rSel); +} + +const SfxItemSet& EditEngine::GetBaseParaAttribs(sal_Int32 nPara) const +{ + return pImpEditEngine->GetParaAttribs(nPara); +} + +void EditEngine::SetParaAttribsOnly(sal_Int32 nPara, const SfxItemSet& rSet) +{ + pImpEditEngine->SetParaAttribs(nPara, rSet); +} + +void EditEngine::SetAttribs(const EditSelection& rSel, const SfxItemSet& rSet, SetAttribsMode nSpecial) +{ + pImpEditEngine->SetAttribs(rSel, rSet, nSpecial); +} + +OUString EditEngine::GetSelected(const EditSelection& rSel) const +{ + return pImpEditEngine->GetSelected(rSel); +} + +EditPaM EditEngine::DeleteSelected(const EditSelection& rSel) +{ + return pImpEditEngine->DeleteSelected(rSel); +} + +void EditEngine::HandleBeginPasteOrDrop(PasteOrDropInfos& rInfos) +{ + pImpEditEngine->aBeginPasteOrDropHdl.Call(rInfos); +} + +void EditEngine::HandleEndPasteOrDrop(PasteOrDropInfos& rInfos) +{ + pImpEditEngine->aEndPasteOrDropHdl.Call(rInfos); +} + +bool EditEngine::HasText() const +{ + return pImpEditEngine->ImplHasText(); +} + +const EditSelectionEngine& EditEngine::GetSelectionEngine() const +{ + return pImpEditEngine->aSelEngine; +} + +void EditEngine::SetInSelectionMode(bool b) +{ + pImpEditEngine->bInSelection = b; +} + +bool EditEngine::PostKeyEvent( const KeyEvent& rKeyEvent, EditView* pEditView, vcl::Window const * pFrameWin ) +{ + DBG_ASSERT( pEditView, "no View - no cookie !" ); + + bool bDone = true; + + bool bModified = false; + bool bMoved = false; + bool bAllowIdle = true; + bool bReadOnly = pEditView->IsReadOnly(); + + GetCursorFlags nNewCursorFlags = GetCursorFlags::NONE; + bool bSetCursorFlags = true; + + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + DBG_ASSERT( !aCurSel.IsInvalid(), "Blinde Selection in EditEngine::PostKeyEvent" ); + + OUString aAutoText( pImpEditEngine->GetAutoCompleteText() ); + if (!pImpEditEngine->GetAutoCompleteText().isEmpty()) + pImpEditEngine->SetAutoCompleteText(OUString(), true); + + sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode(); + KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction(); + if ( eFunc != KeyFuncType::DONTKNOW ) + { + switch ( eFunc ) + { + case KeyFuncType::UNDO: + { + if ( !bReadOnly ) + pEditView->Undo(); + return true; + } + case KeyFuncType::REDO: + { + if ( !bReadOnly ) + pEditView->Redo(); + return true; + } + + default: // is then possible edited below. + eFunc = KeyFuncType::DONTKNOW; + } + } + + if ( eFunc == KeyFuncType::DONTKNOW ) + { + switch ( nCode ) + { +#if defined( DBG_UTIL ) || (OSL_DEBUG_LEVEL > 1) + case KEY_F1: + { + if ( rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() ) + { + sal_Int32 nParas = GetParagraphCount(); + Point aPos; + Point aViewStart( pEditView->GetOutputArea().TopLeft() ); + tools::Long n20 = 40 * pImpEditEngine->nOnePixelInRef; + for ( sal_Int32 n = 0; n < nParas; n++ ) + { + tools::Long nH = GetTextHeight( n ); + Point P1( aViewStart.X() + n20 + n20*(n%2), aViewStart.Y() + aPos.Y() ); + Point P2( P1 ); + P2.AdjustX(n20 ); + P2.AdjustY(nH ); + pEditView->GetWindow()->GetOutDev()->SetLineColor(); + pEditView->GetWindow()->GetOutDev()->SetFillColor( (n%2) ? COL_YELLOW : COL_LIGHTGREEN ); + pEditView->GetWindow()->GetOutDev()->DrawRect( tools::Rectangle( P1, P2 ) ); + aPos.AdjustY(nH ); + } + } + bDone = false; + } + break; + case KEY_F11: + { + if ( rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() ) + { + bDebugPaint = !bDebugPaint; + OStringBuffer aInfo("DebugPaint: "); + aInfo.append(bDebugPaint ? "On" : "Off"); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pEditView->GetWindow()->GetFrameWeld(), + VclMessageType::Info, VclButtonsType::Ok, + OStringToOUString(aInfo.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US))); + xInfoBox->run(); + + } + bDone = false; + } + break; + case KEY_F12: + { + if ( rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() ) + DumpData(this, true); + bDone = false; + } + break; +#endif + case KEY_UP: + case KEY_DOWN: + case KEY_LEFT: + case KEY_RIGHT: + case KEY_HOME: + case KEY_END: + case KEY_PAGEUP: + case KEY_PAGEDOWN: + case css::awt::Key::MOVE_WORD_FORWARD: + case css::awt::Key::SELECT_WORD_FORWARD: + case css::awt::Key::MOVE_WORD_BACKWARD: + case css::awt::Key::SELECT_WORD_BACKWARD: + case css::awt::Key::MOVE_TO_BEGIN_OF_LINE: + case css::awt::Key::MOVE_TO_END_OF_LINE: + case css::awt::Key::SELECT_TO_BEGIN_OF_LINE: + case css::awt::Key::SELECT_TO_END_OF_LINE: + case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH: + case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH: + case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH: + case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH: + case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT: + case css::awt::Key::MOVE_TO_END_OF_DOCUMENT: + case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT: + case css::awt::Key::SELECT_TO_END_OF_DOCUMENT: + { + if ( !rKeyEvent.GetKeyCode().IsMod2() || ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) ) + { + if ( pImpEditEngine->DoVisualCursorTraveling() && ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) /* || ( nCode == KEY_HOME ) || ( nCode == KEY_END ) */ ) ) + bSetCursorFlags = false; // Will be manipulated within visual cursor move + + aCurSel = pImpEditEngine->MoveCursor( rKeyEvent, pEditView ); + + if ( aCurSel.HasRange() ) { + Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection()); + pEditView->pImpEditView->CutCopy( aSelection, false ); + } + + bMoved = true; + if ( nCode == KEY_HOME ) + nNewCursorFlags |= GetCursorFlags::StartOfLine; + else if ( nCode == KEY_END ) + nNewCursorFlags |= GetCursorFlags::EndOfLine; + + } +#if OSL_DEBUG_LEVEL > 1 + GetLanguage( pImpEditEngine->GetEditDoc().GetPos( aCurSel.Max().GetNode() ), aCurSel.Max().GetIndex() ); +#endif + } + break; + case KEY_BACKSPACE: + case KEY_DELETE: + case css::awt::Key::DELETE_WORD_BACKWARD: + case css::awt::Key::DELETE_WORD_FORWARD: + case css::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH: + case css::awt::Key::DELETE_TO_END_OF_PARAGRAPH: + { + if ( !bReadOnly && !rKeyEvent.GetKeyCode().IsMod2() ) + { + // check if we are behind a bullet and using the backspace key + ContentNode *pNode = aCurSel.Min().GetNode(); + const SvxNumberFormat *pFmt = pImpEditEngine->GetNumberFormat( pNode ); + if (pFmt && nCode == KEY_BACKSPACE && + !aCurSel.HasRange() && aCurSel.Min().GetIndex() == 0) + { + // if the bullet is still visible, just make it invisible. + // Otherwise continue as usual. + + + sal_Int32 nPara = pImpEditEngine->GetEditDoc().GetPos( pNode ); + SfxBoolItem aBulletState( pImpEditEngine->GetParaAttrib( nPara, EE_PARA_BULLETSTATE ) ); + + if ( aBulletState.GetValue() ) + { + + aBulletState.SetValue( false ); + SfxItemSet aSet( pImpEditEngine->GetParaAttribs( nPara ) ); + aSet.Put( aBulletState ); + pImpEditEngine->SetParaAttribs( nPara, aSet ); + + // have this and the following paragraphs formatted and repainted. + // (not painting a numbering in the list may cause the following + // numberings to have different numbers than before and thus the + // length may have changed as well ) + pImpEditEngine->FormatAndLayout( pImpEditEngine->GetActiveView() ); + + break; + } + } + + sal_uInt8 nDel = 0; + DeleteMode nMode = DeleteMode::Simple; + switch( nCode ) + { + case css::awt::Key::DELETE_WORD_BACKWARD: + nMode = DeleteMode::RestOfWord; + nDel = DEL_LEFT; + break; + case css::awt::Key::DELETE_WORD_FORWARD: + nMode = DeleteMode::RestOfWord; + nDel = DEL_RIGHT; + break; + case css::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH: + nMode = DeleteMode::RestOfContent; + nDel = DEL_LEFT; + break; + case css::awt::Key::DELETE_TO_END_OF_PARAGRAPH: + nMode = DeleteMode::RestOfContent; + nDel = DEL_RIGHT; + break; + default: + nDel = ( nCode == KEY_DELETE ) ? DEL_RIGHT : DEL_LEFT; + nMode = rKeyEvent.GetKeyCode().IsMod1() ? DeleteMode::RestOfWord : DeleteMode::Simple; + if ( ( nMode == DeleteMode::RestOfWord ) && rKeyEvent.GetKeyCode().IsShift() ) + nMode = DeleteMode::RestOfContent; + break; + } + + pEditView->pImpEditView->DrawSelectionXOR(); + pImpEditEngine->UndoActionStart( EDITUNDO_DELETE ); + aCurSel = pImpEditEngine->DeleteLeftOrRight( aCurSel, nDel, nMode ); + pImpEditEngine->UndoActionEnd(); + bModified = true; + bAllowIdle = false; + } + } + break; + case KEY_TAB: + { + if ( !bReadOnly && !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() ) + { + bool bShift = rKeyEvent.GetKeyCode().IsShift(); + if ( !bShift ) + { + bool bSel = pEditView->HasSelection(); + if ( bSel ) + pImpEditEngine->UndoActionStart( EDITUNDO_INSERT ); + if ( pImpEditEngine->GetStatus().DoAutoCorrect() ) + aCurSel = pImpEditEngine->AutoCorrect( aCurSel, 0, !pEditView->IsInsertMode(), pFrameWin ); + aCurSel = pImpEditEngine->InsertTab( aCurSel ); + if ( bSel ) + pImpEditEngine->UndoActionEnd(); + bModified = true; + } + } + else + bDone = false; + } + break; + case KEY_RETURN: + { + if ( !bReadOnly ) + { + pEditView->pImpEditView->DrawSelectionXOR(); + if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() ) + { + pImpEditEngine->UndoActionStart( EDITUNDO_INSERT ); + if ( rKeyEvent.GetKeyCode().IsShift() ) + { + aCurSel = pImpEditEngine->AutoCorrect( aCurSel, 0, !pEditView->IsInsertMode(), pFrameWin ); + aCurSel = pImpEditEngine->InsertLineBreak( aCurSel ); + } + else + { + if (aAutoText.isEmpty()) + { + if ( pImpEditEngine->GetStatus().DoAutoCorrect() ) + aCurSel = pImpEditEngine->AutoCorrect( aCurSel, 0, !pEditView->IsInsertMode(), pFrameWin ); + aCurSel = pImpEditEngine->InsertParaBreak( aCurSel ); + } + else + { + DBG_ASSERT( !aCurSel.HasRange(), "Selection on complete?!" ); + EditPaM aStart( pImpEditEngine->WordLeft( aCurSel.Max() ) ); + aCurSel = pImpEditEngine->InsertText( + EditSelection( aStart, aCurSel.Max() ), aAutoText ); + pImpEditEngine->SetAutoCompleteText( OUString(), true ); + } + } + pImpEditEngine->UndoActionEnd(); + bModified = true; + } + } + } + break; + case KEY_INSERT: + { + if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() ) + pEditView->SetInsertMode( !pEditView->IsInsertMode() ); + } + break; + default: + { + #if (OSL_DEBUG_LEVEL > 1) && defined(DBG_UTIL) + if ( ( nCode == KEY_W ) && rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() ) + { + SfxItemSet aAttribs = pEditView->GetAttribs(); + const SvxFrameDirectionItem& rCurrentWritingMode = (const SvxFrameDirectionItem&)aAttribs.Get( EE_PARA_WRITINGDIR ); + SvxFrameDirectionItem aNewItem( SvxFrameDirection::Horizontal_LR_TB, EE_PARA_WRITINGDIR ); + if ( rCurrentWritingMode.GetValue() != SvxFrameDirection::Horizontal_RL_TB ) + aNewItem.SetValue( SvxFrameDirection::Horizontal_RL_TB ); + aAttribs.Put( aNewItem ); + pEditView->SetAttribs( aAttribs ); + } + #endif + if ( !bReadOnly && IsSimpleCharInput( rKeyEvent ) ) + { + sal_Unicode nCharCode = rKeyEvent.GetCharCode(); + pEditView->pImpEditView->DrawSelectionXOR(); + // Autocorrection? + if ( ( pImpEditEngine->GetStatus().DoAutoCorrect() ) && + ( SvxAutoCorrect::IsAutoCorrectChar( nCharCode ) || + pImpEditEngine->IsNbspRunNext() ) ) + { + aCurSel = pImpEditEngine->AutoCorrect( + aCurSel, nCharCode, !pEditView->IsInsertMode(), pFrameWin ); + } + else + { + aCurSel = pImpEditEngine->InsertTextUserInput( aCurSel, nCharCode, !pEditView->IsInsertMode() ); + } + // AutoComplete ??? + if ( pImpEditEngine->GetStatus().DoAutoComplete() && ( nCharCode != ' ' ) ) + { + // Only at end of word... + sal_Int32 nIndex = aCurSel.Max().GetIndex(); + if ( ( nIndex >= aCurSel.Max().GetNode()->Len() ) || + ( pImpEditEngine->aWordDelimiters.indexOf( aCurSel.Max().GetNode()->GetChar( nIndex ) ) != -1 ) ) + { + EditPaM aStart( pImpEditEngine->WordLeft( aCurSel.Max() ) ); + OUString aWord = pImpEditEngine->GetSelected( EditSelection( aStart, aCurSel.Max() ) ); + if ( aWord.getLength() >= 3 ) + { + OUString aComplete; + + LanguageType eLang = pImpEditEngine->GetLanguage( EditPaM( aStart.GetNode(), aStart.GetIndex()+1)).nLang; + LanguageTag aLanguageTag( eLang); + + if (!pImpEditEngine->xLocaleDataWrapper.isInitialized()) + pImpEditEngine->xLocaleDataWrapper.init( SvtSysLocale().GetLocaleData().getComponentContext(), aLanguageTag); + else + pImpEditEngine->xLocaleDataWrapper.changeLocale( aLanguageTag); + + if (!pImpEditEngine->xTransliterationWrapper.isInitialized()) + pImpEditEngine->xTransliterationWrapper.init( SvtSysLocale().GetLocaleData().getComponentContext(), eLang); + else + pImpEditEngine->xTransliterationWrapper.changeLocale( eLang); + + const ::utl::TransliterationWrapper* pTransliteration = pImpEditEngine->xTransliterationWrapper.get(); + Sequence< i18n::CalendarItem2 > xItem = pImpEditEngine->xLocaleDataWrapper->getDefaultCalendarDays(); + sal_Int32 nCount = xItem.getLength(); + const i18n::CalendarItem2* pArr = xItem.getConstArray(); + for( sal_Int32 n = 0; n <= nCount; ++n ) + { + const OUString& rDay = pArr[n].FullName; + if( pTransliteration->isMatch( aWord, rDay) ) + { + aComplete = rDay; + break; + } + } + + if ( aComplete.isEmpty() ) + { + xItem = pImpEditEngine->xLocaleDataWrapper->getDefaultCalendarMonths(); + sal_Int32 nMonthCount = xItem.getLength(); + const i18n::CalendarItem2* pMonthArr = xItem.getConstArray(); + for( sal_Int32 n = 0; n <= nMonthCount; ++n ) + { + const OUString& rMon = pMonthArr[n].FullName; + if( pTransliteration->isMatch( aWord, rMon) ) + { + aComplete = rMon; + break; + } + } + } + + if( !aComplete.isEmpty() && ( ( aWord.getLength() + 1 ) < aComplete.getLength() ) ) + { + pImpEditEngine->SetAutoCompleteText( aComplete, false ); + Point aPos = pImpEditEngine->PaMtoEditCursor( aCurSel.Max() ).TopLeft(); + aPos = pEditView->pImpEditView->GetWindowPos( aPos ); + aPos = pEditView->pImpEditView->GetWindow()->LogicToPixel( aPos ); + aPos = pEditView->GetWindow()->OutputToScreenPixel( aPos ); + aPos.AdjustY( -3 ); + Help::ShowQuickHelp( pEditView->GetWindow(), tools::Rectangle( aPos, Size( 1, 1 ) ), aComplete, QuickHelpFlags::Bottom|QuickHelpFlags::Left ); + } + } + } + } + bModified = true; + } + else + bDone = false; + } + } + } + + pEditView->pImpEditView->SetEditSelection( aCurSel ); + if (comphelper::LibreOfficeKit::isActive()) + { + pEditView->pImpEditView->DrawSelectionXOR(); + } + pImpEditEngine->UpdateSelections(); + + if ( ( !IsEffectivelyVertical() && ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) ) || + ( IsEffectivelyVertical() && ( nCode != KEY_LEFT ) && ( nCode != KEY_RIGHT ) )) + { + pEditView->pImpEditView->nTravelXPos = TRAVEL_X_DONTKNOW; + } + + if ( /* ( nCode != KEY_HOME ) && ( nCode != KEY_END ) && */ + ( !IsEffectivelyVertical() && ( nCode != KEY_LEFT ) && ( nCode != KEY_RIGHT ) ) || + ( IsEffectivelyVertical() && ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) )) + { + pEditView->pImpEditView->SetCursorBidiLevel( CURSOR_BIDILEVEL_DONTKNOW ); + } + + if ( bSetCursorFlags ) + pEditView->pImpEditView->nExtraCursorFlags = nNewCursorFlags; + + if ( bModified ) + { + DBG_ASSERT( !bReadOnly, "ReadOnly but modified???" ); + // Idle-Formatter only when AnyInput. + if ( bAllowIdle && pImpEditEngine->GetStatus().UseIdleFormatter() + && Application::AnyInput( VclInputFlags::KEYBOARD) ) + pImpEditEngine->IdleFormatAndLayout( pEditView ); + else + pImpEditEngine->FormatAndLayout( pEditView ); + } + else if ( bMoved ) + { + bool bGotoCursor = pEditView->pImpEditView->DoAutoScroll(); + pEditView->pImpEditView->ShowCursor( bGotoCursor, true ); + pImpEditEngine->CallStatusHdl(); + } + + return bDone; +} + +sal_uInt32 EditEngine::GetTextHeight() const +{ + + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + sal_uInt32 nHeight = !IsEffectivelyVertical() ? pImpEditEngine->GetTextHeight() : pImpEditEngine->CalcTextWidth( true ); + return nHeight; +} + +sal_uInt32 EditEngine::GetTextHeightNTP() const +{ + + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + if ( IsEffectivelyVertical() ) + return pImpEditEngine->CalcTextWidth( true ); + + return pImpEditEngine->GetTextHeightNTP(); +} + +sal_uInt32 EditEngine::CalcTextWidth() +{ + + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + sal_uInt32 nWidth = !IsEffectivelyVertical() ? pImpEditEngine->CalcTextWidth( true ) : pImpEditEngine->GetTextHeight(); + return nWidth; +} + +bool EditEngine::SetUpdateLayout(bool bUpdate, bool bRestoring) +{ + bool bPrevUpdateLayout = pImpEditEngine->SetUpdateLayout( bUpdate ); + if (pImpEditEngine->pActiveView) + { + // Not an activation if we are restoring the previous update mode. + pImpEditEngine->pActiveView->ShowCursor(false, false, /*bActivate=*/!bRestoring); + } + return bPrevUpdateLayout; +} + +bool EditEngine::IsUpdateLayout() const +{ + return pImpEditEngine->IsUpdateLayout(); +} + +void EditEngine::Clear() +{ + pImpEditEngine->Clear(); +} + +void EditEngine::SetText( const OUString& rText ) +{ + pImpEditEngine->SetText( rText ); + if ( !rText.isEmpty() && pImpEditEngine->IsUpdateLayout() ) + pImpEditEngine->FormatAndLayout(); +} + +ErrCode EditEngine::Read( SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs /* = NULL */ ) +{ + bool bUndoEnabled = pImpEditEngine->IsUndoEnabled(); + pImpEditEngine->EnableUndo( false ); + pImpEditEngine->SetText( OUString() ); + EditPaM aPaM( pImpEditEngine->GetEditDoc().GetStartPaM() ); + pImpEditEngine->Read( rInput, rBaseURL, eFormat, EditSelection( aPaM, aPaM ), pHTTPHeaderAttrs ); + pImpEditEngine->EnableUndo( bUndoEnabled ); + return rInput.GetError(); +} + +void EditEngine::Write( SvStream& rOutput, EETextFormat eFormat ) +{ + EditPaM aStartPaM( pImpEditEngine->GetEditDoc().GetStartPaM() ); + EditPaM aEndPaM( pImpEditEngine->GetEditDoc().GetEndPaM() ); + pImpEditEngine->Write( rOutput, eFormat, EditSelection( aStartPaM, aEndPaM ) ); +} + +std::unique_ptr<EditTextObject> EditEngine::CreateTextObject() +{ + return pImpEditEngine->CreateTextObject(); +} + +std::unique_ptr<EditTextObject> EditEngine::CreateTextObject( const ESelection& rESelection ) +{ + EditSelection aSel( pImpEditEngine->CreateSel( rESelection ) ); + return pImpEditEngine->CreateTextObject( aSel ); +} + +std::unique_ptr<EditTextObject> EditEngine::GetEmptyTextObject() const +{ + return pImpEditEngine->GetEmptyTextObject(); +} + + +void EditEngine::SetText( const EditTextObject& rTextObject ) +{ + pImpEditEngine->SetText( rTextObject ); + pImpEditEngine->FormatAndLayout(); +} + +void EditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow ) +{ + pImpEditEngine->ShowParagraph( nParagraph, bShow ); +} + +void EditEngine::SetNotifyHdl( const Link<EENotify&,void>& rLink ) +{ + pImpEditEngine->SetNotifyHdl( rLink ); +} + +Link<EENotify&,void> const & EditEngine::GetNotifyHdl() const +{ + return pImpEditEngine->GetNotifyHdl(); +} + +void EditEngine::SetStatusEventHdl( const Link<EditStatus&, void>& rLink ) +{ + pImpEditEngine->SetStatusEventHdl( rLink ); +} + +Link<EditStatus&, void> const & EditEngine::GetStatusEventHdl() const +{ + return pImpEditEngine->GetStatusEventHdl(); +} + +void EditEngine::SetHtmlImportHdl( const Link<HtmlImportInfo&,void>& rLink ) +{ + pImpEditEngine->aHtmlImportHdl = rLink; +} + +const Link<HtmlImportInfo&,void>& EditEngine::GetHtmlImportHdl() const +{ + return pImpEditEngine->aHtmlImportHdl; +} + +void EditEngine::SetRtfImportHdl( const Link<RtfImportInfo&,void>& rLink ) +{ + pImpEditEngine->aRtfImportHdl = rLink; +} + +const Link<RtfImportInfo&,void>& EditEngine::GetRtfImportHdl() const +{ + return pImpEditEngine->aRtfImportHdl; +} + +void EditEngine::SetBeginMovingParagraphsHdl( const Link<MoveParagraphsInfo&,void>& rLink ) +{ + pImpEditEngine->aBeginMovingParagraphsHdl = rLink; +} + +void EditEngine::SetEndMovingParagraphsHdl( const Link<MoveParagraphsInfo&,void>& rLink ) +{ + pImpEditEngine->aEndMovingParagraphsHdl = rLink; +} + +void EditEngine::SetBeginPasteOrDropHdl( const Link<PasteOrDropInfos&,void>& rLink ) +{ + + pImpEditEngine->aBeginPasteOrDropHdl = rLink; +} + +void EditEngine::SetEndPasteOrDropHdl( const Link<PasteOrDropInfos&,void>& rLink ) +{ + pImpEditEngine->aEndPasteOrDropHdl = rLink; +} + +std::unique_ptr<EditTextObject> EditEngine::CreateTextObject( sal_Int32 nPara, sal_Int32 nParas ) +{ + DBG_ASSERT( 0 <= nPara && nPara < pImpEditEngine->GetEditDoc().Count(), "CreateTextObject: Startpara out of Range" ); + DBG_ASSERT( nParas <= pImpEditEngine->GetEditDoc().Count() - nPara, "CreateTextObject: Endpara out of Range" ); + + ContentNode* pStartNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + ContentNode* pEndNode = pImpEditEngine->GetEditDoc().GetObject( nPara+nParas-1 ); + DBG_ASSERT( pStartNode, "Start-Paragraph does not exist: CreateTextObject" ); + DBG_ASSERT( pEndNode, "End-Paragraph does not exist: CreateTextObject" ); + + if ( pStartNode && pEndNode ) + { + EditSelection aTmpSel; + aTmpSel.Min() = EditPaM( pStartNode, 0 ); + aTmpSel.Max() = EditPaM( pEndNode, pEndNode->Len() ); + return pImpEditEngine->CreateTextObject( aTmpSel ); + } + return nullptr; +} + +void EditEngine::RemoveParagraph( sal_Int32 nPara ) +{ + DBG_ASSERT( pImpEditEngine->GetEditDoc().Count() > 1, "The first paragraph should not be deleted!" ); + if( pImpEditEngine->GetEditDoc().Count() <= 1 ) + return; + + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + const ParaPortion* pPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara ); + DBG_ASSERT( pPortion && pNode, "Paragraph not found: RemoveParagraph" ); + if ( pNode && pPortion ) + { + // No Undo encapsulation needed. + pImpEditEngine->ImpRemoveParagraph( nPara ); + pImpEditEngine->InvalidateFromParagraph( nPara ); + pImpEditEngine->UpdateSelections(); + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); + } +} + +sal_Int32 EditEngine::GetTextLen( sal_Int32 nPara ) const +{ + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + DBG_ASSERT( pNode, "Paragraph not found: GetTextLen" ); + if ( pNode ) + return pNode->Len(); + return 0; +} + +OUString EditEngine::GetText( sal_Int32 nPara ) const +{ + OUString aStr; + if ( 0 <= nPara && nPara < pImpEditEngine->GetEditDoc().Count() ) + aStr = pImpEditEngine->GetEditDoc().GetParaAsString( nPara ); + return aStr; +} + +void EditEngine::SetModifyHdl( const Link<LinkParamNone*,void>& rLink ) +{ + pImpEditEngine->SetModifyHdl( rLink ); +} + +Link<LinkParamNone*,void> const & EditEngine::GetModifyHdl() const +{ + return pImpEditEngine->GetModifyHdl(); +} + + +void EditEngine::ClearModifyFlag() +{ + pImpEditEngine->SetModifyFlag( false ); +} + +void EditEngine::SetModified() +{ + pImpEditEngine->SetModifyFlag( true ); +} + +bool EditEngine::IsModified() const +{ + return pImpEditEngine->IsModified(); +} + +bool EditEngine::IsInSelectionMode() const +{ + return ( pImpEditEngine->IsInSelectionMode() || + pImpEditEngine->GetSelEngine().IsInSelection() ); +} + +void EditEngine::InsertParagraph( sal_Int32 nPara, const EditTextObject& rTxtObj, bool bAppend ) +{ + if ( nPara > GetParagraphCount() ) + { + SAL_WARN_IF( nPara != EE_PARA_APPEND, "editeng", "Paragraph number too large, but not EE_PARA_APPEND!" ); + nPara = GetParagraphCount(); + } + + pImpEditEngine->UndoActionStart( EDITUNDO_INSERT ); + + // No Undo compounding needed. + EditPaM aPaM( pImpEditEngine->InsertParagraph( nPara ) ); + // When InsertParagraph from the outside, no hard attributes + // should be taken over! + pImpEditEngine->RemoveCharAttribs( nPara ); + pImpEditEngine->InsertText( rTxtObj, EditSelection( aPaM, aPaM ) ); + + if ( bAppend && nPara ) + pImpEditEngine->ConnectContents( nPara-1, /*bBackwards=*/false ); + + pImpEditEngine->UndoActionEnd(); + + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); +} + +void EditEngine::InsertParagraph(sal_Int32 nPara, const OUString& rTxt) +{ + if ( nPara > GetParagraphCount() ) + { + SAL_WARN_IF( nPara != EE_PARA_APPEND, "editeng", "Paragraph number too large, but not EE_PARA_APPEND!" ); + nPara = GetParagraphCount(); + } + + pImpEditEngine->UndoActionStart( EDITUNDO_INSERT ); + EditPaM aPaM( pImpEditEngine->InsertParagraph( nPara ) ); + // When InsertParagraph from the outside, no hard attributes + // should be taken over! + pImpEditEngine->RemoveCharAttribs( nPara ); + pImpEditEngine->UndoActionEnd(); + pImpEditEngine->ImpInsertText( EditSelection( aPaM, aPaM ), rTxt ); + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); +} + +void EditEngine::SetText(sal_Int32 nPara, const OUString& rTxt) +{ + std::optional<EditSelection> pSel = pImpEditEngine->SelectParagraph( nPara ); + if ( pSel ) + { + pImpEditEngine->UndoActionStart( EDITUNDO_INSERT ); + pImpEditEngine->ImpInsertText( *pSel, rTxt ); + pImpEditEngine->UndoActionEnd(); + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); + } +} + +void EditEngine::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + pImpEditEngine->SetParaAttribs( nPara, rSet ); + if ( pImpEditEngine->IsUpdateLayout() ) + pImpEditEngine->FormatAndLayout(); +} + +const SfxItemSet& EditEngine::GetParaAttribs( sal_Int32 nPara ) const +{ + return pImpEditEngine->GetParaAttribs( nPara ); +} + +bool EditEngine::HasParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + return pImpEditEngine->HasParaAttrib( nPara, nWhich ); +} + +const SfxPoolItem& EditEngine::GetParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + return pImpEditEngine->GetParaAttrib( nPara, nWhich ); +} + +void EditEngine::SetCharAttribs(sal_Int32 nPara, const SfxItemSet& rSet) +{ + EditSelection aSel(pImpEditEngine->ConvertSelection(nPara, 0, nPara, GetTextLen(nPara))); + // This is called by sd::View::OnBeginPasteOrDrop(), updating the cursor position on undo is not + // wanted. + pImpEditEngine->SetAttribs(aSel, rSet, /*nSpecial=*/SetAttribsMode::NONE, /*bSetSelection=*/false); + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); +} + +void EditEngine::GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const +{ + pImpEditEngine->GetCharAttribs( nPara, rLst ); +} + +SfxItemSet EditEngine::GetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib ) +{ + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + return pImpEditEngine->GetAttribs( aSel, nOnlyHardAttrib ); +} + +SfxItemSet EditEngine::GetAttribs( sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, GetAttribsFlags nFlags ) const +{ + return pImpEditEngine->GetAttribs( nPara, nStart, nEnd, nFlags ); +} + +void EditEngine::RemoveAttribs( const ESelection& rSelection, bool bRemoveParaAttribs, sal_uInt16 nWhich ) +{ + const EERemoveParaAttribsMode eMode = bRemoveParaAttribs? + EERemoveParaAttribsMode::RemoveAll : + EERemoveParaAttribsMode::RemoveCharItems; + + pImpEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS ); + EditSelection aSel( pImpEditEngine->ConvertSelection( rSelection.nStartPara, rSelection.nStartPos, rSelection.nEndPara, rSelection.nEndPos ) ); + pImpEditEngine->RemoveCharAttribs( aSel, eMode, nWhich ); + pImpEditEngine->UndoActionEnd(); + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); +} + +vcl::Font EditEngine::GetStandardFont( sal_Int32 nPara ) +{ + return GetStandardSvxFont( nPara ); +} + +SvxFont EditEngine::GetStandardSvxFont( sal_Int32 nPara ) +{ + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + return pNode->GetCharAttribs().GetDefFont(); +} + +void EditEngine::StripPortions() +{ + ScopedVclPtrInstance< VirtualDevice > aTmpDev; + tools::Rectangle aBigRect( Point( 0, 0 ), Size( 0x7FFFFFFF, 0x7FFFFFFF ) ); + if ( IsEffectivelyVertical() ) + { + if( IsTopToBottom() ) + { + aBigRect.SetRight( 0 ); + aBigRect.SetLeft( -0x7FFFFFFF ); + } + else + { + aBigRect.SetTop( -0x7FFFFFFF ); + aBigRect.SetBottom( 0 ); + } + } + pImpEditEngine->Paint(*aTmpDev, aBigRect, Point(), true); +} + +void EditEngine::GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList ) +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatFullDoc(); + + const ParaPortion* pParaPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara ); + if ( pParaPortion ) + { + sal_Int32 nEnd = 0; + sal_Int32 nTextPortions = pParaPortion->GetTextPortions().Count(); + for ( sal_Int32 n = 0; n < nTextPortions; n++ ) + { + nEnd = nEnd + pParaPortion->GetTextPortions()[n].GetLen(); + rList.push_back( nEnd ); + } + } +} + +void EditEngine::SetFlatMode( bool bFlat) +{ + pImpEditEngine->SetFlatMode( bFlat ); +} + +bool EditEngine::IsFlatMode() const +{ + return !( pImpEditEngine->aStatus.UseCharAttribs() ); +} + +void EditEngine::SetControlWord( EEControlBits nWord ) +{ + + if ( nWord == pImpEditEngine->aStatus.GetControlWord() ) + return; + + EEControlBits nPrev = pImpEditEngine->aStatus.GetControlWord(); + pImpEditEngine->aStatus.GetControlWord() = nWord; + + EEControlBits nChanges = nPrev ^ nWord; + if ( pImpEditEngine->IsFormatted() ) + { + // possibly reformat: + if ( ( nChanges & EEControlBits::USECHARATTRIBS ) || + ( nChanges & EEControlBits::ONECHARPERLINE ) || + ( nChanges & EEControlBits::STRETCHING ) || + ( nChanges & EEControlBits::OUTLINER ) || + ( nChanges & EEControlBits::NOCOLORS ) || + ( nChanges & EEControlBits::OUTLINER2 ) ) + { + if ( nChanges & EEControlBits::USECHARATTRIBS ) + { + pImpEditEngine->GetEditDoc().CreateDefFont( true ); + } + + pImpEditEngine->FormatFullDoc(); + pImpEditEngine->UpdateViews( pImpEditEngine->GetActiveView() ); + } + } + + bool bSpellingChanged = bool(nChanges & EEControlBits::ONLINESPELLING); + + if ( !bSpellingChanged ) + return; + + pImpEditEngine->StopOnlineSpellTimer(); + if (nWord & EEControlBits::ONLINESPELLING) + { + // Create WrongList, start timer... + sal_Int32 nNodes = pImpEditEngine->GetEditDoc().Count(); + for ( sal_Int32 n = 0; n < nNodes; n++ ) + { + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( n ); + pNode->CreateWrongList(); + } + if (pImpEditEngine->IsFormatted()) + pImpEditEngine->StartOnlineSpellTimer(); + } + else + { + tools::Long nY = 0; + sal_Int32 nNodes = pImpEditEngine->GetEditDoc().Count(); + for ( sal_Int32 n = 0; n < nNodes; n++ ) + { + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( n ); + const ParaPortion* pPortion = pImpEditEngine->GetParaPortions()[n]; + bool bWrongs = false; + if (pNode->GetWrongList() != nullptr) + bWrongs = !pNode->GetWrongList()->empty(); + pNode->DestroyWrongList(); + if ( bWrongs ) + { + pImpEditEngine->aInvalidRect.SetLeft( 0 ); + pImpEditEngine->aInvalidRect.SetRight( pImpEditEngine->GetPaperSize().Width() ); + pImpEditEngine->aInvalidRect.SetTop( nY+1 ); + pImpEditEngine->aInvalidRect.SetBottom( nY+pPortion->GetHeight()-1 ); + pImpEditEngine->UpdateViews( pImpEditEngine->pActiveView ); + } + nY += pPortion->GetHeight(); + } + } +} + +EEControlBits EditEngine::GetControlWord() const +{ + return pImpEditEngine->aStatus.GetControlWord(); +} + +tools::Long EditEngine::GetFirstLineStartX( sal_Int32 nParagraph ) +{ + + tools::Long nX = 0; + const ParaPortion* pPPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nParagraph ); + if ( pPPortion ) + { + DBG_ASSERT( pImpEditEngine->IsFormatted() || !pImpEditEngine->IsFormatting(), "GetFirstLineStartX: Doc not formatted - unable to format!" ); + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + const EditLine& rFirstLine = pPPortion->GetLines()[0]; + nX = rFirstLine.GetStartPosX(); + } + return nX; +} + +Point EditEngine::GetDocPos( const Point& rPaperPos ) const +{ + Point aDocPos( rPaperPos ); + if ( IsEffectivelyVertical() ) + { + if ( IsTopToBottom() ) + { + aDocPos.setX( rPaperPos.Y() ); + aDocPos.setY( GetPaperSize().Width() - rPaperPos.X() ); + } + else + { + aDocPos.setX( rPaperPos.Y() ); + aDocPos.setY( rPaperPos.X() ); + } + } + return aDocPos; +} + +Point EditEngine::GetDocPosTopLeft( sal_Int32 nParagraph ) +{ + const ParaPortion* pPPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nParagraph ); + DBG_ASSERT( pPPortion, "Paragraph not found: GetWindowPosTopLeft" ); + Point aPoint; + if ( pPPortion ) + { + + // If someone calls GetLineHeight() with an empty Engine. + DBG_ASSERT( pImpEditEngine->IsFormatted() || !pImpEditEngine->IsFormatting(), "GetDocPosTopLeft: Doc not formatted - unable to format!" ); + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatAndLayout(); + if ( pPPortion->GetLines().Count() ) + { + // Correct it if large Bullet. + const EditLine& rFirstLine = pPPortion->GetLines()[0]; + aPoint.setX( rFirstLine.GetStartPosX() ); + } + else + { + const SvxLRSpaceItem& rLRItem = pImpEditEngine->GetLRSpaceItem( pPPortion->GetNode() ); +// TL_NF_LR aPoint.X() = pImpEditEngine->GetXValue( (short)(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset()) ); + sal_Int32 nSpaceBefore = 0; + pImpEditEngine->GetSpaceBeforeAndMinLabelWidth( pPPortion->GetNode(), &nSpaceBefore ); + short nX = static_cast<short>(rLRItem.GetTextLeft() + + rLRItem.GetTextFirstLineOffset() + + nSpaceBefore); + aPoint.setX( pImpEditEngine->GetXValue( nX + ) ); + } + aPoint.setY( pImpEditEngine->GetParaPortions().GetYOffset( pPPortion ) ); + } + return aPoint; +} + +const SvxNumberFormat* EditEngine::GetNumberFormat( sal_Int32 ) const +{ + // derived objects may override this function to give access to + // bullet information (see Outliner) + return nullptr; +} + +bool EditEngine::IsRightToLeft( sal_Int32 nPara ) const +{ + return pImpEditEngine->IsRightToLeft( nPara ); +} + +bool EditEngine::IsTextPos( const Point& rPaperPos, sal_uInt16 nBorder ) +{ + + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + // take unrotated positions for calculation here + Point aDocPos = GetDocPos( rPaperPos ); + + if ( ( aDocPos.Y() > 0 ) && ( o3tl::make_unsigned(aDocPos.Y()) < pImpEditEngine->GetTextHeight() ) ) + return pImpEditEngine->IsTextPos(aDocPos, nBorder); + return false; +} + +void EditEngine::SetEditTextObjectPool( SfxItemPool* pPool ) +{ + pImpEditEngine->SetEditTextObjectPool( pPool ); +} + +SfxItemPool* EditEngine::GetEditTextObjectPool() const +{ + return pImpEditEngine->GetEditTextObjectPool(); +} + +void EditEngine::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) +{ + + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + + pImpEditEngine->SetAttribs( aSel, rSet ); +} + +void EditEngine::QuickMarkInvalid( const ESelection& rSel ) +{ + DBG_ASSERT( rSel.nStartPara < pImpEditEngine->GetEditDoc().Count(), "MarkInvalid: Start out of Range!" ); + DBG_ASSERT( rSel.nEndPara < pImpEditEngine->GetEditDoc().Count(), "MarkInvalid: End out of Range!" ); + for ( sal_Int32 nPara = rSel.nStartPara; nPara <= rSel.nEndPara; nPara++ ) + { + ParaPortion* pPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara ); + if ( pPortion ) + pPortion->MarkSelectionInvalid( 0 ); + } +} + +void EditEngine::QuickInsertText(const OUString& rText, const ESelection& rSel) +{ + + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + + pImpEditEngine->ImpInsertText( aSel, rText ); +} + +void EditEngine::QuickDelete( const ESelection& rSel ) +{ + + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + + pImpEditEngine->ImpDeleteSelection( aSel ); +} + +void EditEngine::QuickMarkToBeRepainted( sal_Int32 nPara ) +{ + ParaPortion* pPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara ); + if ( pPortion ) + pPortion->SetMustRepaint( true ); +} + +void EditEngine::QuickInsertLineBreak( const ESelection& rSel ) +{ + + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + + pImpEditEngine->InsertLineBreak( aSel ); +} + +void EditEngine::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) +{ + + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + + pImpEditEngine->ImpInsertFeature( aSel, rFld ); +} + +void EditEngine::QuickFormatDoc( bool bFull ) +{ + if ( bFull ) + pImpEditEngine->FormatFullDoc(); + else + pImpEditEngine->FormatDoc(); + + // Don't pass active view, maybe selection is not updated yet... + pImpEditEngine->UpdateViews(); +} + +void EditEngine::SetStyleSheet(const EditSelection& aSel, SfxStyleSheet* pStyle) +{ + pImpEditEngine->SetStyleSheet(aSel, pStyle); +} + +void EditEngine::SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle ) +{ + pImpEditEngine->SetStyleSheet( nPara, pStyle ); +} + +const SfxStyleSheet* EditEngine::GetStyleSheet( sal_Int32 nPara ) const +{ + return pImpEditEngine->GetStyleSheet( nPara ); +} + +SfxStyleSheet* EditEngine::GetStyleSheet( sal_Int32 nPara ) +{ + return pImpEditEngine->GetStyleSheet( nPara ); +} + +void EditEngine::SetStyleSheetPool( SfxStyleSheetPool* pSPool ) +{ + pImpEditEngine->SetStyleSheetPool( pSPool ); +} + +SfxStyleSheetPool* EditEngine::GetStyleSheetPool() +{ + return pImpEditEngine->GetStyleSheetPool(); +} + +void EditEngine::SetWordDelimiters( const OUString& rDelimiters ) +{ + pImpEditEngine->aWordDelimiters = rDelimiters; + if (pImpEditEngine->aWordDelimiters.indexOf(CH_FEATURE) == -1) + pImpEditEngine->aWordDelimiters += OUStringChar(CH_FEATURE); +} + +const OUString& EditEngine::GetWordDelimiters() const +{ + return pImpEditEngine->aWordDelimiters; +} + +void EditEngine::EraseVirtualDevice() +{ + pImpEditEngine->EraseVirtualDevice(); +} + +void EditEngine::SetSpeller( Reference< XSpellChecker1 > const &xSpeller ) +{ + pImpEditEngine->SetSpeller( xSpeller ); +} + +Reference< XSpellChecker1 > const & EditEngine::GetSpeller() +{ + return pImpEditEngine->GetSpeller(); +} + +void EditEngine::SetHyphenator( Reference< XHyphenator > const & xHyph ) +{ + pImpEditEngine->SetHyphenator( xHyph ); +} + +void EditEngine::GetAllMisspellRanges( std::vector<editeng::MisspellRanges>& rRanges ) const +{ + pImpEditEngine->GetAllMisspellRanges(rRanges); +} + +void EditEngine::SetAllMisspellRanges( const std::vector<editeng::MisspellRanges>& rRanges ) +{ + pImpEditEngine->SetAllMisspellRanges(rRanges); +} + +void EditEngine::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars) +{ + ImpEditEngine::SetForbiddenCharsTable( xForbiddenChars ); +} + +void EditEngine::SetDefaultLanguage( LanguageType eLang ) +{ + pImpEditEngine->SetDefaultLanguage( eLang ); +} + +LanguageType EditEngine::GetDefaultLanguage() const +{ + return pImpEditEngine->GetDefaultLanguage(); +} + +bool EditEngine::SpellNextDocument() +{ + return false; +} + +EESpellState EditEngine::HasSpellErrors() +{ + if ( !pImpEditEngine->GetSpeller().is() ) + return EESpellState::NoSpeller; + + return pImpEditEngine->HasSpellErrors(); +} + +void EditEngine::ClearSpellErrors() +{ + pImpEditEngine->ClearSpellErrors(); +} + +bool EditEngine::SpellSentence(EditView const & rView, svx::SpellPortions& rToFill ) +{ + return pImpEditEngine->SpellSentence( rView, rToFill ); +} + +void EditEngine::PutSpellingToSentenceStart( EditView const & rEditView ) +{ + pImpEditEngine->PutSpellingToSentenceStart( rEditView ); +} + +void EditEngine::ApplyChangedSentence(EditView const & rEditView, const svx::SpellPortions& rNewPortions, bool bRecheck ) +{ + pImpEditEngine->ApplyChangedSentence( rEditView, rNewPortions, bRecheck ); +} + +bool EditEngine::HasConvertibleTextPortion( LanguageType nLang ) +{ + return pImpEditEngine->HasConvertibleTextPortion( nLang ); +} + +bool EditEngine::ConvertNextDocument() +{ + return false; +} + +bool EditEngine::HasText( const SvxSearchItem& rSearchItem ) +{ + return pImpEditEngine->HasText( rSearchItem ); +} + +void EditEngine::SetGlobalCharStretching( sal_uInt16 nX, sal_uInt16 nY ) +{ + pImpEditEngine->SetCharStretching( nX, nY ); +} + +void EditEngine::GetGlobalCharStretching( sal_uInt16& rX, sal_uInt16& rY ) const +{ + pImpEditEngine->GetCharStretching( rX, rY ); +} + +bool EditEngine::ShouldCreateBigTextObject() const +{ + sal_Int32 nTextPortions = 0; + sal_Int32 nParas = pImpEditEngine->GetEditDoc().Count(); + for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) + { + ParaPortion* pParaPortion = pImpEditEngine->GetParaPortions()[nPara]; + nTextPortions = nTextPortions + pParaPortion->GetTextPortions().Count(); + } + return nTextPortions >= pImpEditEngine->GetBigTextObjectStart(); +} + +sal_uInt16 EditEngine::GetFieldCount( sal_Int32 nPara ) const +{ + sal_uInt16 nFields = 0; + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + if ( pNode ) + { + for (auto const& attrib : pNode->GetCharAttribs().GetAttribs()) + { + if (attrib->Which() == EE_FEATURE_FIELD) + ++nFields; + } + } + + return nFields; +} + +EFieldInfo EditEngine::GetFieldInfo( sal_Int32 nPara, sal_uInt16 nField ) const +{ + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + if ( pNode ) + { + sal_uInt16 nCurrentField = 0; + for (auto const& attrib : pNode->GetCharAttribs().GetAttribs()) + { + const EditCharAttrib& rAttr = *attrib; + if (rAttr.Which() == EE_FEATURE_FIELD) + { + if ( nCurrentField == nField ) + { + const SvxFieldItem* p = static_cast<const SvxFieldItem*>(rAttr.GetItem()); + EFieldInfo aInfo(*p, nPara, rAttr.GetStart()); + aInfo.aCurrentText = static_cast<const EditCharAttribField&>(rAttr).GetFieldValue(); + return aInfo; + } + + ++nCurrentField; + } + } + } + return EFieldInfo(); +} + + +bool EditEngine::UpdateFields() +{ + bool bChanges = pImpEditEngine->UpdateFields(); + if ( bChanges && pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); + return bChanges; +} + +bool EditEngine::UpdateFieldsOnly() +{ + return pImpEditEngine->UpdateFields(); +} + +void EditEngine::RemoveFields( const std::function<bool ( const SvxFieldData* )>& isFieldData ) +{ + pImpEditEngine->UpdateFields(); + + sal_Int32 nParas = pImpEditEngine->GetEditDoc().Count(); + for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) + { + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); + for (size_t nAttr = rAttrs.size(); nAttr; ) + { + const EditCharAttrib& rAttr = *rAttrs[--nAttr]; + if (rAttr.Which() == EE_FEATURE_FIELD) + { + const SvxFieldData* pFldData = static_cast<const SvxFieldItem*>(rAttr.GetItem())->GetField(); + if ( pFldData && ( isFieldData( pFldData ) ) ) + { + DBG_ASSERT( dynamic_cast<const SvxFieldItem*>(rAttr.GetItem()), "no field item..." ); + EditSelection aSel( EditPaM(pNode, rAttr.GetStart()), EditPaM(pNode, rAttr.GetEnd()) ); + OUString aFieldText = static_cast<const EditCharAttribField&>(rAttr).GetFieldValue(); + pImpEditEngine->ImpInsertText( aSel, aFieldText ); + } + } + } + } +} + +bool EditEngine::HasOnlineSpellErrors() const +{ + sal_Int32 nNodes = pImpEditEngine->GetEditDoc().Count(); + for ( sal_Int32 n = 0; n < nNodes; n++ ) + { + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( n ); + if ( pNode->GetWrongList() && !pNode->GetWrongList()->empty() ) + return true; + } + return false; +} + +void EditEngine::CompleteOnlineSpelling() +{ + if ( pImpEditEngine->GetStatus().DoOnlineSpelling() ) + { + if( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatAndLayout(); + + pImpEditEngine->StopOnlineSpellTimer(); + pImpEditEngine->DoOnlineSpelling( nullptr, true, false ); + } +} + +sal_Int32 EditEngine::FindParagraph( tools::Long nDocPosY ) +{ + return pImpEditEngine->GetParaPortions().FindParagraph( nDocPosY ); +} + +EPosition EditEngine::FindDocPosition( const Point& rDocPos ) const +{ + EPosition aPos; + // From the point of the API, this is const... + EditPaM aPaM = const_cast<EditEngine*>(this)->pImpEditEngine->GetPaM( rDocPos, false ); + if ( aPaM.GetNode() ) + { + aPos.nPara = pImpEditEngine->aEditDoc.GetPos( aPaM.GetNode() ); + aPos.nIndex = aPaM.GetIndex(); + } + return aPos; +} + +tools::Rectangle EditEngine::GetCharacterBounds( const EPosition& rPos ) const +{ + tools::Rectangle aBounds; + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( rPos.nPara ); + + // Check against index, not paragraph + if ( pNode && ( rPos.nIndex < pNode->Len() ) ) + { + aBounds = pImpEditEngine->PaMtoEditCursor( EditPaM( pNode, rPos.nIndex ), GetCursorFlags::TextOnly ); + tools::Rectangle aR2 = pImpEditEngine->PaMtoEditCursor( EditPaM( pNode, rPos.nIndex+1 ), GetCursorFlags::TextOnly|GetCursorFlags::EndOfLine ); + if ( aR2.Right() > aBounds.Right() ) + aBounds.SetRight( aR2.Right() ); + } + return aBounds; +} + +ParagraphInfos EditEngine::GetParagraphInfos( sal_Int32 nPara ) +{ + + // This only works if not already in the format ... + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + ParagraphInfos aInfos; + aInfos.bValid = pImpEditEngine->IsFormatted(); + if ( pImpEditEngine->IsFormatted() ) + { + const ParaPortion* pParaPortion = pImpEditEngine->GetParaPortions()[nPara]; + const EditLine* pLine = (pParaPortion && pParaPortion->GetLines().Count()) ? + &pParaPortion->GetLines()[0] : nullptr; + DBG_ASSERT( pParaPortion && pLine, "GetParagraphInfos - Paragraph out of range" ); + if ( pParaPortion && pLine ) + { + aInfos.nFirstLineHeight = pLine->GetHeight(); + aInfos.nFirstLineTextHeight = pLine->GetTxtHeight(); + aInfos.nFirstLineMaxAscent = pLine->GetMaxAscent(); + } + } + return aInfos; +} + +css::uno::Reference< css::datatransfer::XTransferable > + EditEngine::CreateTransferable( const ESelection& rSelection ) const +{ + EditSelection aSel( pImpEditEngine->CreateSel( rSelection ) ); + return pImpEditEngine->CreateTransferable( aSel ); +} + + +// ====================== Virtual Methods ======================== + +void EditEngine::DrawingText( const Point&, const OUString&, sal_Int32, sal_Int32, + o3tl::span<const sal_Int32>, const SvxFont&, sal_Int32 /*nPara*/, sal_uInt8 /*nRightToLeft*/, + const EEngineData::WrongSpellVector*, const SvxFieldData*, bool, bool, + const css::lang::Locale*, const Color&, const Color&) + +{ +} + +void EditEngine::DrawingTab( const Point& /*rStartPos*/, tools::Long /*nWidth*/, + const OUString& /*rChar*/, const SvxFont& /*rFont*/, + sal_Int32 /*nPara*/, sal_uInt8 /*nRightToLeft*/, bool /*bEndOfLine*/, + bool /*bEndOfParagraph*/, const Color& /*rOverlineColor*/, + const Color& /*rTextLineColor*/) +{ +} + +void EditEngine::PaintingFirstLine(sal_Int32, const Point&, const Point&, Degree10, OutputDevice&) +{ +} + +void EditEngine::ParagraphInserted( sal_Int32 nPara ) +{ + + if ( GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_PARAGRAPHINSERTED ); + aNotify.nParagraph = nPara; + pImpEditEngine->GetNotifyHdl().Call( aNotify ); + } +} + +void EditEngine::ParagraphDeleted( sal_Int32 nPara ) +{ + + if ( GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_PARAGRAPHREMOVED ); + aNotify.nParagraph = nPara; + pImpEditEngine->GetNotifyHdl().Call( aNotify ); + } +} +void EditEngine::ParagraphConnected( sal_Int32 /*nLeftParagraph*/, sal_Int32 /*nRightParagraph*/ ) +{ +} + +void EditEngine::ParaAttribsChanged( sal_Int32 /* nParagraph */ ) +{ +} + +void EditEngine::StyleSheetChanged( SfxStyleSheet* /* pStyle */ ) +{ +} + +void EditEngine::ParagraphHeightChanged( sal_Int32 nPara ) +{ + if ( GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_TextHeightChanged ); + aNotify.nParagraph = nPara; + pImpEditEngine->GetNotifyHdl().Call( aNotify ); + } + + for (EditView* pView : pImpEditEngine->aEditViews) + pView->pImpEditView->ScrollStateChange(); +} + +OUString EditEngine::GetUndoComment( sal_uInt16 nId ) const +{ + OUString aComment; + switch ( nId ) + { + case EDITUNDO_REMOVECHARS: + case EDITUNDO_CONNECTPARAS: + case EDITUNDO_DELCONTENT: + case EDITUNDO_DELETE: + case EDITUNDO_CUT: + aComment = EditResId(RID_EDITUNDO_DEL); + break; + case EDITUNDO_MOVEPARAGRAPHS: + case EDITUNDO_MOVEPARAS: + case EDITUNDO_DRAGANDDROP: + aComment = EditResId(RID_EDITUNDO_MOVE); + break; + case EDITUNDO_INSERTFEATURE: + case EDITUNDO_SPLITPARA: + case EDITUNDO_INSERTCHARS: + case EDITUNDO_PASTE: + case EDITUNDO_INSERT: + case EDITUNDO_READ: + aComment = EditResId(RID_EDITUNDO_INSERT); + break; + case EDITUNDO_REPLACEALL: + aComment = EditResId(RID_EDITUNDO_REPLACE); + break; + case EDITUNDO_ATTRIBS: + case EDITUNDO_PARAATTRIBS: + aComment = EditResId(RID_EDITUNDO_SETATTRIBS); + break; + case EDITUNDO_RESETATTRIBS: + aComment = EditResId(RID_EDITUNDO_RESETATTRIBS); + break; + case EDITUNDO_STYLESHEET: + aComment = EditResId(RID_EDITUNDO_SETSTYLE); + break; + case EDITUNDO_TRANSLITERATE: + aComment = EditResId(RID_EDITUNDO_TRANSLITERATE); + break; + case EDITUNDO_INDENTBLOCK: + case EDITUNDO_UNINDENTBLOCK: + aComment = EditResId(RID_EDITUNDO_INDENT); + break; + } + return aComment; +} + +tools::Rectangle EditEngine::GetBulletArea( sal_Int32 ) +{ + return tools::Rectangle( Point(), Point() ); +} + +OUString EditEngine::CalcFieldValue( const SvxFieldItem&, sal_Int32, sal_Int32, std::optional<Color>&, std::optional<Color>& ) +{ + return OUString(' '); +} + +bool EditEngine::FieldClicked( const SvxFieldItem& ) +{ + return false; +} + + +// ====================== Static Methods ======================= + +rtl::Reference<SfxItemPool> EditEngine::CreatePool() +{ + return new EditEngineItemPool(); +} + + +/** If we let the libc runtime clean us up, we trigger a crash */ +namespace +{ +class TerminateListener : public ::cppu::WeakImplHelper< css::frame::XTerminateListener > +{ + void SAL_CALL queryTermination( const lang::EventObject& ) override + {} + void SAL_CALL notifyTermination( const lang::EventObject& ) override + { + pGlobalPool.clear(); + } + virtual void SAL_CALL disposing( const ::css::lang::EventObject& ) override + {} +}; +}; + +SfxItemPool& EditEngine::GetGlobalItemPool() +{ + if ( !pGlobalPool ) + { + pGlobalPool = CreatePool(); + uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create(comphelper::getProcessComponentContext()); + uno::Reference< frame::XTerminateListener > xListener( new TerminateListener ); + xDesktop->addTerminateListener( xListener ); + } + return *pGlobalPool; +} + +void EditEngine::SetFontInfoInItemSet( SfxItemSet& rSet, const vcl::Font& rFont ) +{ + SvxFont aSvxFont( rFont ); + SetFontInfoInItemSet( rSet, aSvxFont ); + +} + +void EditEngine::SetFontInfoInItemSet( SfxItemSet& rSet, const SvxFont& rFont ) +{ + rSet.Put( SvxLanguageItem( rFont.GetLanguage(), EE_CHAR_LANGUAGE ) ); + rSet.Put( SvxFontItem( rFont.GetFamilyType(), rFont.GetFamilyName(), OUString(), rFont.GetPitch(), rFont.GetCharSet(), EE_CHAR_FONTINFO ) ); + rSet.Put( SvxFontHeightItem( rFont.GetFontSize().Height(), 100, EE_CHAR_FONTHEIGHT ) ); + rSet.Put( SvxCharScaleWidthItem( 100, EE_CHAR_FONTWIDTH ) ); + rSet.Put( SvxShadowedItem( rFont.IsShadow(), EE_CHAR_SHADOW ) ); + rSet.Put( SvxEscapementItem( rFont.GetEscapement(), rFont.GetPropr(), EE_CHAR_ESCAPEMENT ) ); + rSet.Put( SvxWeightItem( rFont.GetWeight(), EE_CHAR_WEIGHT ) ); + rSet.Put( SvxColorItem( rFont.GetColor(), EE_CHAR_COLOR ) ); + rSet.Put( SvxColorItem( rFont.GetFillColor(), EE_CHAR_BKGCOLOR ) ); + rSet.Put( SvxUnderlineItem( rFont.GetUnderline(), EE_CHAR_UNDERLINE ) ); + rSet.Put( SvxOverlineItem( rFont.GetOverline(), EE_CHAR_OVERLINE ) ); + rSet.Put( SvxCrossedOutItem( rFont.GetStrikeout(), EE_CHAR_STRIKEOUT ) ); + rSet.Put( SvxCaseMapItem( rFont.GetCaseMap(), EE_CHAR_CASEMAP ) ); + rSet.Put( SvxPostureItem( rFont.GetItalic(), EE_CHAR_ITALIC ) ); + rSet.Put( SvxContourItem( rFont.IsOutline(), EE_CHAR_OUTLINE ) ); + rSet.Put( SvxAutoKernItem( rFont.IsKerning(), EE_CHAR_PAIRKERNING ) ); + rSet.Put( SvxKerningItem( rFont.GetFixKerning(), EE_CHAR_KERNING ) ); + rSet.Put( SvxWordLineModeItem( rFont.IsWordLineMode(), EE_CHAR_WLM ) ); + rSet.Put( SvxEmphasisMarkItem( rFont.GetEmphasisMark(), EE_CHAR_EMPHASISMARK ) ); + rSet.Put( SvxCharReliefItem( rFont.GetRelief(), EE_CHAR_RELIEF ) ); +} + +vcl::Font EditEngine::CreateFontFromItemSet( const SfxItemSet& rItemSet, SvtScriptType nScriptType ) +{ + SvxFont aFont; + CreateFont( aFont, rItemSet, true, nScriptType ); +#if HAVE_P1155R3 + return aFont; +#else + return std::move(aFont); +#endif +} + +SvxFont EditEngine::CreateSvxFontFromItemSet( const SfxItemSet& rItemSet ) +{ + SvxFont aFont; + CreateFont( aFont, rItemSet ); + return aFont; +} + +bool EditEngine::DoesKeyMoveCursor( const KeyEvent& rKeyEvent ) +{ + bool bDoesMove = false; + + switch ( rKeyEvent.GetKeyCode().GetCode() ) + { + case KEY_UP: + case KEY_DOWN: + case KEY_LEFT: + case KEY_RIGHT: + case KEY_HOME: + case KEY_END: + case KEY_PAGEUP: + case KEY_PAGEDOWN: + { + if ( !rKeyEvent.GetKeyCode().IsMod2() ) + bDoesMove = true; + } + break; + } + return bDoesMove; +} + +bool EditEngine::DoesKeyChangeText( const KeyEvent& rKeyEvent ) +{ + bool bDoesChange = false; + + KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction(); + if ( eFunc != KeyFuncType::DONTKNOW ) + { + switch ( eFunc ) + { + case KeyFuncType::UNDO: + case KeyFuncType::REDO: + case KeyFuncType::CUT: + case KeyFuncType::PASTE: bDoesChange = true; + break; + default: // is then possibly edited below. + eFunc = KeyFuncType::DONTKNOW; + } + } + if ( eFunc == KeyFuncType::DONTKNOW ) + { + switch ( rKeyEvent.GetKeyCode().GetCode() ) + { + case KEY_DELETE: + case KEY_BACKSPACE: bDoesChange = true; + break; + case KEY_RETURN: + case KEY_TAB: + { + if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() ) + bDoesChange = true; + } + break; + default: + { + bDoesChange = IsSimpleCharInput( rKeyEvent ); + } + } + } + return bDoesChange; +} + +bool EditEngine::IsSimpleCharInput( const KeyEvent& rKeyEvent ) +{ + return EditEngine::IsPrintable( rKeyEvent.GetCharCode() ) && + ( KEY_MOD2 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT ) ) && + ( KEY_MOD1 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT ) ); +} + +bool EditEngine::HasValidData( const css::uno::Reference< css::datatransfer::XTransferable >& rTransferable ) +{ + bool bValidData = false; + + if ( comphelper::LibreOfficeKit::isActive()) + return true; + + if ( rTransferable.is() ) + { + // Every application that copies rtf or any other text format also copies plain text into the clipboard... + datatransfer::DataFlavor aFlavor; + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); + bValidData = rTransferable->isDataFlavorSupported( aFlavor ); + } + + return bValidData; +} + +/** sets a link that is called at the beginning of a drag operation at an edit view */ +void EditEngine::SetBeginDropHdl( const Link<EditView*,void>& rLink ) +{ + pImpEditEngine->SetBeginDropHdl( rLink ); +} + +Link<EditView*,void> const & EditEngine::GetBeginDropHdl() const +{ + return pImpEditEngine->GetBeginDropHdl(); +} + +/** sets a link that is called at the end of a drag operation at an edit view */ +void EditEngine::SetEndDropHdl( const Link<EditView*,void>& rLink ) +{ + pImpEditEngine->SetEndDropHdl( rLink ); +} + +Link<EditView*,void> const & EditEngine::GetEndDropHdl() const +{ + return pImpEditEngine->GetEndDropHdl(); +} + +void EditEngine::SetFirstWordCapitalization( bool bCapitalize ) +{ + pImpEditEngine->SetFirstWordCapitalization( bCapitalize ); +} + +void EditEngine::SetReplaceLeadingSingleQuotationMark( bool bReplace ) +{ + pImpEditEngine->SetReplaceLeadingSingleQuotationMark( bReplace ); +} + +bool EditEngine::IsHtmlImportHandlerSet() const +{ + return pImpEditEngine->aHtmlImportHdl.IsSet(); +} + +bool EditEngine::IsRtfImportHandlerSet() const +{ + return pImpEditEngine->aRtfImportHdl.IsSet(); +} + +bool EditEngine::IsImportRTFStyleSheetsSet() const +{ + return pImpEditEngine->GetStatus().DoImportRTFStyleSheets(); +} + +void EditEngine::CallHtmlImportHandler(HtmlImportInfo& rInfo) +{ + pImpEditEngine->aHtmlImportHdl.Call(rInfo); +} + +void EditEngine::CallRtfImportHandler(RtfImportInfo& rInfo) +{ + pImpEditEngine->aRtfImportHdl.Call(rInfo); +} + +EditPaM EditEngine::InsertParaBreak(const EditSelection& rEditSelection) +{ + return pImpEditEngine->ImpInsertParaBreak(rEditSelection); +} + +EditPaM EditEngine::InsertLineBreak(const EditSelection& rEditSelection) +{ + return pImpEditEngine->InsertLineBreak(rEditSelection); +} + +sal_Int32 EditEngine::GetOverflowingParaNum() const { + return pImpEditEngine->GetOverflowingParaNum(); +} + +sal_Int32 EditEngine::GetOverflowingLineNum() const { + return pImpEditEngine->GetOverflowingLineNum(); +} + +void EditEngine::ClearOverflowingParaNum() { + pImpEditEngine->ClearOverflowingParaNum(); +} + +bool EditEngine::IsPageOverflow() { + pImpEditEngine->CheckPageOverflow(); + return pImpEditEngine->IsPageOverflow(); +} + +void EditEngine::DisableAttributeExpanding() { + pImpEditEngine->GetEditDoc().DisableAttributeExpanding(); +} + +void EditEngine::EnableSkipOutsideFormat(bool set) +{ + pImpEditEngine->EnableSkipOutsideFormat(set); +} + +void EditEngine::SetLOKSpecialPaperSize(const Size& rSize) +{ + pImpEditEngine->SetLOKSpecialPaperSize(rSize); +} + +const Size& EditEngine::GetLOKSpecialPaperSize() const +{ + return pImpEditEngine->GetLOKSpecialPaperSize(); +} + +EFieldInfo::EFieldInfo() +{ +} + + +EFieldInfo::EFieldInfo( const SvxFieldItem& rFieldItem, sal_Int32 nPara, sal_Int32 nPos ) : + pFieldItem( new SvxFieldItem( rFieldItem ) ), + aPosition( nPara, nPos ) +{ +} + +EFieldInfo::~EFieldInfo() +{ +} + +EFieldInfo::EFieldInfo( const EFieldInfo& rFldInfo ) +{ + *this = rFldInfo; +} + +EFieldInfo& EFieldInfo::operator= ( const EFieldInfo& rFldInfo ) +{ + if( this == &rFldInfo ) + return *this; + + pFieldItem.reset( rFldInfo.pFieldItem ? new SvxFieldItem( *rFldInfo.pFieldItem ) : nullptr ); + aCurrentText = rFldInfo.aCurrentText; + aPosition = rFldInfo.aPosition; + + return *this; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editobj.cxx b/editeng/source/editeng/editobj.cxx new file mode 100644 index 000000000..ce39e37d3 --- /dev/null +++ b/editeng/source/editeng/editobj.cxx @@ -0,0 +1,775 @@ +/* -*- 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 <o3tl/safeint.hxx> +#include <sal/log.hxx> + +#include <editeng/macros.hxx> +#include <editeng/section.hxx> +#include "editobj2.hxx" +#include <editeng/editdata.hxx> +#include <editeng/editeng.hxx> +#include <editeng/flditem.hxx> + +#include <svl/sharedstringpool.hxx> + +#include <libxml/xmlwriter.h> +#include <algorithm> +#include <cassert> + +#if DEBUG_EDIT_ENGINE +#include <iostream> +using std::cout; +using std::endl; +#endif + +using namespace com::sun::star; + + +static XEditAttribute MakeXEditAttribute( SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 nStart, sal_Int32 nEnd ) +{ + // Create the new attribute in the pool + const SfxPoolItem& rNew = rPool.Put( rItem ); + + return XEditAttribute( rNew, nStart, nEnd ); +} + +XEditAttribute::XEditAttribute( const SfxPoolItem& rAttr, sal_Int32 nS, sal_Int32 nE ) + : pItem(&rAttr) + , nStart(nS) + , nEnd(nE) +{ +} + +bool XEditAttribute::IsFeature() const +{ + sal_uInt16 nWhich = pItem->Which(); + return ((nWhich >= EE_FEATURE_START) && (nWhich <= EE_FEATURE_END)); +} + +void XEditAttribute::SetItem(const SfxPoolItem& rNew) +{ + pItem = &rNew; +} + +XParaPortionList::XParaPortionList( + OutputDevice* pRefDev, sal_uInt32 nPW, sal_uInt16 _nStretchX, sal_uInt16 _nStretchY) + : pRefDevPtr(pRefDev) + , nStretchX(_nStretchX) + , nStretchY(_nStretchY) + , nPaperWidth(nPW) +{ +} + +void XParaPortionList::push_back(XParaPortion* p) +{ + maList.push_back(std::unique_ptr<XParaPortion>(p)); +} + +const XParaPortion& XParaPortionList::operator [](size_t i) const +{ + return *maList[i]; +} + +ContentInfo::ContentInfo( SfxItemPool& rPool ) : + eFamily(SfxStyleFamily::Para), + aParaAttribs(rPool) +{ +} + +// the real Copy constructor is nonsense, since I have to work with another Pool! +ContentInfo::ContentInfo( const ContentInfo& rCopyFrom, SfxItemPool& rPoolToUse ) : + maText(rCopyFrom.maText), + aStyle(rCopyFrom.aStyle), + eFamily(rCopyFrom.eFamily), + aParaAttribs(rPoolToUse) +{ + // this should ensure that the Items end up in the correct Pool! + aParaAttribs.Set( rCopyFrom.GetParaAttribs() ); + + for (const XEditAttribute & rAttr : rCopyFrom.maCharAttribs) + { + XEditAttribute aMyAttr = MakeXEditAttribute( + rPoolToUse, *rAttr.GetItem(), rAttr.GetStart(), rAttr.GetEnd()); + maCharAttribs.push_back(aMyAttr); + } + + if ( rCopyFrom.GetWrongList() ) + mpWrongs.reset(rCopyFrom.GetWrongList()->Clone()); +} + +ContentInfo::~ContentInfo() +{ + for (auto const& charAttrib : maCharAttribs) + aParaAttribs.GetPool()->Remove(*charAttrib.GetItem()); + maCharAttribs.clear(); +} + +void ContentInfo::NormalizeString( svl::SharedStringPool& rPool ) +{ + maText = rPool.intern(OUString(maText.getData())); +} + + +OUString ContentInfo::GetText() const +{ + rtl_uString* p = const_cast<rtl_uString*>(maText.getData()); + return OUString(p); +} + +void ContentInfo::SetText( const OUString& rStr ) +{ + maText = svl::SharedString(rStr.pData, nullptr); +} + +void ContentInfo::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ContentInfo")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("style"), BAD_CAST(aStyle.toUtf8().getStr())); + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("text")); + OUString aText = GetText(); + // TODO share code with sax_fastparser::FastSaxSerializer::write(). + (void)xmlTextWriterWriteString(pWriter, BAD_CAST(aText.replaceAll("\x01", "").toUtf8().getStr())); + (void)xmlTextWriterEndElement(pWriter); + aParaAttribs.dumpAsXml(pWriter); + for (size_t i=0; i<maCharAttribs.size(); ++i) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("attribs")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("start"), "%" SAL_PRIdINT32, maCharAttribs[i].GetStart()); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("end"), "%" SAL_PRIdINT32, maCharAttribs[i].GetEnd()); + maCharAttribs[i].GetItem()->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); +} + +const WrongList* ContentInfo::GetWrongList() const +{ + return mpWrongs.get(); +} + +void ContentInfo::SetWrongList( WrongList* p ) +{ + mpWrongs.reset(p); +} + +// #i102062# +bool ContentInfo::isWrongListEqual(const ContentInfo& rCompare) const +{ + if(GetWrongList() == rCompare.GetWrongList()) + return true; + + if(!GetWrongList() || !rCompare.GetWrongList()) + return false; + + return (*GetWrongList() == *rCompare.GetWrongList()); +} + +#if DEBUG_EDIT_ENGINE +void ContentInfo::Dump() const +{ + cout << "--" << endl; + cout << "text: '" << OUString(const_cast<rtl_uString*>(maText.getData())) << "'" << endl; + cout << "style: '" << aStyle << "'" << endl; + + for (auto const& attrib : aAttribs) + { + cout << "attribute: " << endl; + cout << " span: [begin=" << attrib.GetStart() << ", end=" << attrib.GetEnd() << "]" << endl; + cout << " feature: " << (attrib.IsFeature() ? "yes":"no") << endl; + } +} +#endif + +bool ContentInfo::Equals(const ContentInfo& rCompare, bool bComparePool) const +{ + return maText == rCompare.maText && aStyle == rCompare.aStyle && eFamily == rCompare.eFamily + && aParaAttribs.Equals(rCompare.aParaAttribs, bComparePool) + && maCharAttribs == rCompare.maCharAttribs; +} + +EditTextObject::~EditTextObject() = default; + +std::unique_ptr<EditTextObject> EditTextObjectImpl::Clone() const +{ + return std::make_unique<EditTextObjectImpl>(*this); +} + +bool EditTextObject::Equals( const EditTextObject& rCompare ) const +{ + return toImpl(*this).Equals(toImpl(rCompare), false /*bComparePool*/); +} + +void EditTextObjectImpl::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + bool bOwns = false; + if (!pWriter) + { + pWriter = xmlNewTextWriterFilename("editTextObject.xml", 0); + xmlTextWriterSetIndent(pWriter,1); + (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); + (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr); + bOwns = true; + } + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("EditTextObject")); + sal_Int32 nCount = GetParagraphCount(); + for (sal_Int32 i = 0; i < nCount; ++i) + { + maContents[i]->dumpAsXml(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + + if (bOwns) + { + (void)xmlTextWriterEndDocument(pWriter); + xmlFreeTextWriter(pWriter); + } +} + +#if DEBUG_EDIT_ENGINE +void EditTextObjectImpl::Dump() const +{ + for (auto const& content : maContents) + content.Dump(); +} +#endif + +static rtl::Reference<SfxItemPool> getEditEngineItemPool(SfxItemPool* pPool, MapUnit eDefaultMetric) +{ + // #i101239# ensure target is an EditEngineItemPool, so that at + // pool destruction time of an alien pool, the pool is still alive. + // When registering would happen at an alien pool which just uses an + // EditEngineItemPool as some sub-pool, that pool could already + // be decoupled and deleted which would lead to crashes. + for (; pPool; pPool = pPool->GetSecondaryPool()) + if (dynamic_cast<EditEngineItemPool*>(pPool)) + return pPool; + + auto pRetval = EditEngine::CreatePool(); + pRetval->SetDefaultMetric(eDefaultMetric); + return pRetval; +} + +EditTextObjectImpl::EditTextObjectImpl(SfxItemPool* pP, MapUnit eDefaultMetric, bool bVertical, + TextRotation eRotation, SvtScriptType eScriptType) + : mpPool(getEditEngineItemPool(pP, eDefaultMetric)) + , meUserType(OutlinerMode::DontKnow) + , meScriptType(eScriptType) + , meRotation(eRotation) + , meMetric(eDefaultMetric) + , mbVertical(bVertical) +{ +} + +EditTextObjectImpl::EditTextObjectImpl( const EditTextObjectImpl& r ) + : mpPool(r.mpPool) + , meUserType(r.meUserType) + , meScriptType(r.meScriptType) + , meRotation(r.meRotation) + , meMetric(r.meMetric) + , mbVertical(r.mbVertical) +{ + // Do not copy PortionInfo + + maContents.reserve(r.maContents.size()); + for (auto const& content : r.maContents) + maContents.push_back(std::unique_ptr<ContentInfo>(new ContentInfo(*content, *mpPool))); +} + +EditTextObjectImpl::~EditTextObjectImpl() +{ + ClearPortionInfo(); + + // Remove contents before deleting the pool instance since each content + // has to access the pool instance in its destructor. + maContents.clear(); +} + + +void EditTextObjectImpl::SetUserType( OutlinerMode n ) +{ + meUserType = n; +} + +void EditTextObjectImpl::NormalizeString( svl::SharedStringPool& rPool ) +{ + for (auto const& content : maContents) + { + ContentInfo& rInfo = *content; + rInfo.NormalizeString(rPool); + } +} + +std::vector<svl::SharedString> EditTextObjectImpl::GetSharedStrings() const +{ + std::vector<svl::SharedString> aSSs; + aSSs.reserve(maContents.size()); + for (auto const& content : maContents) + { + const ContentInfo& rInfo = *content; + aSSs.push_back(rInfo.GetSharedString()); + } + return aSSs; +} + +bool EditTextObjectImpl::IsEffectivelyVertical() const +{ + return (mbVertical && meRotation == TextRotation::NONE) || + (!mbVertical && meRotation != TextRotation::NONE); +} + +bool EditTextObjectImpl::IsTopToBottom() const +{ + return (mbVertical && meRotation == TextRotation::NONE) || + (!mbVertical && meRotation == TextRotation::TOPTOBOTTOM); +} + +void EditTextObjectImpl::SetVertical( bool bVert) +{ + if (bVert != mbVertical) + { + mbVertical = bVert; + ClearPortionInfo(); + } +} + +bool EditTextObjectImpl::GetVertical() const +{ + return mbVertical; +} + +void EditTextObjectImpl::SetRotation(TextRotation nRotation) +{ + if (meRotation != nRotation) + { + meRotation = nRotation; + ClearPortionInfo(); + } +} + +TextRotation EditTextObjectImpl::GetRotation() const +{ + return meRotation; +} + +XEditAttribute EditTextObjectImpl::CreateAttrib( const SfxPoolItem& rItem, sal_Int32 nStart, sal_Int32 nEnd ) +{ + return MakeXEditAttribute( *mpPool, rItem, nStart, nEnd ); +} + +void EditTextObjectImpl::DestroyAttrib( const XEditAttribute& rAttr ) +{ + mpPool->Remove( *rAttr.GetItem() ); +} + + +ContentInfo* EditTextObjectImpl::CreateAndInsertContent() +{ + maContents.push_back(std::unique_ptr<ContentInfo>(new ContentInfo(*mpPool))); + return maContents.back().get(); +} + +sal_Int32 EditTextObjectImpl::GetParagraphCount() const +{ + size_t nSize = maContents.size(); + if (nSize > EE_PARA_MAX_COUNT) + { + SAL_WARN( "editeng", "EditTextObjectImpl::GetParagraphCount - overflow " << nSize); + return EE_PARA_MAX_COUNT; + } + return static_cast<sal_Int32>(nSize); +} + +OUString EditTextObjectImpl::GetText(sal_Int32 nPara) const +{ + if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size()) + return OUString(); + + return maContents[nPara]->GetText(); +} + +void EditTextObjectImpl::ClearPortionInfo() +{ + mpPortionInfo.reset(); +} + +bool EditTextObjectImpl::HasOnlineSpellErrors() const +{ + for (auto const& content : maContents) + { + if ( content->GetWrongList() && !content->GetWrongList()->empty() ) + return true; + } + return false; +} + +void EditTextObjectImpl::GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const +{ + if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size()) + return; + + rLst.clear(); + const ContentInfo& rC = *maContents[nPara]; + for (const XEditAttribute & rAttr : rC.maCharAttribs) + { + EECharAttrib aEEAttr(rAttr.GetStart(), rAttr.GetEnd(), rAttr.GetItem()); + rLst.push_back(aEEAttr); + } +} + +bool EditTextObjectImpl::IsFieldObject() const +{ + return GetField() != nullptr; +} + +const SvxFieldItem* EditTextObjectImpl::GetField() const +{ + if (maContents.size() == 1) + { + const ContentInfo& rC = *maContents[0]; + if (rC.GetText().getLength() == 1) + { + size_t nAttribs = rC.maCharAttribs.size(); + for (size_t nAttr = nAttribs; nAttr; ) + { + const XEditAttribute& rX = rC.maCharAttribs[--nAttr]; + if (rX.GetItem()->Which() == EE_FEATURE_FIELD) + return static_cast<const SvxFieldItem*>(rX.GetItem()); + } + } + } + return nullptr; +} + +const SvxFieldData* EditTextObjectImpl::GetFieldData(sal_Int32 nPara, size_t nPos, sal_Int32 nType) const +{ + if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size()) + return nullptr; + + const ContentInfo& rC = *maContents[nPara]; + if (nPos >= rC.maCharAttribs.size()) + // URL position is out-of-bound. + return nullptr; + + size_t nCurPos = 0; + for (XEditAttribute const& rAttr : rC.maCharAttribs) + { + if (rAttr.GetItem()->Which() != EE_FEATURE_FIELD) + // Skip attributes that are not fields. + continue; + + const SvxFieldItem* pField = static_cast<const SvxFieldItem*>(rAttr.GetItem()); + const SvxFieldData* pFldData = pField->GetField(); + if (nType != text::textfield::Type::UNSPECIFIED && nType != pFldData->GetClassId()) + // Field type doesn't match. Skip it. UNSPECIFIED matches all field types. + continue; + + if (nCurPos == nPos) + // Found it! + return pFldData; + + ++nCurPos; + } + + return nullptr; // field not found. +} + +bool EditTextObjectImpl::HasField( sal_Int32 nType ) const +{ + size_t nParagraphs = maContents.size(); + for (size_t nPara = 0; nPara < nParagraphs; ++nPara) + { + const ContentInfo& rC = *maContents[nPara]; + size_t nAttrs = rC.maCharAttribs.size(); + for (size_t nAttr = 0; nAttr < nAttrs; ++nAttr) + { + const XEditAttribute& rAttr = rC.maCharAttribs[nAttr]; + if (rAttr.GetItem()->Which() != EE_FEATURE_FIELD) + continue; + + if (nType == text::textfield::Type::UNSPECIFIED) + // Match any field type. + return true; + + const SvxFieldData* pFldData = static_cast<const SvxFieldItem*>(rAttr.GetItem())->GetField(); + if (pFldData && pFldData->GetClassId() == nType) + return true; + } + } + return false; +} + +const SfxItemSet& EditTextObjectImpl::GetParaAttribs(sal_Int32 nPara) const +{ + const ContentInfo& rC = *maContents[nPara]; + return rC.GetParaAttribs(); +} + +bool EditTextObjectImpl::RemoveCharAttribs( sal_uInt16 _nWhich ) +{ + bool bChanged = false; + + for ( size_t nPara = maContents.size(); nPara; ) + { + ContentInfo& rC = *maContents[--nPara]; + + for (size_t nAttr = rC.maCharAttribs.size(); nAttr; ) + { + XEditAttribute& rAttr = rC.maCharAttribs[--nAttr]; + if ( !_nWhich || (rAttr.GetItem()->Which() == _nWhich) ) + { + mpPool->Remove(*rAttr.GetItem()); + rC.maCharAttribs.erase(rC.maCharAttribs.begin()+nAttr); + bChanged = true; + } + } + } + + if ( bChanged ) + ClearPortionInfo(); + + return bChanged; +} + +namespace { + +class FindByParagraph +{ + sal_Int32 mnPara; +public: + explicit FindByParagraph(sal_Int32 nPara) : mnPara(nPara) {} + bool operator() (const editeng::Section& rAttr) const + { + return rAttr.mnParagraph == mnPara; + } +}; + +class FindBySectionStart +{ + sal_Int32 mnPara; + sal_Int32 mnStart; +public: + FindBySectionStart(sal_Int32 nPara, sal_Int32 nStart) : mnPara(nPara), mnStart(nStart) {} + bool operator() (const editeng::Section& rAttr) const + { + return rAttr.mnParagraph == mnPara && rAttr.mnStart == mnStart; + } +}; + +} + +void EditTextObjectImpl::GetAllSections( std::vector<editeng::Section>& rAttrs ) const +{ + std::vector<editeng::Section> aAttrs; + aAttrs.reserve(maContents.size()); + std::vector<size_t> aBorders; + + for (size_t nPara = 0; nPara < maContents.size(); ++nPara) + { + aBorders.clear(); + const ContentInfo& rC = *maContents[nPara]; + aBorders.push_back(0); + aBorders.push_back(rC.GetText().getLength()); + for (const XEditAttribute & rAttr : rC.maCharAttribs) + { + const SfxPoolItem* pItem = rAttr.GetItem(); + if (!pItem) + continue; + + aBorders.push_back(rAttr.GetStart()); + aBorders.push_back(rAttr.GetEnd()); + } + + // Sort and remove duplicates for each paragraph. + std::sort(aBorders.begin(), aBorders.end()); + auto itUniqueEnd = std::unique(aBorders.begin(), aBorders.end()); + aBorders.erase(itUniqueEnd, aBorders.end()); + + // Create storage for each section. Note that this creates storage even + // for unformatted sections. The entries are sorted first by paragraph, + // then by section positions. They don't overlap with each other. + + if (aBorders.size() == 1 && aBorders[0] == 0) + { + // Empty paragraph. Push an empty section. + aAttrs.emplace_back(nPara, 0, 0); + continue; + } + + auto itBorder = aBorders.begin(), itBorderEnd = aBorders.end(); + size_t nPrev = *itBorder; + size_t nCur; + for (++itBorder; itBorder != itBorderEnd; ++itBorder, nPrev = nCur) + { + nCur = *itBorder; + aAttrs.emplace_back(nPara, nPrev, nCur); + } + } + + if (aAttrs.empty()) + return; + + // Go through all formatted paragraphs, and store format items. + std::vector<editeng::Section>::iterator itAttr = aAttrs.begin(); + for (sal_Int32 nPara = 0; nPara < static_cast<sal_Int32>(maContents.size()); ++nPara) + { + const ContentInfo& rC = *maContents[nPara]; + + itAttr = std::find_if(itAttr, aAttrs.end(), FindByParagraph(nPara)); + if (itAttr == aAttrs.end()) + { + // This should never happen. There is a logic error somewhere... + assert(false); + return; + } + + for (const XEditAttribute & rXAttr : rC.maCharAttribs) + { + const SfxPoolItem* pItem = rXAttr.GetItem(); + if (!pItem) + continue; + + sal_Int32 nStart = rXAttr.GetStart(), nEnd = rXAttr.GetEnd(); + + // Find the container whose start position matches. + std::vector<editeng::Section>::iterator itCurAttr = std::find_if(itAttr, aAttrs.end(), FindBySectionStart(nPara, nStart)); + if (itCurAttr == aAttrs.end()) + { + // This should never happen. There is a logic error somewhere... + assert(false); + return; + } + + for (; itCurAttr != aAttrs.end() && itCurAttr->mnParagraph == nPara && itCurAttr->mnEnd <= nEnd; ++itCurAttr) + { + editeng::Section& rSecAttr = *itCurAttr; + // serious bug: will cause duplicate attributes to be exported + if (std::none_of(rSecAttr.maAttributes.begin(), rSecAttr.maAttributes.end(), + [&pItem](SfxPoolItem const*const pIt) + { return pIt->Which() == pItem->Which(); })) + { + rSecAttr.maAttributes.push_back(pItem); + } + else + { + SAL_WARN("editeng", "GetAllSections(): duplicate attribute suppressed"); + } + } + } + } + + rAttrs.swap(aAttrs); +} + +void EditTextObjectImpl::GetStyleSheet(sal_Int32 nPara, OUString& rName, SfxStyleFamily& rFamily) const +{ + if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size()) + return; + + const ContentInfo& rC = *maContents[nPara]; + rName = rC.GetStyle(); + rFamily = rC.GetFamily(); +} + +void EditTextObjectImpl::SetStyleSheet(sal_Int32 nPara, const OUString& rName, const SfxStyleFamily& rFamily) +{ + if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size()) + return; + + ContentInfo& rC = *maContents[nPara]; + rC.SetStyle(rName); + rC.SetFamily(rFamily); +} + +bool EditTextObjectImpl::ImpChangeStyleSheets( + std::u16string_view rOldName, SfxStyleFamily eOldFamily, + const OUString& rNewName, SfxStyleFamily eNewFamily ) +{ + const size_t nParagraphs = maContents.size(); + bool bChanges = false; + + for (size_t nPara = 0; nPara < nParagraphs; ++nPara) + { + ContentInfo& rC = *maContents[nPara]; + if ( rC.GetFamily() == eOldFamily ) + { + if ( rC.GetStyle() == rOldName ) + { + rC.SetStyle(rNewName); + rC.SetFamily(eNewFamily); + bChanges = true; + } + } + } + return bChanges; +} + +bool EditTextObjectImpl::ChangeStyleSheets( + std::u16string_view rOldName, SfxStyleFamily eOldFamily, + const OUString& rNewName, SfxStyleFamily eNewFamily) +{ + bool bChanges = ImpChangeStyleSheets( rOldName, eOldFamily, rNewName, eNewFamily ); + if ( bChanges ) + ClearPortionInfo(); + + return bChanges; +} + +void EditTextObjectImpl::ChangeStyleSheetName( SfxStyleFamily eFamily, + std::u16string_view rOldName, const OUString& rNewName ) +{ + ImpChangeStyleSheets( rOldName, eFamily, rNewName, eFamily ); +} + +bool EditTextObjectImpl::operator==( const EditTextObject& rCompare ) const +{ + return Equals(toImpl(rCompare), true); +} + +bool EditTextObjectImpl::Equals( const EditTextObjectImpl& rCompare, bool bComparePool ) const +{ + if( this == &rCompare ) + return true; + + if( ( bComparePool && mpPool != rCompare.mpPool ) || + ( meMetric != rCompare.meMetric ) || + ( meUserType!= rCompare.meUserType ) || + ( meScriptType != rCompare.meScriptType ) || + ( mbVertical != rCompare.mbVertical ) || + ( meRotation != rCompare.meRotation ) ) + return false; + + return std::equal( + maContents.begin(), maContents.end(), rCompare.maContents.begin(), rCompare.maContents.end(), + [bComparePool](const auto& c1, const auto& c2) { return c1->Equals(*c2, bComparePool); }); +} + +// #i102062# +bool EditTextObjectImpl::isWrongListEqual(const EditTextObject& rComp) const +{ + const EditTextObjectImpl& rCompare = toImpl(rComp); + return std::equal( + maContents.begin(), maContents.end(), rCompare.maContents.begin(), rCompare.maContents.end(), + [](const auto& c1, const auto& c2) { return c1->isWrongListEqual(*c2); }); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editobj2.hxx b/editeng/source/editeng/editobj2.hxx new file mode 100644 index 000000000..250341fd4 --- /dev/null +++ b/editeng/source/editeng/editobj2.hxx @@ -0,0 +1,281 @@ +/* -*- 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 <editeng/editobj.hxx> +#include <editeng/fieldupdater.hxx> +#include <editeng/outliner.hxx> +#include <editdoc.hxx> + +#include <svl/sharedstring.hxx> +#include <svl/languageoptions.hxx> +#include <tools/long.hxx> +#include <tools/mapunit.hxx> + +#include <memory> +#include <vector> + +namespace editeng { + +struct Section; + +} + +namespace svl { + +class SharedStringPool; + +} + +class XEditAttribute +{ +private: + const SfxPoolItem* pItem; + sal_Int32 nStart; + sal_Int32 nEnd; + +public: + XEditAttribute( const SfxPoolItem& rAttr, sal_Int32 nStart, sal_Int32 nEnd ); + + const SfxPoolItem* GetItem() const { return pItem; } + + sal_Int32& GetStart() { return nStart; } + sal_Int32& GetEnd() { return nEnd; } + + sal_Int32 GetStart() const { return nStart; } + sal_Int32 GetEnd() const { return nEnd; } + + sal_Int32 GetLen() const { return nEnd-nStart; } + + bool IsFeature() const; + void SetItem(const SfxPoolItem& rNew); + + inline bool operator==( const XEditAttribute& rCompare ) const; +}; + +inline bool XEditAttribute::operator==( const XEditAttribute& rCompare ) const +{ + return (nStart == rCompare.nStart) && + (nEnd == rCompare.nEnd) && + ((pItem == rCompare.pItem) || + ((pItem->Which() == rCompare.pItem->Which()) && + (*pItem == *rCompare.pItem))); +} + +struct XParaPortion +{ + tools::Long nHeight; + sal_uInt16 nFirstLineOffset; + + EditLineList aLines; + TextPortionList aTextPortions; +}; + +class XParaPortionList +{ + typedef std::vector<std::unique_ptr<XParaPortion> > ListType; + ListType maList; + + VclPtr<OutputDevice> pRefDevPtr; + sal_uInt16 nStretchX; + sal_uInt16 nStretchY; + sal_uInt32 nPaperWidth; + +public: + XParaPortionList(OutputDevice* pRefDev, sal_uInt32 nPW, sal_uInt16 _nStretchX, sal_uInt16 _nStretchY); + + void push_back(XParaPortion* p); + const XParaPortion& operator[](size_t i) const; + + OutputDevice* GetRefDevPtr() const { return pRefDevPtr; } + sal_uInt32 GetPaperWidth() const { return nPaperWidth; } + bool RefDevIsVirtual() const {return pRefDevPtr->IsVirtual();} + const MapMode& GetRefMapMode() const { return pRefDevPtr->GetMapMode(); } + sal_uInt16 GetStretchX() const { return nStretchX; } + sal_uInt16 GetStretchY() const { return nStretchY; } +}; + +class ContentInfo +{ + friend class EditTextObjectImpl; + +private: + svl::SharedString maText; + OUString aStyle; + + std::vector<XEditAttribute> maCharAttribs; + SfxStyleFamily eFamily; + SfxItemSetFixed<EE_PARA_START, EE_CHAR_END> aParaAttribs; + std::unique_ptr<WrongList> + mpWrongs; + + ContentInfo( SfxItemPool& rPool ); + ContentInfo( const ContentInfo& rCopyFrom, SfxItemPool& rPoolToUse ); + +public: + ~ContentInfo(); + ContentInfo(const ContentInfo&) = delete; + ContentInfo& operator=(const ContentInfo&) = delete; + + void NormalizeString( svl::SharedStringPool& rPool ); + const svl::SharedString& GetSharedString() const { return maText;} + OUString GetText() const; + void SetText( const OUString& rStr ); + + void dumpAsXml(xmlTextWriterPtr pWriter) const; + + const std::vector<XEditAttribute>& GetCharAttribs() const { return maCharAttribs; } + std::vector<XEditAttribute>& GetCharAttribs() { return maCharAttribs; } + + const OUString& GetStyle() const { return aStyle; } + SfxStyleFamily GetFamily() const { return eFamily; } + + void SetStyle(const OUString& rStyle) { aStyle = rStyle; } + void SetFamily(const SfxStyleFamily& rFamily) { eFamily = rFamily; } + + const SfxItemSet& GetParaAttribs() const { return aParaAttribs; } + SfxItemSet& GetParaAttribs() { return aParaAttribs; } + + const WrongList* GetWrongList() const; + void SetWrongList( WrongList* p ); + bool Equals( const ContentInfo& rCompare, bool bComparePool ) const; + + // #i102062# + bool isWrongListEqual(const ContentInfo& rCompare) const; + +#if DEBUG_EDIT_ENGINE + void Dump() const; +#endif +}; + +class EditTextObjectImpl final : public EditTextObject +{ +public: + typedef std::vector<std::unique_ptr<ContentInfo> > ContentInfosType; + +private: + ContentInfosType maContents; + rtl::Reference<SfxItemPool> mpPool; + std::unique_ptr<XParaPortionList> mpPortionInfo; + + OutlinerMode meUserType; + SvtScriptType meScriptType; + TextRotation meRotation; + MapUnit meMetric; + + bool mbVertical; + + bool ImpChangeStyleSheets( std::u16string_view rOldName, SfxStyleFamily eOldFamily, + const OUString& rNewName, SfxStyleFamily eNewFamily ); + +public: + EditTextObjectImpl(SfxItemPool* pPool, MapUnit eDefaultMetric, bool bVertical, + TextRotation eRotation, SvtScriptType eScriptType); + EditTextObjectImpl( const EditTextObjectImpl& r ); + virtual ~EditTextObjectImpl() override; + + EditTextObjectImpl& operator=(const EditTextObjectImpl&) = delete; + + virtual OutlinerMode GetUserType() const override { return meUserType;} + virtual void SetUserType( OutlinerMode n ) override; + + virtual void NormalizeString( svl::SharedStringPool& rPool ) override; + virtual std::vector<svl::SharedString> GetSharedStrings() const override; + + virtual bool IsEffectivelyVertical() const override; + virtual bool GetVertical() const override; + virtual bool IsTopToBottom() const override; + virtual void SetVertical( bool bVert) override; + virtual void SetRotation(TextRotation nRotation) override; + virtual TextRotation GetRotation() const override; + + virtual SvtScriptType GetScriptType() const override { return meScriptType;} + + virtual std::unique_ptr<EditTextObject> Clone() const override; + + ContentInfo* CreateAndInsertContent(); + XEditAttribute CreateAttrib( const SfxPoolItem& rItem, sal_Int32 nStart, sal_Int32 nEnd ); + void DestroyAttrib( const XEditAttribute& rAttr ); + + ContentInfosType& GetContents() { return maContents;} + const ContentInfosType& GetContents() const { return maContents;} + SfxItemPool* GetPool() { return mpPool.get(); } + virtual const SfxItemPool* GetPool() const override { return mpPool.get(); } + XParaPortionList* GetPortionInfo() const { return mpPortionInfo.get(); } + void SetPortionInfo( std::unique_ptr<XParaPortionList> pP ) + { mpPortionInfo = std::move(pP); } + + virtual sal_Int32 GetParagraphCount() const override; + virtual OUString GetText(sal_Int32 nParagraph) const override; + + virtual void ClearPortionInfo() override; + + virtual bool HasOnlineSpellErrors() const override; + + virtual void GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const override; + + virtual bool RemoveCharAttribs( sal_uInt16 nWhich ) override; + + virtual void GetAllSections( std::vector<editeng::Section>& rAttrs ) const override; + + virtual bool IsFieldObject() const override; + virtual const SvxFieldItem* GetField() const override; + virtual const SvxFieldData* GetFieldData(sal_Int32 nPara, size_t nPos, sal_Int32 nType) const override; + + virtual bool HasField( sal_Int32 nType = css::text::textfield::Type::UNSPECIFIED ) const override; + + virtual const SfxItemSet& GetParaAttribs(sal_Int32 nPara) const override; + + virtual void GetStyleSheet(sal_Int32 nPara, OUString& rName, SfxStyleFamily& eFamily) const override; + virtual void SetStyleSheet(sal_Int32 nPara, const OUString& rName, const SfxStyleFamily& eFamily) override; + virtual bool ChangeStyleSheets( + std::u16string_view rOldName, SfxStyleFamily eOldFamily, const OUString& rNewName, SfxStyleFamily eNewFamily) override; + virtual void ChangeStyleSheetName(SfxStyleFamily eFamily, std::u16string_view rOldName, const OUString& rNewName) override; + + virtual editeng::FieldUpdater GetFieldUpdater() override { return editeng::FieldUpdater(*this); } + + bool HasMetric() const { return meMetric != MapUnit::LASTENUMDUMMY; } + MapUnit GetMetric() const { return meMetric; } + + virtual bool operator==( const EditTextObject& rCompare ) const override; + bool Equals( const EditTextObjectImpl& rCompare, bool bComparePool ) const; + + // #i102062# + virtual bool isWrongListEqual(const EditTextObject& rCompare) const override; + +#if DEBUG_EDIT_ENGINE + virtual void Dump() const override; +#endif + virtual void dumpAsXml(xmlTextWriterPtr pWriter) const override; +}; + +inline EditTextObjectImpl& toImpl(EditTextObject& rObj) +{ + assert(dynamic_cast<EditTextObjectImpl*>(&rObj)); + return static_cast<EditTextObjectImpl&>(rObj); +} + +inline const EditTextObjectImpl& toImpl(const EditTextObject& rObj) +{ + assert(dynamic_cast<const EditTextObjectImpl*>(&rObj)); + return static_cast<const EditTextObjectImpl&>(rObj); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editsel.cxx b/editeng/source/editeng/editsel.cxx new file mode 100644 index 000000000..3aeed7a6e --- /dev/null +++ b/editeng/source/editeng/editsel.cxx @@ -0,0 +1,94 @@ +/* -*- 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 "editsel.hxx" +#include "impedit.hxx" +#include <editeng/editview.hxx> + + + +EditSelFunctionSet::EditSelFunctionSet() +{ + pCurView = nullptr; +} + +void EditSelFunctionSet::CreateAnchor() +{ + if ( pCurView ) + pCurView->pImpEditView->CreateAnchor(); +} + +void EditSelFunctionSet::DestroyAnchor() +{ + // Only with multiple selection +} + +void EditSelFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool ) +{ + if ( pCurView ) + pCurView->pImpEditView->SetCursorAtPoint( rPointPixel ); +} + +bool EditSelFunctionSet::IsSelectionAtPoint( const Point& rPointPixel ) +{ + if ( pCurView ) + return pCurView->pImpEditView->IsSelectionAtPoint( rPointPixel ); + + return false; +} + +void EditSelFunctionSet::DeselectAtPoint( const Point& ) +{ +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// ! Implement when multiple selection is possible ! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +} + +void EditSelFunctionSet::BeginDrag() +{ + // Only with multiple selection +} + + +void EditSelFunctionSet::DeselectAll() +{ + if ( pCurView ) + pCurView->pImpEditView->DeselectAll(); +} + + + +EditSelectionEngine::EditSelectionEngine() : SelectionEngine( nullptr ) +{ + SetSelectionMode( SelectionMode::Range ); + EnableDrag( true ); +} + +void EditSelectionEngine::SetCurView( EditView* pNewView ) +{ + if ( GetFunctionSet() ) + const_cast<EditSelFunctionSet*>(static_cast<const EditSelFunctionSet*>(GetFunctionSet()))->SetCurView( pNewView ); + + if ( pNewView ) + SetWindow( pNewView->GetWindow() ); + else + SetWindow( nullptr ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editsel.hxx b/editeng/source/editeng/editsel.hxx new file mode 100644 index 000000000..8d2adfb67 --- /dev/null +++ b/editeng/source/editeng/editsel.hxx @@ -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 . + */ + +#pragma once + +#include <vcl/seleng.hxx> + +class EditView; + +class EditSelFunctionSet: public FunctionSet +{ +private: + EditView* pCurView; + +public: + EditSelFunctionSet(); + + virtual void BeginDrag() override; + + virtual void CreateAnchor() override; + virtual void DestroyAnchor() override; + + virtual void SetCursorAtPoint( const Point& rPointPixel, bool bDontSelectAtCursor = false ) override; + + virtual bool IsSelectionAtPoint( const Point& rPointPixel ) override; + virtual void DeselectAtPoint( const Point& rPointPixel ) override; + virtual void DeselectAll() override; + + void SetCurView( EditView* pView ) { pCurView = pView; } +}; + +class EditSelectionEngine : public SelectionEngine +{ +private: + +public: + EditSelectionEngine(); + + void SetCurView( EditView* pNewView ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editstt2.hxx b/editeng/source/editeng/editstt2.hxx new file mode 100644 index 000000000..4474df8b4 --- /dev/null +++ b/editeng/source/editeng/editstt2.hxx @@ -0,0 +1,96 @@ +/* -*- 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 <editeng/editstat.hxx> + +class InternalEditStatus : public EditStatus +{ + +public: + void TurnOnFlags( EEControlBits nFlags ) + { nControlBits |= nFlags; } + + void TurnOffFlags( EEControlBits nFlags ) + { nControlBits &= ~nFlags; } + + bool UseCharAttribs() const + { return bool( nControlBits & EEControlBits::USECHARATTRIBS ); } + + bool UseIdleFormatter() const + { return bool( nControlBits & EEControlBits::DOIDLEFORMAT); } + + bool AllowPasteSpecial() const + { return bool( nControlBits & EEControlBits::PASTESPECIAL ); } + + bool DoAutoIndenting() const + { return bool( nControlBits & EEControlBits::AUTOINDENTING ); } + + bool DoUndoAttribs() const + { return bool( nControlBits & EEControlBits::UNDOATTRIBS ); } + + bool OneCharPerLine() const + { return bool( nControlBits & EEControlBits::ONECHARPERLINE ); } + + bool IsOutliner() const + { return bool( nControlBits & EEControlBits::OUTLINER ); } + + bool DoNotUseColors() const + { return bool( nControlBits & EEControlBits::NOCOLORS ); } + + bool AllowBigObjects() const + { return bool( nControlBits & EEControlBits::ALLOWBIGOBJS ); } + + bool DoOnlineSpelling() const + { return bool( nControlBits & EEControlBits::ONLINESPELLING ); } + + bool DoStretch() const + { return bool( nControlBits & EEControlBits::STRETCHING ); } + + bool AutoPageSize() const + { return bool( nControlBits & EEControlBits::AUTOPAGESIZE ); } + bool AutoPageWidth() const + { return bool( nControlBits & EEControlBits::AUTOPAGESIZEX ); } + bool AutoPageHeight() const + { return bool( nControlBits & EEControlBits::AUTOPAGESIZEY ); } + + bool MarkNonUrlFields() const + { return bool( nControlBits & EEControlBits::MARKNONURLFIELDS ); } + + bool MarkUrlFields() const + { return bool( nControlBits & EEControlBits::MARKURLFIELDS ); } + + bool DoImportRTFStyleSheets() const + { return bool( nControlBits & EEControlBits::RTFSTYLESHEETS ); } + + bool DoAutoCorrect() const + { return bool( nControlBits & EEControlBits::AUTOCORRECT ); } + + bool DoAutoComplete() const + { return bool( nControlBits & EEControlBits::AUTOCOMPLETE ); } + + bool DoFormat100() const + { return bool( nControlBits & EEControlBits::FORMAT100 ); } + + bool ULSpaceSummation() const + { return bool( nControlBits & EEControlBits::ULSPACESUMMATION ); } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editundo.cxx b/editeng/source/editeng/editundo.cxx new file mode 100644 index 000000000..d957da25a --- /dev/null +++ b/editeng/source/editeng/editundo.cxx @@ -0,0 +1,679 @@ +/* -*- 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 "impedit.hxx" +#include "editundo.hxx" +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> +#include <utility> +#include <osl/diagnose.h> + + +static void lcl_DoSetSelection( EditView const * pView, sal_uInt16 nPara ) +{ + EPaM aEPaM( nPara, 0 ); + EditPaM aPaM( pView->GetImpEditEngine()->CreateEditPaM( aEPaM ) ); + aPaM.SetIndex( aPaM.GetNode()->Len() ); + EditSelection aSel( aPaM, aPaM ); + pView->GetImpEditView()->SetEditSelection( aSel ); +} + +EditUndoManager::EditUndoManager(sal_uInt16 nMaxUndoActionCount ) +: SfxUndoManager(nMaxUndoActionCount), + mpEditEngine(nullptr) +{ +} + +void EditUndoManager::SetEditEngine(EditEngine* pNew) +{ + mpEditEngine = pNew; +} + +bool EditUndoManager::Undo() +{ + if ( !mpEditEngine || GetUndoActionCount() == 0 ) + return false; + + DBG_ASSERT( mpEditEngine->GetActiveView(), "Active View?" ); + + if ( !mpEditEngine->GetActiveView() ) + { + if (!mpEditEngine->GetEditViews().empty()) + mpEditEngine->SetActiveView(mpEditEngine->GetEditViews()[0]); + else + { + OSL_FAIL("Undo in engine is not possible without a View! "); + return false; + } + } + + mpEditEngine->GetActiveView()->GetImpEditView()->DrawSelectionXOR(); // Remove the old selection + + mpEditEngine->SetUndoMode( true ); + bool bDone = SfxUndoManager::Undo(); + mpEditEngine->SetUndoMode( false ); + + EditSelection aNewSel( mpEditEngine->GetActiveView()->GetImpEditView()->GetEditSelection() ); + DBG_ASSERT( !aNewSel.IsInvalid(), "Invalid selection after Undo () "); + DBG_ASSERT( !aNewSel.DbgIsBuggy( mpEditEngine->GetEditDoc() ), "Broken selection afte Undo () "); + + aNewSel.Min() = aNewSel.Max(); + mpEditEngine->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); + if (mpEditEngine->IsUpdateLayout()) + mpEditEngine->FormatAndLayout( mpEditEngine->GetActiveView(), true ); + + return bDone; +} + +bool EditUndoManager::Redo() +{ + if ( !mpEditEngine || GetRedoActionCount() == 0 ) + return false; + + DBG_ASSERT( mpEditEngine->GetActiveView(), "Active View?" ); + + if ( !mpEditEngine->GetActiveView() ) + { + if (!mpEditEngine->GetEditViews().empty()) + mpEditEngine->SetActiveView(mpEditEngine->GetEditViews()[0]); + else + { + OSL_FAIL( "Redo in Engine without View not possible!" ); + return false; + } + } + + mpEditEngine->GetActiveView()->GetImpEditView()->DrawSelectionXOR(); // Remove the old selection + + mpEditEngine->SetUndoMode( true ); + bool bDone = SfxUndoManager::Redo(); + mpEditEngine->SetUndoMode( false ); + + EditSelection aNewSel( mpEditEngine->GetActiveView()->GetImpEditView()->GetEditSelection() ); + DBG_ASSERT( !aNewSel.IsInvalid(), "Invalid selection after Undo () "); + DBG_ASSERT( !aNewSel.DbgIsBuggy( mpEditEngine->GetEditDoc() ), "Broken selection afte Undo () "); + + aNewSel.Min() = aNewSel.Max(); + mpEditEngine->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); + if (mpEditEngine->IsUpdateLayout()) + mpEditEngine->FormatAndLayout( mpEditEngine->GetActiveView() ); + + return bDone; +} + +EditUndo::EditUndo(sal_uInt16 nI, EditEngine* pEE) : + nId(nI), mnViewShellId(-1), mpEditEngine(pEE) +{ + const EditView* pEditView = mpEditEngine ? mpEditEngine->GetActiveView() : nullptr; + const OutlinerViewShell* pViewShell = pEditView ? pEditView->GetImpEditView()->GetViewShell() : nullptr; + if (pViewShell) + mnViewShellId = pViewShell->GetViewShellId(); +} + +EditUndo::~EditUndo() +{ +} + + +sal_uInt16 EditUndo::GetId() const +{ + return nId; +} + +bool EditUndo::CanRepeat(SfxRepeatTarget&) const +{ + return false; +} + +OUString EditUndo::GetComment() const +{ + OUString aComment; + + if (mpEditEngine) + aComment = mpEditEngine->GetUndoComment( GetId() ); + + return aComment; +} + +ViewShellId EditUndo::GetViewShellId() const +{ + return mnViewShellId; +} + +EditUndoDelContent::EditUndoDelContent( + EditEngine* pEE, ContentNode* pNode, sal_Int32 nPortion) : + EditUndo(EDITUNDO_DELCONTENT, pEE), + bDelObject(true), + nNode(nPortion), + pContentNode(pNode) {} + +EditUndoDelContent::~EditUndoDelContent() +{ + if ( bDelObject ) + delete pContentNode; +} + +void EditUndoDelContent::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + GetEditEngine()->InsertContent( pContentNode, nNode ); + bDelObject = false; // belongs to the Engine again + EditSelection aSel( EditPaM( pContentNode, 0 ), EditPaM( pContentNode, pContentNode->Len() ) ); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel); +} + +void EditUndoDelContent::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + + EditEngine* pEE = GetEditEngine(); + + // pNode is no longer correct, if the paragraphs where merged + // in between Undos + pContentNode = pEE->GetEditDoc().GetObject( nNode ); + DBG_ASSERT( pContentNode, "EditUndoDelContent::Redo(): Node?!" ); + + pEE->RemoveParaPortion(nNode); + + // Do not delete node, depends on the undo! + pEE->GetEditDoc().Release( nNode ); + if (pEE->IsCallParaInsertedOrDeleted()) + pEE->ParagraphDeleted( nNode ); + + DeletedNodeInfo* pInf = new DeletedNodeInfo( pContentNode, nNode ); + pEE->AppendDeletedNodeInfo(pInf); + pEE->UpdateSelections(); + + ContentNode* pN = ( nNode < pEE->GetEditDoc().Count() ) + ? pEE->GetEditDoc().GetObject( nNode ) + : pEE->GetEditDoc().GetObject( nNode-1 ); + DBG_ASSERT( pN && ( pN != pContentNode ), "?! RemoveContent !? " ); + EditPaM aPaM( pN, pN->Len() ); + + bDelObject = true; // belongs to the Engine again + + pEE->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) ); +} + +EditUndoConnectParas::EditUndoConnectParas( + EditEngine* pEE, sal_Int32 nN, sal_uInt16 nSP, + SfxItemSet _aLeftParaAttribs, SfxItemSet _aRightParaAttribs, + const SfxStyleSheet* pLeftStyle, const SfxStyleSheet* pRightStyle, bool bBkwrd) : + EditUndo(EDITUNDO_CONNECTPARAS, pEE), + nNode(nN), + nSepPos(nSP), + aLeftParaAttribs(std::move(_aLeftParaAttribs)), + aRightParaAttribs(std::move(_aRightParaAttribs)), + eLeftStyleFamily(SfxStyleFamily::All), + eRightStyleFamily(SfxStyleFamily::All), + bBackward(bBkwrd) +{ + if ( pLeftStyle ) + { + aLeftStyleName = pLeftStyle->GetName(); + eLeftStyleFamily = pLeftStyle->GetFamily(); + } + if ( pRightStyle ) + { + aRightStyleName = pRightStyle->GetName(); + eRightStyleFamily = pRightStyle->GetFamily(); + } +} + +EditUndoConnectParas::~EditUndoConnectParas() +{ +} + +void EditUndoConnectParas::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + + // For SplitContent ParagraphInserted can not be called yet because the + // Outliner relies on the attributes to initialize the depth + + bool bCall = GetEditEngine()->IsCallParaInsertedOrDeleted(); + GetEditEngine()->SetCallParaInsertedOrDeleted(false); + + EditPaM aPaM = GetEditEngine()->SplitContent(nNode, nSepPos); + + GetEditEngine()->SetCallParaInsertedOrDeleted( bCall ); + if (GetEditEngine()->IsCallParaInsertedOrDeleted()) + { + GetEditEngine()->ParagraphInserted( nNode+1 ); + GetEditEngine()->SetParaAttribs( nNode+1, aRightParaAttribs ); + } + + // Calling SetParaAttribs is effective only after ParagraphInserted + GetEditEngine()->SetParaAttribs( nNode, aLeftParaAttribs ); + + if (GetEditEngine()->GetStyleSheetPool()) + { + if ( !aLeftStyleName.isEmpty() ) + GetEditEngine()->SetStyleSheet( nNode, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aLeftStyleName, eLeftStyleFamily )) ); + if ( !aRightStyleName.isEmpty() ) + GetEditEngine()->SetStyleSheet( nNode+1, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aRightStyleName, eRightStyleFamily )) ); + } + + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) ); +} + +void EditUndoConnectParas::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: Np Active View!" ); + EditPaM aPaM = GetEditEngine()->ConnectContents( nNode, bBackward ); + + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) ); +} + +EditUndoSplitPara::EditUndoSplitPara( + EditEngine* pEE, sal_Int32 nN, sal_uInt16 nSP) : + EditUndo(EDITUNDO_SPLITPARA, pEE), + nNode(nN), nSepPos(nSP) {} + +EditUndoSplitPara::~EditUndoSplitPara() {} + +void EditUndoSplitPara::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->ConnectContents(nNode, false); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) ); +} + +void EditUndoSplitPara::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->SplitContent(nNode, nSepPos); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) ); +} + +EditUndoInsertChars::EditUndoInsertChars( + EditEngine* pEE, const EPaM& rEPaM, OUString aStr) : + EditUndo(EDITUNDO_INSERTCHARS, pEE), + aEPaM(rEPaM), + aText(std::move(aStr)) {} + +void EditUndoInsertChars::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + EditSelection aSel( aPaM, aPaM ); + aSel.Max().SetIndex( aSel.Max().GetIndex() + aText.getLength() ); + EditPaM aNewPaM( GetEditEngine()->DeleteSelection(aSel) ); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aNewPaM, aNewPaM ) ); +} + +void EditUndoInsertChars::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + GetEditEngine()->InsertText(EditSelection(aPaM, aPaM), aText); + EditPaM aNewPaM( aPaM ); + aNewPaM.SetIndex( aNewPaM.GetIndex() + aText.getLength() ); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aNewPaM ) ); +} + +bool EditUndoInsertChars::Merge( SfxUndoAction* pNextAction ) +{ + EditUndoInsertChars* pNext = dynamic_cast<EditUndoInsertChars*>(pNextAction); + if (!pNext) + return false; + + if ( aEPaM.nPara != pNext->aEPaM.nPara ) + return false; + + if ( ( aEPaM.nIndex + aText.getLength() ) == pNext->aEPaM.nIndex ) + { + aText += pNext->aText; + return true; + } + return false; +} + +EditUndoRemoveChars::EditUndoRemoveChars( + EditEngine* pEE, const EPaM& rEPaM, OUString aStr) : + EditUndo(EDITUNDO_REMOVECHARS, pEE), + aEPaM(rEPaM), aText(std::move(aStr)) {} + +void EditUndoRemoveChars::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + EditSelection aSel( aPaM, aPaM ); + GetEditEngine()->InsertText(aSel, aText); + aSel.Max().SetIndex( aSel.Max().GetIndex() + aText.getLength() ); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel); +} + +void EditUndoRemoveChars::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + EditSelection aSel( aPaM, aPaM ); + aSel.Max().SetIndex( aSel.Max().GetIndex() + aText.getLength() ); + EditPaM aNewPaM = GetEditEngine()->DeleteSelection(aSel); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aNewPaM); +} + +EditUndoInsertFeature::EditUndoInsertFeature( + EditEngine* pEE, const EPaM& rEPaM, const SfxPoolItem& rFeature) : + EditUndo(EDITUNDO_INSERTFEATURE, pEE), + aEPaM(rEPaM), + pFeature(rFeature.Clone()) +{ + DBG_ASSERT( pFeature, "Feature could not be duplicated: EditUndoInsertFeature" ); +} + +EditUndoInsertFeature::~EditUndoInsertFeature() +{ +} + +void EditUndoInsertFeature::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + EditSelection aSel( aPaM, aPaM ); + // Attributes are then corrected implicitly by the document ... + aSel.Max().SetIndex( aSel.Max().GetIndex()+1 ); + GetEditEngine()->DeleteSelection(aSel); + aSel.Max().SetIndex( aSel.Max().GetIndex()-1 ); // For Selection + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel); +} + +void EditUndoInsertFeature::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + EditSelection aSel( aPaM, aPaM ); + GetEditEngine()->InsertFeature(aSel, *pFeature); + if ( pFeature->Which() == EE_FEATURE_FIELD ) + GetEditEngine()->UpdateFieldsOnly(); + aSel.Max().SetIndex( aSel.Max().GetIndex()+1 ); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel); +} + +EditUndoMoveParagraphs::EditUndoMoveParagraphs( + EditEngine* pEE, const Range& rParas, sal_Int32 n) : + EditUndo(EDITUNDO_MOVEPARAGRAPHS, pEE), nParagraphs(rParas), nDest(n) {} + +EditUndoMoveParagraphs::~EditUndoMoveParagraphs() {} + +void EditUndoMoveParagraphs::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + Range aTmpRange( nParagraphs ); + tools::Long nTmpDest = aTmpRange.Min(); + + tools::Long nDiff = nDest - aTmpRange.Min(); + aTmpRange.Min() += nDiff; + aTmpRange.Max() += nDiff; + + if ( nParagraphs.Min() < static_cast<tools::Long>(nDest) ) + { + tools::Long nLen = aTmpRange.Len(); + aTmpRange.Min() -= nLen; + aTmpRange.Max() -= nLen; + } + else + nTmpDest += aTmpRange.Len(); + + EditSelection aNewSel = GetEditEngine()->MoveParagraphs(aTmpRange, nTmpDest); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); +} + +void EditUndoMoveParagraphs::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditSelection aNewSel = GetEditEngine()->MoveParagraphs(nParagraphs, nDest); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); +} + +EditUndoSetStyleSheet::EditUndoSetStyleSheet( + EditEngine* pEE, sal_Int32 nP, OUString _aPrevName, SfxStyleFamily ePrevFam, + OUString _aNewName, SfxStyleFamily eNewFam, SfxItemSet _aPrevParaAttribs) : + EditUndo(EDITUNDO_STYLESHEET, pEE), + nPara(nP), + aPrevName(std::move(_aPrevName)), + aNewName(std::move(_aNewName)), + ePrevFamily(ePrevFam), + eNewFamily(eNewFam), + aPrevParaAttribs(std::move(_aPrevParaAttribs)) +{ +} + +EditUndoSetStyleSheet::~EditUndoSetStyleSheet() +{ +} + +void EditUndoSetStyleSheet::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + GetEditEngine()->SetStyleSheet( nPara, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aPrevName, ePrevFamily )) ); + GetEditEngine()->SetParaAttribsOnly( nPara, aPrevParaAttribs ); + lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara ); +} + +void EditUndoSetStyleSheet::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + GetEditEngine()->SetStyleSheet( nPara, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aNewName, eNewFamily )) ); + lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara ); +} + +EditUndoSetParaAttribs::EditUndoSetParaAttribs( + EditEngine* pEE, sal_Int32 nP, SfxItemSet _aPrevItems, SfxItemSet _aNewItems) : + EditUndo(EDITUNDO_PARAATTRIBS, pEE), + nPara(nP), + aPrevItems(std::move(_aPrevItems)), + aNewItems(std::move(_aNewItems)) {} + +EditUndoSetParaAttribs::~EditUndoSetParaAttribs() {} + +void EditUndoSetParaAttribs::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + GetEditEngine()->SetParaAttribsOnly( nPara, aPrevItems ); + lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara ); +} + +void EditUndoSetParaAttribs::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + GetEditEngine()->SetParaAttribsOnly( nPara, aNewItems ); + lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara ); +} + +EditUndoSetAttribs::EditUndoSetAttribs(EditEngine* pEE, const ESelection& rESel, SfxItemSet aNewItems) : + EditUndo(EDITUNDO_ATTRIBS, pEE), + aESel(rESel), + aNewAttribs(std::move(aNewItems)), + nSpecial(SetAttribsMode::NONE), + m_bSetSelection(true), + // When EditUndoSetAttribs actually is a RemoveAttribs this could be + // recognize by the empty itemset, but then it would have to be caught in + // its own place, which possible a setAttribs does with an empty itemset. + bSetIsRemove(false), + bRemoveParaAttribs(false), + nRemoveWhich(0) +{ +} + +namespace { + +struct RemoveAttribsFromPool +{ + SfxItemPool& mrPool; +public: + explicit RemoveAttribsFromPool(SfxItemPool& rPool) : mrPool(rPool) {} + void operator() (std::unique_ptr<ContentAttribsInfo> const & rInfo) + { + rInfo->RemoveAllCharAttribsFromPool(mrPool); + } +}; + +} + +EditUndoSetAttribs::~EditUndoSetAttribs() +{ + // Get Items from Pool... + SfxItemPool* pPool = aNewAttribs.GetPool(); + std::for_each(aPrevAttribs.begin(), aPrevAttribs.end(), RemoveAttribsFromPool(*pPool)); +} + +void EditUndoSetAttribs::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditEngine* pEE = GetEditEngine(); + bool bFields = false; + for ( sal_Int32 nPara = aESel.nStartPara; nPara <= aESel.nEndPara; nPara++ ) + { + const ContentAttribsInfo& rInf = *aPrevAttribs[nPara-aESel.nStartPara]; + + // first the paragraph attributes ... + pEE->SetParaAttribsOnly(nPara, rInf.GetPrevParaAttribs()); + + // Then the character attributes ... + // Remove all attributes including features, are later re-established. + pEE->RemoveCharAttribs(nPara, 0, true); + DBG_ASSERT( pEE->GetEditDoc().GetObject( nPara ), "Undo (SetAttribs): pNode = NULL!" ); + ContentNode* pNode = pEE->GetEditDoc().GetObject( nPara ); + for (const auto & nAttr : rInf.GetPrevCharAttribs()) + { + const EditCharAttrib& rX = *nAttr; + // is automatically "poolsized" + pEE->GetEditDoc().InsertAttrib(pNode, rX.GetStart(), rX.GetEnd(), *rX.GetItem()); + if (rX.Which() == EE_FEATURE_FIELD) + bFields = true; + } + } + if ( bFields ) + pEE->UpdateFieldsOnly(); + if (m_bSetSelection) + { + ImpSetSelection(); + } +} + +void EditUndoSetAttribs::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditEngine* pEE = GetEditEngine(); + + EditSelection aSel = pEE->CreateSelection(aESel); + if ( !bSetIsRemove ) + pEE->SetAttribs( aSel, aNewAttribs, nSpecial ); + else + pEE->RemoveCharAttribs( aSel, bRemoveParaAttribs, nRemoveWhich ); + + if (m_bSetSelection) + { + ImpSetSelection(); + } +} + +void EditUndoSetAttribs::AppendContentInfo(ContentAttribsInfo* pNew) +{ + aPrevAttribs.push_back(std::unique_ptr<ContentAttribsInfo>(pNew)); +} + +void EditUndoSetAttribs::ImpSetSelection() +{ + EditEngine* pEE = GetEditEngine(); + EditSelection aSel = pEE->CreateSelection(aESel); + pEE->GetActiveView()->GetImpEditView()->SetEditSelection(aSel); +} + +EditUndoTransliteration::EditUndoTransliteration(EditEngine* pEE, const ESelection& rESel, TransliterationFlags nM) : + EditUndo(EDITUNDO_TRANSLITERATE, pEE), + aOldESel(rESel), nMode(nM) {} + +EditUndoTransliteration::~EditUndoTransliteration() +{ +} + +void EditUndoTransliteration::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + + EditEngine* pEE = GetEditEngine(); + + EditSelection aSel = pEE->CreateSelection(aNewESel); + + // Insert text, but don't expand Attribs at the current position: + aSel = pEE->DeleteSelected( aSel ); + EditSelection aDelSel( aSel ); + aSel = pEE->InsertParaBreak( aSel ); + aDelSel.Max() = aSel.Min(); + aDelSel.Max().GetNode()->GetCharAttribs().DeleteEmptyAttribs( pEE->GetEditDoc().GetItemPool() ); + EditSelection aNewSel; + if ( pTxtObj ) + { + aNewSel = pEE->InsertText( *pTxtObj, aSel ); + } + else + { + aNewSel = pEE->InsertText( aSel, aText ); + } + if ( aNewSel.Min().GetNode() == aDelSel.Max().GetNode() ) + { + aNewSel.Min().SetNode( aDelSel.Min().GetNode() ); + aNewSel.Min().SetIndex( aNewSel.Min().GetIndex() + aDelSel.Min().GetIndex() ); + } + if ( aNewSel.Max().GetNode() == aDelSel.Max().GetNode() ) + { + aNewSel.Max().SetNode( aDelSel.Min().GetNode() ); + aNewSel.Max().SetIndex( aNewSel.Max().GetIndex() + aDelSel.Min().GetIndex() ); + } + pEE->DeleteSelected( aDelSel ); + pEE->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); +} + +void EditUndoTransliteration::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditEngine* pEE = GetEditEngine(); + + EditSelection aSel = pEE->CreateSelection(aOldESel); + EditSelection aNewSel = pEE->TransliterateText( aSel, nMode ); + pEE->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); +} + +EditUndoMarkSelection::EditUndoMarkSelection(EditEngine* pEE, const ESelection& rSel) : + EditUndo(EDITUNDO_MARKSELECTION, pEE), aSelection(rSel) {} + +EditUndoMarkSelection::~EditUndoMarkSelection() {} + +void EditUndoMarkSelection::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + if ( GetEditEngine()->GetActiveView() ) + { + if ( GetEditEngine()->IsFormatted() ) + GetEditEngine()->GetActiveView()->SetSelection( aSelection ); + else + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( GetEditEngine()->CreateSelection(aSelection) ); + } +} + +void EditUndoMarkSelection::Redo() +{ + // For redo unimportant, because at the beginning of the undo parentheses +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editundo.hxx b/editeng/source/editeng/editundo.hxx new file mode 100644 index 000000000..d08bc4810 --- /dev/null +++ b/editeng/source/editeng/editundo.hxx @@ -0,0 +1,291 @@ +/* -*- 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 <editdoc.hxx> +#include <editeng/editund2.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editobj.hxx> +#include <vector> +#include <memory> + +class EditTextObject; +class EditEngine; +enum class SetAttribsMode; +enum class TransliterationFlags; + +// EditUndoDelContent + +class EditUndoDelContent : public EditUndo +{ +private: + bool bDelObject; + sal_Int32 nNode; + ContentNode* pContentNode; // Points to the valid, + // undestroyed object! + +public: + EditUndoDelContent(EditEngine* pEE, ContentNode* pNode, sal_Int32 nPortion); + virtual ~EditUndoDelContent() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoConnectParas + +class EditUndoConnectParas : public EditUndo +{ +private: + sal_Int32 nNode; + sal_uInt16 nSepPos; + SfxItemSet aLeftParaAttribs; + SfxItemSet aRightParaAttribs; + + // 2 Pointers would be nicer but then it would have to be a SfxListener. + OUString aLeftStyleName; + OUString aRightStyleName; + SfxStyleFamily eLeftStyleFamily; + SfxStyleFamily eRightStyleFamily; + + bool bBackward; + +public: + EditUndoConnectParas(EditEngine* pEE, sal_Int32 nNode, sal_uInt16 nSepPos, + SfxItemSet aLeftParaAttribs, SfxItemSet aRightParaAttribs, + const SfxStyleSheet* pLeftStyle, const SfxStyleSheet* pRightStyle, bool bBackward); + virtual ~EditUndoConnectParas() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoSplitPara + +class EditUndoSplitPara : public EditUndo +{ +private: + sal_Int32 nNode; + sal_uInt16 nSepPos; + +public: + EditUndoSplitPara(EditEngine* pEE, sal_Int32 nNode, sal_uInt16 nSepPos); + virtual ~EditUndoSplitPara() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoInsertChars + +class EditUndoInsertChars : public EditUndo +{ +private: + EPaM aEPaM; + OUString aText; + +public: + EditUndoInsertChars(EditEngine* pEE, const EPaM& rEPaM, OUString aStr); + + virtual void Undo() override; + virtual void Redo() override; + + virtual bool Merge( SfxUndoAction *pNextAction ) override; +}; + + +// EditUndoRemoveChars + +class EditUndoRemoveChars : public EditUndo +{ +private: + EPaM aEPaM; + OUString aText; + +public: + EditUndoRemoveChars(EditEngine* pEE, const EPaM& rEPaM, OUString aStr); + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoInsertFeature + +class EditUndoInsertFeature : public EditUndo +{ +private: + EPaM aEPaM; + std::unique_ptr<SfxPoolItem> pFeature; + +public: + EditUndoInsertFeature(EditEngine* pEE, const EPaM& rEPaM, const SfxPoolItem& rFeature); + virtual ~EditUndoInsertFeature() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoMoveParagraphs + +class EditUndoMoveParagraphs: public EditUndo +{ +private: + Range nParagraphs; + sal_Int32 nDest; + +public: + EditUndoMoveParagraphs(EditEngine* pEE, const Range& rParas, sal_Int32 nDest); + virtual ~EditUndoMoveParagraphs() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoSetStyleSheet + +class EditUndoSetStyleSheet: public EditUndo +{ +private: + sal_Int32 nPara; + OUString aPrevName; + OUString aNewName; + SfxStyleFamily ePrevFamily; + SfxStyleFamily eNewFamily; + SfxItemSet aPrevParaAttribs; + +public: + EditUndoSetStyleSheet(EditEngine* pEE, sal_Int32 nPara, + OUString aPrevName, SfxStyleFamily ePrevFamily, + OUString aNewName, SfxStyleFamily eNewFamily, + SfxItemSet aPrevParaAttribs); + virtual ~EditUndoSetStyleSheet() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoSetParaAttribs + +class EditUndoSetParaAttribs: public EditUndo +{ +private: + sal_Int32 nPara; + SfxItemSet aPrevItems; + SfxItemSet aNewItems; + +public: + EditUndoSetParaAttribs(EditEngine* pEE, sal_Int32 nPara, SfxItemSet aPrevItems, SfxItemSet aNewItems); + virtual ~EditUndoSetParaAttribs() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoSetAttribs + +class EditUndoSetAttribs: public EditUndo +{ +private: + typedef std::vector<std::unique_ptr<ContentAttribsInfo> > InfoArrayType; + + ESelection aESel; + SfxItemSet aNewAttribs; + InfoArrayType aPrevAttribs; + + SetAttribsMode nSpecial; + /// Once the attributes are set / unset, set the selection to the end of the formatted range? + bool m_bSetSelection; + bool bSetIsRemove; + bool bRemoveParaAttribs; + sal_uInt16 nRemoveWhich; + + void ImpSetSelection(); + + +public: + EditUndoSetAttribs(EditEngine* pEE, const ESelection& rESel, SfxItemSet aNewItems); + virtual ~EditUndoSetAttribs() override; + + SfxItemSet& GetNewAttribs() { return aNewAttribs; } + + void SetSpecial( SetAttribsMode n ) { nSpecial = n; } + void SetUpdateSelection( bool bSetSelection ) { m_bSetSelection = bSetSelection; } + void SetRemoveAttribs( bool b ) { bSetIsRemove = b; } + void SetRemoveParaAttribs( bool b ) { bRemoveParaAttribs = b; } + void SetRemoveWhich( sal_uInt16 n ) { nRemoveWhich = n; } + + virtual void Undo() override; + virtual void Redo() override; + + void AppendContentInfo(ContentAttribsInfo* pNew); +}; + + +// EditUndoTransliteration + +class EditUndoTransliteration: public EditUndo +{ +private: + ESelection aOldESel; + ESelection aNewESel; + + TransliterationFlags + nMode; + std::unique_ptr<EditTextObject> + pTxtObj; + OUString aText; + +public: + EditUndoTransliteration(EditEngine* pEE, const ESelection& rESel, TransliterationFlags nMode); + virtual ~EditUndoTransliteration() override; + + void SetText( const OUString& rText ) { aText = rText; } + void SetText( std::unique_ptr<EditTextObject> pObj ) { pTxtObj = std::move( pObj ); } + void SetNewSelection( const ESelection& rSel ) { aNewESel = rSel; } + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoMarkSelection + +class EditUndoMarkSelection: public EditUndo +{ +private: + ESelection aSelection; + +public: + EditUndoMarkSelection(EditEngine* pEE, const ESelection& rSel); + virtual ~EditUndoMarkSelection() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editview.cxx b/editeng/source/editeng/editview.cxx new file mode 100644 index 000000000..320fd52ea --- /dev/null +++ b/editeng/source/editeng/editview.cxx @@ -0,0 +1,1702 @@ +/* -*- 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 <vcl/image.hxx> + +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> + +#include <i18nlangtag/languagetag.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <svl/languageoptions.hxx> +#include <svtools/ctrltool.hxx> +#include <svtools/langtab.hxx> +#include <tools/stream.hxx> + +#include <svl/srchitem.hxx> + +#include "impedit.hxx" +#include <comphelper/propertyvalue.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/flditem.hxx> +#include <editeng/svxacorr.hxx> +#include <editeng/langitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/eerdll.hxx> +#include <eerdll2.hxx> +#include <editeng/editrids.hrc> +#include <editeng.hxx> +#include <i18nlangtag/lang.h> +#include <vcl/window.hxx> +#include <editeng/acorrcfg.hxx> +#include <editeng/unolingu.hxx> +#include <unotools/lingucfg.hxx> + +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/linguistic2/XDictionary.hpp> +#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp> +#include <linguistic/lngprops.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <sfx2/viewsh.hxx> +#include <osl/diagnose.h> +#include <boost/property_tree/json_parser.hpp> + +#include <com/sun/star/lang/XServiceInfo.hpp> + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; + + +// static +LanguageType EditView::CheckLanguage( + const OUString &rText, + const Reference< linguistic2::XSpellChecker1 >& xSpell, + const Reference< linguistic2::XLanguageGuessing >& xLangGuess, + bool bIsParaText ) +{ + LanguageType nLang = LANGUAGE_NONE; + if (bIsParaText) // check longer texts with language-guessing... + { + if (!xLangGuess.is()) + return nLang; + + LanguageTag aGuessTag( xLangGuess->guessPrimaryLanguage( rText, 0, rText.getLength()) ); + + // If the result from language guessing does not provide a 'Country' + // part, try to get it by looking up the locale setting of the office, + // "Tools/Options - Language Settings - Languages: Locale setting", if + // the language matches. + if ( aGuessTag.getCountry().isEmpty() ) + { + const LanguageTag& rAppLocaleTag = Application::GetSettings().GetLanguageTag(); + if (rAppLocaleTag.getLanguage() == aGuessTag.getLanguage()) + nLang = rAppLocaleTag.getLanguageType(); + } + if (nLang == LANGUAGE_NONE) // language not found by looking up the system language... + nLang = aGuessTag.makeFallback().getLanguageType(); // best known locale match + if (nLang == LANGUAGE_SYSTEM) + nLang = Application::GetSettings().GetLanguageTag().getLanguageType(); + if (nLang == LANGUAGE_DONTKNOW) + nLang = LANGUAGE_NONE; + } + else // check single word + { + if (!xSpell.is()) + return nLang; + + + // build list of languages to check + + LanguageType aLangList[4]; + const AllSettings& rSettings = Application::GetSettings(); + SvtLinguOptions aLinguOpt; + SvtLinguConfig().GetOptions( aLinguOpt ); + // The default document language from "Tools/Options - Language Settings - Languages: Western" + aLangList[0] = MsLangId::resolveSystemLanguageByScriptType( aLinguOpt.nDefaultLanguage, + css::i18n::ScriptType::LATIN); + // The one from "Tools/Options - Language Settings - Languages: User interface" + aLangList[1] = rSettings.GetUILanguageTag().getLanguageType(); + // The one from "Tools/Options - Language Settings - Languages: Locale setting" + aLangList[2] = rSettings.GetLanguageTag().getLanguageType(); + // en-US + aLangList[3] = LANGUAGE_ENGLISH_US; +#ifdef DEBUG + lang::Locale a0( LanguageTag::convertToLocale( aLangList[0] ) ); + lang::Locale a1( LanguageTag::convertToLocale( aLangList[1] ) ); + lang::Locale a2( LanguageTag::convertToLocale( aLangList[2] ) ); + lang::Locale a3( LanguageTag::convertToLocale( aLangList[3] ) ); +#endif + + for (const LanguageType& nTmpLang : aLangList) + { + if (nTmpLang != LANGUAGE_NONE && nTmpLang != LANGUAGE_DONTKNOW) + { + if (xSpell->hasLanguage( static_cast<sal_uInt16>(nTmpLang) ) && + xSpell->isValid( rText, static_cast<sal_uInt16>(nTmpLang), Sequence< PropertyValue >() )) + { + nLang = nTmpLang; + break; + } + } + } + } + + return nLang; +} + +EditViewCallbacks::~EditViewCallbacks() +{ +} + +EditView::EditView( EditEngine* pEng, vcl::Window* pWindow ) +{ + pImpEditView.reset( new ImpEditView( this, pEng, pWindow ) ); +} + +EditView::~EditView() +{ +} + +void EditView::setEditViewCallbacks(EditViewCallbacks* pEditViewCallbacks) +{ + pImpEditView->setEditViewCallbacks(pEditViewCallbacks); +} + +EditViewCallbacks* EditView::getEditViewCallbacks() const +{ + return pImpEditView->getEditViewCallbacks(); +} + +ImpEditEngine* EditView::GetImpEditEngine() const +{ + return pImpEditView->pEditEngine->pImpEditEngine.get(); +} + +EditEngine* EditView::GetEditEngine() const +{ + return pImpEditView->pEditEngine; +} + +tools::Rectangle EditView::GetInvalidateRect() const +{ + if ( !pImpEditView->DoInvalidateMore() ) + return pImpEditView->aOutArea; + else + { + tools::Rectangle aRect( pImpEditView->aOutArea ); + tools::Long nMore = pImpEditView->GetOutputDevice().PixelToLogic( Size( pImpEditView->GetInvalidateMore(), 0 ) ).Width(); + aRect.AdjustLeft( -nMore ); + aRect.AdjustRight(nMore ); + aRect.AdjustTop( -nMore ); + aRect.AdjustBottom(nMore ); + return aRect; + } +} + +namespace { + +tools::Rectangle lcl_negateRectX(const tools::Rectangle& rRect) +{ + return tools::Rectangle(-rRect.Right(), rRect.Top(), -rRect.Left(), rRect.Bottom()); +} + +} + +void EditView::InvalidateWindow(const tools::Rectangle& rClipRect) +{ + bool bNegativeX = IsNegativeX(); + if (EditViewCallbacks* pEditViewCallbacks = pImpEditView->getEditViewCallbacks()) + { + // do not invalidate and trigger a global repaint, but forward + // the need for change to the applied EditViewCallback, can e.g. + // be used to visualize the active edit text in an OverlayObject + pEditViewCallbacks->EditViewInvalidate(bNegativeX ? lcl_negateRectX(rClipRect) : rClipRect); + } + else + { + // classic mode: invalidate and trigger full repaint + // of the changed area + GetWindow()->Invalidate(bNegativeX ? lcl_negateRectX(rClipRect) : rClipRect); + } +} + +void EditView::InvalidateOtherViewWindows( const tools::Rectangle& rInvRect ) +{ + if (comphelper::LibreOfficeKit::isActive()) + { + bool bNegativeX = IsNegativeX(); + for (auto& pWin : pImpEditView->aOutWindowSet) + { + if (pWin) + pWin->Invalidate( bNegativeX ? lcl_negateRectX(rInvRect) : rInvRect ); + } + } +} + +void EditView::Invalidate() +{ + const tools::Rectangle& rInvRect = GetInvalidateRect(); + pImpEditView->InvalidateAtWindow(rInvRect); + InvalidateOtherViewWindows(rInvRect); +} + +void EditView::SetReadOnly( bool bReadOnly ) +{ + pImpEditView->bReadOnly = bReadOnly; +} + +bool EditView::IsReadOnly() const +{ + return pImpEditView->bReadOnly; +} + +void EditView::SetSelection( const ESelection& rESel ) +{ + // If someone has just left an empty attribute, and then the outliner manipulates the + // selection, call the CursorMoved method so that empty attributes get cleaned up. + if ( !HasSelection() ) + { + // tdf#113591 Get node from EditDoc, as the selection might have a pointer to an + // already deleted node. + const ContentNode* pNode = pImpEditView->pEditEngine->GetEditDoc().GetEndPaM().GetNode(); + pImpEditView->pEditEngine->CursorMoved( pNode ); + } + EditSelection aNewSelection( pImpEditView->pEditEngine->pImpEditEngine->ConvertSelection( + rESel.nStartPara, rESel.nStartPos, rESel.nEndPara, rESel.nEndPos ) ); + + // If the selection is manipulated after a KeyInput: + pImpEditView->pEditEngine->CheckIdleFormatter(); + + // Selection may not start/end at an invisible paragraph: + const ParaPortion* pPortion = pImpEditView->pEditEngine->FindParaPortion( aNewSelection.Min().GetNode() ); + if ( !pPortion->IsVisible() ) + { + pPortion = pImpEditView->pEditEngine->GetPrevVisPortion( pPortion ); + ContentNode* pNode = pPortion ? pPortion->GetNode() : pImpEditView->pEditEngine->GetEditDoc().GetObject( 0 ); + aNewSelection.Min() = EditPaM( pNode, pNode->Len() ); + } + pPortion = pImpEditView->pEditEngine->FindParaPortion( aNewSelection.Max().GetNode() ); + if ( !pPortion->IsVisible() ) + { + pPortion = pImpEditView->pEditEngine->GetPrevVisPortion( pPortion ); + ContentNode* pNode = pPortion ? pPortion->GetNode() : pImpEditView->pEditEngine->GetEditDoc().GetObject( 0 ); + aNewSelection.Max() = EditPaM( pNode, pNode->Len() ); + } + + pImpEditView->DrawSelectionXOR(); + pImpEditView->SetEditSelection( aNewSelection ); + pImpEditView->DrawSelectionXOR(); + bool bGotoCursor = pImpEditView->DoAutoScroll(); + + // comments section in Writer: + // don't scroll to the selection if it is + // out of visible area of comment canvas. + if (HasSelection()) + ShowCursor( bGotoCursor ); +} + +ESelection EditView::GetSelection() const +{ + ESelection aSelection; + + aSelection.nStartPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( pImpEditView->GetEditSelection().Min().GetNode() ); + aSelection.nEndPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( pImpEditView->GetEditSelection().Max().GetNode() ); + + aSelection.nStartPos = pImpEditView->GetEditSelection().Min().GetIndex(); + aSelection.nEndPos = pImpEditView->GetEditSelection().Max().GetIndex(); + + return aSelection; +} + +bool EditView::HasSelection() const +{ + return pImpEditView->HasSelection(); +} + +bool EditView::IsSelectionAtPoint(const Point& rPointPixel) +{ + return pImpEditView->IsSelectionAtPoint(rPointPixel); +} + +void EditView::DeleteSelected() +{ + pImpEditView->DeleteSelected(); +} + +SvtScriptType EditView::GetSelectedScriptType() const +{ + return pImpEditView->pEditEngine->GetScriptType( pImpEditView->GetEditSelection() ); +} + +void EditView::GetSelectionRectangles(std::vector<tools::Rectangle>& rLogicRects) const +{ + return pImpEditView->GetSelectionRectangles(pImpEditView->GetEditSelection(), rLogicRects); +} + +void EditView::Paint( const tools::Rectangle& rRect, OutputDevice* pTargetDevice ) +{ + pImpEditView->pEditEngine->pImpEditEngine->Paint( pImpEditView.get(), rRect, pTargetDevice ); +} + +void EditView::SetEditEngine( EditEngine* pEditEng ) +{ + pImpEditView->pEditEngine = pEditEng; + EditSelection aStartSel = pImpEditView->pEditEngine->GetEditDoc().GetStartPaM(); + pImpEditView->SetEditSelection( aStartSel ); +} + +void EditView::SetWindow( vcl::Window* pWin ) +{ + pImpEditView->pOutWin = pWin; + pImpEditView->pEditEngine->pImpEditEngine->GetSelEngine().Reset(); +} + +vcl::Window* EditView::GetWindow() const +{ + return pImpEditView->pOutWin; +} + +OutputDevice& EditView::GetOutputDevice() const +{ + return pImpEditView->GetOutputDevice(); +} + +LanguageType EditView::GetInputLanguage() const +{ + // it might make sense to add this to getEditViewCallbacks + if (const vcl::Window* pWindow = GetWindow()) + return pWindow->GetInputLanguage(); + return LANGUAGE_DONTKNOW; +} + +bool EditView::HasOtherViewWindow( vcl::Window* pWin ) +{ + OutWindowSet& rOutWindowSet = pImpEditView->aOutWindowSet; + auto found = std::find(rOutWindowSet.begin(), rOutWindowSet.end(), pWin); + return (found != rOutWindowSet.end()); +} + +bool EditView::AddOtherViewWindow( vcl::Window* pWin ) +{ + if (HasOtherViewWindow(pWin)) + return false; + pImpEditView->aOutWindowSet.emplace_back(pWin); + return true; +} + +bool EditView::RemoveOtherViewWindow( vcl::Window* pWin ) +{ + OutWindowSet& rOutWindowSet = pImpEditView->aOutWindowSet; + auto found = std::find(rOutWindowSet.begin(), rOutWindowSet.end(), pWin); + if (found == rOutWindowSet.end()) + return false; + rOutWindowSet.erase(found); + return true; +} + +void EditView::SetVisArea( const tools::Rectangle& rRect ) +{ + pImpEditView->SetVisDocStartPos( rRect.TopLeft() ); +} + +tools::Rectangle EditView::GetVisArea() const +{ + return pImpEditView->GetVisDocArea(); +} + +void EditView::SetOutputArea( const tools::Rectangle& rRect ) +{ + pImpEditView->SetOutputArea( rRect ); + + // the rest here only if it is an API call: + pImpEditView->CalcAnchorPoint(); + if ( pImpEditView->pEditEngine->pImpEditEngine->GetStatus().AutoPageSize() ) + pImpEditView->RecalcOutputArea(); + pImpEditView->ShowCursor( false, false ); +} + +const tools::Rectangle& EditView::GetOutputArea() const +{ + return pImpEditView->GetOutputArea(); +} + +PointerStyle EditView::GetPointer() const +{ + return pImpEditView->GetPointer(); +} + +vcl::Cursor* EditView::GetCursor() const +{ + return pImpEditView->pCursor.get(); +} + +void EditView::InsertText( const OUString& rStr, bool bSelect, bool bLOKShowSelect ) +{ + + EditEngine* pEE = pImpEditView->pEditEngine; + + if (bLOKShowSelect) + pImpEditView->DrawSelectionXOR(); + + EditPaM aPaM1; + if ( bSelect ) + { + EditSelection aTmpSel( pImpEditView->GetEditSelection() ); + aTmpSel.Adjust( pEE->GetEditDoc() ); + aPaM1 = aTmpSel.Min(); + } + + pEE->UndoActionStart( EDITUNDO_INSERT ); + EditPaM aPaM2( pEE->InsertText( pImpEditView->GetEditSelection(), rStr ) ); + pEE->UndoActionEnd(); + + if ( bSelect ) + { + DBG_ASSERT( !aPaM1.DbgIsBuggy( pEE->GetEditDoc() ), "Insert: PaM broken" ); + pImpEditView->SetEditSelection( EditSelection( aPaM1, aPaM2 ) ); + } + else + pImpEditView->SetEditSelection( EditSelection( aPaM2, aPaM2 ) ); + + if (bLOKShowSelect) + pEE->FormatAndLayout( this ); +} + +bool EditView::PostKeyEvent( const KeyEvent& rKeyEvent, vcl::Window const * pFrameWin ) +{ + return pImpEditView->PostKeyEvent( rKeyEvent, pFrameWin ); +} + +bool EditView::MouseButtonUp( const MouseEvent& rMouseEvent ) +{ + return pImpEditView->MouseButtonUp( rMouseEvent ); +} + +void EditView::ReleaseMouse() +{ + return pImpEditView->ReleaseMouse(); +} + +bool EditView::MouseButtonDown( const MouseEvent& rMouseEvent ) +{ + return pImpEditView->MouseButtonDown( rMouseEvent ); +} + +bool EditView::MouseMove( const MouseEvent& rMouseEvent ) +{ + return pImpEditView->MouseMove( rMouseEvent ); +} + +bool EditView::Command(const CommandEvent& rCEvt) +{ + return pImpEditView->Command(rCEvt); +} + +void EditView::SetBroadcastLOKViewCursor(bool bSet) +{ + pImpEditView->SetBroadcastLOKViewCursor(bSet); +} + +tools::Rectangle EditView::GetEditCursor() const +{ + return pImpEditView->GetEditCursor(); +} + +void EditView::ShowCursor( bool bGotoCursor, bool bForceVisCursor, bool bActivate ) +{ + if ( !pImpEditView->pEditEngine->HasView( this ) ) + return; + + // The control word is more important: + if ( !pImpEditView->DoAutoScroll() ) + bGotoCursor = false; + pImpEditView->ShowCursor( bGotoCursor, bForceVisCursor ); + + if (pImpEditView->mpViewShell && !bActivate) + { + if (!pImpEditView->pOutWin) + return; + VclPtr<vcl::Window> pParent = pImpEditView->pOutWin->GetParentWithLOKNotifier(); + if (pParent && pParent->GetLOKWindowId() != 0) + return; + + static const OString aPayload = OString::boolean(true); + pImpEditView->mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload.getStr()); + pImpEditView->mpViewShell->NotifyOtherViews(LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); + } +} + +void EditView::HideCursor(bool bDeactivate) +{ + pImpEditView->GetCursor()->Hide(); + + if (pImpEditView->mpViewShell && !bDeactivate) + { + if (!pImpEditView->pOutWin) + return; + VclPtr<vcl::Window> pParent = pImpEditView->pOutWin->GetParentWithLOKNotifier(); + if (pParent && pParent->GetLOKWindowId() != 0) + return; + + OString aPayload = OString::boolean(false); + pImpEditView->mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload.getStr()); + pImpEditView->mpViewShell->NotifyOtherViews(LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); + } +} + +Pair EditView::Scroll( tools::Long ndX, tools::Long ndY, ScrollRangeCheck nRangeCheck ) +{ + return pImpEditView->Scroll( ndX, ndY, nRangeCheck ); +} + +const SfxItemSet& EditView::GetEmptyItemSet() const +{ + return pImpEditView->pEditEngine->GetEmptyItemSet(); +} + +void EditView::SetAttribs( const SfxItemSet& rSet ) +{ + DBG_ASSERT( !pImpEditView->aEditSelection.IsInvalid(), "Blind Selection in..." ); + + pImpEditView->DrawSelectionXOR(); + pImpEditView->pEditEngine->SetAttribs( pImpEditView->GetEditSelection(), rSet, SetAttribsMode::WholeWord ); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +void EditView::RemoveAttribsKeepLanguages( bool bRemoveParaAttribs ) +{ + + pImpEditView->DrawSelectionXOR(); + pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS ); + EditSelection aSelection( pImpEditView->GetEditSelection() ); + + for (sal_uInt16 nWID = EE_ITEMS_START; nWID <= EE_ITEMS_END; ++nWID) + { + bool bIsLang = EE_CHAR_LANGUAGE == nWID || + EE_CHAR_LANGUAGE_CJK == nWID || + EE_CHAR_LANGUAGE_CTL == nWID; + if (!bIsLang) + pImpEditView->pEditEngine->RemoveCharAttribs( aSelection, bRemoveParaAttribs, nWID ); + } + + pImpEditView->pEditEngine->UndoActionEnd(); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +void EditView::RemoveAttribs( bool bRemoveParaAttribs, sal_uInt16 nWhich ) +{ + RemoveAttribs(bRemoveParaAttribs ? EERemoveParaAttribsMode::RemoveAll + : EERemoveParaAttribsMode::RemoveCharItems, nWhich); +} + +void EditView::RemoveAttribs( EERemoveParaAttribsMode eMode, sal_uInt16 nWhich ) +{ + pImpEditView->DrawSelectionXOR(); + pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS ); + pImpEditView->pEditEngine->RemoveCharAttribs( pImpEditView->GetEditSelection(), eMode, nWhich ); + pImpEditView->pEditEngine->UndoActionEnd(); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +void EditView::RemoveCharAttribs( sal_Int32 nPara, sal_uInt16 nWhich ) +{ + pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS ); + pImpEditView->pEditEngine->RemoveCharAttribs( nPara, nWhich ); + pImpEditView->pEditEngine->UndoActionEnd(); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +SfxItemSet EditView::GetAttribs() +{ + DBG_ASSERT( !pImpEditView->aEditSelection.IsInvalid(), "Blind Selection in..." ); + return pImpEditView->pEditEngine->pImpEditEngine->GetAttribs( pImpEditView->GetEditSelection() ); +} + +void EditView::Undo() +{ + pImpEditView->pEditEngine->Undo( this ); +} + +void EditView::Redo() +{ + pImpEditView->pEditEngine->Redo( this ); +} + +ErrCode EditView::Read( SvStream& rInput, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs ) +{ + EditSelection aOldSel( pImpEditView->GetEditSelection() ); + pImpEditView->DrawSelectionXOR(); + pImpEditView->pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_READ ); + EditPaM aEndPaM = pImpEditView->pEditEngine->pImpEditEngine->Read( rInput, "", eFormat, aOldSel, pHTTPHeaderAttrs ); + pImpEditView->pEditEngine->pImpEditEngine->UndoActionEnd(); + EditSelection aNewSel( aEndPaM, aEndPaM ); + + pImpEditView->SetEditSelection( aNewSel ); + bool bGotoCursor = pImpEditView->DoAutoScroll(); + ShowCursor( bGotoCursor ); + + return rInput.GetError(); +} + +void EditView::Cut() +{ + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + pImpEditView->CutCopy( aClipBoard, true ); +} + +Reference<css::datatransfer::clipboard::XClipboard> EditView::GetClipboard() const +{ + return pImpEditView->GetClipboard(); +} + +css::uno::Reference< css::datatransfer::XTransferable > EditView::GetTransferable() const +{ + uno::Reference< datatransfer::XTransferable > xData = + GetEditEngine()->CreateTransferable( pImpEditView->GetEditSelection() ); + return xData; +} + +void EditView::Copy() +{ + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + pImpEditView->CutCopy( aClipBoard, false ); +} + +void EditView::Paste() +{ + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + pImpEditView->Paste( aClipBoard ); +} + +void EditView::PasteSpecial() +{ + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + pImpEditView->Paste(aClipBoard, true ); +} + +Point EditView::GetWindowPosTopLeft( sal_Int32 nParagraph ) +{ + Point aDocPos( pImpEditView->pEditEngine->GetDocPosTopLeft( nParagraph ) ); + return pImpEditView->GetWindowPos( aDocPos ); +} + +void EditView::SetSelectionMode( EESelectionMode eMode ) +{ + pImpEditView->SetSelectionMode( eMode ); +} + +OUString EditView::GetSelected() const +{ + return pImpEditView->pEditEngine->pImpEditEngine->GetSelected( pImpEditView->GetEditSelection() ); +} + +void EditView::MoveParagraphs( Range aParagraphs, sal_Int32 nNewPos ) +{ + pImpEditView->pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_MOVEPARAS ); + pImpEditView->pEditEngine->pImpEditEngine->MoveParagraphs( aParagraphs, nNewPos, this ); + pImpEditView->pEditEngine->pImpEditEngine->UndoActionEnd(); +} + +void EditView::MoveParagraphs( tools::Long nDiff ) +{ + ESelection aSel = GetSelection(); + Range aRange( aSel.nStartPara, aSel.nEndPara ); + aRange.Justify(); + tools::Long nDest = ( nDiff > 0 ? aRange.Max() : aRange.Min() ) + nDiff; + if ( nDiff > 0 ) + nDest++; + DBG_ASSERT( ( nDest >= 0 ) && ( nDest <= pImpEditView->pEditEngine->GetParagraphCount() ), "MoveParagraphs - wrong Parameters!" ); + MoveParagraphs( aRange, sal::static_int_cast< sal_Int32 >( nDest ) ); +} + +void EditView::SetBackgroundColor( const Color& rColor ) +{ + pImpEditView->SetBackgroundColor( rColor ); +} + +Color const & EditView::GetBackgroundColor() const +{ + return pImpEditView->GetBackgroundColor(); +} + +void EditView::RegisterViewShell(OutlinerViewShell* pViewShell) +{ + pImpEditView->RegisterViewShell(pViewShell); +} + +void EditView::RegisterOtherShell(OutlinerViewShell* pOtherShell) +{ + pImpEditView->RegisterOtherShell(pOtherShell); +} + +void EditView::SetControlWord( EVControlBits nWord ) +{ + pImpEditView->nControl = nWord; +} + +EVControlBits EditView::GetControlWord() const +{ + return pImpEditView->nControl; +} + +std::unique_ptr<EditTextObject> EditView::CreateTextObject() +{ + return pImpEditView->pEditEngine->pImpEditEngine->CreateTextObject( pImpEditView->GetEditSelection() ); +} + +void EditView::InsertText( const EditTextObject& rTextObject ) +{ + pImpEditView->DrawSelectionXOR(); + + pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_INSERT ); + EditSelection aTextSel( pImpEditView->pEditEngine->InsertText( rTextObject, pImpEditView->GetEditSelection() ) ); + pImpEditView->pEditEngine->UndoActionEnd(); + + aTextSel.Min() = aTextSel.Max(); // Selection not retained. + pImpEditView->SetEditSelection( aTextSel ); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +void EditView::InsertText( css::uno::Reference< css::datatransfer::XTransferable > const & xDataObj, const OUString& rBaseURL, bool bUseSpecial ) +{ + pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_INSERT ); + pImpEditView->DeleteSelected(); + EditSelection aTextSel = + pImpEditView->pEditEngine->InsertText(xDataObj, rBaseURL, pImpEditView->GetEditSelection().Max(), bUseSpecial); + pImpEditView->pEditEngine->UndoActionEnd(); + + aTextSel.Min() = aTextSel.Max(); // Selection not retained. + pImpEditView->SetEditSelection( aTextSel ); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +bool EditView::SetEditEngineUpdateLayout( bool bUpdate ) +{ + return pImpEditView->pEditEngine->pImpEditEngine->SetUpdateLayout( bUpdate, this ); +} + +void EditView::ForceLayoutCalculation() +{ + pImpEditView->pEditEngine->pImpEditEngine->SetUpdateLayout( true, this, true ); +} + +SfxStyleSheet* EditView::GetStyleSheet() +{ + EditSelection aSel( pImpEditView->GetEditSelection() ); + aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() ); + sal_Int32 nStartPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( aSel.Max().GetNode() ); + + SfxStyleSheet* pStyle = nullptr; + for ( sal_Int32 n = nStartPara; n <= nEndPara; n++ ) + { + SfxStyleSheet* pTmpStyle = pImpEditView->pEditEngine->GetStyleSheet( n ); + if ( ( n != nStartPara ) && ( pStyle != pTmpStyle ) ) + return nullptr; // Not unique. + pStyle = pTmpStyle; + } + return pStyle; +} + +const SfxStyleSheet* EditView::GetStyleSheet() const +{ + return const_cast< EditView* >( this )->GetStyleSheet(); +} + +bool EditView::IsInsertMode() const +{ + return pImpEditView->IsInsertMode(); +} + +void EditView::SetInsertMode( bool bInsert ) +{ + pImpEditView->SetInsertMode( bInsert ); +} + +void EditView::SetAnchorMode( EEAnchorMode eMode ) +{ + pImpEditView->SetAnchorMode( eMode ); +} + +EEAnchorMode EditView::GetAnchorMode() const +{ + return pImpEditView->GetAnchorMode(); +} + +void EditView::TransliterateText( TransliterationFlags nTransliterationMode ) +{ + EditSelection aOldSel( pImpEditView->GetEditSelection() ); + EditSelection aNewSel = pImpEditView->pEditEngine->TransliterateText( pImpEditView->GetEditSelection(), nTransliterationMode ); + if ( aNewSel != aOldSel ) + { + pImpEditView->DrawSelectionXOR(); + pImpEditView->SetEditSelection( aNewSel ); + pImpEditView->DrawSelectionXOR(); + } +} + +void EditView::CompleteAutoCorrect( vcl::Window const * pFrameWin ) +{ + if ( !HasSelection() && pImpEditView->pEditEngine->pImpEditEngine->GetStatus().DoAutoCorrect() ) + { + pImpEditView->DrawSelectionXOR(); + EditSelection aSel = pImpEditView->GetEditSelection(); + aSel = pImpEditView->pEditEngine->EndOfWord( aSel.Max() ); + aSel = pImpEditView->pEditEngine->pImpEditEngine->AutoCorrect( aSel, 0, !IsInsertMode(), pFrameWin ); + pImpEditView->SetEditSelection( aSel ); + if ( pImpEditView->pEditEngine->IsModified() ) + pImpEditView->pEditEngine->FormatAndLayout( this ); + } +} + +EESpellState EditView::StartSpeller(weld::Widget* pDialogParent, bool bMultipleDoc) +{ + if ( !pImpEditView->pEditEngine->pImpEditEngine->GetSpeller().is() ) + return EESpellState::NoSpeller; + + return pImpEditView->pEditEngine->pImpEditEngine->Spell(this, pDialogParent, bMultipleDoc); +} + +EESpellState EditView::StartThesaurus(weld::Widget* pDialogParent) +{ + if ( !pImpEditView->pEditEngine->pImpEditEngine->GetSpeller().is() ) + return EESpellState::NoSpeller; + + return pImpEditView->pEditEngine->pImpEditEngine->StartThesaurus(this, pDialogParent); +} + +void EditView::StartTextConversion(weld::Widget* pDialogParent, + LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont, + sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc ) +{ + pImpEditView->pEditEngine->pImpEditEngine->Convert(this, pDialogParent, nSrcLang, nDestLang, pDestFont, nOptions, bIsInteractive, bMultipleDoc); +} + +sal_Int32 EditView::StartSearchAndReplace( const SvxSearchItem& rSearchItem ) +{ + return pImpEditView->pEditEngine->pImpEditEngine->StartSearchAndReplace( this, rSearchItem ); +} + +bool EditView::IsCursorAtWrongSpelledWord() +{ + bool bIsWrong = false; + if ( !HasSelection() ) + { + EditPaM aPaM = pImpEditView->GetEditSelection().Max(); + bIsWrong = pImpEditView->IsWrongSpelledWord( aPaM, false/*bMarkIfWrong*/ ); + } + return bIsWrong; +} + +bool EditView::IsWrongSpelledWordAtPos( const Point& rPosPixel, bool bMarkIfWrong ) +{ + Point aPos(pImpEditView->GetOutputDevice().PixelToLogic(rPosPixel)); + aPos = pImpEditView->GetDocPos( aPos ); + EditPaM aPaM = pImpEditView->pEditEngine->GetPaM(aPos, false); + return pImpEditView->IsWrongSpelledWord( aPaM , bMarkIfWrong ); +} + +static void LOKSendSpellPopupMenu(const weld::Menu& rMenu, LanguageType nGuessLangWord, + LanguageType nGuessLangPara, sal_uInt16 nSuggestions) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + // Generate the menu structure and send it to the client code. + SfxViewShell* pViewShell = SfxViewShell::Current(); + if (!pViewShell) + return; + + boost::property_tree::ptree aMenu; + + boost::property_tree::ptree aItemTree; + if (nSuggestions) + { + for(int i = 0; i < nSuggestions; ++i) + { + OString sItemId = OString::number(MN_ALTSTART + i); + OUString sText = rMenu.get_label(sItemId); + aItemTree.put("text", sText.toUtf8().getStr()); + aItemTree.put("type", "command"); + OUString sCommandString = ".uno:SpellCheckApplySuggestion?ApplyRule:string=Spelling_" + sText; + aItemTree.put("command", sCommandString.toUtf8().getStr()); + aItemTree.put("enabled", rMenu.get_sensitive(sItemId)); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + } + + aItemTree.put("type", "separator"); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + } + + // First we need to set item commands for the context menu. + OUString aTmpWord( SvtLanguageTable::GetLanguageString( nGuessLangWord ) ); + OUString aTmpPara( SvtLanguageTable::GetLanguageString( nGuessLangPara ) ); + + aItemTree.put("text", rMenu.get_label("ignore").toUtf8().getStr()); + aItemTree.put("type", "command"); + aItemTree.put("command", ".uno:SpellCheckIgnoreAll?Type:string=Spelling"); + aItemTree.put("enabled", rMenu.get_sensitive("ignore")); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + + aItemTree.put("type", "separator"); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + + aItemTree.put("text", rMenu.get_label("wordlanguage").toUtf8().getStr()); + aItemTree.put("type", "command"); + OUString sCommandString = ".uno:LanguageStatus?Language:string=Current_" + aTmpWord; + aItemTree.put("command", sCommandString.toUtf8().getStr()); + aItemTree.put("enabled", rMenu.get_sensitive("wordlanguage")); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + + aItemTree.put("text", rMenu.get_label("paralanguage").toUtf8().getStr()); + aItemTree.put("type", "command"); + sCommandString = ".uno:LanguageStatus?Language:string=Paragraph_" + aTmpPara; + aItemTree.put("command", sCommandString.toUtf8().getStr()); + aItemTree.put("enabled", rMenu.get_sensitive("paralanguage")); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + + boost::property_tree::ptree aRoot; + aRoot.add_child("menu", aMenu); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aRoot, true); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CONTEXT_MENU, aStream.str().c_str()); +} + +bool EditView::ExecuteSpellPopup(const Point& rPosPixel, const Link<SpellCallbackInfo&,void> &rCallBack) +{ + OutputDevice& rDevice = pImpEditView->GetOutputDevice(); + Point aPos(rDevice.PixelToLogic(rPosPixel)); + aPos = pImpEditView->GetDocPos( aPos ); + EditPaM aPaM = pImpEditView->pEditEngine->GetPaM(aPos, false); + Reference< linguistic2::XSpellChecker1 > xSpeller( pImpEditView->pEditEngine->pImpEditEngine->GetSpeller() ); + ESelection aOldSel = GetSelection(); + if ( !(xSpeller.is() && pImpEditView->IsWrongSpelledWord( aPaM, true )) ) + return false; + + // PaMtoEditCursor returns Logical units + tools::Rectangle aTempRect = pImpEditView->pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM, GetCursorFlags::TextOnly ); + // GetWindowPos works in Logical units + aTempRect = pImpEditView->GetWindowPos(aTempRect); + // Convert to pixels + aTempRect = rDevice.LogicToPixel(aTempRect); + + weld::Widget* pPopupParent = pImpEditView->GetPopupParent(aTempRect); + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pPopupParent, "editeng/ui/spellmenu.ui")); + std::unique_ptr<weld::Menu> xPopupMenu(xBuilder->weld_menu("editviewspellmenu")); + std::unique_ptr<weld::Menu> xInsertMenu(xBuilder->weld_menu("insertmenu")); // add word to user-dictionaries + std::unique_ptr<weld::Menu> xAutoMenu(xBuilder->weld_menu("automenu")); + + EditPaM aPaM2( aPaM ); + aPaM2.SetIndex( aPaM2.GetIndex()+1 ); + + // Are there any replace suggestions? + OUString aSelected( GetSelected() ); + + // restrict the maximal number of suggestions displayed + // in the context menu. + // Note: That could of course be done by clipping the + // resulting sequence but the current third party + // implementations result differs greatly if the number of + // suggestions to be returned gets changed. Statistically + // it gets much better if told to return e.g. only 7 strings + // than returning e.g. 16 suggestions and using only the + // first 7. Thus we hand down the value to use to that + // implementation here by providing an additional parameter. + Sequence< PropertyValue > aPropVals { comphelper::makePropertyValue(UPN_MAX_NUMBER_OF_SUGGESTIONS, sal_Int16(7)) }; + + // Are there any replace suggestions? + Reference< linguistic2::XSpellAlternatives > xSpellAlt = + xSpeller->spell( aSelected, static_cast<sal_uInt16>(pImpEditView->pEditEngine->pImpEditEngine->GetLanguage( aPaM2 ).nLang), aPropVals ); + + Reference< linguistic2::XLanguageGuessing > xLangGuesser( EditDLL::Get().GetGlobalData()->GetLanguageGuesser() ); + + // check if text might belong to a different language... + LanguageType nGuessLangWord = LANGUAGE_NONE; + LanguageType nGuessLangPara = LANGUAGE_NONE; + if (xSpellAlt.is() && xLangGuesser.is()) + { + OUString aParaText; + ContentNode *pNode = aPaM.GetNode(); + if (pNode) + { + aParaText = pNode->GetString(); + } + else + { + OSL_FAIL( "content node is NULL" ); + } + + nGuessLangWord = CheckLanguage( xSpellAlt->getWord(), xSpeller, xLangGuesser, false ); + nGuessLangPara = CheckLanguage( aParaText, xSpeller, xLangGuesser, true ); + } + if (nGuessLangWord != LANGUAGE_NONE || nGuessLangPara != LANGUAGE_NONE) + { + // make sure LANGUAGE_NONE gets not used as menu entry + if (nGuessLangWord == LANGUAGE_NONE) + nGuessLangWord = nGuessLangPara; + if (nGuessLangPara == LANGUAGE_NONE) + nGuessLangPara = nGuessLangWord; + + xPopupMenu->append_separator("separator1"); + OUString aTmpWord( SvtLanguageTable::GetLanguageString( nGuessLangWord ) ); + OUString aTmpPara( SvtLanguageTable::GetLanguageString( nGuessLangPara ) ); + OUString aWordStr( EditResId( RID_STR_WORD ) ); + aWordStr = aWordStr.replaceFirst( "%x", aTmpWord ); + OUString aParaStr( EditResId( RID_STR_PARAGRAPH ) ); + aParaStr = aParaStr.replaceFirst( "%x", aTmpPara ); + xPopupMenu->append("wordlanguage", aWordStr); + xPopupMenu->append("paralanguage", aParaStr); + } + + // Replace suggestions... + Sequence< OUString > aAlt; + if (xSpellAlt.is()) + aAlt = xSpellAlt->getAlternatives(); + const OUString *pAlt = aAlt.getConstArray(); + sal_uInt16 nWords = static_cast<sal_uInt16>(aAlt.getLength()); + if ( nWords ) + { + for ( sal_uInt16 nW = 0; nW < nWords; nW++ ) + { + OUString aAlternate( pAlt[nW] ); + xPopupMenu->append(OUString::number(MN_ALTSTART + nW), aAlternate); + xAutoMenu->append(OUString::number(MN_AUTOSTART + nW), aAlternate); + } + xPopupMenu->append_separator("separator2"); + } + else + { + xAutoMenu.reset(); + xPopupMenu->remove("autocorrect"); + } + + SvtLinguConfig aCfg; + + Reference< linguistic2::XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() ); + Sequence< Reference< linguistic2::XDictionary > > aDics; + if (xDicList.is()) + { + const Reference< linguistic2::XDictionary > *pDic = nullptr; + // add the default positive dictionary to dic-list (if not already done). + // This is to ensure that there is at least one dictionary to which + // words could be added. + uno::Reference< linguistic2::XDictionary > xDic( LinguMgr::GetStandardDic() ); + if (xDic.is()) + xDic->setActive( true ); + + aDics = xDicList->getDictionaries(); + pDic = aDics.getConstArray(); + LanguageType nCheckedLanguage = pImpEditView->pEditEngine->pImpEditEngine->GetLanguage( aPaM2 ).nLang; + sal_uInt16 nDicCount = static_cast<sal_uInt16>(aDics.getLength()); + for (sal_uInt16 i = 0; i < nDicCount; i++) + { + uno::Reference< linguistic2::XDictionary > xDicTmp = pDic[i]; + if (!xDicTmp.is() || LinguMgr::GetIgnoreAllList() == xDicTmp) + continue; + + uno::Reference< frame::XStorable > xStor( xDicTmp, uno::UNO_QUERY ); + LanguageType nActLanguage = LanguageTag( xDicTmp->getLocale() ).getLanguageType(); + if( xDicTmp->isActive() + && xDicTmp->getDictionaryType() != linguistic2::DictionaryType_NEGATIVE + && (nCheckedLanguage == nActLanguage || LANGUAGE_NONE == nActLanguage ) + && (!xStor.is() || !xStor->isReadonly()) ) + { + OUString sImage; + + uno::Reference< lang::XServiceInfo > xSvcInfo( xDicTmp, uno::UNO_QUERY ); + if (xSvcInfo.is()) + { + OUString aDictionaryImageUrl( aCfg.GetSpellAndGrammarContextDictionaryImage( + xSvcInfo->getImplementationName()) ); + if (!aDictionaryImageUrl.isEmpty() ) + sImage = aDictionaryImageUrl; + } + + if (sImage.isEmpty()) + { + xInsertMenu->append(OUString::number(MN_DICTSTART + i), xDicTmp->getName()); + } + else + { + Image aImage(sImage); + ScopedVclPtr<VirtualDevice> xVirDev(pPopupParent->create_virtual_device()); + Size aSize(aImage.GetSizePixel()); + xVirDev->SetOutputSizePixel(aSize); + xVirDev->DrawImage(Point(0, 0), aImage); + xInsertMenu->append(OUString::number(MN_DICTSTART + i), xDicTmp->getName(), *xVirDev); + } + aDicNameSingle = xDicTmp->getName(); + } + } + } + + if (xInsertMenu->n_children() != 1) + xPopupMenu->remove("add"); + if (xInsertMenu->n_children() < 2) + { + xInsertMenu.reset(); + xPopupMenu->remove("insert"); + } + + //tdf#106123 store and restore the EditPaM around the menu Execute + //because the loss of focus in the current editeng causes writer + //annotations to save their contents, making the pContent of the + //current EditPams invalid + EPaM aP = pImpEditView->pEditEngine->pImpEditEngine->CreateEPaM(aPaM); + EPaM aP2 = pImpEditView->pEditEngine->pImpEditEngine->CreateEPaM(aPaM2); + + if (comphelper::LibreOfficeKit::isActive()) + { + xPopupMenu->remove("autocorrect"); + xPopupMenu->remove("autocorrectdlg"); + + LOKSendSpellPopupMenu(*xPopupMenu, nGuessLangWord, nGuessLangPara, nWords); + return true; + } + + OString sId = xPopupMenu->popup_at_rect(pPopupParent, aTempRect); + + aPaM2 = pImpEditView->pEditEngine->pImpEditEngine->CreateEditPaM(aP2); + aPaM = pImpEditView->pEditEngine->pImpEditEngine->CreateEditPaM(aP); + + if (sId == "ignore") + { + OUString aWord = pImpEditView->SpellIgnoreWord(); + SpellCallbackInfo aInf( SpellCallbackCommand::IGNOREWORD, aWord ); + rCallBack.Call(aInf); + SetSelection( aOldSel ); + } + else if (sId == "wordlanguage" || sId == "paralanguage") + { + LanguageType nLangToUse = (sId == "wordlanguage") ? nGuessLangWord : nGuessLangPara; + SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( nLangToUse ); + + SfxItemSet aAttrs = GetEditEngine()->GetEmptyItemSet(); + if (nScriptType == SvtScriptType::LATIN) + aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE ) ); + if (nScriptType == SvtScriptType::COMPLEX) + aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE_CTL ) ); + if (nScriptType == SvtScriptType::ASIAN) + aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE_CJK ) ); + if (sId == "paralanguage") + { + ESelection aSel = GetSelection(); + aSel.nStartPos = 0; + aSel.nEndPos = EE_TEXTPOS_ALL; + SetSelection( aSel ); + } + SetAttribs( aAttrs ); + pImpEditView->pEditEngine->pImpEditEngine->StartOnlineSpellTimer(); + + SpellCallbackInfo aInf((sId == "wordlanguage") ? SpellCallbackCommand::WORDLANGUAGE : SpellCallbackCommand::PARALANGUAGE); + rCallBack.Call(aInf); + SetSelection( aOldSel ); + } + else if (sId == "check") + { + SpellCallbackInfo aInf( SpellCallbackCommand::STARTSPELLDLG, OUString() ); + rCallBack.Call(aInf); + } + else if (sId == "autocorrectdlg") + { + SpellCallbackInfo aInf( SpellCallbackCommand::AUTOCORRECT_OPTIONS, OUString() ); + rCallBack.Call(aInf); + } + else if ( sId.toInt32() >= MN_DICTSTART || sId == "add") + { + OUString aDicName; + if (sId.toInt32() >= MN_DICTSTART) + { + assert(xInsertMenu && "this case only occurs when xInsertMenu exists"); + // strip_mnemonic is necessary to retrieve the correct dictionary name + aDicName = pPopupParent->strip_mnemonic(xInsertMenu->get_label(sId)); + } + else + aDicName = aDicNameSingle; + + uno::Reference< linguistic2::XDictionary > xDic; + if (xDicList.is()) + xDic = xDicList->getDictionaryByName( aDicName ); + + if (xDic.is()) + xDic->add( aSelected, false, OUString() ); + // save modified user-dictionary if it is persistent + Reference< frame::XStorable > xSavDic( xDic, UNO_QUERY ); + if (xSavDic.is()) + xSavDic->store(); + + aPaM.GetNode()->GetWrongList()->ResetInvalidRange(0, aPaM.GetNode()->Len()); + pImpEditView->pEditEngine->pImpEditEngine->StartOnlineSpellTimer(); + + SpellCallbackInfo aInf( SpellCallbackCommand::ADDTODICTIONARY, aSelected ); + rCallBack.Call(aInf); + SetSelection( aOldSel ); + } + else if ( sId.toInt32() >= MN_AUTOSTART ) + { + DBG_ASSERT(sId.toInt32() - MN_AUTOSTART < aAlt.getLength(), "index out of range"); + OUString aWord = pAlt[sId.toInt32() - MN_AUTOSTART]; + SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect(); + if ( pAutoCorrect ) + pAutoCorrect->PutText( aSelected, aWord, pImpEditView->pEditEngine->pImpEditEngine->GetLanguage( aPaM2 ).nLang ); + InsertText( aWord ); + } + else if ( sId.toInt32() >= MN_ALTSTART ) // Replace + { + DBG_ASSERT(sId.toInt32() - MN_ALTSTART < aAlt.getLength(), "index out of range"); + OUString aWord = pAlt[sId.toInt32() - MN_ALTSTART]; + InsertText( aWord ); + } + else + { + SetSelection( aOldSel ); + } + return true; +} + +OUString EditView::SpellIgnoreWord() +{ + return pImpEditView->SpellIgnoreWord(); +} + +void EditView::SelectCurrentWord( sal_Int16 nWordType ) +{ + EditSelection aCurSel( pImpEditView->GetEditSelection() ); + pImpEditView->DrawSelectionXOR(); + aCurSel = pImpEditView->pEditEngine->SelectWord(aCurSel.Max(), nWordType); + pImpEditView->SetEditSelection( aCurSel ); + pImpEditView->DrawSelectionXOR(); + ShowCursor( true, false ); +} + +void EditView::InsertParaBreak() +{ + pImpEditView->pEditEngine->UndoActionStart(EDITUNDO_INSERT); + pImpEditView->DeleteSelected(); + EditPaM aPaM(pImpEditView->pEditEngine->InsertParaBreak(pImpEditView->GetEditSelection())); + pImpEditView->pEditEngine->UndoActionEnd(); + pImpEditView->SetEditSelection(EditSelection(aPaM, aPaM)); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout(this); +} + +void EditView::InsertField( const SvxFieldItem& rFld ) +{ + EditEngine* pEE = pImpEditView->pEditEngine; + pImpEditView->DrawSelectionXOR(); + pEE->UndoActionStart( EDITUNDO_INSERT ); + EditPaM aPaM( pEE->InsertField( pImpEditView->GetEditSelection(), rFld ) ); + pEE->UndoActionEnd(); + pImpEditView->SetEditSelection( EditSelection( aPaM, aPaM ) ); + pEE->UpdateFields(); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pEE->FormatAndLayout( this ); +} + +const SvxFieldItem* EditView::GetFieldUnderMousePointer() const +{ + sal_Int32 nPara; + sal_Int32 nPos; + return GetFieldUnderMousePointer( nPara, nPos ); +} + +const SvxFieldItem* EditView::GetField( const Point& rPos, sal_Int32* pPara, sal_Int32* pPos ) const +{ + return pImpEditView->GetField( rPos, pPara, pPos ); +} + +const SvxFieldItem* EditView::GetFieldUnderMousePointer( sal_Int32& nPara, sal_Int32& nPos ) const +{ + Point aPos; + if (EditViewCallbacks* pEditViewCallbacks = pImpEditView->getEditViewCallbacks()) + aPos = pEditViewCallbacks->EditViewPointerPosPixel(); + else + aPos = pImpEditView->GetWindow()->GetPointerPosPixel(); + OutputDevice& rDevice = pImpEditView->GetOutputDevice(); + aPos = rDevice.PixelToLogic(aPos); + return GetField( aPos, &nPara, &nPos ); +} + +const SvxFieldItem* EditView::GetFieldAtSelection() const +{ + EditSelection aSel( pImpEditView->GetEditSelection() ); + aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() ); + // Only when cursor is in font of field, no selection, + // or only selecting field + if ( ( aSel.Min().GetNode() == aSel.Max().GetNode() ) && + ( ( aSel.Max().GetIndex() == aSel.Min().GetIndex() ) || + ( aSel.Max().GetIndex() == aSel.Min().GetIndex()+1 ) ) ) + { + EditPaM aPaM = aSel.Min(); + const CharAttribList::AttribsType& rAttrs = aPaM.GetNode()->GetCharAttribs().GetAttribs(); + const sal_Int32 nXPos = aPaM.GetIndex(); + for (size_t nAttr = rAttrs.size(); nAttr; ) + { + const EditCharAttrib& rAttr = *rAttrs[--nAttr]; + if (rAttr.GetStart() == nXPos) + if (rAttr.Which() == EE_FEATURE_FIELD) + { + DBG_ASSERT(dynamic_cast<const SvxFieldItem* >(rAttr.GetItem() ) != nullptr, "No FieldItem..."); + return static_cast<const SvxFieldItem*>(rAttr.GetItem()); + } + } + } + return nullptr; +} + +void EditView::SelectFieldAtCursor() +{ + const SvxFieldItem* pFieldItem = GetFieldAtSelection(); + if (pFieldItem) + { + // Make sure the whole field is selected + ESelection aSel = GetSelection(); + if (aSel.nStartPos == aSel.nEndPos) + { + aSel.nEndPos++; + SetSelection(aSel); + } + } + if (!pFieldItem) + { + // Cursor probably behind the field - extend selection to select the field + ESelection aSel = GetSelection(); + if (aSel.nStartPos > 0 && aSel.nStartPos == aSel.nEndPos) + { + aSel.nStartPos--; + SetSelection(aSel); + } + } +} + +const SvxFieldData* EditView::GetFieldAtCursor() const +{ + const SvxFieldItem* pFieldItem = GetFieldUnderMousePointer(); + if (!pFieldItem) + pFieldItem = GetFieldAtSelection(); + + return pFieldItem ? pFieldItem->GetField() : nullptr; +} + +void EditView::SetInvalidateMore( sal_uInt16 nPixel ) +{ + pImpEditView->SetInvalidateMore( nPixel ); +} + +sal_uInt16 EditView::GetInvalidateMore() const +{ + return pImpEditView->GetInvalidateMore(); +} + +static void ChangeFontSizeImpl( EditView* pEditView, bool bGrow, const ESelection& rSel, const FontList* pFontList ) +{ + pEditView->SetSelection( rSel ); + + SfxItemSet aSet( pEditView->GetAttribs() ); + if( EditView::ChangeFontSize( bGrow, aSet, pFontList ) ) + { + SfxItemSet aNewSet( pEditView->GetEmptyItemSet() ); + aNewSet.Put( aSet.Get( EE_CHAR_FONTHEIGHT ) ); + aNewSet.Put( aSet.Get( EE_CHAR_FONTHEIGHT_CJK ) ); + aNewSet.Put( aSet.Get( EE_CHAR_FONTHEIGHT_CTL ) ); + pEditView->SetAttribs( aNewSet ); + } +} + +void EditView::ChangeFontSize( bool bGrow, const FontList* pFontList ) +{ + + EditEngine& rEditEngine = *pImpEditView->pEditEngine; + + ESelection aSel( GetSelection() ); + ESelection aOldSelection( aSel ); + aSel.Adjust(); + + if( !aSel.HasRange() ) + { + aSel = rEditEngine.GetWord( aSel, css::i18n::WordType::DICTIONARY_WORD ); + } + + if( aSel.HasRange() ) + { + for( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + std::vector<sal_Int32> aPortions; + rEditEngine.GetPortions( nPara, aPortions ); + + if( aPortions.empty() ) + aPortions.push_back( rEditEngine.GetTextLen(nPara) ); + + const sal_Int32 nBeginPos = (nPara == aSel.nStartPara) ? aSel.nStartPos : 0; + const sal_Int32 nEndPos = (nPara == aSel.nEndPara) ? aSel.nEndPos : EE_TEXTPOS_ALL; + + for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos ) + { + sal_Int32 nPortionEnd = aPortions[ nPos ]; + sal_Int32 nPortionStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0; + + if( (nPortionEnd < nBeginPos) || (nPortionStart > nEndPos) ) + continue; + + if( nPortionStart < nBeginPos ) + nPortionStart = nBeginPos; + if( nPortionEnd > nEndPos ) + nPortionEnd = nEndPos; + + if( nPortionStart == nPortionEnd ) + continue; + + ESelection aPortionSel( nPara, nPortionStart, nPara, nPortionEnd ); + ChangeFontSizeImpl( this, bGrow, aPortionSel, pFontList ); + } + } + } + else + { + ChangeFontSizeImpl( this, bGrow, aSel, pFontList ); + } + + SetSelection( aOldSelection ); +} + +bool EditView::ChangeFontSize( bool bGrow, SfxItemSet& rSet, const FontList* pFontList ) +{ + if (!pFontList) + return false; + + static const sal_uInt16 gFontSizeWichMap[] = { EE_CHAR_FONTHEIGHT, EE_CHAR_FONTHEIGHT_CJK, EE_CHAR_FONTHEIGHT_CTL, 0 }; + bool bRet = false; + + const sal_uInt16* pWhich = gFontSizeWichMap; + while( *pWhich ) + { + SvxFontHeightItem aFontHeightItem( static_cast<const SvxFontHeightItem&>(rSet.Get( *pWhich )) ); + tools::Long nHeight = aFontHeightItem.GetHeight(); + const MapUnit eUnit = rSet.GetPool()->GetMetric( *pWhich ); + nHeight = OutputDevice::LogicToLogic(nHeight * 10, eUnit, MapUnit::MapPoint); + + const int* pAry = FontList::GetStdSizeAry(); + + if( bGrow ) + { + while( *pAry ) + { + if( *pAry > nHeight ) + { + nHeight = *pAry; + break; + } + pAry++; + } + + if( *pAry == 0 ) + { + nHeight += (nHeight + 5) / 10; + if( nHeight > 9999 ) + nHeight = 9999; + } + + } + else if( *pAry ) + { + bool bFound = false; + if( *pAry < nHeight ) + { + pAry++; + while( *pAry ) + { + if( *pAry >= nHeight ) + { + nHeight = pAry[-1]; + bFound = true; + break; + } + pAry++; + } + } + + if( !bFound ) + { + nHeight -= (nHeight + 5) / 10; + if( nHeight < 2 ) + nHeight = 2; + } + } + + if( (nHeight >= 2) && (nHeight <= 9999 ) ) + { + nHeight = OutputDevice::LogicToLogic( nHeight, MapUnit::MapPoint, eUnit ) / 10; + + if( nHeight != static_cast<tools::Long>(aFontHeightItem.GetHeight()) ) + { + aFontHeightItem.SetHeight( nHeight ); + rSet.Put( aFontHeightItem.CloneSetWhich(*pWhich) ); + bRet = true; + } + } + pWhich++; + } + return bRet; +} + +OUString EditView::GetSurroundingText() const +{ + EditSelection aSel( pImpEditView->GetEditSelection() ); + aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() ); + + if( HasSelection() ) + { + OUString aStr = pImpEditView->pEditEngine->GetSelected(aSel); + + // Stop reconversion if the selected text includes a line break. + if ( aStr.indexOf( 0x0A ) == -1 ) + return aStr; + else + return OUString(); + } + else + { + aSel.Min().SetIndex( 0 ); + aSel.Max().SetIndex( aSel.Max().GetNode()->Len() ); + return pImpEditView->pEditEngine->GetSelected(aSel); + } +} + +Selection EditView::GetSurroundingTextSelection() const +{ + ESelection aSelection( GetSelection() ); + aSelection.Adjust(); + + if( HasSelection() ) + { + EditSelection aSel( pImpEditView->GetEditSelection() ); + aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() ); + OUString aStr = pImpEditView->pEditEngine->GetSelected(aSel); + + // Stop reconversion if the selected text includes a line break. + if ( aStr.indexOf( 0x0A ) == -1 ) + return Selection( 0, aSelection.nEndPos - aSelection.nStartPos ); + else + return Selection( 0, 0 ); + } + else + { + return Selection( aSelection.nStartPos, aSelection.nEndPos ); + } +} + +bool EditView::DeleteSurroundingText(const Selection& rRange) +{ + ESelection aSel(GetSelection()); + aSel.nEndPara = aSel.nStartPara; + aSel.nStartPos = rRange.Min(); + aSel.nEndPos = rRange.Max(); + SetSelection(aSel); + DeleteSelected(); + return true; +} + +void EditView::SetCursorLogicPosition(const Point& rPosition, bool bPoint, bool bClearMark) +{ + Point aDocPos(pImpEditView->GetDocPos(rPosition)); + EditPaM aPaM = pImpEditView->pEditEngine->GetPaM(aDocPos); + EditSelection aSelection(pImpEditView->GetEditSelection()); + + // Explicitly create or delete the selection. + if (bClearMark) + { + pImpEditView->DeselectAll(); + aSelection = pImpEditView->GetEditSelection(); + } + else + pImpEditView->CreateAnchor(); + + if (bPoint) + aSelection.Max() = aPaM; + else + aSelection.Min() = aPaM; + + if (pImpEditView->GetEditSelection().Min() != aSelection.Min()) + pImpEditView->pEditEngine->CursorMoved(pImpEditView->GetEditSelection().Min().GetNode()); + pImpEditView->DrawSelectionXOR(aSelection); + if (pImpEditView->GetEditSelection() != aSelection) + pImpEditView->SetEditSelection(aSelection); + ShowCursor(/*bGotoCursor=*/false); +} + +void EditView::DrawSelectionXOR(OutlinerViewShell* pOtherShell) +{ + pImpEditView->RegisterOtherShell(pOtherShell); + pImpEditView->DrawSelectionXOR(); + pImpEditView->RegisterOtherShell(nullptr); +} + +void EditView::InitLOKSpecialPositioning(MapUnit eUnit, + const tools::Rectangle& rOutputArea, + const Point& rVisDocStartPos) +{ + pImpEditView->InitLOKSpecialPositioning(eUnit, rOutputArea, rVisDocStartPos); +} + +void EditView::SetLOKSpecialOutputArea(const tools::Rectangle& rOutputArea) +{ + pImpEditView->SetLOKSpecialOutputArea(rOutputArea); +} + +const tools::Rectangle & EditView::GetLOKSpecialOutputArea() const +{ + return pImpEditView->GetLOKSpecialOutputArea(); +} + +void EditView::SetLOKSpecialVisArea(const tools::Rectangle& rVisArea) +{ + pImpEditView->SetLOKSpecialVisArea(rVisArea); +} + +tools::Rectangle EditView::GetLOKSpecialVisArea() const +{ + return pImpEditView->GetLOKSpecialVisArea(); +} + +bool EditView::HasLOKSpecialPositioning() const +{ + return pImpEditView->HasLOKSpecialPositioning(); +} + +void EditView::SuppressLOKMessages(bool bSet) +{ + pImpEditView->SuppressLOKMessages(bSet); +} + +bool EditView::IsSuppressLOKMessages() const +{ + return pImpEditView->IsSuppressLOKMessages(); +} + +void EditView::SetNegativeX(bool bSet) +{ + pImpEditView->SetNegativeX(bSet); +} + +bool EditView::IsNegativeX() const +{ + return pImpEditView->IsNegativeX(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/edtspell.cxx b/editeng/source/editeng/edtspell.cxx new file mode 100644 index 000000000..c07361bd1 --- /dev/null +++ b/editeng/source/editeng/edtspell.cxx @@ -0,0 +1,708 @@ +/* -*- 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 "impedit.hxx" +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> +#include <edtspell.hxx> +#include <editeng/flditem.hxx> +#include <svl/intitem.hxx> +#include <svl/eitem.hxx> +#include <editeng/unolingu.hxx> +#include <com/sun/star/linguistic2/XDictionary.hpp> + +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::linguistic2; + + +EditSpellWrapper::EditSpellWrapper(weld::Widget* pWindow, + bool bIsStart, EditView* pView ) + : SvxSpellWrapper(pWindow, bIsStart, false/*bIsAllRight*/) +{ + SAL_WARN_IF( !pView, "editeng", "One view has to be abandoned!" ); + // Keep IgnoreList, delete ReplaceList... + if (LinguMgr::GetChangeAllList().is()) + LinguMgr::GetChangeAllList()->clear(); + pEditView = pView; +} + +void EditSpellWrapper::SpellStart( SvxSpellArea eArea ) +{ + EditEngine* pEE = pEditView->GetEditEngine(); + ImpEditEngine* pImpEE = pEditView->GetImpEditEngine(); + SpellInfo* pSpellInfo = pImpEE->GetSpellInfo(); + + if ( eArea == SvxSpellArea::BodyStart ) + { + // Is called when + // a) Spell-Forward has arrived at the end and should restart at the top + // IsEndDone() returns also true, when backward-spelling is started at the end! + if ( IsEndDone() ) + { + pSpellInfo->bSpellToEnd = false; + pSpellInfo->aSpellTo = pSpellInfo->aSpellStart; + pEditView->GetImpEditView()->SetEditSelection( + pEE->GetEditDoc().GetStartPaM() ); + } + else + { + pSpellInfo->bSpellToEnd = true; + pSpellInfo->aSpellTo = pImpEE->CreateEPaM( + pEE->GetEditDoc().GetStartPaM() ); + } + } + else if ( eArea == SvxSpellArea::BodyEnd ) + { + // Is called when + // a) Spell-Forward is launched + // IsStartDone() return also true, when forward-spelling is started at the beginning! + if ( !IsStartDone() ) + { + pSpellInfo->bSpellToEnd = true; + pSpellInfo->aSpellTo = pImpEE->CreateEPaM( + pEE->GetEditDoc().GetEndPaM() ); + } + else + { + pSpellInfo->bSpellToEnd = false; + pSpellInfo->aSpellTo = pSpellInfo->aSpellStart; + pEditView->GetImpEditView()->SetEditSelection( + pEE->GetEditDoc().GetEndPaM() ); + } + } + else if ( eArea == SvxSpellArea::Body ) + { + ; // Is handled by the App through SpellNextDocument + } + else + { + OSL_FAIL( "SpellStart: Unknown Area!" ); + } +} + +void EditSpellWrapper::SpellContinue() +{ + SetLast( pEditView->GetImpEditEngine()->ImpSpell( pEditView ) ); +} + +bool EditSpellWrapper::SpellMore() +{ + EditEngine* pEE = pEditView->GetEditEngine(); + ImpEditEngine* pImpEE = pEditView->GetImpEditEngine(); + SpellInfo* pSpellInfo = pImpEE->GetSpellInfo(); + bool bMore = false; + if ( pSpellInfo->bMultipleDoc ) + { + bMore = pEE->SpellNextDocument(); + if ( bMore ) + { + // The text has been entered into the engine, when backwards then + // it must be behind the selection. + pEditView->GetImpEditView()->SetEditSelection( + pEE->GetEditDoc().GetStartPaM() ); + } + } + return bMore; +} + +void EditSpellWrapper::ReplaceAll( const OUString &rNewText ) +{ + // Is called when the word is in ReplaceList of the spell checker + pEditView->InsertText( rNewText ); + CheckSpellTo(); +} + +void EditSpellWrapper::CheckSpellTo() +{ + ImpEditEngine* pImpEE = pEditView->GetImpEditEngine(); + SpellInfo* pSpellInfo = pImpEE->GetSpellInfo(); + EditPaM aPaM( pEditView->GetImpEditView()->GetEditSelection().Max() ); + EPaM aEPaM = pImpEE->CreateEPaM( aPaM ); + if ( aEPaM.nPara == pSpellInfo->aSpellTo.nPara ) + { + // Check if SpellToEnd still has a valid Index, if replace has been + // performed in the paragraph. + if ( pSpellInfo->aSpellTo.nIndex > aPaM.GetNode()->Len() ) + pSpellInfo->aSpellTo.nIndex = aPaM.GetNode()->Len(); + } +} + +WrongList::WrongList() : mnInvalidStart(0), mnInvalidEnd(Valid) {} + +void WrongList::SetRanges( std::vector<editeng::MisspellRange>&&rRanges ) +{ + maRanges = std::move(rRanges); +} + +bool WrongList::IsValid() const +{ + return mnInvalidStart == Valid; +} + +void WrongList::SetValid() +{ + mnInvalidStart = Valid; + mnInvalidEnd = 0; +} + +void WrongList::SetInvalidRange( size_t nStart, size_t nEnd ) +{ + if (mnInvalidStart == Valid || nStart < mnInvalidStart) + mnInvalidStart = nStart; + + if (mnInvalidEnd < nEnd) + mnInvalidEnd = nEnd; +} + +void WrongList::ResetInvalidRange( size_t nStart, size_t nEnd ) +{ + mnInvalidStart = nStart; + mnInvalidEnd = nEnd; +} + +void WrongList::TextInserted( size_t nPos, size_t nLength, bool bPosIsSep ) +{ + if (IsValid()) + { + mnInvalidStart = nPos; + mnInvalidEnd = nPos + nLength; + } + else + { + if ( mnInvalidStart > nPos ) + mnInvalidStart = nPos; + if ( mnInvalidEnd >= nPos ) + mnInvalidEnd = mnInvalidEnd + nLength; + else + mnInvalidEnd = nPos + nLength; + } + + for (size_t i = 0, n = maRanges.size(); i < n; ++i) + { + editeng::MisspellRange& rWrong = maRanges[i]; + bool bRefIsValid = true; + if (rWrong.mnEnd >= nPos) + { + // Move all Wrongs after the insert position... + if (rWrong.mnStart > nPos) + { + rWrong.mnStart += nLength; + rWrong.mnEnd += nLength; + } + // 1: Starts before and goes until nPos... + else if (rWrong.mnEnd == nPos) + { + // Should be halted at a blank! + if ( !bPosIsSep ) + rWrong.mnEnd += nLength; + } + // 2: Starts before and goes until after nPos... + else if ((rWrong.mnStart < nPos) && (rWrong.mnEnd > nPos)) + { + rWrong.mnEnd += nLength; + // When a separator remove and re-examine the Wrong + if ( bPosIsSep ) + { + // Split Wrong... + editeng::MisspellRange aNewWrong(rWrong.mnStart, nPos); + rWrong.mnStart = nPos + 1; + maRanges.insert(maRanges.begin() + i, aNewWrong); + // Reference no longer valid after Insert, the other + // was inserted in front of this position + bRefIsValid = false; + ++i; // Not this again... + } + } + // 3: Attribute starts at position .. + else if (rWrong.mnStart == nPos) + { + rWrong.mnEnd += nLength; + if ( bPosIsSep ) + ++(rWrong.mnStart); + } + } + SAL_WARN_IF(bRefIsValid && rWrong.mnStart >= rWrong.mnEnd, "editeng", + "TextInserted, editeng::MisspellRange: Start >= End?!"); + } + + SAL_WARN_IF(DbgIsBuggy(), "editeng", "InsertWrong: WrongList broken!"); +} + +void WrongList::TextDeleted( size_t nPos, size_t nLength ) +{ + size_t nEndPos = nPos + nLength; + if (IsValid()) + { + const size_t nNewInvalidStart = nPos ? nPos - 1 : 0; + mnInvalidStart = nNewInvalidStart; + mnInvalidEnd = nNewInvalidStart + 1; + } + else + { + if ( mnInvalidStart > nPos ) + mnInvalidStart = nPos; + if ( mnInvalidEnd > nPos ) + { + if (mnInvalidEnd > nEndPos) + mnInvalidEnd = mnInvalidEnd - nLength; + else + mnInvalidEnd = nPos+1; + } + } + + for (WrongList::iterator i = maRanges.begin(); i != maRanges.end(); ) + { + bool bDelWrong = false; + if (i->mnEnd >= nPos) + { + // Move all Wrongs after the insert position... + if (i->mnStart >= nEndPos) + { + i->mnStart -= nLength; + i->mnEnd -= nLength; + } + // 1. Delete Internal Wrongs ... + else if (i->mnStart >= nPos && i->mnEnd <= nEndPos) + { + bDelWrong = true; + } + // 2. Wrong begins before, ends inside or behind it ... + else if (i->mnStart <= nPos && i->mnEnd > nPos) + { + if (i->mnEnd <= nEndPos) // ends inside + i->mnEnd = nPos; + else + i->mnEnd -= nLength; // ends after + } + // 3. Wrong begins inside, ending after ... + else if (i->mnStart >= nPos && i->mnEnd > nEndPos) + { + i->mnStart = nEndPos - nLength; + i->mnEnd -= nLength; + } + } + SAL_WARN_IF(i->mnStart >= i->mnEnd, "editeng", + "TextDeleted, editeng::MisspellRange: Start >= End?!"); + if ( bDelWrong ) + { + i = maRanges.erase(i); + } + else + { + ++i; + } + } + + SAL_WARN_IF(DbgIsBuggy(), "editeng", "TextDeleted: WrongList broken!"); +} + +bool WrongList::NextWrong( size_t& rnStart, size_t& rnEnd ) const +{ + /* + rnStart get the start position, is possibly adjusted wrt. Wrong start + rnEnd does not have to be initialized. + */ + for (auto const& range : maRanges) + { + if (range.mnEnd > rnStart) + { + rnStart = range.mnStart; + rnEnd = range.mnEnd; + return true; + } + } + return false; +} + +bool WrongList::HasWrong( size_t nStart, size_t nEnd ) const +{ + for (auto const& range : maRanges) + { + if (range.mnStart == nStart && range.mnEnd == nEnd) + return true; + else if (range.mnStart >= nStart) + break; + } + return false; +} + +bool WrongList::HasAnyWrong( size_t nStart, size_t nEnd ) const +{ + for (auto const& range : maRanges) + { + if (range.mnEnd >= nStart && range.mnStart < nEnd) + return true; + else if (range.mnStart >= nEnd) + break; + } + return false; +} + +void WrongList::ClearWrongs( size_t nStart, size_t nEnd, + const ContentNode* pNode ) +{ + for (WrongList::iterator i = maRanges.begin(); i != maRanges.end(); ) + { + if (i->mnEnd > nStart && i->mnStart < nEnd) + { + if (i->mnEnd > nEnd) // Runs out + { + i->mnStart = nEnd; + // Blanks? + while (i->mnStart < o3tl::make_unsigned(pNode->Len()) && + (pNode->GetChar(i->mnStart) == ' ' || + pNode->IsFeature(i->mnStart))) + { + ++i->mnStart; + } + ++i; + } + else + { + i = maRanges.erase(i); + // no increment here + } + } + else + { + ++i; + } + } + + SAL_WARN_IF(DbgIsBuggy(), "editeng", "ClearWrongs: WrongList broken!"); +} + +void WrongList::InsertWrong( size_t nStart, size_t nEnd ) +{ + WrongList::iterator nPos = std::find_if(maRanges.begin(), maRanges.end(), + [&nStart](const editeng::MisspellRange& rRange) { return rRange.mnStart >= nStart; }); + + if (nPos != maRanges.end()) + { + { + // It can really only happen that the Wrong starts exactly here + // and runs along, but not that there are several ranges ... + // Exactly in the range is no one allowed to be, otherwise this + // Method can not be called! + SAL_WARN_IF((nPos->mnStart != nStart || nPos->mnEnd <= nEnd) && nPos->mnStart <= nEnd, "editeng", "InsertWrong: RangeMismatch!"); + if (nPos->mnStart == nStart && nPos->mnEnd > nEnd) + nPos->mnStart = nEnd + 1; + } + maRanges.insert(nPos, editeng::MisspellRange(nStart, nEnd)); + } + else + maRanges.emplace_back(nStart, nEnd); + + SAL_WARN_IF(DbgIsBuggy(), "editeng", "InsertWrong: WrongList broken!"); +} + +void WrongList::MarkWrongsInvalid() +{ + if (!maRanges.empty()) + SetInvalidRange(maRanges.front().mnStart, maRanges.back().mnEnd); +} + +WrongList* WrongList::Clone() const +{ + return new WrongList(*this); +} + +// #i102062# +bool WrongList::operator==(const WrongList& rCompare) const +{ + // check direct members + if(GetInvalidStart() != rCompare.GetInvalidStart() + || GetInvalidEnd() != rCompare.GetInvalidEnd()) + return false; + + return std::equal(maRanges.begin(), maRanges.end(), rCompare.maRanges.begin(), rCompare.maRanges.end(), + [](const editeng::MisspellRange& a, const editeng::MisspellRange& b) { + return a.mnStart == b.mnStart && a.mnEnd == b.mnEnd; }); +} + +bool WrongList::empty() const +{ + return maRanges.empty(); +} + +void WrongList::push_back(const editeng::MisspellRange& rRange) +{ + maRanges.push_back(rRange); +} + +editeng::MisspellRange& WrongList::back() +{ + return maRanges.back(); +} + +const editeng::MisspellRange& WrongList::back() const +{ + return maRanges.back(); +} + +WrongList::iterator WrongList::begin() +{ + return maRanges.begin(); +} + +WrongList::iterator WrongList::end() +{ + return maRanges.end(); +} + +WrongList::const_iterator WrongList::begin() const +{ + return maRanges.begin(); +} + +WrongList::const_iterator WrongList::end() const +{ + return maRanges.end(); +} + +bool WrongList::DbgIsBuggy() const +{ + // Check if the ranges overlap. + bool bError = false; + for (WrongList::const_iterator i = maRanges.begin(); !bError && (i != maRanges.end()); ++i) + { + bError = std::any_of(i + 1, maRanges.end(), [&i](const editeng::MisspellRange& rRange) { + return i->mnStart <= rRange.mnEnd && rRange.mnStart <= i->mnEnd; }); + } + return bError; +} + + +EdtAutoCorrDoc::EdtAutoCorrDoc( + EditEngine* pE, ContentNode* pN, sal_Int32 nCrsr, sal_Unicode cIns) : + mpEditEngine(pE), + pCurNode(pN), + nCursor(nCrsr), + bAllowUndoAction(cIns != 0), + bUndoAction(false) {} + +EdtAutoCorrDoc::~EdtAutoCorrDoc() +{ + if ( bUndoAction ) + mpEditEngine->UndoActionEnd(); +} + +bool EdtAutoCorrDoc::Delete(sal_Int32 nStt, sal_Int32 nEnd) +{ + EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) ); + mpEditEngine->DeleteSelection(aSel); + SAL_WARN_IF(nCursor < nEnd, "editeng", + "Cursor in the heart of the action?!"); + nCursor -= ( nEnd-nStt ); + bAllowUndoAction = false; + return true; +} + +bool EdtAutoCorrDoc::Insert(sal_Int32 nPos, const OUString& rTxt) +{ + EditSelection aSel = EditPaM( pCurNode, nPos ); + mpEditEngine->InsertText(aSel, rTxt); + SAL_WARN_IF(nCursor < nPos, "editeng", + "Cursor in the heart of the action?!"); + nCursor = nCursor + rTxt.getLength(); + + if ( bAllowUndoAction && ( rTxt.getLength() == 1 ) ) + ImplStartUndoAction(); + bAllowUndoAction = false; + + return true; +} + +bool EdtAutoCorrDoc::Replace(sal_Int32 nPos, const OUString& rTxt) +{ + return ReplaceRange( nPos, rTxt.getLength(), rTxt ); +} + +bool EdtAutoCorrDoc::ReplaceRange(sal_Int32 nPos, sal_Int32 nSourceLength, const OUString& rTxt) +{ + // Actually a Replace introduce => corresponds to UNDO + sal_Int32 nEnd = nPos+nSourceLength; + if ( nEnd > pCurNode->Len() ) + nEnd = pCurNode->Len(); + + // #i5925# First insert new text behind to be deleted text, for keeping attributes. + mpEditEngine->InsertText(EditSelection(EditPaM(pCurNode, nEnd)), rTxt); + mpEditEngine->DeleteSelection( + EditSelection(EditPaM(pCurNode, nPos), EditPaM(pCurNode, nEnd))); + + if ( nPos == nCursor ) + nCursor = nCursor + rTxt.getLength(); + + if ( bAllowUndoAction && ( rTxt.getLength() == 1 ) ) + ImplStartUndoAction(); + + bAllowUndoAction = false; + + return true; +} + +void EdtAutoCorrDoc::SetAttr(sal_Int32 nStt, sal_Int32 nEnd, + sal_uInt16 nSlotId, SfxPoolItem& rItem) +{ + SfxItemPool* pPool = &mpEditEngine->GetEditDoc().GetItemPool(); + while ( pPool->GetSecondaryPool() && + pPool->GetName() != "EditEngineItemPool" ) + { + pPool = pPool->GetSecondaryPool(); + + } + sal_uInt16 nWhich = pPool->GetWhich( nSlotId ); + if ( nWhich ) + { + rItem.SetWhich( nWhich ); + + SfxItemSet aSet = mpEditEngine->GetEmptyItemSet(); + aSet.Put( rItem ); + + EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) ); + aSel.Max().SetIndex( nEnd ); // ??? + mpEditEngine->SetAttribs( aSel, aSet, SetAttribsMode::Edge ); + bAllowUndoAction = false; + } +} + +bool EdtAutoCorrDoc::SetINetAttr(sal_Int32 nStt, sal_Int32 nEnd, + const OUString& rURL) +{ + // Turn the Text into a command field ... + EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) ); + OUString aText = mpEditEngine->GetSelected(aSel); + aSel = mpEditEngine->DeleteSelection(aSel); + SAL_WARN_IF(nCursor < nEnd, "editeng", + "Cursor in the heart of the action?!"); + nCursor -= ( nEnd-nStt ); + SvxFieldItem aField( SvxURLField( rURL, aText, SvxURLFormat::Repr ), + EE_FEATURE_FIELD ); + mpEditEngine->InsertField(aSel, aField); + nCursor++; + mpEditEngine->UpdateFieldsOnly(); + bAllowUndoAction = false; + return true; +} + +OUString const* EdtAutoCorrDoc::GetPrevPara(bool const) +{ + // Return previous paragraph, so that it can be determined, + // whether the current word is at the beginning of a sentence. + + bAllowUndoAction = false; // Not anymore ... + + EditDoc& rNodes = mpEditEngine->GetEditDoc(); + sal_Int32 nPos = rNodes.GetPos( pCurNode ); + + // Special case: Bullet => Paragraph start => simply return NULL... + const SfxBoolItem& rBulletState = mpEditEngine->GetParaAttrib( nPos, EE_PARA_BULLETSTATE ); + bool bBullet = rBulletState.GetValue(); + if ( !bBullet && (mpEditEngine->GetControlWord() & EEControlBits::OUTLINER) ) + { + // The Outliner has still a Bullet at Level 0. + const SfxInt16Item& rLevel = mpEditEngine->GetParaAttrib( nPos, EE_PARA_OUTLLEVEL ); + if ( rLevel.GetValue() == 0 ) + bBullet = true; + } + if ( bBullet ) + return nullptr; + + for ( sal_Int32 n = nPos; n; ) + { + n--; + ContentNode* pNode = rNodes[n]; + if ( pNode->Len() ) + return & pNode->GetString(); + } + return nullptr; + +} + +bool EdtAutoCorrDoc::ChgAutoCorrWord( sal_Int32& rSttPos, + sal_Int32 nEndPos, SvxAutoCorrect& rACorrect, + OUString* pPara ) +{ + // Paragraph-start or a blank found, search for the word + // shortcut in Auto + bAllowUndoAction = false; // Not anymore ... + + OUString aShort( pCurNode->Copy( rSttPos, nEndPos - rSttPos ) ); + bool bRet = false; + + if( aShort.isEmpty() ) + return bRet; + + LanguageTag aLanguageTag( mpEditEngine->GetLanguage( EditPaM( pCurNode, rSttPos+1 ) ).nLang ); + const SvxAutocorrWord* pFnd = rACorrect.SearchWordsInList( + pCurNode->GetString(), rSttPos, nEndPos, *this, aLanguageTag); + if( pFnd && pFnd->IsTextOnly() ) + { + + // replace also last colon of keywords surrounded by colons (for example, ":name:") + bool replaceLastChar = pFnd->GetShort()[0] == ':' && pFnd->GetShort().endsWith(":"); + + // then replace + EditSelection aSel( EditPaM( pCurNode, rSttPos ), + EditPaM( pCurNode, nEndPos + (replaceLastChar ? 1 : 0) )); + aSel = mpEditEngine->DeleteSelection(aSel); + SAL_WARN_IF(nCursor < nEndPos, "editeng", + "Cursor in the heart of the action?!"); + nCursor -= ( nEndPos-rSttPos ); + mpEditEngine->InsertText(aSel, pFnd->GetLong()); + nCursor = nCursor + pFnd->GetLong().getLength(); + if( pPara ) + *pPara = pCurNode->GetString(); + bRet = true; + } + + return bRet; +} + +bool EdtAutoCorrDoc::TransliterateRTLWord( sal_Int32& /*rSttPos*/, + sal_Int32 /*nEndPos*/, bool /*bApply*/ ) +{ + // Paragraph-start or a blank found, search for the word + // shortcut in Auto + bool bRet = false; + + return bRet; +} + + +LanguageType EdtAutoCorrDoc::GetLanguage( sal_Int32 nPos ) const +{ + return mpEditEngine->GetLanguage( EditPaM( pCurNode, nPos+1 ) ).nLang; +} + +void EdtAutoCorrDoc::ImplStartUndoAction() +{ + sal_Int32 nPara = mpEditEngine->GetEditDoc().GetPos( pCurNode ); + ESelection aSel( nPara, nCursor, nPara, nCursor ); + mpEditEngine->UndoActionStart( EDITUNDO_INSERT, aSel ); + bUndoAction = true; + bAllowUndoAction = false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eehtml.cxx b/editeng/source/editeng/eehtml.cxx new file mode 100644 index 000000000..0820c48df --- /dev/null +++ b/editeng/source/editeng/eehtml.cxx @@ -0,0 +1,820 @@ +/* -*- 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 "eehtml.hxx" +#include <editeng/adjustitem.hxx> +#include <editeng/flditem.hxx> +#include <tools/urlobj.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/wghtitem.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <tools/tenccvt.hxx> + +#include <editeng/editeng.hxx> +#include <utility> + +#define STYLE_PRE 101 + +EditHTMLParser::EditHTMLParser( SvStream& rIn, OUString _aBaseURL, SvKeyValueIterator* pHTTPHeaderAttrs ) + : HTMLParser( rIn, true ), + aBaseURL(std::move( _aBaseURL )), + mpEditEngine(nullptr), + bInPara(false), + bWasInPara(false), + bFieldsInserted(false), + bInTitle(false), + nInTable(0), + nInCell(0), + nDefListLevel(0) +{ + DBG_ASSERT( !IsSwitchToUCS2(), "EditHTMLParser::::EditHTMLParser: Switch to UCS2?" ); + + // Although the real default encoding is ISO8859-1, we use MS-1252 + // as default encoding. + SetSrcEncoding( GetExtendedCompatibilityTextEncoding( RTL_TEXTENCODING_ISO_8859_1 ) ); + + // If the file starts with a BOM, switch to UCS2. + SetSwitchToUCS2( true ); + + if ( pHTTPHeaderAttrs ) + SetEncodingByHTTPHeader( pHTTPHeaderAttrs ); +} + +EditHTMLParser::~EditHTMLParser() +{ +} + +SvParserState EditHTMLParser::CallParser(EditEngine* pEE, const EditPaM& rPaM) +{ + DBG_ASSERT(pEE, "CallParser: ImpEditEngine ?!"); + mpEditEngine = pEE; + SvParserState _eState = SvParserState::NotStarted; + if ( mpEditEngine ) + { + // Build in wrap mimic in RTF import? + aCurSel = EditSelection( rPaM, rPaM ); + + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::Start, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + + ImpSetStyleSheet( 0 ); + _eState = HTMLParser::CallParser(); + + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::End, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + + if ( bFieldsInserted ) + mpEditEngine->UpdateFieldsOnly(); + } + return _eState; +} + +void EditHTMLParser::NextToken( HtmlTokenId nToken ) +{ + switch( nToken ) + { + case HtmlTokenId::META: + { + const HTMLOptions& aOptions = GetOptions(); + size_t nArrLen = aOptions.size(); + bool bEquiv = false; + for ( size_t i = 0; i < nArrLen; i++ ) + { + const HTMLOption& aOption = aOptions[i]; + switch( aOption.GetToken() ) + { + case HtmlOptionId::HTTPEQUIV: + { + bEquiv = true; + } + break; + case HtmlOptionId::CONTENT: + { + if ( bEquiv ) + { + rtl_TextEncoding eEnc = GetEncodingByMIME( aOption.GetString() ); + if ( eEnc != RTL_TEXTENCODING_DONTKNOW ) + SetSrcEncoding( eEnc ); + } + } + break; + default: break; + } + } + + } + break; + case HtmlTokenId::PLAINTEXT_ON: + case HtmlTokenId::PLAINTEXT2_ON: + bInPara = true; + break; + case HtmlTokenId::PLAINTEXT_OFF: + case HtmlTokenId::PLAINTEXT2_OFF: + bInPara = false; + break; + + case HtmlTokenId::LINEBREAK: + case HtmlTokenId::NEWPARA: + { + if ( ( bInPara || nInTable ) && + ( ( nToken == HtmlTokenId::LINEBREAK ) || HasTextInCurrentPara() ) ) + { + ImpInsertParaBreak(); + } + } + break; + case HtmlTokenId::HORZRULE: + { + if ( HasTextInCurrentPara() ) + ImpInsertParaBreak(); + ImpInsertParaBreak(); + } + break; + case HtmlTokenId::NONBREAKSPACE: + { + if ( bInPara ) + { + ImpInsertText( " " ); + } + } + break; + case HtmlTokenId::RAWDATA: + if (IsReadStyle() && !aToken.isEmpty()) + { + // Each token represents a single line. + maStyleSource.append(aToken); + maStyleSource.append('\n'); + } + break; + case HtmlTokenId::TEXTTOKEN: + { + // #i110937# for <title> content, call aImportHdl (no SkipGroup), but don't insert the text into the EditEngine + if (!bInTitle) + { + if ( !bInPara ) + StartPara( false ); + + OUString aText = aToken.toString(); + if ( aText.startsWith(" ") && ThrowAwayBlank() && !IsReadPRE() ) + aText = aText.copy( 1 ); + + if ( pCurAnchor ) + { + pCurAnchor->aText += aText; + } + else + { + // Only written until HTML with 319? + if ( IsReadPRE() ) + { + sal_Int32 nTabPos = aText.indexOf( '\t'); + while ( nTabPos != -1 ) + { + aText = aText.replaceAt( nTabPos, 1, u" " ); + nTabPos = aText.indexOf( '\t', nTabPos+8 ); + } + } + ImpInsertText( aText ); + } + } + } + break; + + case HtmlTokenId::CENTER_ON: + case HtmlTokenId::CENTER_OFF: + { + sal_Int32 nNode = mpEditEngine->GetEditDoc().GetPos( aCurSel.Max().GetNode() ); + SfxItemSet aItems( aCurSel.Max().GetNode()->GetContentAttribs().GetItems() ); + aItems.ClearItem( EE_PARA_JUST ); + if ( nToken == HtmlTokenId::CENTER_ON ) + aItems.Put( SvxAdjustItem( SvxAdjust::Center, EE_PARA_JUST ) ); + mpEditEngine->SetParaAttribsOnly(nNode, aItems); + } + break; + + case HtmlTokenId::ANCHOR_ON: AnchorStart(); + break; + case HtmlTokenId::ANCHOR_OFF: AnchorEnd(); + break; + + case HtmlTokenId::PARABREAK_ON: + if( bInPara && HasTextInCurrentPara() ) + EndPara(); + StartPara( true ); + break; + + case HtmlTokenId::PARABREAK_OFF: + if( bInPara ) + EndPara(); + break; + + case HtmlTokenId::HEAD1_ON: + case HtmlTokenId::HEAD2_ON: + case HtmlTokenId::HEAD3_ON: + case HtmlTokenId::HEAD4_ON: + case HtmlTokenId::HEAD5_ON: + case HtmlTokenId::HEAD6_ON: + { + HeadingStart( nToken ); + } + break; + + case HtmlTokenId::HEAD1_OFF: + case HtmlTokenId::HEAD2_OFF: + case HtmlTokenId::HEAD3_OFF: + case HtmlTokenId::HEAD4_OFF: + case HtmlTokenId::HEAD5_OFF: + case HtmlTokenId::HEAD6_OFF: + { + HeadingEnd(); + } + break; + + case HtmlTokenId::PREFORMTXT_ON: + case HtmlTokenId::XMP_ON: + case HtmlTokenId::LISTING_ON: + { + StartPara( true ); + ImpSetStyleSheet( STYLE_PRE ); + } + break; + + case HtmlTokenId::DEFLIST_ON: + { + nDefListLevel++; + } + break; + + case HtmlTokenId::DEFLIST_OFF: + { + if( nDefListLevel ) + nDefListLevel--; + } + break; + + case HtmlTokenId::TABLE_ON: nInTable++; + break; + case HtmlTokenId::TABLE_OFF: DBG_ASSERT( nInTable, "Not in Table, but TABLE_OFF?" ); + nInTable--; + break; + + case HtmlTokenId::TABLEHEADER_ON: + case HtmlTokenId::TABLEDATA_ON: + nInCell++; + [[fallthrough]]; + case HtmlTokenId::BLOCKQUOTE_ON: + case HtmlTokenId::BLOCKQUOTE_OFF: + case HtmlTokenId::BLOCKQUOTE30_ON: + case HtmlTokenId::BLOCKQUOTE30_OFF: + case HtmlTokenId::LISTHEADER_ON: + case HtmlTokenId::LI_ON: + case HtmlTokenId::DD_ON: + case HtmlTokenId::DT_ON: + case HtmlTokenId::ORDERLIST_ON: + case HtmlTokenId::UNORDERLIST_ON: + { + bool bHasText = HasTextInCurrentPara(); + if ( bHasText ) + ImpInsertParaBreak(); + StartPara( false ); + } + break; + + case HtmlTokenId::TABLEHEADER_OFF: + case HtmlTokenId::TABLEDATA_OFF: + { + if ( nInCell ) + nInCell--; + [[fallthrough]]; + } + case HtmlTokenId::LISTHEADER_OFF: + case HtmlTokenId::LI_OFF: + case HtmlTokenId::DD_OFF: + case HtmlTokenId::DT_OFF: + case HtmlTokenId::ORDERLIST_OFF: + case HtmlTokenId::UNORDERLIST_OFF: EndPara(); + break; + + case HtmlTokenId::TABLEROW_ON: + case HtmlTokenId::TABLEROW_OFF: // A RETURN only after a CELL, for Calc + + case HtmlTokenId::COL_ON: + case HtmlTokenId::COLGROUP_ON: + case HtmlTokenId::COLGROUP_OFF: break; + + case HtmlTokenId::FONT_ON: + break; + case HtmlTokenId::FONT_OFF: + break; + + case HtmlTokenId::TITLE_ON: + bInTitle = true; + break; + case HtmlTokenId::TITLE_OFF: + bInTitle = false; + break; + + // globals + case HtmlTokenId::HTML_ON: + case HtmlTokenId::HTML_OFF: + case HtmlTokenId::STYLE_ON: + case HtmlTokenId::STYLE_OFF: + case HtmlTokenId::BODY_ON: + case HtmlTokenId::BODY_OFF: + case HtmlTokenId::HEAD_ON: + case HtmlTokenId::HEAD_OFF: + case HtmlTokenId::FORM_ON: + case HtmlTokenId::FORM_OFF: + case HtmlTokenId::THEAD_ON: + case HtmlTokenId::THEAD_OFF: + case HtmlTokenId::TBODY_ON: + case HtmlTokenId::TBODY_OFF: + // inline elements, structural markup + // HTML 3.0 + case HtmlTokenId::BANNER_ON: + case HtmlTokenId::BANNER_OFF: + case HtmlTokenId::DIVISION_ON: + case HtmlTokenId::DIVISION_OFF: +// case HtmlTokenId::LISTHEADER_ON: //! special handling +// case HtmlTokenId::LISTHEADER_OFF: + case HtmlTokenId::NOTE_ON: + case HtmlTokenId::NOTE_OFF: + // inline elements, logical markup + // HTML 2.0 + case HtmlTokenId::ADDRESS_ON: + case HtmlTokenId::ADDRESS_OFF: +// case HtmlTokenId::BLOCKQUOTE_ON: //! special handling +// case HtmlTokenId::BLOCKQUOTE_OFF: + case HtmlTokenId::CITATION_ON: + case HtmlTokenId::CITATION_OFF: + case HtmlTokenId::CODE_ON: + case HtmlTokenId::CODE_OFF: + case HtmlTokenId::DEFINSTANCE_ON: + case HtmlTokenId::DEFINSTANCE_OFF: + case HtmlTokenId::EMPHASIS_ON: + case HtmlTokenId::EMPHASIS_OFF: + case HtmlTokenId::KEYBOARD_ON: + case HtmlTokenId::KEYBOARD_OFF: + case HtmlTokenId::SAMPLE_ON: + case HtmlTokenId::SAMPLE_OFF: + case HtmlTokenId::STRIKE_ON: + case HtmlTokenId::STRIKE_OFF: + case HtmlTokenId::STRONG_ON: + case HtmlTokenId::STRONG_OFF: + case HtmlTokenId::VARIABLE_ON: + case HtmlTokenId::VARIABLE_OFF: + // HTML 3.0 + case HtmlTokenId::ABBREVIATION_ON: + case HtmlTokenId::ABBREVIATION_OFF: + case HtmlTokenId::ACRONYM_ON: + case HtmlTokenId::ACRONYM_OFF: + case HtmlTokenId::AUTHOR_ON: + case HtmlTokenId::AUTHOR_OFF: +// case HtmlTokenId::BLOCKQUOTE30_ON: //! special handling +// case HtmlTokenId::BLOCKQUOTE30_OFF: + case HtmlTokenId::DELETEDTEXT_ON: + case HtmlTokenId::DELETEDTEXT_OFF: + case HtmlTokenId::INSERTEDTEXT_ON: + case HtmlTokenId::INSERTEDTEXT_OFF: + case HtmlTokenId::LANGUAGE_ON: + case HtmlTokenId::LANGUAGE_OFF: + case HtmlTokenId::PERSON_ON: + case HtmlTokenId::PERSON_OFF: + case HtmlTokenId::SHORTQUOTE_ON: + case HtmlTokenId::SHORTQUOTE_OFF: + case HtmlTokenId::SUBSCRIPT_ON: + case HtmlTokenId::SUBSCRIPT_OFF: + case HtmlTokenId::SUPERSCRIPT_ON: + case HtmlTokenId::SUPERSCRIPT_OFF: + // inline elements, visual markup + // HTML 2.0 + case HtmlTokenId::BOLD_ON: + case HtmlTokenId::BOLD_OFF: + case HtmlTokenId::ITALIC_ON: + case HtmlTokenId::ITALIC_OFF: + case HtmlTokenId::TELETYPE_ON: + case HtmlTokenId::TELETYPE_OFF: + case HtmlTokenId::UNDERLINE_ON: + case HtmlTokenId::UNDERLINE_OFF: + // HTML 3.0 + case HtmlTokenId::BIGPRINT_ON: + case HtmlTokenId::BIGPRINT_OFF: + case HtmlTokenId::STRIKETHROUGH_ON: + case HtmlTokenId::STRIKETHROUGH_OFF: + case HtmlTokenId::SMALLPRINT_ON: + case HtmlTokenId::SMALLPRINT_OFF: + // figures + case HtmlTokenId::FIGURE_ON: + case HtmlTokenId::FIGURE_OFF: + case HtmlTokenId::CAPTION_ON: + case HtmlTokenId::CAPTION_OFF: + case HtmlTokenId::CREDIT_ON: + case HtmlTokenId::CREDIT_OFF: + // misc + case HtmlTokenId::DIRLIST_ON: + case HtmlTokenId::DIRLIST_OFF: + case HtmlTokenId::FOOTNOTE_ON: //! they land so in the text + case HtmlTokenId::FOOTNOTE_OFF: + case HtmlTokenId::MENULIST_ON: + case HtmlTokenId::MENULIST_OFF: +// case HtmlTokenId::PLAINTEXT_ON: //! special handling +// case HtmlTokenId::PLAINTEXT_OFF: +// case HtmlTokenId::PREFORMTXT_ON: //! special handling +// case HtmlTokenId::PREFORMTXT_OFF: + case HtmlTokenId::SPAN_ON: + case HtmlTokenId::SPAN_OFF: + // obsolete +// case HtmlTokenId::XMP_ON: //! special handling +// case HtmlTokenId::XMP_OFF: +// case HtmlTokenId::LISTING_ON: //! special handling +// case HtmlTokenId::LISTING_OFF: + // Netscape + case HtmlTokenId::BLINK_ON: + case HtmlTokenId::BLINK_OFF: + case HtmlTokenId::NOBR_ON: + case HtmlTokenId::NOBR_OFF: + case HtmlTokenId::NOEMBED_ON: + case HtmlTokenId::NOEMBED_OFF: + case HtmlTokenId::NOFRAMES_ON: + case HtmlTokenId::NOFRAMES_OFF: + // Internet Explorer + case HtmlTokenId::MARQUEE_ON: + case HtmlTokenId::MARQUEE_OFF: +// case HtmlTokenId::PLAINTEXT2_ON: //! special handling +// case HtmlTokenId::PLAINTEXT2_OFF: + break; + + default: + { + if ( nToken >= HtmlTokenId::ONOFF_START ) + { + if ( ( nToken == HtmlTokenId::UNKNOWNCONTROL_ON ) || ( nToken == HtmlTokenId::UNKNOWNCONTROL_OFF ) ) + { + ; + } + else if ( !isOffToken(nToken) ) + { + DBG_ASSERT( !isOffToken( nToken ), "No Start-Token ?!" ); + SkipGroup( static_cast<HtmlTokenId>(static_cast<int>(nToken) + 1) ); + } + } + } + } // SWITCH + + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::NextToken, this, mpEditEngine->CreateESelection(aCurSel)); + aImportInfo.nToken = nToken; + if ( nToken == HtmlTokenId::TEXTTOKEN ) + aImportInfo.aText = aToken; + else if (nToken == HtmlTokenId::STYLE_OFF) + aImportInfo.aText = maStyleSource.makeStringAndClear(); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + +} + +void EditHTMLParser::ImpInsertParaBreak() +{ + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::InsertPara, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + aCurSel = mpEditEngine->InsertParaBreak(aCurSel); +} + +void EditHTMLParser::ImpSetAttribs( const SfxItemSet& rItems ) +{ + // pSel, when character attributes, otherwise paragraph attributes for + // the current paragraph. + DBG_ASSERT( aCurSel.Min().GetNode() == aCurSel.Max().GetNode(), "ImpInsertAttribs: Selection?" ); + + EditPaM aStartPaM( aCurSel.Min() ); + EditPaM aEndPaM( aCurSel.Max() ); + + aStartPaM.SetIndex( 0 ); + aEndPaM.SetIndex( aEndPaM.GetNode()->Len() ); + + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + EditSelection aSel( aStartPaM, aEndPaM ); + HtmlImportInfo aImportInfo(HtmlImportState::SetAttr, this, mpEditEngine->CreateESelection(aSel)); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + + ContentNode* pSN = aStartPaM.GetNode(); + sal_Int32 nStartNode = mpEditEngine->GetEditDoc().GetPos( pSN ); + + // If an attribute goes from 0 to current Paragraph length, + // then it should be a paragraph attribute! + + // Note: Selection can reach over several Paragraphs. + // All complete paragraphs are paragraph attributes ... + + // not really HTML: +#ifdef DBG_UTIL + ContentNode* pEN = aEndPaM.GetNode(); + sal_Int32 nEndNode = mpEditEngine->GetEditDoc().GetPos( pEN ); + DBG_ASSERT( nStartNode == nEndNode, "ImpSetAttribs: Several paragraphs?" ); +#endif + + if ( ( aStartPaM.GetIndex() == 0 ) && ( aEndPaM.GetIndex() == aEndPaM.GetNode()->Len() ) ) + { + // Has to be merged: + SfxItemSet aItems = mpEditEngine->GetBaseParaAttribs(nStartNode); + aItems.Put( rItems ); + mpEditEngine->SetParaAttribsOnly(nStartNode, aItems); + } + else + mpEditEngine->SetAttribs( EditSelection( aStartPaM, aEndPaM ), rItems ); +} + +void EditHTMLParser::ImpSetStyleSheet( sal_uInt16 nHLevel ) +{ + /* + nHLevel: 0: Turn off + 1-6: Heading + STYLE_PRE: Preformatted + */ + // Create hard attributes ... + // Enough for Calc, would have to be clarified with StyleSheets + // that they should also be in the app so that when they are feed + // in a different engine still are here ... + sal_Int32 nNode = mpEditEngine->GetEditDoc().GetPos( aCurSel.Max().GetNode() ); + + SfxItemSet aItems( aCurSel.Max().GetNode()->GetContentAttribs().GetItems() ); + + aItems.ClearItem( EE_PARA_ULSPACE ); + + aItems.ClearItem( EE_CHAR_FONTHEIGHT ); + aItems.ClearItem( EE_CHAR_FONTINFO ); + aItems.ClearItem( EE_CHAR_WEIGHT ); + + aItems.ClearItem( EE_CHAR_FONTHEIGHT_CJK ); + aItems.ClearItem( EE_CHAR_FONTINFO_CJK ); + aItems.ClearItem( EE_CHAR_WEIGHT_CJK ); + + aItems.ClearItem( EE_CHAR_FONTHEIGHT_CTL ); + aItems.ClearItem( EE_CHAR_FONTINFO_CTL ); + aItems.ClearItem( EE_CHAR_WEIGHT_CTL ); + + // Bold in the first 3 Headings + if ( ( nHLevel >= 1 ) && ( nHLevel <= 3 ) ) + { + SvxWeightItem aWeightItem( WEIGHT_BOLD, EE_CHAR_WEIGHT ); + aItems.Put( aWeightItem ); + + SvxWeightItem aWeightItemCJK( WEIGHT_BOLD, EE_CHAR_WEIGHT_CJK ); + aItems.Put( aWeightItemCJK ); + + SvxWeightItem aWeightItemCTL( WEIGHT_BOLD, EE_CHAR_WEIGHT_CTL ); + aItems.Put( aWeightItemCTL ); + } + + // Font height and margins, when LogicToLogic is possible: + MapUnit eUnit = mpEditEngine->GetRefMapMode().GetMapUnit(); + if ( ( eUnit != MapUnit::MapPixel ) && ( eUnit != MapUnit::MapSysFont ) && + ( eUnit != MapUnit::MapAppFont ) && ( eUnit != MapUnit::MapRelative ) ) + { + tools::Long nPoints = 10; + if ( nHLevel == 1 ) + nPoints = 22; + else if ( nHLevel == 2 ) + nPoints = 16; + else if ( nHLevel == 3 ) + nPoints = 12; + else if ( nHLevel == 4 ) + nPoints = 11; + + nPoints = OutputDevice::LogicToLogic( nPoints, MapUnit::MapPoint, eUnit ); + + SvxFontHeightItem aHeightItem( nPoints, 100, EE_CHAR_FONTHEIGHT ); + aItems.Put( aHeightItem ); + + SvxFontHeightItem aHeightItemCJK( nPoints, 100, EE_CHAR_FONTHEIGHT_CJK ); + aItems.Put( aHeightItemCJK ); + + SvxFontHeightItem aHeightItemCTL( nPoints, 100, EE_CHAR_FONTHEIGHT_CTL ); + aItems.Put( aHeightItemCTL ); + + // Paragraph margins, when Heading: + if (nHLevel <= 6) + { + SvxULSpaceItem aULSpaceItem( EE_PARA_ULSPACE ); + aULSpaceItem.SetUpper( static_cast<sal_uInt16>(OutputDevice::LogicToLogic( 42, MapUnit::Map10thMM, eUnit )) ); + aULSpaceItem.SetLower( static_cast<sal_uInt16>(OutputDevice::LogicToLogic( 35, MapUnit::Map10thMM, eUnit )) ); + aItems.Put( aULSpaceItem ); + } + } + + // Choose a proportional Font for Pre + if ( nHLevel == STYLE_PRE ) + { + vcl::Font aFont = OutputDevice::GetDefaultFont( DefaultFontType::FIXED, LANGUAGE_SYSTEM, GetDefaultFontFlags::NONE ); + SvxFontItem aFontItem( aFont.GetFamilyType(), aFont.GetFamilyName(), OUString(), aFont.GetPitch(), aFont.GetCharSet(), EE_CHAR_FONTINFO ); + aItems.Put( aFontItem ); + + SvxFontItem aFontItemCJK( aFont.GetFamilyType(), aFont.GetFamilyName(), OUString(), aFont.GetPitch(), aFont.GetCharSet(), EE_CHAR_FONTINFO_CJK ); + aItems.Put( aFontItemCJK ); + + SvxFontItem aFontItemCTL( aFont.GetFamilyType(), aFont.GetFamilyName(), OUString(), aFont.GetPitch(), aFont.GetCharSet(), EE_CHAR_FONTINFO_CTL ); + aItems.Put( aFontItemCTL ); + } + + mpEditEngine->SetParaAttribsOnly(nNode, aItems); +} + +void EditHTMLParser::ImpInsertText( const OUString& rText ) +{ + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::InsertText, this, mpEditEngine->CreateESelection(aCurSel)); + aImportInfo.aText = rText; + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + + aCurSel = mpEditEngine->InsertText(aCurSel, rText); +} + +void EditHTMLParser::SkipGroup( HtmlTokenId nEndToken ) +{ + // groups in cells are closed upon leaving the cell, because those + // ******* web authors don't know their job + // for example: <td><form></td> lacks a closing </form> + sal_uInt8 nCellLevel = nInCell; + HtmlTokenId nToken; + while( nCellLevel <= nInCell ) + { + nToken = GetNextToken(); + if (nToken == nEndToken || nToken == HtmlTokenId::NONE) + break; + switch ( nToken ) + { + case HtmlTokenId::TABLEHEADER_ON: + case HtmlTokenId::TABLEDATA_ON: + nInCell++; + break; + case HtmlTokenId::TABLEHEADER_OFF: + case HtmlTokenId::TABLEDATA_OFF: + if ( nInCell ) + nInCell--; + break; + default: break; + } + } +} + +void EditHTMLParser::StartPara( bool bReal ) +{ + if ( bReal ) + { + const HTMLOptions& aOptions = GetOptions(); + SvxAdjust eAdjust = SvxAdjust::Left; + for (const auto & aOption : aOptions) + { + if( aOption.GetToken() == HtmlOptionId::ALIGN ) + { + OUString const& rTmp(aOption.GetString()); + if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_right)) + eAdjust = SvxAdjust::Right; + else if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_middle)) + eAdjust = SvxAdjust::Center; + else if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_center)) + eAdjust = SvxAdjust::Center; + else + eAdjust = SvxAdjust::Left; + } + } + SfxItemSet aItemSet = mpEditEngine->GetEmptyItemSet(); + aItemSet.Put( SvxAdjustItem( eAdjust, EE_PARA_JUST ) ); + ImpSetAttribs( aItemSet ); + } + bInPara = true; +} + +void EditHTMLParser::EndPara() +{ + if ( bInPara ) + { + bool bHasText = HasTextInCurrentPara(); + if ( bHasText ) + ImpInsertParaBreak(); + } + bInPara = false; +} + +bool EditHTMLParser::ThrowAwayBlank() +{ + // A blank must be thrown away if the new text begins with a Blank and + // if the current paragraph is empty or ends with a Blank... + ContentNode* pNode = aCurSel.Max().GetNode(); + return !(pNode->Len() && ( pNode->GetChar( pNode->Len()-1 ) != ' ' )); +} + +bool EditHTMLParser::HasTextInCurrentPara() +{ + return aCurSel.Max().GetNode()->Len() != 0; +} + +void EditHTMLParser::AnchorStart() +{ + // ignore anchor in anchor + if ( pCurAnchor ) + return; + + const HTMLOptions& aOptions = GetOptions(); + OUString aRef; + + for (const auto & aOption : aOptions) + { + if( aOption.GetToken() == HtmlOptionId::HREF) + aRef = aOption.GetString(); + } + + if ( aRef.isEmpty() ) + return; + + OUString aURL = aRef; + if ( !aURL.isEmpty() && ( aURL[ 0 ] != '#' ) ) + { + INetURLObject aTargetURL; + INetURLObject aRootURL( aBaseURL ); + aRootURL.GetNewAbsURL( aRef, &aTargetURL ); + aURL = aTargetURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); + } + pCurAnchor.reset( new AnchorInfo ); + pCurAnchor->aHRef = aURL; +} + +void EditHTMLParser::AnchorEnd() +{ + if ( !pCurAnchor ) + return; + + // Insert as URL-Field... + SvxFieldItem aFld( SvxURLField( pCurAnchor->aHRef, pCurAnchor->aText, SvxURLFormat::Repr ), EE_FEATURE_FIELD ); + aCurSel = mpEditEngine->InsertField(aCurSel, aFld); + bFieldsInserted = true; + pCurAnchor.reset(); + + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::InsertField, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } +} + +void EditHTMLParser::HeadingStart( HtmlTokenId nToken ) +{ + bWasInPara = bInPara; + StartPara( false ); + + if ( bWasInPara && HasTextInCurrentPara() ) + ImpInsertParaBreak(); + + sal_uInt16 nId = sal::static_int_cast< sal_uInt16 >( + 1 + ( ( static_cast<int>(nToken) - int(HtmlTokenId::HEAD1_ON) ) / 2 ) ); + DBG_ASSERT( (nId >= 1) && (nId <= 9), "HeadingStart: ID can not be correct!" ); + ImpSetStyleSheet( nId ); +} + +void EditHTMLParser::HeadingEnd() +{ + EndPara(); + ImpSetStyleSheet( 0 ); + + if ( bWasInPara ) + { + bInPara = true; + bWasInPara = false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eehtml.hxx b/editeng/source/editeng/eehtml.hxx new file mode 100644 index 000000000..ebcb4def0 --- /dev/null +++ b/editeng/source/editeng/eehtml.hxx @@ -0,0 +1,84 @@ +/* -*- 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 <memory> +#include <editdoc.hxx> +#include <rtl/ustrbuf.hxx> +#include <svtools/parhtml.hxx> + +class EditEngine; + +struct AnchorInfo +{ + OUString aHRef; + OUString aText; +}; + +class EditHTMLParser : public HTMLParser +{ + using HTMLParser::CallParser; +private: + OUStringBuffer maStyleSource; + EditSelection aCurSel; + OUString aBaseURL; + EditEngine* mpEditEngine; + std::unique_ptr<AnchorInfo> + pCurAnchor; + + bool bInPara:1; + bool bWasInPara:1; // Remember bInPara before HeadingStart, because afterwards it will be gone. + bool bFieldsInserted:1; + bool bInTitle:1; + + sal_uInt8 nInTable; + sal_uInt8 nInCell; + sal_uInt8 nDefListLevel; + + void StartPara( bool bReal ); + void EndPara(); + void AnchorStart(); + void AnchorEnd(); + void HeadingStart( HtmlTokenId nToken ); + void HeadingEnd(); + void SkipGroup( HtmlTokenId nEndToken ); + bool ThrowAwayBlank(); + bool HasTextInCurrentPara(); + + void ImpInsertParaBreak(); + void ImpInsertText( const OUString& rText ); + void ImpSetAttribs( const SfxItemSet& rItems ); + void ImpSetStyleSheet( sal_uInt16 nHeadingLevel ); + +protected: + virtual void NextToken( HtmlTokenId nToken ) override; + +public: + EditHTMLParser(SvStream& rIn, OUString aBaseURL, SvKeyValueIterator* pHTTPHeaderAttrs); + virtual ~EditHTMLParser() override; + + SvParserState CallParser(EditEngine* pEE, const EditPaM& rPaM); + + const EditSelection& GetCurSelection() const { return aCurSel; } +}; + +typedef tools::SvRef<EditHTMLParser> EditHTMLParserRef; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eeobj.cxx b/editeng/source/editeng/eeobj.cxx new file mode 100644 index 000000000..bfd81c40c --- /dev/null +++ b/editeng/source/editeng/eeobj.cxx @@ -0,0 +1,92 @@ +/* -*- 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 <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> +#include "eeobj.hxx" +#include <sot/exchange.hxx> +#include <sot/formats.hxx> + +using namespace ::com::sun::star; + + +EditDataObject::EditDataObject() +{ +} + +EditDataObject::~EditDataObject() +{ +} + +// datatransfer::XTransferable +uno::Any EditDataObject::getTransferData( const datatransfer::DataFlavor& rFlavor ) +{ + uno::Any aAny; + + SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor ); + if ( nT == SotClipboardFormatId::STRING ) + { + aAny <<= GetString(); + } + else if ( ( nT == SotClipboardFormatId::RTF ) || ( nT == SotClipboardFormatId::RICHTEXT ) || ( nT == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) ) + { + // No RTF on demand any more: + // 1) Was not working, because I had to flush() the clipboard immediately anyway + // 2) Don't have the old pool defaults and the StyleSheetPool here. + + SvMemoryStream* pStream = (nT == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) ? &GetODFStream() : &GetRTFStream(); + sal_Int32 nLen = pStream->TellEnd(); + if (nLen < 0) { abort(); } + + aAny <<= uno::Sequence< sal_Int8 >( static_cast< const sal_Int8* >(pStream->GetData()), pStream->TellEnd() ); + } + else + { + datatransfer::UnsupportedFlavorException aException; + throw aException; + } + + return aAny; +} + +uno::Sequence< datatransfer::DataFlavor > EditDataObject::getTransferDataFlavors( ) +{ + uno::Sequence< datatransfer::DataFlavor > aDataFlavors(4); + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT, aDataFlavors.getArray()[0] ); + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aDataFlavors.getArray()[1] ); + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RTF, aDataFlavors.getArray()[2] ); + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RICHTEXT, aDataFlavors.getArray()[3] ); + + return aDataFlavors; +} + +sal_Bool EditDataObject::isDataFlavorSupported( const datatransfer::DataFlavor& rFlavor ) +{ + bool bSupported = false; + + SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor ); + if ( ( nT == SotClipboardFormatId::STRING ) || ( nT == SotClipboardFormatId::RTF ) || ( nT == SotClipboardFormatId::RICHTEXT ) + || ( nT == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) ) + bSupported = true; + + return bSupported; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eeobj.hxx b/editeng/source/editeng/eeobj.hxx new file mode 100644 index 000000000..de06f1b70 --- /dev/null +++ b/editeng/source/editeng/eeobj.hxx @@ -0,0 +1,50 @@ +/* -*- 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 <cppuhelper/implbase.hxx> +#include <com/sun/star/datatransfer/XTransferable.hpp> + +#include <tools/stream.hxx> + +class EditDataObject : public ::cppu::WeakImplHelper<css::datatransfer::XTransferable> +{ +private: + SvMemoryStream maRTFData; + SvMemoryStream maODFData; + OUString maText; + OUString maOfficeBookmark; + +public: + EditDataObject(); + virtual ~EditDataObject() override; + + SvMemoryStream& GetRTFStream() { return maRTFData; } + SvMemoryStream& GetODFStream() { return maODFData; } + OUString& GetString() { return maText; } + OUString& GetURL() { return maOfficeBookmark; } + + // css::datatransfer::XTransferable + css::uno::Any SAL_CALL getTransferData( const css::datatransfer::DataFlavor& aFlavor ) override; + css::uno::Sequence< css::datatransfer::DataFlavor > SAL_CALL getTransferDataFlavors( ) override; + sal_Bool SAL_CALL isDataFlavorSupported( const css::datatransfer::DataFlavor& aFlavor ) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eerdll.cxx b/editeng/source/editeng/eerdll.cxx new file mode 100644 index 000000000..b7120bd1b --- /dev/null +++ b/editeng/source/editeng/eerdll.cxx @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <unotools/resmgr.hxx> +#include <com/sun/star/linguistic2/LanguageGuessing.hpp> + +#include <comphelper/processfactory.hxx> + +#include <editeng/eeitem.hxx> +#include <editeng/eerdll.hxx> +#include <eerdll2.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/bulletitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <editeng/hngpnctitem.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include <svl/grabbagitem.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> + +#include <editeng/autokernitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/numitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/xmlcnitm.hxx> +#include <editeng/forbiddencharacterstable.hxx> +#include <editeng/justifyitem.hxx> +#include <tools/mapunit.hxx> + +using namespace ::com::sun::star; + +EditDLL& EditDLL::Get() +{ + static EditDLL theEditDLL; + return theEditDLL; +} + +DefItems::DefItems() + : mvDefItems(EDITITEMCOUNT) +{ + std::vector<SfxPoolItem*>& rDefItems = mvDefItems; + + // Paragraph attributes: + SvxNumRule aDefaultNumRule( SvxNumRuleFlags::NONE, 0, false ); + + rDefItems[0] = new SvxFrameDirectionItem( SvxFrameDirection::Horizontal_LR_TB, EE_PARA_WRITINGDIR ); + rDefItems[1] = new SvXMLAttrContainerItem( EE_PARA_XMLATTRIBS ); + rDefItems[2] = new SvxHangingPunctuationItem(false, EE_PARA_HANGINGPUNCTUATION); + rDefItems[3] = new SvxForbiddenRuleItem(true, EE_PARA_FORBIDDENRULES); + rDefItems[4] = new SvxScriptSpaceItem( true, EE_PARA_ASIANCJKSPACING ); + rDefItems[5] = new SvxNumBulletItem( aDefaultNumRule, EE_PARA_NUMBULLET ); + rDefItems[6] = new SfxBoolItem( EE_PARA_HYPHENATE, false ); + rDefItems[7] = new SfxBoolItem( EE_PARA_HYPHENATE_NO_CAPS, false ); + rDefItems[8] = new SfxBoolItem( EE_PARA_HYPHENATE_NO_LAST_WORD, false ); + rDefItems[9] = new SfxBoolItem( EE_PARA_BULLETSTATE, true ); + rDefItems[10] = new SvxLRSpaceItem( EE_PARA_OUTLLRSPACE ); + rDefItems[11] = new SfxInt16Item( EE_PARA_OUTLLEVEL, -1 ); + rDefItems[12] = new SvxBulletItem( EE_PARA_BULLET ); + rDefItems[13] = new SvxLRSpaceItem( EE_PARA_LRSPACE ); + rDefItems[14] = new SvxULSpaceItem( EE_PARA_ULSPACE ); + rDefItems[15] = new SvxLineSpacingItem( 0, EE_PARA_SBL ); + rDefItems[16] = new SvxAdjustItem( SvxAdjust::Left, EE_PARA_JUST ); + rDefItems[17] = new SvxTabStopItem( 0, 0, SvxTabAdjust::Left, EE_PARA_TABS ); + rDefItems[18] = new SvxJustifyMethodItem( SvxCellJustifyMethod::Auto, EE_PARA_JUST_METHOD ); + rDefItems[19] = new SvxVerJustifyItem( SvxCellVerJustify::Standard, EE_PARA_VER_JUST ); + + // Character attributes: + rDefItems[20] = new SvxColorItem( COL_AUTO, EE_CHAR_COLOR ); + rDefItems[21] = new SvxFontItem( EE_CHAR_FONTINFO ); + rDefItems[22] = new SvxFontHeightItem( 240, 100, EE_CHAR_FONTHEIGHT ); + rDefItems[23] = new SvxCharScaleWidthItem( 100, EE_CHAR_FONTWIDTH ); + rDefItems[24] = new SvxWeightItem( WEIGHT_NORMAL, EE_CHAR_WEIGHT ); + rDefItems[25] = new SvxUnderlineItem( LINESTYLE_NONE, EE_CHAR_UNDERLINE ); + rDefItems[26] = new SvxCrossedOutItem( STRIKEOUT_NONE, EE_CHAR_STRIKEOUT ); + rDefItems[27] = new SvxPostureItem( ITALIC_NONE, EE_CHAR_ITALIC ); + rDefItems[28] = new SvxContourItem( false, EE_CHAR_OUTLINE ); + rDefItems[29] = new SvxShadowedItem( false, EE_CHAR_SHADOW ); + rDefItems[30] = new SvxEscapementItem( 0, 100, EE_CHAR_ESCAPEMENT ); + rDefItems[31] = new SvxAutoKernItem( false, EE_CHAR_PAIRKERNING ); + rDefItems[32] = new SvxKerningItem( 0, EE_CHAR_KERNING ); + rDefItems[33] = new SvxWordLineModeItem( false, EE_CHAR_WLM ); + rDefItems[34] = new SvxLanguageItem( LANGUAGE_DONTKNOW, EE_CHAR_LANGUAGE ); + rDefItems[35] = new SvxLanguageItem( LANGUAGE_DONTKNOW, EE_CHAR_LANGUAGE_CJK ); + rDefItems[36] = new SvxLanguageItem( LANGUAGE_DONTKNOW, EE_CHAR_LANGUAGE_CTL ); + rDefItems[37] = new SvxFontItem( EE_CHAR_FONTINFO_CJK ); + rDefItems[38] = new SvxFontItem( EE_CHAR_FONTINFO_CTL ); + rDefItems[39] = new SvxFontHeightItem( 240, 100, EE_CHAR_FONTHEIGHT_CJK ); + rDefItems[40] = new SvxFontHeightItem( 240, 100, EE_CHAR_FONTHEIGHT_CTL ); + rDefItems[41] = new SvxWeightItem( WEIGHT_NORMAL, EE_CHAR_WEIGHT_CJK ); + rDefItems[42] = new SvxWeightItem( WEIGHT_NORMAL, EE_CHAR_WEIGHT_CTL ); + rDefItems[43] = new SvxPostureItem( ITALIC_NONE, EE_CHAR_ITALIC_CJK ); + rDefItems[44] = new SvxPostureItem( ITALIC_NONE, EE_CHAR_ITALIC_CTL ); + rDefItems[45] = new SvxEmphasisMarkItem( FontEmphasisMark::NONE, EE_CHAR_EMPHASISMARK ); + rDefItems[46] = new SvxCharReliefItem( FontRelief::NONE, EE_CHAR_RELIEF ); + rDefItems[47] = new SfxVoidItem( EE_CHAR_RUBI_DUMMY ); + rDefItems[48] = new SvXMLAttrContainerItem( EE_CHAR_XMLATTRIBS ); + rDefItems[49] = new SvxOverlineItem( LINESTYLE_NONE, EE_CHAR_OVERLINE ); + rDefItems[50] = new SvxCaseMapItem( SvxCaseMap::NotMapped, EE_CHAR_CASEMAP ); + rDefItems[51] = new SfxGrabBagItem( EE_CHAR_GRABBAG ); + rDefItems[52] = new SvxColorItem( COL_AUTO, EE_CHAR_BKGCOLOR ); + // Features + rDefItems[53] = new SfxVoidItem( EE_FEATURE_TAB ); + rDefItems[54] = new SfxVoidItem( EE_FEATURE_LINEBR ); + rDefItems[55] = new SvxColorItem( COL_RED, EE_FEATURE_NOTCONV ); + rDefItems[56] = new SvxFieldItem( SvxFieldData(), EE_FEATURE_FIELD ); + + assert(EDITITEMCOUNT == 57 && "ITEMCOUNT changed, adjust DefItems!"); + + // Init DefFonts: + GetDefaultFonts( *static_cast<SvxFontItem*>(rDefItems[EE_CHAR_FONTINFO - EE_ITEMS_START]), + *static_cast<SvxFontItem*>(rDefItems[EE_CHAR_FONTINFO_CJK - EE_ITEMS_START]), + *static_cast<SvxFontItem*>(rDefItems[EE_CHAR_FONTINFO_CTL - EE_ITEMS_START]) ); +} + +DefItems::~DefItems() +{ + for (const auto& rItem : mvDefItems) + delete rItem; +} + +std::shared_ptr<DefItems> GlobalEditData::GetDefItems() +{ + auto xDefItems = m_xDefItems.lock(); + if (!xDefItems) + { + xDefItems = std::make_shared<DefItems>(); + m_xDefItems = xDefItems; + } + return xDefItems; +} + +std::shared_ptr<SvxForbiddenCharactersTable> const & GlobalEditData::GetForbiddenCharsTable() +{ + if (!xForbiddenCharsTable) + xForbiddenCharsTable = SvxForbiddenCharactersTable::makeForbiddenCharactersTable(::comphelper::getProcessComponentContext()); + return xForbiddenCharsTable; +} + +uno::Reference< linguistic2::XLanguageGuessing > const & GlobalEditData::GetLanguageGuesser() +{ + if (!xLanguageGuesser.is()) + { + xLanguageGuesser = linguistic2::LanguageGuessing::create( comphelper::getProcessComponentContext() ); + } + return xLanguageGuesser; +} + +OUString EditResId(TranslateId aId) +{ + return Translate::get(aId, Translate::Create("editeng")); +} + +EditDLL::EditDLL() + : pGlobalData( new GlobalEditData ) +{ +} + +EditDLL::~EditDLL() +{ +} + +editeng::SharedVclResources::SharedVclResources() + : m_pVirDev(VclPtr<VirtualDevice>::Create()) +{ + m_pVirDev->SetMapMode(MapMode(MapUnit::MapTwip)); +} + +editeng::SharedVclResources::~SharedVclResources() + { m_pVirDev.disposeAndClear(); } + +VclPtr<VirtualDevice> const & editeng::SharedVclResources::GetVirtualDevice() const + { return m_pVirDev; } + +std::shared_ptr<editeng::SharedVclResources> EditDLL::GetSharedVclResources() +{ + SolarMutexGuard g; + auto pLocked(pSharedVcl.lock()); + if(!pLocked) + pSharedVcl = pLocked = std::make_shared<editeng::SharedVclResources>(); + return pLocked; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eertfpar.cxx b/editeng/source/editeng/eertfpar.cxx new file mode 100644 index 000000000..a5737b4c9 --- /dev/null +++ b/editeng/source/editeng/eertfpar.cxx @@ -0,0 +1,634 @@ +/* -*- 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 <comphelper/string.hxx> +#include <utility> + +#include "eertfpar.hxx" +#include "impedit.hxx" +#include <svl/intitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/editeng.hxx> + +#include <svtools/rtftoken.h> +#include <svtools/htmltokn.h> +#include <unotools/configmgr.hxx> + +using namespace com::sun::star; + +HtmlImportInfo::HtmlImportInfo( HtmlImportState eSt, SvParser<HtmlTokenId>* pPrsrs, const ESelection& rSel ) + : aSelection( rSel ) +{ + pParser = pPrsrs; + eState = eSt; + nToken = HtmlTokenId::NONE; +} + +HtmlImportInfo::~HtmlImportInfo() +{ +} + +RtfImportInfo::RtfImportInfo( RtfImportState eSt, SvParser<int>* pPrsrs, const ESelection& rSel ) + : aSelection( rSel ) +{ + pParser = pPrsrs; + eState = eSt; + nToken = 0; + nTokenValue = 0; +} + +constexpr MapUnit gRTFMapUnit = MapUnit::MapTwip; + +EditRTFParser::EditRTFParser( + SvStream& rIn, EditSelection aSel, SfxItemPool& rAttrPool, EditEngine* pEditEngine) : + SvxRTFParser(rAttrPool, rIn), + aCurSel(std::move(aSel)), + mpEditEngine(pEditEngine), + nDefFont(0), + bLastActionInsertParaBreak(false) +{ + SetInsPos(EditPosition(mpEditEngine, &aCurSel)); + + // Convert the twips values ... + SetCalcValue(true); + SetChkStyleAttr(mpEditEngine->IsImportRTFStyleSheetsSet()); + SetNewDoc(false); // So that the Pool-Defaults are not overwritten... + aEditMapMode = MapMode(mpEditEngine->GetRefDevice()->GetMapMode().GetMapUnit()); +} + +EditRTFParser::~EditRTFParser() +{ +} + +SvParserState EditRTFParser::CallParser() +{ + DBG_ASSERT( !aCurSel.HasRange(), "Selection for CallParser!" ); + // Separate the part that is imported from the rest. + // This expression should be used for all imports. + // aStart1PaM: Last position before the imported content + // aEnd1PaM: First position after the imported content + // aStart2PaM: First position of the imported content + // aEnd2PaM: Last position of the imported content + EditPaM aStart1PaM( aCurSel.Min().GetNode(), aCurSel.Min().GetIndex() ); + aCurSel = mpEditEngine->InsertParaBreak(aCurSel); + EditPaM aStart2PaM = aCurSel.Min(); + // Useful or not? + aStart2PaM.GetNode()->GetContentAttribs().GetItems().ClearItem(); + AddRTFDefaultValues( aStart2PaM, aStart2PaM ); + EditPaM aEnd1PaM = mpEditEngine->InsertParaBreak(aCurSel.Max()); + // aCurCel now points to the gap + + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::Start, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } + + SvParserState _eState = SvxRTFParser::CallParser(); + + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::End, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } + + if (bLastActionInsertParaBreak) + { + ContentNode* pCurNode = aCurSel.Max().GetNode(); + sal_Int32 nPara = mpEditEngine->GetEditDoc().GetPos(pCurNode); + ContentNode* pPrevNode = mpEditEngine->GetEditDoc().GetObject(nPara-1); + DBG_ASSERT( pPrevNode, "Invalid RTF-Document?!" ); + EditSelection aSel; + aSel.Min() = EditPaM( pPrevNode, pPrevNode->Len() ); + aSel.Max() = EditPaM( pCurNode, 0 ); + aCurSel.Max() = mpEditEngine->DeleteSelection(aSel); + } + EditPaM aEnd2PaM( aCurSel.Max() ); + //AddRTFDefaultValues( aStart2PaM, aEnd2PaM ); + bool bOnlyOnePara = ( aEnd2PaM.GetNode() == aStart2PaM.GetNode() ); + // Paste the chunk again ... + // Problem: Paragraph attributes may not possibly be taken over + // => Do Character attributes. + + bool bSpecialBackward = aStart1PaM.GetNode()->Len() == 0; + if ( bOnlyOnePara || aStart1PaM.GetNode()->Len() ) + mpEditEngine->ParaAttribsToCharAttribs( aStart2PaM.GetNode() ); + aCurSel.Min() = mpEditEngine->ConnectParagraphs( + aStart1PaM.GetNode(), aStart2PaM.GetNode(), bSpecialBackward ); + bSpecialBackward = aEnd1PaM.GetNode()->Len() != 0; + // when bOnlyOnePara, then the node is gone on Connect. + if ( !bOnlyOnePara && aEnd1PaM.GetNode()->Len() ) + mpEditEngine->ParaAttribsToCharAttribs( aEnd2PaM.GetNode() ); + aCurSel.Max() = mpEditEngine->ConnectParagraphs( + ( bOnlyOnePara ? aStart1PaM.GetNode() : aEnd2PaM.GetNode() ), + aEnd1PaM.GetNode(), bSpecialBackward ); + + return _eState; +} + +void EditRTFParser::AddRTFDefaultValues( const EditPaM& rStart, const EditPaM& rEnd ) +{ + // Problem: DefFont and DefFontHeight + Size aSz( 12, 0 ); + MapMode aPntMode( MapUnit::MapPoint ); + MapMode _aEditMapMode(mpEditEngine->GetRefDevice()->GetMapMode().GetMapUnit()); + aSz = mpEditEngine->GetRefDevice()->LogicToLogic(aSz, &aPntMode, &_aEditMapMode); + SvxFontHeightItem aFontHeightItem( aSz.Width(), 100, EE_CHAR_FONTHEIGHT ); + vcl::Font aDefFont( GetFont( nDefFont ) ); + SvxFontItem aFontItem( aDefFont.GetFamilyType(), aDefFont.GetFamilyName(), + aDefFont.GetStyleName(), aDefFont.GetPitch(), aDefFont.GetCharSet(), EE_CHAR_FONTINFO ); + + sal_Int32 nStartPara = mpEditEngine->GetEditDoc().GetPos( rStart.GetNode() ); + sal_Int32 nEndPara = mpEditEngine->GetEditDoc().GetPos( rEnd.GetNode() ); + for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ ) + { + ContentNode* pNode = mpEditEngine->GetEditDoc().GetObject( nPara ); + assert(pNode && "AddRTFDefaultValues - No paragraph?!"); + if ( !pNode->GetContentAttribs().HasItem( EE_CHAR_FONTINFO ) ) + pNode->GetContentAttribs().GetItems().Put( aFontItem ); + if ( !pNode->GetContentAttribs().HasItem( EE_CHAR_FONTHEIGHT ) ) + pNode->GetContentAttribs().GetItems().Put( aFontHeightItem ); + } +} + +void EditRTFParser::NextToken( int nToken ) +{ + switch( nToken ) + { + case RTF_DEFF: + { + nDefFont = sal_uInt16(nTokenValue); + } + break; + case RTF_DEFTAB: + break; + case RTF_CELL: + { + aCurSel = mpEditEngine->InsertParaBreak(aCurSel); + } + break; + case RTF_LINE: + { + aCurSel = mpEditEngine->InsertLineBreak(aCurSel); + } + break; + case RTF_FIELD: + { + ReadField(); + } + break; + case RTF_SHPINST: // fdo#76776 process contents of shpinst + break; + case RTF_SP: // fdo#76776 but skip SP groups + { + SkipGroup(); + } + break; + case RTF_PGDSCTBL: // #i29453# ignore \*\pgdsctbl destination + case RTF_LISTTEXT: + { + SkipGroup(); + } + break; + default: + { + SvxRTFParser::NextToken( nToken ); + if ( nToken == RTF_STYLESHEET ) + CreateStyleSheets(); + } + break; + } + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::NextToken, this, mpEditEngine->CreateESelection(aCurSel)); + aImportInfo.nToken = nToken; + aImportInfo.nTokenValue = short(nTokenValue); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } +} + +void EditRTFParser::UnknownAttrToken( int nToken ) +{ + // for Tokens which are not evaluated in ReadAttr + // Actually, only for Calc (RTFTokenHdl), so that RTF_INTBL + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::UnknownAttr, this, mpEditEngine->CreateESelection(aCurSel)); + aImportInfo.nToken = nToken; + aImportInfo.nTokenValue = short(nTokenValue); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } +} + +void EditRTFParser::InsertText() +{ + OUString aText( aToken ); + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::InsertText, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } + aCurSel = mpEditEngine->InsertText(aCurSel, aText); + bLastActionInsertParaBreak = false; +} + +void EditRTFParser::InsertPara() +{ + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::InsertPara, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } + aCurSel = mpEditEngine->InsertParaBreak(aCurSel); + bLastActionInsertParaBreak = true; +} + +void EditRTFParser::MovePos( bool const bForward ) +{ + if( bForward ) + aCurSel = mpEditEngine->CursorRight( + aCurSel.Max(), i18n::CharacterIteratorMode::SKIPCHARACTER); + else + aCurSel = mpEditEngine->CursorLeft( + aCurSel.Max(), i18n::CharacterIteratorMode::SKIPCHARACTER); +} + +void EditRTFParser::SetEndPrevPara( std::optional<EditNodeIdx>& rpNodePos, + sal_Int32& rCntPos ) +{ + // The Intention is to: determine the current insert position of the + // previous paragraph and set the end from this. + // This "\pard" always apply on the right paragraph. + + ContentNode* pN = aCurSel.Max().GetNode(); + sal_Int32 nCurPara = mpEditEngine->GetEditDoc().GetPos( pN ); + DBG_ASSERT( nCurPara != 0, "Paragraph equal to 0: SetEnfPrevPara" ); + if ( nCurPara ) + nCurPara--; + ContentNode* pPrevNode = mpEditEngine->GetEditDoc().GetObject( nCurPara ); + assert(pPrevNode && "pPrevNode = 0!"); + rpNodePos = EditNodeIdx(mpEditEngine, pPrevNode); + rCntPos = pPrevNode->Len(); +} + +bool EditRTFParser::IsEndPara( EditNodeIdx* pNd, sal_Int32 nCnt ) const +{ + return nCnt == pNd->GetNode()->Len(); +} + +void EditRTFParser::SetAttrInDoc( SvxRTFItemStackType &rSet ) +{ + ContentNode* pSttNode = const_cast<EditNodeIdx&>(rSet.GetSttNode()).GetNode(); + ContentNode* pEndNode = const_cast<EditNodeIdx&>(rSet.GetEndNode()).GetNode(); + + EditPaM aStartPaM( pSttNode, rSet.GetSttCnt() ); + EditPaM aEndPaM( pEndNode, rSet.GetEndCnt() ); + + // If possible adjust the Escapement-Item: + + // #i66167# adapt font heights to destination MapUnit if necessary + const MapUnit eDestUnit = mpEditEngine->GetEditDoc().GetItemPool().GetMetric(0); + if (eDestUnit != gRTFMapUnit) + { + sal_uInt16 const aFntHeightIems[3] = { EE_CHAR_FONTHEIGHT, EE_CHAR_FONTHEIGHT_CJK, EE_CHAR_FONTHEIGHT_CTL }; + for (unsigned short aFntHeightIem : aFntHeightIems) + { + const SfxPoolItem* pItem; + if (SfxItemState::SET == rSet.GetAttrSet().GetItemState( aFntHeightIem, false, &pItem )) + { + sal_uInt32 nHeight = static_cast<const SvxFontHeightItem*>(pItem)->GetHeight(); + tools::Long nNewHeight; + nNewHeight = OutputDevice::LogicToLogic( static_cast<tools::Long>(nHeight), gRTFMapUnit, eDestUnit ); + + SvxFontHeightItem aFntHeightItem( nNewHeight, 100, aFntHeightIem ); + aFntHeightItem.SetProp( + static_cast<const SvxFontHeightItem*>(pItem)->GetProp(), + static_cast<const SvxFontHeightItem*>(pItem)->GetPropUnit()); + rSet.GetAttrSet().Put( aFntHeightItem ); + } + } + } + + if( const SvxEscapementItem* pItem = rSet.GetAttrSet().GetItemIfSet( EE_CHAR_ESCAPEMENT, false ) ) + { + // the correct one + tools::Long nEsc = pItem->GetEsc(); + tools::Long nEscFontHeight = 0; + if( ( DFLT_ESC_AUTO_SUPER != nEsc ) && ( DFLT_ESC_AUTO_SUB != nEsc ) ) + { + nEsc *= 10; //HalfPoints => Twips was embezzled in RTFITEM.CXX! + SvxFont aFont; + if (utl::ConfigManager::IsFuzzing()) + { + // ofz#24932 detecting RTL vs LTR is slow + aFont = aStartPaM.GetNode()->GetCharAttribs().GetDefFont(); + } + else + mpEditEngine->SeekCursor(aStartPaM.GetNode(), aStartPaM.GetIndex()+1, aFont); + nEscFontHeight = aFont.GetFontSize().Height(); + } + if (nEscFontHeight) + { + nEsc = nEsc * 100 / nEscFontHeight; + + SvxEscapementItem aEscItem( static_cast<short>(nEsc), pItem->GetProportionalHeight(), EE_CHAR_ESCAPEMENT ); + rSet.GetAttrSet().Put( aEscItem ); + } + } + + if (mpEditEngine->IsRtfImportHandlerSet()) + { + EditSelection aSel( aStartPaM, aEndPaM ); + RtfImportInfo aImportInfo(RtfImportState::SetAttr, this, mpEditEngine->CreateESelection(aSel)); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } + + ContentNode* pSN = aStartPaM.GetNode(); + ContentNode* pEN = aEndPaM.GetNode(); + sal_Int32 nStartNode = mpEditEngine->GetEditDoc().GetPos( pSN ); + sal_Int32 nEndNode = mpEditEngine->GetEditDoc().GetPos( pEN ); + sal_Int16 nOutlLevel = 0xff; + + if (rSet.StyleNo() && mpEditEngine->GetStyleSheetPool() && mpEditEngine->IsImportRTFStyleSheetsSet()) + { + SvxRTFStyleTbl::iterator it = GetStyleTbl().find( rSet.StyleNo() ); + DBG_ASSERT( it != GetStyleTbl().end(), "Template not defined in RTF!" ); + if ( it != GetStyleTbl().end() ) + { + auto const& pS = it->second; + mpEditEngine->SetStyleSheet( + EditSelection(aStartPaM, aEndPaM), + static_cast<SfxStyleSheet*>(mpEditEngine->GetStyleSheetPool()->Find(pS.sName, SfxStyleFamily::All))); + nOutlLevel = pS.nOutlineNo; + } + } + + // When an Attribute goes from 0 to the current paragraph length, + // it should be a paragraph attribute! + + // Note: Selection can reach over several paragraphs. + // All Complete paragraphs are paragraph attributes ... + for ( sal_Int32 z = nStartNode+1; z < nEndNode; z++ ) + { + DBG_ASSERT(mpEditEngine->GetEditDoc().GetObject(z), "Node does not exist yet(RTF)"); + mpEditEngine->SetParaAttribsOnly(z, rSet.GetAttrSet()); + } + + if ( aStartPaM.GetNode() != aEndPaM.GetNode() ) + { + // The rest of the StartNodes... + if ( aStartPaM.GetIndex() == 0 ) + mpEditEngine->SetParaAttribsOnly(nStartNode, rSet.GetAttrSet()); + else + mpEditEngine->SetAttribs( + EditSelection(aStartPaM, EditPaM(aStartPaM.GetNode(), aStartPaM.GetNode()->Len())), rSet.GetAttrSet()); + + // the beginning of the EndNodes... + if ( aEndPaM.GetIndex() == aEndPaM.GetNode()->Len() ) + mpEditEngine->SetParaAttribsOnly(nEndNode, rSet.GetAttrSet()); + else + mpEditEngine->SetAttribs( + EditSelection(EditPaM(aEndPaM.GetNode(), 0), aEndPaM), rSet.GetAttrSet()); + } + else + { + if ( ( aStartPaM.GetIndex() == 0 ) && ( aEndPaM.GetIndex() == aEndPaM.GetNode()->Len() ) ) + { + // When settings char attribs as para attribs, we must merge with existing attribs, not overwrite the ItemSet! + SfxItemSet aAttrs = mpEditEngine->GetBaseParaAttribs(nStartNode); + aAttrs.Put( rSet.GetAttrSet() ); + mpEditEngine->SetParaAttribsOnly(nStartNode, aAttrs); + } + else + { + mpEditEngine->SetAttribs( + EditSelection(aStartPaM, aEndPaM), rSet.GetAttrSet()); + } + } + + // OutlLevel... + if ( nOutlLevel != 0xff ) + { + for ( sal_Int32 n = nStartNode; n <= nEndNode; n++ ) + { + ContentNode* pNode = mpEditEngine->GetEditDoc().GetObject( n ); + pNode->GetContentAttribs().GetItems().Put( SfxInt16Item( EE_PARA_OUTLLEVEL, nOutlLevel ) ); + } + } +} + +SvxRTFStyleType* EditRTFParser::FindStyleSheet( std::u16string_view rName ) +{ + SvxRTFStyleTbl& rTable = GetStyleTbl(); + for (auto & iter : rTable) + { + if (iter.second.sName == rName) + return &iter.second; + } + return nullptr; +} + +SfxStyleSheet* EditRTFParser::CreateStyleSheet( SvxRTFStyleType const * pRTFStyle ) +{ + // Check if a template exists, then it will not be changed! + SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>(mpEditEngine->GetStyleSheetPool()->Find( pRTFStyle->sName, SfxStyleFamily::All )); + if ( pStyle ) + return pStyle; + + OUString aName( pRTFStyle->sName ); + OUString aParent; + if ( pRTFStyle->nBasedOn ) + { + SvxRTFStyleTbl::iterator it = GetStyleTbl().find( pRTFStyle->nBasedOn ); + if ( it != GetStyleTbl().end()) + { + SvxRTFStyleType const& rS = it->second; + if ( &rS != pRTFStyle ) + aParent = rS.sName; + } + } + + pStyle = static_cast<SfxStyleSheet*>( &mpEditEngine->GetStyleSheetPool()->Make( aName, SfxStyleFamily::Para ) ); + + // 1) convert and take over Items ... + ConvertAndPutItems( pStyle->GetItemSet(), pRTFStyle->aAttrSet ); + + // 2) As long as Parent is not in the pool, also create this ... + if ( !aParent.isEmpty() && ( aParent != aName ) ) + { + SfxStyleSheet* pS = static_cast<SfxStyleSheet*>(mpEditEngine->GetStyleSheetPool()->Find( aParent, SfxStyleFamily::All )); + if ( !pS ) + { + // If not found anywhere, create from RTF ... + SvxRTFStyleType* _pRTFStyle = FindStyleSheet( aParent ); + if ( _pRTFStyle ) + pS = CreateStyleSheet( _pRTFStyle ); + } + // 2b) Link Itemset with Parent ... + if ( pS ) + pStyle->GetItemSet().SetParent( &pS->GetItemSet() ); + } + return pStyle; +} + +void EditRTFParser::CreateStyleSheets() +{ + // the SvxRTFParser has now created the template... + if (mpEditEngine->GetStyleSheetPool() && mpEditEngine->IsImportRTFStyleSheetsSet()) + { + for (auto & elem : GetStyleTbl()) + { + SvxRTFStyleType& rRTFStyle = elem.second; + CreateStyleSheet( &rRTFStyle ); + } + } +} + +void EditRTFParser::CalcValue() +{ + const MapUnit eDestUnit = aEditMapMode.GetMapUnit(); + if (eDestUnit != gRTFMapUnit) + nTokenValue = OutputDevice::LogicToLogic( nTokenValue, gRTFMapUnit, eDestUnit ); +} + +void EditRTFParser::ReadField() +{ + // From SwRTFParser::ReadField() + int _nOpenBrackets = 1; // the first was already detected earlier + bool bFldInst = false; + bool bFldRslt = false; + OUString aFldInst; + OUString aFldRslt; + + while( _nOpenBrackets && IsParserWorking() ) + { + switch( GetNextToken() ) + { + case '}': + { + _nOpenBrackets--; + if ( _nOpenBrackets == 1 ) + { + bFldInst = false; + bFldRslt = false; + } + } + break; + + case '{': _nOpenBrackets++; + break; + + case RTF_FIELD: SkipGroup(); + break; + + case RTF_FLDINST: bFldInst = true; + break; + + case RTF_FLDRSLT: bFldRslt = true; + break; + + case RTF_TEXTTOKEN: + { + if ( bFldInst ) + aFldInst += aToken; + else if ( bFldRslt ) + aFldRslt += aToken; + } + break; + } + } + if ( !aFldInst.isEmpty() ) + { + OUString aHyperLinkMarker( "HYPERLINK " ); + if ( aFldInst.startsWithIgnoreAsciiCase( aHyperLinkMarker ) ) + { + aFldInst = aFldInst.copy( aHyperLinkMarker.getLength() ); + aFldInst = comphelper::string::strip(aFldInst, ' '); + // strip start and end quotes + aFldInst = aFldInst.copy( 1, aFldInst.getLength()-2 ); + + if ( aFldRslt.isEmpty() ) + aFldRslt = aFldInst; + + SvxFieldItem aField( SvxURLField( aFldInst, aFldRslt, SvxURLFormat::Repr ), EE_FEATURE_FIELD ); + aCurSel = mpEditEngine->InsertField(aCurSel, aField); + mpEditEngine->UpdateFieldsOnly(); + bLastActionInsertParaBreak = false; + } + } + + SkipToken(); // the closing brace is evaluated "above" +} + +void EditRTFParser::SkipGroup() +{ + int _nOpenBrackets = 1; // the first was already detected earlier + + while( _nOpenBrackets && IsParserWorking() ) + { + switch( GetNextToken() ) + { + case '}': + { + _nOpenBrackets--; + } + break; + + case '{': + { + _nOpenBrackets++; + } + break; + } + } + + SkipToken(); // the closing brace is evaluated "above" +} + +EditNodeIdx::EditNodeIdx(EditEngine* pEE, ContentNode* pNd) : + mpEditEngine(pEE), mpNode(pNd) {} + +sal_Int32 EditNodeIdx::GetIdx() const +{ + return mpEditEngine->GetEditDoc().GetPos(mpNode); +} + +EditPosition::EditPosition(EditEngine* pEE, EditSelection* pSel) : + mpEditEngine(pEE), mpCurSel(pSel) {} + +EditNodeIdx EditPosition::MakeNodeIdx() const +{ + return EditNodeIdx(mpEditEngine, mpCurSel->Max().GetNode()); +} + +sal_Int32 EditPosition::GetNodeIdx() const +{ + ContentNode* pN = mpCurSel->Max().GetNode(); + return mpEditEngine->GetEditDoc().GetPos(pN); +} + +sal_Int32 EditPosition::GetCntIdx() const +{ + return mpCurSel->Max().GetIndex(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eertfpar.hxx b/editeng/source/editeng/eertfpar.hxx new file mode 100644 index 000000000..19ce00ce3 --- /dev/null +++ b/editeng/source/editeng/eertfpar.hxx @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#pragma once + +#include <editeng/svxrtf.hxx> + +#include <editdoc.hxx> + +class EditEngine; + +class EditRTFParser final : public SvxRTFParser +{ +private: + EditSelection aCurSel; + EditEngine* mpEditEngine; + MapMode aEditMapMode; + + sal_uInt16 nDefFont; + bool bLastActionInsertParaBreak; + + virtual void InsertPara() override; + virtual void InsertText() override; + virtual void MovePos( bool bForward = true ) override; + virtual void SetEndPrevPara( std::optional<EditNodeIdx>& rpNodePos, + sal_Int32& rCntPos ) override; + + virtual void UnknownAttrToken( int nToken ) override; + virtual void NextToken( int nToken ) override; + virtual void SetAttrInDoc( SvxRTFItemStackType &rSet ) override; + virtual bool IsEndPara( EditNodeIdx* pNd, sal_Int32 nCnt ) const override; + virtual void CalcValue() override; + void CreateStyleSheets(); + SfxStyleSheet* CreateStyleSheet( SvxRTFStyleType const * pRTFStyle ); + SvxRTFStyleType* FindStyleSheet( std::u16string_view rName ); + void AddRTFDefaultValues( const EditPaM& rStart, const EditPaM& rEnd ); + void ReadField(); + void SkipGroup(); + +public: + EditRTFParser(SvStream& rIn, EditSelection aCurSel, SfxItemPool& rAttrPool, EditEngine* pEditEngine); + virtual ~EditRTFParser() override; + + virtual SvParserState CallParser() override; + + EditPaM const & GetCurPaM() const { return aCurSel.Max(); } +}; + +typedef tools::SvRef<EditRTFParser> EditRTFParserRef; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/fieldupdater.cxx b/editeng/source/editeng/fieldupdater.cxx new file mode 100644 index 000000000..41d9be7ae --- /dev/null +++ b/editeng/source/editeng/fieldupdater.cxx @@ -0,0 +1,70 @@ +/* -*- 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/. + */ + +#include <memory> +#include <editeng/fieldupdater.hxx> +#include <editeng/flditem.hxx> +#include "editobj2.hxx" + +#include <com/sun/star/text/textfield/Type.hpp> + +using namespace com::sun::star; + +namespace editeng { + +class FieldUpdaterImpl +{ + EditTextObjectImpl& mrObj; +public: + explicit FieldUpdaterImpl(EditTextObject& rObj) : mrObj(toImpl(rObj)) {} + + void updateTableFields(int nTab) + { + SfxItemPool* pPool = mrObj.GetPool(); + EditTextObjectImpl::ContentInfosType& rContents = mrObj.GetContents(); + for (std::unique_ptr<ContentInfo> & i : rContents) + { + ContentInfo& rContent = *i; + for (XEditAttribute & rAttr : rContent.GetCharAttribs()) + { + const SfxPoolItem* pItem = rAttr.GetItem(); + if (pItem->Which() != EE_FEATURE_FIELD) + // This is not a field item. + continue; + + const SvxFieldItem* pFI = static_cast<const SvxFieldItem*>(pItem); + const SvxFieldData* pData = pFI->GetField(); + if (pData->GetClassId() != text::textfield::Type::TABLE) + // This is not a table field. + continue; + + // Create a new table field with the new ID, and set it to the + // attribute object. + SvxFieldItem aNewItem(SvxTableField(nTab), EE_FEATURE_FIELD); + rAttr.SetItem(pPool->Put(aNewItem)); + } + } + } +}; + +FieldUpdater::FieldUpdater(EditTextObject& rObj) : mpImpl(new FieldUpdaterImpl(rObj)) {} +FieldUpdater::FieldUpdater(const FieldUpdater& r) : mpImpl(new FieldUpdaterImpl(*r.mpImpl)) {} + +FieldUpdater::~FieldUpdater() +{ +} + +void FieldUpdater::updateTableFields(int nTab) +{ + mpImpl->updateTableFields(nTab); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit.cxx b/editeng/source/editeng/impedit.cxx new file mode 100644 index 000000000..53aefa9b8 --- /dev/null +++ b/editeng/source/editeng/impedit.cxx @@ -0,0 +1,2753 @@ +/* -*- 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 "impedit.hxx" +#include <sal/log.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/outliner.hxx> +#include <tools/poly.hxx> +#include <editeng/unolingu.hxx> +#include <com/sun/star/linguistic2/XDictionary.hpp> +#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp> +#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp> +#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp> +#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp> +#include <comphelper/lok.hxx> +#include <editeng/flditem.hxx> +#include <svl/intitem.hxx> +#include <vcl/inputctx.hxx> +#include <vcl/transfer.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weldutils.hxx> +#include <vcl/window.hxx> +#include <sot/exchange.hxx> +#include <sot/formats.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/string.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/lokhelper.hxx> +#include <boost/property_tree/ptree.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::linguistic2; + +#define SCRLRANGE 20 // Scroll 1/20 of the width/height, when in QueryDrop + +static void lcl_AlignToPixel(Point& rPoint, const OutputDevice& rOutDev, short nDiffX, short nDiffY) +{ + rPoint = rOutDev.LogicToPixel( rPoint ); + + if ( nDiffX ) + rPoint.AdjustX(nDiffX ); + if ( nDiffY ) + rPoint.AdjustY(nDiffY ); + + rPoint = rOutDev.PixelToLogic( rPoint ); +} + +LOKSpecialPositioning::LOKSpecialPositioning(const ImpEditView& rImpEditView, MapUnit eUnit, + const tools::Rectangle& rOutputArea, + const Point& rVisDocStartPos) : + mrImpEditView(rImpEditView), + maOutArea(rOutputArea), + maVisDocStartPos(rVisDocStartPos), + meUnit(eUnit) +{ +} + +void LOKSpecialPositioning::ReInit(MapUnit eUnit, const tools::Rectangle& rOutputArea, const Point& rVisDocStartPos) +{ + meUnit = eUnit; + maOutArea = rOutputArea; + maVisDocStartPos = rVisDocStartPos; +} + +void LOKSpecialPositioning::SetOutputArea(const tools::Rectangle& rOutputArea) +{ + maOutArea = rOutputArea; +} + +const tools::Rectangle& LOKSpecialPositioning::GetOutputArea() const +{ + return maOutArea; +} + +void LOKSpecialPositioning::SetVisDocStartPos(const Point& rVisDocStartPos) +{ + maVisDocStartPos = rVisDocStartPos; +} + +tools::Rectangle LOKSpecialPositioning::GetVisDocArea() const +{ + return tools::Rectangle(GetVisDocLeft(), GetVisDocTop(), GetVisDocRight(), GetVisDocBottom()); +} + +bool LOKSpecialPositioning::IsVertical() const +{ + return mrImpEditView.IsVertical(); +} + +bool LOKSpecialPositioning::IsTopToBottom() const +{ + return mrImpEditView.IsTopToBottom(); +} + +Point LOKSpecialPositioning::GetWindowPos(const Point& rDocPos, MapUnit eDocPosUnit) const +{ + const Point aDocPos = convertUnit(rDocPos, eDocPosUnit); + Point aPoint; + if ( !IsVertical() ) + { + aPoint.setX(aDocPos.X() + maOutArea.Left() - GetVisDocLeft()); + aPoint.setY(aDocPos.Y() + maOutArea.Top() - GetVisDocTop()); + } + else + { + if (IsTopToBottom()) + { + aPoint.setX(maOutArea.Right() - aDocPos.Y() + GetVisDocTop()); + aPoint.setY(aDocPos.X() + maOutArea.Top() - GetVisDocLeft()); + } + else + { + aPoint.setX(maOutArea.Left() + aDocPos.Y() - GetVisDocTop()); + aPoint.setY(maOutArea.Bottom() - aDocPos.X() + GetVisDocLeft()); + } + } + + return aPoint; +} + +tools::Rectangle LOKSpecialPositioning::GetWindowPos(const tools::Rectangle& rDocRect, MapUnit eDocRectUnit) const +{ + const tools::Rectangle aDocRect = convertUnit(rDocRect, eDocRectUnit); + Point aPos(GetWindowPos(aDocRect.TopLeft(), meUnit)); + Size aSz = aDocRect.GetSize(); + tools::Rectangle aRect; + if (!IsVertical()) + { + aRect = tools::Rectangle(aPos, aSz); + } + else + { + Point aNewPos(aPos.X() - aSz.Height(), aPos.Y()); + // coverity[swapped_arguments : FALSE] - this is in the correct order + aRect = tools::Rectangle(aNewPos, Size(aSz.Height(), aSz.Width())); + } + return aRect; +} + +Point LOKSpecialPositioning::convertUnit(const Point& rPos, MapUnit ePosUnit) const +{ + if (ePosUnit == meUnit) + return rPos; + + return OutputDevice::LogicToLogic(rPos, MapMode(ePosUnit), MapMode(meUnit)); +} + +tools::Rectangle LOKSpecialPositioning::convertUnit(const tools::Rectangle& rRect, MapUnit eRectUnit) const +{ + if (eRectUnit == meUnit) + return rRect; + + return OutputDevice::LogicToLogic(rRect, MapMode(eRectUnit), MapMode(meUnit)); +} + +Point LOKSpecialPositioning::GetRefPoint() const +{ + return maOutArea.TopLeft(); +} + +// class ImpEditView + +ImpEditView::ImpEditView( EditView* pView, EditEngine* pEng, vcl::Window* pWindow ) : + pEditView(pView), + mpViewShell(nullptr), + mpOtherShell(nullptr), + pEditEngine(pEng), + pOutWin(pWindow), + nInvMore(1), + nControl(EVControlBits::AUTOSCROLL | EVControlBits::ENABLEPASTE), + nTravelXPos(TRAVEL_X_DONTKNOW), + nExtraCursorFlags(GetCursorFlags::NONE), + nCursorBidiLevel(CURSOR_BIDILEVEL_DONTKNOW), + nScrollDiffX(0), + bReadOnly(false), + bClickedInSelection(false), + bActiveDragAndDropListener(false), + aOutArea( Point(), pEng->GetPaperSize() ), + eSelectionMode(EESelectionMode::Std), + eAnchorMode(EEAnchorMode::TopLeft), + mpEditViewCallbacks(nullptr), + mbBroadcastLOKViewCursor(comphelper::LibreOfficeKit::isActive()), + mbSuppressLOKMessages(false), + mbNegativeX(false) +{ + aEditSelection.Min() = pEng->GetEditDoc().GetStartPaM(); + aEditSelection.Max() = pEng->GetEditDoc().GetEndPaM(); + + SelectionChanged(); +} + +ImpEditView::~ImpEditView() +{ + RemoveDragAndDropListeners(); + + if ( pOutWin && ( pOutWin->GetCursor() == pCursor.get() ) ) + pOutWin->SetCursor( nullptr ); +} + +void ImpEditView::SetBackgroundColor( const Color& rColor ) +{ + mxBackgroundColor = rColor; +} + +const Color& ImpEditView::GetBackgroundColor() const +{ + return mxBackgroundColor ? *mxBackgroundColor : GetOutputDevice().GetBackground().GetColor(); +} + +void ImpEditView::RegisterViewShell(OutlinerViewShell* pViewShell) +{ + mpViewShell = pViewShell; +} + +void ImpEditView::RegisterOtherShell(OutlinerViewShell* pOtherShell) +{ + mpOtherShell = pOtherShell; +} + +const OutlinerViewShell* ImpEditView::GetViewShell() const +{ + return mpViewShell; +} + +void ImpEditView::SetEditSelection( const EditSelection& rEditSelection ) +{ + // set state before notification + aEditSelection = rEditSelection; + + SelectionChanged(); + + if (comphelper::LibreOfficeKit::isActive()) + // Tiled rendering: selections are only painted when we are in selection mode. + pEditEngine->SetInSelectionMode(aEditSelection.HasRange()); + + if ( pEditEngine->pImpEditEngine->GetNotifyHdl().IsSet() ) + { + const EditDoc& rDoc = pEditEngine->GetEditDoc(); + const EditPaM pmEnd = rDoc.GetEndPaM(); + EENotifyType eNotifyType; + if (rDoc.Count() > 1 && + pmEnd == rEditSelection.Min() && + pmEnd == rEditSelection.Max())//if move cursor to the last para. + { + eNotifyType = EE_NOTIFY_TEXTVIEWSELECTIONCHANGED_ENDD_PARA; + } + else + { + eNotifyType = EE_NOTIFY_TEXTVIEWSELECTIONCHANGED; + } + EENotify aNotify( eNotifyType ); + pEditEngine->pImpEditEngine->GetNotifyHdl().Call( aNotify ); + } + if(pEditEngine->pImpEditEngine->IsFormatted()) + { + EENotify aNotify(EE_NOTIFY_PROCESSNOTIFICATIONS); + pEditEngine->pImpEditEngine->GetNotifyHdl().Call(aNotify); + } +} + +/// Translate absolute <-> relative twips: LOK wants absolute coordinates as output and gives absolute coordinates as input. +static void lcl_translateTwips(const OutputDevice& rParent, OutputDevice& rChild) +{ + // Don't translate if we already have a non-zero origin. + // This prevents multiple translate calls that negate + // one another. + const Point aOrigin = rChild.GetMapMode().GetOrigin(); + if (aOrigin.getX() != 0 || aOrigin.getY() != 0) + return; + + // Set map mode, so that callback payloads will contain absolute coordinates instead of relative ones. + Point aOffset(rChild.GetOutOffXPixel() - rParent.GetOutOffXPixel(), rChild.GetOutOffYPixel() - rParent.GetOutOffYPixel()); + if (!rChild.IsMapModeEnabled()) + { + MapMode aMapMode(rChild.GetMapMode()); + aMapMode.SetMapUnit(MapUnit::MapTwip); + aMapMode.SetScaleX(rParent.GetMapMode().GetScaleX()); + aMapMode.SetScaleY(rParent.GetMapMode().GetScaleY()); + rChild.SetMapMode(aMapMode); + rChild.EnableMapMode(); + } + aOffset = rChild.PixelToLogic(aOffset); + MapMode aMapMode(rChild.GetMapMode()); + aMapMode.SetOrigin(aOffset); + aMapMode.SetMapUnit(rParent.GetMapMode().GetMapUnit()); + rChild.SetMapMode(aMapMode); + rChild.EnableMapMode(false); +} + +// EditView never had a central/secure place to react on SelectionChange since +// Selection was changed in many places, often by not using SetEditSelection() +// but (mis)using GetEditSelection() and manipulating this non-const return +// value. Sorted this out now to have such a place, this is needed for safely +// change/update the Selection visualization for enhanced mechanisms +void ImpEditView::SelectionChanged() +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + { + // use callback to tell about change in selection visualisation + pCallbacks->EditViewSelectionChange(); + } +} + +// This function is also called when a text's font || size is changed. Because its highlight rectangle must be updated. +void ImpEditView::lokSelectionCallback(const std::optional<tools::PolyPolygon> &pPolyPoly, bool bStartHandleVisible, bool bEndHandleVisible) { + VclPtr<vcl::Window> pParent = pOutWin->GetParentWithLOKNotifier(); + vcl::Region aRegion( *pPolyPoly ); + + if (pParent && pParent->GetLOKWindowId() != 0) + { + const tools::Long nX = pOutWin->GetOutOffXPixel() - pParent->GetOutOffXPixel(); + const tools::Long nY = pOutWin->GetOutOffYPixel() - pParent->GetOutOffYPixel(); + + std::vector<tools::Rectangle> aRectangles; + aRegion.GetRegionRectangles(aRectangles); + + std::vector<OString> v; + for (tools::Rectangle & rRectangle : aRectangles) + { + rRectangle = pOutWin->LogicToPixel(rRectangle); + rRectangle.Move(nX, nY); + v.emplace_back(rRectangle.toString().getStr()); + } + OString sRectangle = comphelper::string::join("; ", v); + + const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier(); + std::vector<vcl::LOKPayloadItem> aItems; + aItems.emplace_back("rectangles", sRectangle); + aItems.emplace_back("startHandleVisible", OString::boolean(bStartHandleVisible)); + aItems.emplace_back("endHandleVisible", OString::boolean(bEndHandleVisible)); + pNotifier->notifyWindow(pParent->GetLOKWindowId(), "text_selection", aItems); + } + else + { + pOutWin->GetOutDev()->Push(vcl::PushFlags::MAPMODE); + if (pOutWin->GetMapMode().GetMapUnit() == MapUnit::MapTwip) + { + // Find the parent that is not right + // on top of us to use its offset. + vcl::Window* parent = pOutWin->GetParent(); + while (parent && + parent->GetOutOffXPixel() == pOutWin->GetOutOffXPixel() && + parent->GetOutOffYPixel() == pOutWin->GetOutOffYPixel()) + { + parent = parent->GetParent(); + } + + if (parent) + { + lcl_translateTwips(*parent->GetOutDev(), *pOutWin->GetOutDev()); + } + } + + bool bMm100ToTwip = !mpLOKSpecialPositioning && + (pOutWin->GetMapMode().GetMapUnit() == MapUnit::Map100thMM); + + Point aOrigin; + if (pOutWin->GetMapMode().GetMapUnit() == MapUnit::MapTwip) + // Writer comments: they use editeng, but are separate widgets. + aOrigin = pOutWin->GetMapMode().GetOrigin(); + + OString sRectangle; + OString sRefPoint; + if (mpLOKSpecialPositioning) + sRefPoint = mpLOKSpecialPositioning->GetRefPoint().toString(); + + std::vector<tools::Rectangle> aRectangles; + aRegion.GetRegionRectangles(aRectangles); + + if (!aRectangles.empty()) + { + if (pOutWin->IsChart()) + { + const vcl::Window* pViewShellWindow = mpViewShell->GetEditWindowForActiveOLEObj(); + if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pOutWin)) + { + Point aOffsetPx = pOutWin->GetOffsetPixelFrom(*pViewShellWindow); + Point aLogicOffset = pOutWin->PixelToLogic(aOffsetPx); + for (tools::Rectangle& rRect : aRectangles) + rRect.Move(aLogicOffset.getX(), aLogicOffset.getY()); + } + } + + std::vector<OString> v; + for (tools::Rectangle & rRectangle : aRectangles) + { + if (bMm100ToTwip) + { + rRectangle = o3tl::convert(rRectangle, o3tl::Length::mm100, o3tl::Length::twip); + } + rRectangle.Move(aOrigin.getX(), aOrigin.getY()); + v.emplace_back(rRectangle.toString().getStr()); + } + sRectangle = comphelper::string::join("; ", v); + + if (mpLOKSpecialPositioning && !sRectangle.isEmpty()) + sRectangle += ":: " + sRefPoint; + + tools::Rectangle& rStart = aRectangles.front(); + tools::Rectangle aStart(rStart.Left(), rStart.Top(), rStart.Left() + 1, rStart.Bottom()); + + OString aPayload = aStart.toString(); + if (mpLOKSpecialPositioning) + aPayload += ":: " + sRefPoint; + + mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_START, aPayload.getStr()); + + tools::Rectangle& rEnd = aRectangles.back(); + tools::Rectangle aEnd(rEnd.Right() - 1, rEnd.Top(), rEnd.Right(), rEnd.Bottom()); + + aPayload = aEnd.toString(); + if (mpLOKSpecialPositioning) + aPayload += ":: " + sRefPoint; + + mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_END, aPayload.getStr()); + } + + if (mpOtherShell) + { + // Another shell wants to know about our existing selection. + if (mpViewShell != mpOtherShell) + mpViewShell->NotifyOtherView(mpOtherShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRectangle); + } + else + { + mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRectangle.getStr()); + mpViewShell->NotifyOtherViews(LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRectangle); + } + + pOutWin->GetOutDev()->Pop(); + } +} + +// renamed from DrawSelection to DrawSelectionXOR to better reflect what this +// method was used for: Paint Selection in XOR, change it and again paint it in XOR. +// This can be safely assumed due to the EditView only being capable of painting the +// selection in XOR until today. +// This also means that all places calling DrawSelectionXOR are thoroughly weighted +// and chosen to make this fragile XOR-paint water-proof and thus contain some +// information in this sense. +// Someone thankfully expanded it to collect the SelectionRectangles when called with +// the Region*, see GetSelectionRectangles below. +void ImpEditView::DrawSelectionXOR( EditSelection aTmpSel, vcl::Region* pRegion, OutputDevice* pTargetDevice ) +{ + if (getEditViewCallbacks() && !pRegion && !comphelper::LibreOfficeKit::isActive()) + { + // we are done, do *not* visualize self + // CAUTION: do not use when comphelper::LibreOfficeKit::isActive() + // due to event stuff triggered below. That *should* probably be moved + // to SelectionChanged() which exists now, but I do not know enough about + // that stuff to do it + return; + } + + if ( eSelectionMode == EESelectionMode::Hidden ) + return; + + // It must be ensured before rendering the selection, that the contents of + // the window is completely valid! Must be here so that in any case if + // empty, then later on two-Paint Events! Must be done even before the + // query from bUpdate, if after Invalidate paints still in the queue, + // but someone switches the update mode! + + // pRegion: When not NULL, then only calculate Region. + + OutputDevice& rTarget = pTargetDevice ? *pTargetDevice : GetOutputDevice(); + bool bClipRegion = rTarget.IsClipRegion(); + vcl::Region aOldRegion = rTarget.GetClipRegion(); + + std::optional<tools::PolyPolygon> pPolyPoly; + + if ( !pRegion && !comphelper::LibreOfficeKit::isActive()) + { + if ( !pEditEngine->pImpEditEngine->IsUpdateLayout() ) + return; + if ( pEditEngine->pImpEditEngine->IsInUndo() ) + return; + + if ( !aTmpSel.HasRange() ) + return; + + // aTmpOutArea: if OutputArea > Paper width and + // Text > Paper width ( over large fields ) + tools::Rectangle aTmpOutArea( aOutArea ); + if ( aTmpOutArea.GetWidth() > pEditEngine->pImpEditEngine->GetPaperSize().Width() ) + aTmpOutArea.SetRight( aTmpOutArea.Left() + pEditEngine->pImpEditEngine->GetPaperSize().Width() ); + rTarget.IntersectClipRegion( aTmpOutArea ); + + if (pOutWin && pOutWin->GetCursor()) + pOutWin->GetCursor()->Hide(); + } + + if (comphelper::LibreOfficeKit::isActive() || pRegion) + pPolyPoly = tools::PolyPolygon(); + + DBG_ASSERT( !pEditEngine->IsIdleFormatterActive(), "DrawSelectionXOR: Not formatted!" ); + aTmpSel.Adjust( pEditEngine->GetEditDoc() ); + + ContentNode* pStartNode = aTmpSel.Min().GetNode(); + ContentNode* pEndNode = aTmpSel.Max().GetNode(); + const sal_Int32 nStartPara = pEditEngine->GetEditDoc().GetPos(pStartNode); + const sal_Int32 nEndPara = pEditEngine->GetEditDoc().GetPos(pEndNode); + if (nStartPara == EE_PARA_NOT_FOUND || nEndPara == EE_PARA_NOT_FOUND) + return; + + bool bStartHandleVisible = false; + bool bEndHandleVisible = false; + bool bLOKCalcRTL = mpLOKSpecialPositioning && pEditEngine->IsRightToLeft(nStartPara); + + auto DrawHighlight = [&, nStartLine = sal_Int32(0), nEndLine = sal_Int32(0)]( + const ImpEditEngine::LineAreaInfo& rInfo) mutable { + if (!rInfo.pLine) // Begin of ParaPortion + { + if (rInfo.nPortion < nStartPara) + return ImpEditEngine::CallbackResult::SkipThisPortion; + if (rInfo.nPortion > nEndPara) + return ImpEditEngine::CallbackResult::Stop; + DBG_ASSERT(!rInfo.rPortion.IsInvalid(), "Portion in Selection not formatted!"); + if (rInfo.rPortion.IsInvalid()) + return ImpEditEngine::CallbackResult::SkipThisPortion; + + if (rInfo.nPortion == nStartPara) + nStartLine = rInfo.rPortion.GetLines().FindLine(aTmpSel.Min().GetIndex(), false); + else + nStartLine = 0; + + if (rInfo.nPortion == nEndPara) + nEndLine = rInfo.rPortion.GetLines().FindLine(aTmpSel.Max().GetIndex(), true); + else + nEndLine = rInfo.rPortion.GetLines().Count() - 1; + } + else // This is a correct ParaPortion + { + if (rInfo.nLine < nStartLine) + return ImpEditEngine::CallbackResult::Continue; + if (rInfo.nLine > nEndLine) + return ImpEditEngine::CallbackResult::SkipThisPortion; + + bool bPartOfLine = false; + sal_Int32 nStartIndex = rInfo.pLine->GetStart(); + sal_Int32 nEndIndex = rInfo.pLine->GetEnd(); + if ((rInfo.nPortion == nStartPara) && (rInfo.nLine == nStartLine) + && (nStartIndex != aTmpSel.Min().GetIndex())) + { + nStartIndex = aTmpSel.Min().GetIndex(); + bPartOfLine = true; + } + if ((rInfo.nPortion == nEndPara) && (rInfo.nLine == nEndLine) + && (nEndIndex != aTmpSel.Max().GetIndex())) + { + nEndIndex = aTmpSel.Max().GetIndex(); + bPartOfLine = true; + } + + // Can happen if at the beginning of a wrapped line. + if (nEndIndex < nStartIndex) + nEndIndex = nStartIndex; + + tools::Rectangle aTmpRect(pEditEngine->pImpEditEngine->GetEditCursor( + &rInfo.rPortion, rInfo.pLine, nStartIndex, GetCursorFlags::NONE)); + const Size aLineOffset = pEditEngine->pImpEditEngine->getTopLeftDocOffset(rInfo.aArea); + aTmpRect.Move(0, aLineOffset.Height()); + + // Only paint if in the visible range ... + if (aTmpRect.Top() > GetVisDocBottom()) + return ImpEditEngine::CallbackResult::Continue; + + if (aTmpRect.Bottom() < GetVisDocTop()) + return ImpEditEngine::CallbackResult::Continue; + + if ((rInfo.nPortion == nStartPara) && (rInfo.nLine == nStartLine)) + bStartHandleVisible = true; + if ((rInfo.nPortion == nEndPara) && (rInfo.nLine == nEndLine)) + bEndHandleVisible = true; + + // Now that we have Bidi, the first/last index doesn't have to be the 'most outside' position + if (!bPartOfLine) + { + Range aLineXPosStartEnd + = pEditEngine->GetLineXPosStartEnd(&rInfo.rPortion, rInfo.pLine); + aTmpRect.SetLeft(aLineXPosStartEnd.Min()); + aTmpRect.SetRight(aLineXPosStartEnd.Max()); + aTmpRect.Move(aLineOffset.Width(), 0); + ImplDrawHighlightRect(rTarget, aTmpRect.TopLeft(), aTmpRect.BottomRight(), + pPolyPoly ? &*pPolyPoly : nullptr, bLOKCalcRTL); + } + else + { + sal_Int32 nTmpStartIndex = nStartIndex; + sal_Int32 nWritingDirStart, nTmpEndIndex; + + while (nTmpStartIndex < nEndIndex) + { + pEditEngine->pImpEditEngine->GetRightToLeft(rInfo.nPortion, nTmpStartIndex + 1, + &nWritingDirStart, &nTmpEndIndex); + if (nTmpEndIndex > nEndIndex) + nTmpEndIndex = nEndIndex; + + DBG_ASSERT(nTmpEndIndex > nTmpStartIndex, "DrawSelectionXOR, Start >= End?"); + + tools::Long nX1 + = pEditEngine->GetXPos(&rInfo.rPortion, rInfo.pLine, nTmpStartIndex, true); + tools::Long nX2 + = pEditEngine->GetXPos(&rInfo.rPortion, rInfo.pLine, nTmpEndIndex); + + aTmpRect.SetLeft(std::min(nX1, nX2)); + aTmpRect.SetRight(std::max(nX1, nX2)); + aTmpRect.Move(aLineOffset.Width(), 0); + + ImplDrawHighlightRect(rTarget, aTmpRect.TopLeft(), aTmpRect.BottomRight(), + pPolyPoly ? &*pPolyPoly : nullptr, bLOKCalcRTL); + nTmpStartIndex = nTmpEndIndex; + } + } + } + return ImpEditEngine::CallbackResult::Continue; + }; + pEditEngine->pImpEditEngine->IterateLineAreas(DrawHighlight, ImpEditEngine::IterFlag::none); + + if (comphelper::LibreOfficeKit::isActive() && mpViewShell && pOutWin) + lokSelectionCallback(pPolyPoly, bStartHandleVisible, bEndHandleVisible); + + if (pRegion || comphelper::LibreOfficeKit::isActive()) + { + if (pRegion) + *pRegion = vcl::Region( *pPolyPoly ); + pPolyPoly.reset(); + } + else + { + if (pOutWin && pOutWin->GetCursor()) + pOutWin->GetCursor()->Show(); + + if (bClipRegion) + rTarget.SetClipRegion(aOldRegion); + else + rTarget.SetClipRegion(); + } +} + +void ImpEditView::GetSelectionRectangles(EditSelection aTmpSel, std::vector<tools::Rectangle>& rLogicRects) +{ + vcl::Region aRegion; + DrawSelectionXOR(aTmpSel, &aRegion); + aRegion.GetRegionRectangles(rLogicRects); +} + +void ImpEditView::ImplDrawHighlightRect( OutputDevice& rTarget, const Point& rDocPosTopLeft, const Point& rDocPosBottomRight, tools::PolyPolygon* pPolyPoly, bool bLOKCalcRTL ) +{ + if ( rDocPosTopLeft.X() == rDocPosBottomRight.X() ) + return; + + if (mpLOKSpecialPositioning && pPolyPoly) + { + MapUnit eDevUnit = rTarget.GetMapMode().GetMapUnit(); + tools::Rectangle aSelRect(rDocPosTopLeft, rDocPosBottomRight); + aSelRect = GetWindowPos(aSelRect); + Point aRefPointLogical = GetOutputArea().TopLeft(); + // Get the relative coordinates w.r.t refpoint in display units. + aSelRect.Move(-aRefPointLogical.X(), -aRefPointLogical.Y()); + if (bLOKCalcRTL) + { + tools::Long nMirrorW = GetOutputArea().GetWidth(); + tools::Long nLeft = aSelRect.Left(), nRight = aSelRect.Right(); + aSelRect.SetLeft(nMirrorW - nRight); + aSelRect.SetRight(nMirrorW - nLeft); + } + // Convert from display unit to twips. + aSelRect = OutputDevice::LogicToLogic(aSelRect, MapMode(eDevUnit), MapMode(MapUnit::MapTwip)); + + tools::Polygon aTmpPoly(4); + aTmpPoly[0] = aSelRect.TopLeft(); + aTmpPoly[1] = aSelRect.TopRight(); + aTmpPoly[2] = aSelRect.BottomRight(); + aTmpPoly[3] = aSelRect.BottomLeft(); + pPolyPoly->Insert(aTmpPoly); + return; + } + + bool bPixelMode = rTarget.GetMapMode().GetMapUnit() == MapUnit::MapPixel; + + Point aPnt1( GetWindowPos( rDocPosTopLeft ) ); + Point aPnt2( GetWindowPos( rDocPosBottomRight ) ); + + if ( !IsVertical() ) + { + lcl_AlignToPixel(aPnt1, rTarget, +1, 0); + lcl_AlignToPixel(aPnt2, rTarget, 0, (bPixelMode ? 0 : -1)); + } + else + { + lcl_AlignToPixel(aPnt1, rTarget, 0, +1 ); + lcl_AlignToPixel(aPnt2, rTarget, (bPixelMode ? 0 : +1), 0); + } + + tools::Rectangle aRect( aPnt1, aPnt2 ); + if ( pPolyPoly ) + { + tools::Polygon aTmpPoly( 4 ); + aTmpPoly[0] = aRect.TopLeft(); + aTmpPoly[1] = aRect.TopRight(); + aTmpPoly[2] = aRect.BottomRight(); + aTmpPoly[3] = aRect.BottomLeft(); + pPolyPoly->Insert( aTmpPoly ); + } + else + { + vcl::Window* pWindow = rTarget.GetOwnerWindow(); + + if (pWindow) + { + pWindow->GetOutDev()->Invert( aRect ); + } + else + { + rTarget.Push(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR|vcl::PushFlags::RASTEROP); + rTarget.SetLineColor(); + rTarget.SetFillColor(COL_BLACK); + rTarget.SetRasterOp(RasterOp::Invert); + rTarget.DrawRect(aRect); + rTarget.Pop(); + } + } +} + + +bool ImpEditView::IsVertical() const +{ + return pEditEngine->pImpEditEngine->IsEffectivelyVertical(); +} + +bool ImpEditView::IsTopToBottom() const +{ + return pEditEngine->pImpEditEngine->IsTopToBottom(); +} + +tools::Rectangle ImpEditView::GetVisDocArea() const +{ + return tools::Rectangle( GetVisDocLeft(), GetVisDocTop(), GetVisDocRight(), GetVisDocBottom() ); +} + +Point ImpEditView::GetDocPos( const Point& rWindowPos ) const +{ + // Window Position => Position Document + Point aPoint; + + if ( !pEditEngine->pImpEditEngine->IsEffectivelyVertical() ) + { + aPoint.setX( rWindowPos.X() - aOutArea.Left() + GetVisDocLeft() ); + aPoint.setY( rWindowPos.Y() - aOutArea.Top() + GetVisDocTop() ); + } + else + { + if (pEditEngine->pImpEditEngine->IsTopToBottom()) + { + aPoint.setX( rWindowPos.Y() - aOutArea.Top() + GetVisDocLeft() ); + aPoint.setY( aOutArea.Right() - rWindowPos.X() + GetVisDocTop() ); + } + else + { + aPoint.setX( aOutArea.Bottom() - rWindowPos.Y() + GetVisDocLeft() ); + aPoint.setY( rWindowPos.X() - aOutArea.Left() + GetVisDocTop() ); + } + } + + return aPoint; +} + +Point ImpEditView::GetWindowPos( const Point& rDocPos ) const +{ + // Document position => window position + Point aPoint; + + if ( !pEditEngine->pImpEditEngine->IsEffectivelyVertical() ) + { + aPoint.setX( rDocPos.X() + aOutArea.Left() - GetVisDocLeft() ); + aPoint.setY( rDocPos.Y() + aOutArea.Top() - GetVisDocTop() ); + } + else + { + if (pEditEngine->pImpEditEngine->IsTopToBottom()) + { + aPoint.setX( aOutArea.Right() - rDocPos.Y() + GetVisDocTop() ); + aPoint.setY( rDocPos.X() + aOutArea.Top() - GetVisDocLeft() ); + } + else + { + aPoint.setX( aOutArea.Left() + rDocPos.Y() - GetVisDocTop() ); + aPoint.setY( aOutArea.Bottom() - rDocPos.X() + GetVisDocLeft() ); + } + } + + return aPoint; +} + +tools::Rectangle ImpEditView::GetWindowPos( const tools::Rectangle& rDocRect ) const +{ + // Document position => window position + Point aPos( GetWindowPos( rDocRect.TopLeft() ) ); + Size aSz = rDocRect.GetSize(); + tools::Rectangle aRect; + if ( !pEditEngine->pImpEditEngine->IsEffectivelyVertical() ) + { + aRect = tools::Rectangle( aPos, aSz ); + } + else + { + Point aNewPos( aPos.X()-aSz.Height(), aPos.Y() ); + // coverity[swapped_arguments : FALSE] - this is in the correct order + aRect = tools::Rectangle( aNewPos, Size( aSz.Height(), aSz.Width() ) ); + } + return aRect; +} + +void ImpEditView::SetSelectionMode( EESelectionMode eNewMode ) +{ + if ( eSelectionMode != eNewMode ) + { + DrawSelectionXOR(); + eSelectionMode = eNewMode; + DrawSelectionXOR(); // redraw + } +} + +OutputDevice& ImpEditView::GetOutputDevice() const +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + return pCallbacks->EditViewOutputDevice(); + return *pOutWin->GetOutDev(); +} + +weld::Widget* ImpEditView::GetPopupParent(tools::Rectangle& rRect) const +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + { + weld::Widget* pParent = pCallbacks->EditViewPopupParent(); + if (pParent) + return pParent; + } + return weld::GetPopupParent(*pOutWin, rRect); +} + +void ImpEditView::SetOutputArea( const tools::Rectangle& rRect ) +{ + const OutputDevice& rOutDev = GetOutputDevice(); + // should be better be aligned on pixels! + tools::Rectangle aNewRect(rOutDev.LogicToPixel(rRect)); + aNewRect = rOutDev.PixelToLogic(aNewRect); + aOutArea = aNewRect; + if ( !aOutArea.IsWidthEmpty() && aOutArea.Right() < aOutArea.Left() ) + aOutArea.SetRight( aOutArea.Left() ); + if ( !aOutArea.IsHeightEmpty() && aOutArea.Bottom() < aOutArea.Top() ) + aOutArea.SetBottom( aOutArea.Top() ); + + SetScrollDiffX( static_cast<sal_uInt16>(aOutArea.GetWidth()) * 2 / 10 ); +} + +namespace { + +tools::Rectangle lcl_negateRectX(const tools::Rectangle& rRect) +{ + return tools::Rectangle(-rRect.Right(), rRect.Top(), -rRect.Left(), rRect.Bottom()); +} + +} + +void ImpEditView::InvalidateAtWindow(const tools::Rectangle& rRect) +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + { + // do not invalidate and trigger a global repaint, but forward + // the need for change to the applied EditViewCallback, can e.g. + // be used to visualize the active edit text in an OverlayObject + pCallbacks->EditViewInvalidate(mbNegativeX ? lcl_negateRectX(rRect) : rRect); + } + else + { + // classic mode: invalidate and trigger full repaint + // of the changed area + GetWindow()->Invalidate(mbNegativeX ? lcl_negateRectX(rRect) : rRect); + } +} + +void ImpEditView::ResetOutputArea( const tools::Rectangle& rRect ) +{ + // remember old out area + const tools::Rectangle aOldArea(aOutArea); + + // apply new one + SetOutputArea(rRect); + + // invalidate surrounding areas if update is true + if(aOldArea.IsEmpty() || !pEditEngine->pImpEditEngine->IsUpdateLayout()) + return; + + // #i119885# use grown area if needed; do when getting bigger OR smaller + const sal_Int32 nMore(DoInvalidateMore() ? GetOutputDevice().PixelToLogic(Size(nInvMore, 0)).Width() : 0); + + if(aOldArea.Left() > aOutArea.Left()) + { + const tools::Rectangle aRect(aOutArea.Left() - nMore, aOldArea.Top() - nMore, aOldArea.Left(), aOldArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } + else if(aOldArea.Left() < aOutArea.Left()) + { + const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Top() - nMore, aOutArea.Left(), aOldArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } + + if(aOldArea.Right() > aOutArea.Right()) + { + const tools::Rectangle aRect(aOutArea.Right(), aOldArea.Top() - nMore, aOldArea.Right() + nMore, aOldArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } + else if(aOldArea.Right() < aOutArea.Right()) + { + const tools::Rectangle aRect(aOldArea.Right(), aOldArea.Top() - nMore, aOutArea.Right() + nMore, aOldArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } + + if(aOldArea.Top() > aOutArea.Top()) + { + const tools::Rectangle aRect(aOldArea.Left() - nMore, aOutArea.Top() - nMore, aOldArea.Right() + nMore, aOldArea.Top()); + InvalidateAtWindow(aRect); + } + else if(aOldArea.Top() < aOutArea.Top()) + { + const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Top() - nMore, aOldArea.Right() + nMore, aOutArea.Top()); + InvalidateAtWindow(aRect); + } + + if(aOldArea.Bottom() > aOutArea.Bottom()) + { + const tools::Rectangle aRect(aOldArea.Left() - nMore, aOutArea.Bottom(), aOldArea.Right() + nMore, aOldArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } + else if(aOldArea.Bottom() < aOutArea.Bottom()) + { + const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Bottom(), aOldArea.Right() + nMore, aOutArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } +} + +void ImpEditView::RecalcOutputArea() +{ + Point aNewTopLeft( aOutArea.TopLeft() ); + Size aNewSz( aOutArea.GetSize() ); + + // X: + if ( DoAutoWidth() ) + { + if ( pEditEngine->pImpEditEngine->GetStatus().AutoPageWidth() ) + aNewSz.setWidth( pEditEngine->pImpEditEngine->GetPaperSize().Width() ); + switch ( eAnchorMode ) + { + case EEAnchorMode::TopLeft: + case EEAnchorMode::VCenterLeft: + case EEAnchorMode::BottomLeft: + { + aNewTopLeft.setX( aAnchorPoint.X() ); + } + break; + case EEAnchorMode::TopHCenter: + case EEAnchorMode::VCenterHCenter: + case EEAnchorMode::BottomHCenter: + { + aNewTopLeft.setX( aAnchorPoint.X() - aNewSz.Width() / 2 ); + } + break; + case EEAnchorMode::TopRight: + case EEAnchorMode::VCenterRight: + case EEAnchorMode::BottomRight: + { + aNewTopLeft.setX( aAnchorPoint.X() - aNewSz.Width() - 1 ); + } + break; + } + } + + // Y: + if ( DoAutoHeight() ) + { + if ( pEditEngine->pImpEditEngine->GetStatus().AutoPageHeight() ) + aNewSz.setHeight( pEditEngine->pImpEditEngine->GetPaperSize().Height() ); + switch ( eAnchorMode ) + { + case EEAnchorMode::TopLeft: + case EEAnchorMode::TopHCenter: + case EEAnchorMode::TopRight: + { + aNewTopLeft.setY( aAnchorPoint.Y() ); + } + break; + case EEAnchorMode::VCenterLeft: + case EEAnchorMode::VCenterHCenter: + case EEAnchorMode::VCenterRight: + { + aNewTopLeft.setY( aAnchorPoint.Y() - aNewSz.Height() / 2 ); + } + break; + case EEAnchorMode::BottomLeft: + case EEAnchorMode::BottomHCenter: + case EEAnchorMode::BottomRight: + { + aNewTopLeft.setY( aAnchorPoint.Y() - aNewSz.Height() - 1 ); + } + break; + } + } + ResetOutputArea( tools::Rectangle( aNewTopLeft, aNewSz ) ); +} + +void ImpEditView::SetAnchorMode( EEAnchorMode eMode ) +{ + eAnchorMode = eMode; + CalcAnchorPoint(); +} + +void ImpEditView::CalcAnchorPoint() +{ + // GetHeight() and GetWidth() -1, because rectangle calculation not preferred. + + // X: + switch ( eAnchorMode ) + { + case EEAnchorMode::TopLeft: + case EEAnchorMode::VCenterLeft: + case EEAnchorMode::BottomLeft: + { + aAnchorPoint.setX( aOutArea.Left() ); + } + break; + case EEAnchorMode::TopHCenter: + case EEAnchorMode::VCenterHCenter: + case EEAnchorMode::BottomHCenter: + { + aAnchorPoint.setX( aOutArea.Left() + (aOutArea.GetWidth()-1) / 2 ); + } + break; + case EEAnchorMode::TopRight: + case EEAnchorMode::VCenterRight: + case EEAnchorMode::BottomRight: + { + aAnchorPoint.setX( aOutArea.Right() ); + } + break; + } + + // Y: + switch ( eAnchorMode ) + { + case EEAnchorMode::TopLeft: + case EEAnchorMode::TopHCenter: + case EEAnchorMode::TopRight: + { + aAnchorPoint.setY( aOutArea.Top() ); + } + break; + case EEAnchorMode::VCenterLeft: + case EEAnchorMode::VCenterHCenter: + case EEAnchorMode::VCenterRight: + { + aAnchorPoint.setY( aOutArea.Top() + (aOutArea.GetHeight()-1) / 2 ); + } + break; + case EEAnchorMode::BottomLeft: + case EEAnchorMode::BottomHCenter: + case EEAnchorMode::BottomRight: + { + aAnchorPoint.setY( aOutArea.Bottom() - 1 ); + } + break; + } +} + +namespace +{ + +// For building JSON message to be sent to Online +boost::property_tree::ptree getHyperlinkPropTree(const OUString& sText, const OUString& sLink) +{ + boost::property_tree::ptree aTree; + aTree.put("text", sText); + aTree.put("link", sLink); + return aTree; +} + +} // End of anon namespace + +tools::Rectangle ImpEditView::ImplGetEditCursor(EditPaM& aPaM, GetCursorFlags nShowCursorFlags, sal_Int32& nTextPortionStart, + const ParaPortion* pParaPortion) const +{ + tools::Rectangle aEditCursor = pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM, nShowCursorFlags ); + if ( !IsInsertMode() && !aEditSelection.HasRange() ) + { + if ( aPaM.GetNode()->Len() && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ) ) + { + // If we are behind a portion, and the next portion has other direction, we must change position... + aEditCursor.SetLeft( pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM, GetCursorFlags::TextOnly|GetCursorFlags::PreferPortionStart ).Left() ); + aEditCursor.SetRight( aEditCursor.Left() ); + + sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true ); + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; + if ( rTextPortion.GetKind() == PortionKind::TAB ) + { + aEditCursor.AdjustRight(rTextPortion.GetSize().Width() ); + } + else + { + EditPaM aNext = pEditEngine->CursorRight( aPaM ); + tools::Rectangle aTmpRect = pEditEngine->pImpEditEngine->PaMtoEditCursor( aNext, GetCursorFlags::TextOnly ); + if ( aTmpRect.Top() != aEditCursor.Top() ) + aTmpRect = pEditEngine->pImpEditEngine->PaMtoEditCursor( aNext, GetCursorFlags::TextOnly|GetCursorFlags::EndOfLine ); + aEditCursor.SetRight( aTmpRect.Left() ); + } + } + } + + tools::Long nMaxHeight = !IsVertical() ? aOutArea.GetHeight() : aOutArea.GetWidth(); + if ( aEditCursor.GetHeight() > nMaxHeight ) + { + aEditCursor.SetBottom( aEditCursor.Top() + nMaxHeight - 1 ); + } + + return aEditCursor; +} + +tools::Rectangle ImpEditView::GetEditCursor() const +{ + EditPaM aPaM( aEditSelection.Max() ); + + sal_Int32 nTextPortionStart = 0; + sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + if (nPara == EE_PARA_NOT_FOUND) // #i94322 + return tools::Rectangle(); + + const ParaPortion* pParaPortion = pEditEngine->GetParaPortions()[nPara]; + + GetCursorFlags nShowCursorFlags = nExtraCursorFlags | GetCursorFlags::TextOnly; + + // Use CursorBidiLevel 0/1 in meaning of + // 0: prefer portion end, normal mode + // 1: prefer portion start + + if ( ( GetCursorBidiLevel() != CURSOR_BIDILEVEL_DONTKNOW ) && GetCursorBidiLevel() ) + { + nShowCursorFlags |= GetCursorFlags::PreferPortionStart; + } + + return ImplGetEditCursor(aPaM, nShowCursorFlags, nTextPortionStart, pParaPortion); +} + +void ImpEditView::ShowCursor( bool bGotoCursor, bool bForceVisCursor ) +{ + // No ShowCursor in an empty View ... + if (aOutArea.IsEmpty()) + return; + if ( ( aOutArea.Left() >= aOutArea.Right() ) && ( aOutArea.Top() >= aOutArea.Bottom() ) ) + return; + + pEditEngine->CheckIdleFormatter(); + if (!pEditEngine->IsFormatted()) + pEditEngine->pImpEditEngine->FormatDoc(); + + // For some reasons I end up here during the formatting, if the Outliner + // is initialized in Paint, because no SetPool(); + if ( pEditEngine->pImpEditEngine->IsFormatting() ) + return; + if ( !pEditEngine->pImpEditEngine->IsUpdateLayout() ) + return; + if ( pEditEngine->pImpEditEngine->IsInUndo() ) + return; + + if (pOutWin && pOutWin->GetCursor() != GetCursor()) + pOutWin->SetCursor(GetCursor()); + + EditPaM aPaM( aEditSelection.Max() ); + + sal_Int32 nTextPortionStart = 0; + sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + if (nPara == EE_PARA_NOT_FOUND) // #i94322 + return; + + const ParaPortion* pParaPortion = pEditEngine->GetParaPortions()[nPara]; + + GetCursorFlags nShowCursorFlags = nExtraCursorFlags | GetCursorFlags::TextOnly; + + // Use CursorBidiLevel 0/1 in meaning of + // 0: prefer portion end, normal mode + // 1: prefer portion start + + if ( ( GetCursorBidiLevel() != CURSOR_BIDILEVEL_DONTKNOW ) && GetCursorBidiLevel() ) + { + nShowCursorFlags |= GetCursorFlags::PreferPortionStart; + } + + tools::Rectangle aEditCursor = ImplGetEditCursor(aPaM, nShowCursorFlags, nTextPortionStart, pParaPortion); + + if ( bGotoCursor ) // && (!pEditEngine->pImpEditEngine->GetStatus().AutoPageSize() ) ) + { + // check if scrolling is necessary... + // if scrolling, then update () and Scroll ()! + tools::Long nDocDiffX = 0; + tools::Long nDocDiffY = 0; + + tools::Rectangle aTmpVisArea( GetVisDocArea() ); + // aTmpOutArea: if OutputArea > Paper width and + // Text > Paper width ( over large fields ) + tools::Long nMaxTextWidth = !IsVertical() ? pEditEngine->pImpEditEngine->GetPaperSize().Width() : pEditEngine->pImpEditEngine->GetPaperSize().Height(); + if ( aTmpVisArea.GetWidth() > nMaxTextWidth ) + aTmpVisArea.SetRight( aTmpVisArea.Left() + nMaxTextWidth ); + + if ( aEditCursor.Bottom() > aTmpVisArea.Bottom() ) + { // Scroll up, here positive + nDocDiffY = aEditCursor.Bottom() - aTmpVisArea.Bottom(); + } + else if ( aEditCursor.Top() < aTmpVisArea.Top() ) + { // Scroll down, here negative + nDocDiffY = aEditCursor.Top() - aTmpVisArea.Top(); + } + + if ( aEditCursor.Right() > aTmpVisArea.Right() ) + { + // Scroll left, positive + nDocDiffX = aEditCursor.Right() - aTmpVisArea.Right(); + // Can it be a little more? + if ( aEditCursor.Right() < ( nMaxTextWidth - GetScrollDiffX() ) ) + nDocDiffX += GetScrollDiffX(); + else + { + tools::Long n = nMaxTextWidth - aEditCursor.Right(); + // If MapMode != RefMapMode then the EditCursor can go beyond + // the paper width! + nDocDiffX += ( n > 0 ? n : -n ); + } + } + else if ( aEditCursor.Left() < aTmpVisArea.Left() ) + { + // Scroll right, negative: + nDocDiffX = aEditCursor.Left() - aTmpVisArea.Left(); + // Can it be a little more? + if ( aEditCursor.Left() > ( - static_cast<tools::Long>(GetScrollDiffX()) ) ) + nDocDiffX -= GetScrollDiffX(); + else + nDocDiffX -= aEditCursor.Left(); + } + if ( aPaM.GetIndex() == 0 ) // Olli needed for the Outliner + { + // But make sure that the cursor is not leaving visible area + // because of this! + if ( aEditCursor.Left() < aTmpVisArea.GetWidth() ) + { + nDocDiffX = -aTmpVisArea.Left(); + } + } + + if ( nDocDiffX | nDocDiffY ) + { + tools::Long nDiffX = !IsVertical() ? nDocDiffX : (IsTopToBottom() ? -nDocDiffY : nDocDiffY); + tools::Long nDiffY = !IsVertical() ? nDocDiffY : (IsTopToBottom() ? nDocDiffX : -nDocDiffX); + + if ( nDiffX ) + pEditEngine->GetInternalEditStatus().GetStatusWord() = pEditEngine->GetInternalEditStatus().GetStatusWord() | EditStatusFlags::HSCROLL; + if ( nDiffY ) + pEditEngine->GetInternalEditStatus().GetStatusWord() = pEditEngine->GetInternalEditStatus().GetStatusWord() | EditStatusFlags::VSCROLL; + Scroll( -nDiffX, -nDiffY ); + pEditEngine->pImpEditEngine->DelayedCallStatusHdl(); + } + } + + // Cursor may trim a little ... + if ( ( aEditCursor.Bottom() > GetVisDocTop() ) && + ( aEditCursor.Top() < GetVisDocBottom() ) ) + { + if ( aEditCursor.Bottom() > GetVisDocBottom() ) + aEditCursor.SetBottom( GetVisDocBottom() ); + if ( aEditCursor.Top() < GetVisDocTop() ) + aEditCursor.SetTop( GetVisDocTop() ); + } + + const OutputDevice& rOutDev = GetOutputDevice(); + + tools::Long nOnePixel = rOutDev.PixelToLogic( Size( 1, 0 ) ).Width(); + + if ( ( aEditCursor.Top() + nOnePixel >= GetVisDocTop() ) && + ( aEditCursor.Bottom() - nOnePixel <= GetVisDocBottom() ) && + ( aEditCursor.Left() + nOnePixel >= GetVisDocLeft() ) && + ( aEditCursor.Right() - nOnePixel <= GetVisDocRight() ) ) + { + tools::Rectangle aCursorRect = GetWindowPos( aEditCursor ); + GetCursor()->SetPos( aCursorRect.TopLeft() ); + Size aCursorSz( aCursorRect.GetSize() ); + // Rectangle is inclusive + aCursorSz.AdjustWidth( -1 ); + aCursorSz.AdjustHeight( -1 ); + if ( !aCursorSz.Width() || !aCursorSz.Height() ) + { + tools::Long nCursorSz = rOutDev.GetSettings().GetStyleSettings().GetCursorSize(); + nCursorSz = rOutDev.PixelToLogic( Size( nCursorSz, 0 ) ).Width(); + if ( !aCursorSz.Width() ) + aCursorSz.setWidth( nCursorSz ); + if ( !aCursorSz.Height() ) + aCursorSz.setHeight( nCursorSz ); + } + // #111036# Let VCL do orientation for cursor, otherwise problem when cursor has direction flag + if ( IsVertical() ) + { + Size aOldSz( aCursorSz ); + aCursorSz.setWidth( aOldSz.Height() ); + aCursorSz.setHeight( aOldSz.Width() ); + GetCursor()->SetPos( aCursorRect.TopRight() ); + GetCursor()->SetOrientation( Degree10(IsTopToBottom() ? 2700 : 900) ); + } + else + // #i32593# Reset correct orientation in horizontal layout + GetCursor()->SetOrientation(); + + GetCursor()->SetSize( aCursorSz ); + + if (comphelper::LibreOfficeKit::isActive() && mpViewShell && !mbSuppressLOKMessages) + { + Point aPos = GetCursor()->GetPos(); + boost::property_tree::ptree aMessageParams; + if (mpLOKSpecialPositioning) + { + // Sending the absolute (pure) logical coordinates of the cursor to the client is not + // enough for it to accurately reconstruct the corresponding tile-twips coordinates of the cursor. + // This is because the editeng(doc) positioning is not pixel aligned for each cell involved in the output-area + // (it better not be!). A simple solution is to send the coordinates of a point ('refpoint') in the output-area + // along with the relative position of the cursor w.r.t this chosen 'refpoint'. + + MapUnit eDevUnit = rOutDev.GetMapMode().GetMapUnit(); + tools::Rectangle aCursorRectPureLogical(aEditCursor.TopLeft(), GetCursor()->GetSize()); + // Get rectangle in window-coordinates from editeng(doc) coordinates in hmm. + aCursorRectPureLogical = GetWindowPos(aCursorRectPureLogical); + Point aRefPointLogical = GetOutputArea().TopLeft(); + // Get the relative coordinates w.r.t refpoint in display hmm. + aCursorRectPureLogical.Move(-aRefPointLogical.X(), -aRefPointLogical.Y()); + if (pEditEngine->IsRightToLeft(nPara)) + { + tools::Long nMirrorW = GetOutputArea().GetWidth(); + tools::Long nLeft = aCursorRectPureLogical.Left(), nRight = aCursorRectPureLogical.Right(); + aCursorRectPureLogical.SetLeft(nMirrorW - nRight); + aCursorRectPureLogical.SetRight(nMirrorW - nLeft); + } + // Convert to twips. + aCursorRectPureLogical = OutputDevice::LogicToLogic(aCursorRectPureLogical, MapMode(eDevUnit), MapMode(MapUnit::MapTwip)); + // "refpoint" in print twips. + const Point aRefPoint = mpLOKSpecialPositioning->GetRefPoint(); + aMessageParams.put("relrect", aCursorRectPureLogical.toString()); + aMessageParams.put("refpoint", aRefPoint.toString()); + } + + if (pOutWin && pOutWin->IsChart()) + { + const vcl::Window* pViewShellWindow = mpViewShell->GetEditWindowForActiveOLEObj(); + if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pOutWin)) + { + Point aOffsetPx = pOutWin->GetOffsetPixelFrom(*pViewShellWindow); + Point aLogicOffset = pOutWin->PixelToLogic(aOffsetPx); + aPos.Move(aLogicOffset.getX(), aLogicOffset.getY()); + } + } + + tools::Rectangle aRect(aPos.getX(), aPos.getY(), aPos.getX() + GetCursor()->GetWidth(), aPos.getY() + GetCursor()->GetHeight()); + + // LOK output is always in twips, convert from mm100 if necessary. + if (rOutDev.GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + { + aRect = o3tl::convert(aRect, o3tl::Length::mm100, o3tl::Length::twip); + } + else if (rOutDev.GetMapMode().GetMapUnit() == MapUnit::MapTwip) + { + // Writer comments: they use editeng, but are separate widgets. + Point aOrigin = rOutDev.GetMapMode().GetOrigin(); + // Move the rectangle, so that we output absolute twips. + aRect.Move(aOrigin.getX(), aOrigin.getY()); + } + // Let the LOK client decide the cursor width. + aRect.setWidth(0); + + OString sRect = aRect.toString(); + aMessageParams.put("rectangle", sRect); + + SfxViewShell* pThisShell = dynamic_cast<SfxViewShell*>(mpViewShell); + SfxViewShell* pOtherShell = dynamic_cast<SfxViewShell*>(mpOtherShell); + assert(pThisShell); + + if (pOtherShell && pThisShell != pOtherShell) + { + // Another shell wants to know about our existing cursor. + SfxLokHelper::notifyOtherView(pThisShell, pOtherShell, + LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, aMessageParams); + } + else + { + // is cursor at a misspelled word ? + Reference< linguistic2::XSpellChecker1 > xSpeller( pEditEngine->pImpEditEngine->GetSpeller() ); + bool bIsWrong = xSpeller.is() && IsWrongSpelledWord(aPaM, /*bMarkIfWrong*/ false); + + boost::property_tree::ptree aHyperlinkTree; + if (const SvxFieldItem* pFld = GetField(aPos, nullptr, nullptr)) + { + if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFld->GetField())) + { + aHyperlinkTree = getHyperlinkPropTree(pUrlField->GetRepresentation(), pUrlField->GetURL()); + } + } + else if (GetEditSelection().HasRange()) + { + EditView* pActiveView = GetEditViewPtr(); + + if (pActiveView) + { + const SvxFieldItem* pFieldItem = pActiveView->GetFieldAtSelection(); + if (pFieldItem) + { + const SvxFieldData* pField = pFieldItem->GetField(); + if ( auto pUrlField = dynamic_cast<const SvxURLField*>( pField) ) + { + aHyperlinkTree = getHyperlinkPropTree(pUrlField->GetRepresentation(), pUrlField->GetURL()); + } + } + } + } + + if (mbBroadcastLOKViewCursor) + SfxLokHelper::notifyOtherViews(pThisShell, + LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, aMessageParams); + + aMessageParams.put("mispelledWord", bIsWrong ? 1 : 0); + aMessageParams.add_child("hyperlink", aHyperlinkTree); + + if (comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation()) + SfxLokHelper::notifyOtherView(pThisShell, pThisShell, + LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, aMessageParams); + else + pThisShell->libreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, + aMessageParams.get<std::string>("rectangle").c_str()); + } + } + + CursorDirection nCursorDir = CursorDirection::NONE; + if ( IsInsertMode() && !aEditSelection.HasRange() && ( pEditEngine->pImpEditEngine->HasDifferentRTLLevels( aPaM.GetNode() ) ) ) + { + sal_uInt16 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, bool(nShowCursorFlags & GetCursorFlags::PreferPortionStart) ); + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; + if (rTextPortion.IsRightToLeft()) + nCursorDir = CursorDirection::RTL; + else + nCursorDir = CursorDirection::LTR; + + } + GetCursor()->SetDirection( nCursorDir ); + + if ( bForceVisCursor ) + GetCursor()->Show(); + { + SvxFont aFont; + pEditEngine->SeekCursor( aPaM.GetNode(), aPaM.GetIndex()+1, aFont ); + + InputContext aInputContext(aFont, InputContextFlags::Text | InputContextFlags::ExtText); + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + pCallbacks->EditViewInputContext(aInputContext); + else if (auto xWindow = GetWindow()) + xWindow->SetInputContext(aInputContext); + } + } + else + { + pEditEngine->pImpEditEngine->GetStatus().GetStatusWord() = pEditEngine->pImpEditEngine->GetStatus().GetStatusWord() | EditStatusFlags::CURSOROUT; + GetCursor()->Hide(); + GetCursor()->SetPos( Point( -1, -1 ) ); + GetCursor()->SetSize( Size( 0, 0 ) ); + } +} + +// call this so users of EditViewCallbacks can update their scrollbar state +// so called when we have either scrolled to a new location +// or the size of document has changed +void ImpEditView::ScrollStateChange() +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + pCallbacks->EditViewScrollStateChange(); +} + +Pair ImpEditView::Scroll( tools::Long ndX, tools::Long ndY, ScrollRangeCheck nRangeCheck ) +{ + DBG_ASSERT( pEditEngine->pImpEditEngine->IsFormatted(), "Scroll: Not formatted!" ); + if ( !ndX && !ndY ) + return Pair( 0, 0 ); + + const OutputDevice& rOutDev = GetOutputDevice(); + +#ifdef DBG_UTIL + tools::Rectangle aR( aOutArea ); + aR = rOutDev.LogicToPixel( aR ); + aR = rOutDev.PixelToLogic( aR ); + SAL_WARN_IF( aR != aOutArea, "editeng", "OutArea before Scroll not aligned" ); +#endif + + tools::Rectangle aNewVisArea( GetVisDocArea() ); + + // Vertical: + if ( !IsVertical() ) + { + aNewVisArea.AdjustTop( -ndY ); + aNewVisArea.AdjustBottom( -ndY ); + } + else + { + if( IsTopToBottom() ) + { + aNewVisArea.AdjustTop(ndX ); + aNewVisArea.AdjustBottom(ndX ); + } + else + { + aNewVisArea.AdjustTop( -ndX ); + aNewVisArea.AdjustBottom( -ndX ); + } + } + if ( ( nRangeCheck == ScrollRangeCheck::PaperWidthTextSize ) && ( aNewVisArea.Bottom() > static_cast<tools::Long>(pEditEngine->pImpEditEngine->GetTextHeight()) ) ) + { + // GetTextHeight still optimizing! + tools::Long nDiff = pEditEngine->pImpEditEngine->GetTextHeight() - aNewVisArea.Bottom(); // negative + aNewVisArea.Move( 0, nDiff ); // could end up in the negative area... + } + if ( aNewVisArea.Top() < 0 ) + aNewVisArea.Move( 0, -aNewVisArea.Top() ); + + // Horizontal: + if ( !IsVertical() ) + { + aNewVisArea.AdjustLeft( -ndX ); + aNewVisArea.AdjustRight( -ndX ); + } + else + { + if (IsTopToBottom()) + { + aNewVisArea.AdjustLeft( -ndY ); + aNewVisArea.AdjustRight( -ndY ); + } + else + { + aNewVisArea.AdjustLeft(ndY ); + aNewVisArea.AdjustRight(ndY ); + } + } + if ( ( nRangeCheck == ScrollRangeCheck::PaperWidthTextSize ) && ( aNewVisArea.Right() > static_cast<tools::Long>(pEditEngine->pImpEditEngine->CalcTextWidth( false )) ) ) + { + tools::Long nDiff = pEditEngine->pImpEditEngine->CalcTextWidth( false ) - aNewVisArea.Right(); // negative + aNewVisArea.Move( nDiff, 0 ); // could end up in the negative area... + } + if ( aNewVisArea.Left() < 0 ) + aNewVisArea.Move( -aNewVisArea.Left(), 0 ); + + // The difference must be alignt on pixel (due to scroll!) + tools::Long nDiffX = !IsVertical() ? ( GetVisDocLeft() - aNewVisArea.Left() ) : (IsTopToBottom() ? -( GetVisDocTop() - aNewVisArea.Top() ) : (GetVisDocTop() - aNewVisArea.Top())); + tools::Long nDiffY = !IsVertical() ? ( GetVisDocTop() - aNewVisArea.Top() ) : (IsTopToBottom() ? (GetVisDocLeft() - aNewVisArea.Left()) : -(GetVisDocTop() - aNewVisArea.Top())); + + Size aDiffs( nDiffX, nDiffY ); + aDiffs = rOutDev.LogicToPixel( aDiffs ); + aDiffs = rOutDev.PixelToLogic( aDiffs ); + + tools::Long nRealDiffX = aDiffs.Width(); + tools::Long nRealDiffY = aDiffs.Height(); + + + if ( nRealDiffX || nRealDiffY ) + { + vcl::Cursor* pCrsr = GetCursor(); + bool bVisCursor = pCrsr->IsVisible(); + pCrsr->Hide(); + if (pOutWin) + pOutWin->PaintImmediately(); + if ( !IsVertical() ) + aVisDocStartPos.Move( -nRealDiffX, -nRealDiffY ); + else + { + if (IsTopToBottom()) + aVisDocStartPos.Move(-nRealDiffY, nRealDiffX); + else + aVisDocStartPos.Move(nRealDiffY, -nRealDiffX); + } + // Move by aligned value does not necessarily result in aligned + // rectangle ... + aVisDocStartPos = rOutDev.LogicToPixel( aVisDocStartPos ); + aVisDocStartPos = rOutDev.PixelToLogic( aVisDocStartPos ); + tools::Rectangle aRect( aOutArea ); + + if (pOutWin) + { + pOutWin->Scroll( nRealDiffX, nRealDiffY, aRect, ScrollFlags::Clip ); + } + + if (comphelper::LibreOfficeKit::isActive() || getEditViewCallbacks()) + { + // Need to invalidate the window, otherwise no tile will be re-painted. + pEditView->Invalidate(); + } + + if (pOutWin) + pOutWin->PaintImmediately(); + pCrsr->SetPos( pCrsr->GetPos() + Point( nRealDiffX, nRealDiffY ) ); + if ( bVisCursor ) + { + tools::Rectangle aCursorRect( pCrsr->GetPos(), pCrsr->GetSize() ); + if ( aOutArea.Contains( aCursorRect ) ) + pCrsr->Show(); + } + + if ( pEditEngine->pImpEditEngine->GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_TEXTVIEWSCROLLED ); + pEditEngine->pImpEditEngine->GetNotifyHdl().Call( aNotify ); + } + + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + pCallbacks->EditViewScrollStateChange(); + + if (comphelper::LibreOfficeKit::isActive()) + { + DrawSelectionXOR(); + } + } + + return Pair( nRealDiffX, nRealDiffY ); +} + +Reference<css::datatransfer::clipboard::XClipboard> ImpEditView::GetClipboard() const +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + return pCallbacks->GetClipboard(); + if (vcl::Window* pWindow = GetWindow()) + return pWindow->GetClipboard(); + SAL_WARN("editeng", "falling back to using GetSystemClipboard"); + return GetSystemClipboard(); +} + +bool ImpEditView::PostKeyEvent( const KeyEvent& rKeyEvent, vcl::Window const * pFrameWin ) +{ + bool bDone = false; + + KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction(); + if ( eFunc != KeyFuncType::DONTKNOW ) + { + switch ( eFunc ) + { + case KeyFuncType::CUT: + { + if ( !bReadOnly ) + { + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + CutCopy( aClipBoard, true ); + bDone = true; + } + } + break; + case KeyFuncType::COPY: + { + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + CutCopy( aClipBoard, false ); + bDone = true; + } + break; + case KeyFuncType::PASTE: + { + if ( !bReadOnly && IsPasteEnabled() ) + { + pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_PASTE ); + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + Paste( aClipBoard, pEditEngine->pImpEditEngine->GetStatus().AllowPasteSpecial() ); + pEditEngine->pImpEditEngine->UndoActionEnd(); + bDone = true; + } + } + break; + default: + break; + } + } + + if( !bDone ) + bDone = pEditEngine->PostKeyEvent( rKeyEvent, GetEditViewPtr(), pFrameWin ); + + return bDone; +} + +bool ImpEditView::MouseButtonUp( const MouseEvent& rMouseEvent ) +{ + nTravelXPos = TRAVEL_X_DONTKNOW; + nCursorBidiLevel = CURSOR_BIDILEVEL_DONTKNOW; + nExtraCursorFlags = GetCursorFlags::NONE; + bClickedInSelection = false; + + if ( rMouseEvent.IsMiddle() && !bReadOnly && + Application::GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) + { + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetSystemPrimarySelection()); + Paste( aClipBoard ); + } + else if ( rMouseEvent.IsLeft() && GetEditSelection().HasRange() ) + { + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetSystemPrimarySelection()); + CutCopy( aClipBoard, false ); + } + + return pEditEngine->pImpEditEngine->MouseButtonUp( rMouseEvent, GetEditViewPtr() ); +} + +void ImpEditView::ReleaseMouse() +{ + pEditEngine->pImpEditEngine->ReleaseMouse(); +} + +bool ImpEditView::MouseButtonDown( const MouseEvent& rMouseEvent ) +{ + pEditEngine->CheckIdleFormatter(); // If fast typing and mouse button downs + nTravelXPos = TRAVEL_X_DONTKNOW; + nExtraCursorFlags = GetCursorFlags::NONE; + nCursorBidiLevel = CURSOR_BIDILEVEL_DONTKNOW; + bool bPrevUpdateLayout = pEditEngine->pImpEditEngine->SetUpdateLayout(true); + bClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() ); + bool bRet = pEditEngine->pImpEditEngine->MouseButtonDown( rMouseEvent, GetEditViewPtr() ); + pEditEngine->pImpEditEngine->SetUpdateLayout(bPrevUpdateLayout); + return bRet; +} + +bool ImpEditView::MouseMove( const MouseEvent& rMouseEvent ) +{ + return pEditEngine->pImpEditEngine->MouseMove( rMouseEvent, GetEditViewPtr() ); +} + +bool ImpEditView::Command(const CommandEvent& rCEvt) +{ + pEditEngine->CheckIdleFormatter(); // If fast typing and mouse button down + return pEditEngine->pImpEditEngine->Command(rCEvt, GetEditViewPtr()); +} + + +void ImpEditView::SetInsertMode( bool bInsert ) +{ + if ( bInsert != IsInsertMode() ) + { + SetFlags( nControl, EVControlBits::OVERWRITE, !bInsert ); + ShowCursor( DoAutoScroll(), false ); + } +} + +bool ImpEditView::IsWrongSpelledWord( const EditPaM& rPaM, bool bMarkIfWrong ) +{ + bool bIsWrong = false; + if ( rPaM.GetNode()->GetWrongList() ) + { + EditSelection aSel = pEditEngine->SelectWord( rPaM, css::i18n::WordType::DICTIONARY_WORD ); + bIsWrong = rPaM.GetNode()->GetWrongList()->HasWrong( aSel.Min().GetIndex(), aSel.Max().GetIndex() ); + if ( bIsWrong && bMarkIfWrong ) + { + DrawSelectionXOR(); + SetEditSelection( aSel ); + DrawSelectionXOR(); + } + } + return bIsWrong; +} + +OUString ImpEditView::SpellIgnoreWord() +{ + OUString aWord; + if ( pEditEngine->pImpEditEngine->GetSpeller().is() ) + { + EditPaM aPaM = GetEditSelection().Max(); + if ( !HasSelection() ) + { + EditSelection aSel = pEditEngine->SelectWord(aPaM); + aWord = pEditEngine->pImpEditEngine->GetSelected( aSel ); + } + else + { + aWord = pEditEngine->pImpEditEngine->GetSelected( GetEditSelection() ); + // And deselect + DrawSelectionXOR(); + SetEditSelection( EditSelection( aPaM, aPaM ) ); + DrawSelectionXOR(); + } + + if ( !aWord.isEmpty() ) + { + Reference< XDictionary > xDic = LinguMgr::GetIgnoreAllList(); + if (xDic.is()) + xDic->add( aWord, false, OUString() ); + EditDoc& rDoc = pEditEngine->GetEditDoc(); + sal_Int32 nNodes = rDoc.Count(); + for ( sal_Int32 n = 0; n < nNodes; n++ ) + { + ContentNode* pNode = rDoc.GetObject( n ); + pNode->GetWrongList()->MarkWrongsInvalid(); + } + pEditEngine->pImpEditEngine->DoOnlineSpelling( aPaM.GetNode() ); + pEditEngine->pImpEditEngine->StartOnlineSpellTimer(); + } + } + return aWord; +} + +void ImpEditView::DeleteSelected() +{ + DrawSelectionXOR(); + + pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_DELETE ); + + EditPaM aPaM = pEditEngine->pImpEditEngine->DeleteSelected( GetEditSelection() ); + + pEditEngine->pImpEditEngine->UndoActionEnd(); + + SetEditSelection( EditSelection( aPaM, aPaM ) ); + + DrawSelectionXOR(); + + pEditEngine->pImpEditEngine->FormatAndLayout( GetEditViewPtr() ); + ShowCursor( DoAutoScroll(), true ); +} + +const SvxFieldItem* ImpEditView::GetField( const Point& rPos, sal_Int32* pPara, sal_Int32* pPos ) const +{ + if( !GetOutputArea().Contains( rPos ) ) + return nullptr; + + Point aDocPos( GetDocPos( rPos ) ); + EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false); + if (!aPaM) + return nullptr; + + if ( aPaM.GetIndex() == aPaM.GetNode()->Len() ) + { + // Otherwise, whenever the Field at the very end and mouse under the text + return nullptr; + } + + const CharAttribList::AttribsType& rAttrs = aPaM.GetNode()->GetCharAttribs().GetAttribs(); + const sal_Int32 nXPos = aPaM.GetIndex(); + for (size_t nAttr = rAttrs.size(); nAttr; ) + { + const EditCharAttrib& rAttr = *rAttrs[--nAttr]; + if (rAttr.GetStart() == nXPos || rAttr.GetEnd() == nXPos) + { + if (rAttr.Which() == EE_FEATURE_FIELD) + { + DBG_ASSERT(dynamic_cast<const SvxFieldItem*>(rAttr.GetItem()), "No FieldItem..."); + if ( pPara ) + *pPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + if ( pPos ) + *pPos = rAttr.GetStart(); + return static_cast<const SvxFieldItem*>(rAttr.GetItem()); + } + } + } + return nullptr; +} + +bool ImpEditView::IsBulletArea( const Point& rPos, sal_Int32* pPara ) +{ + if ( pPara ) + *pPara = EE_PARA_NOT_FOUND; + + if( !GetOutputArea().Contains( rPos ) ) + return false; + + Point aDocPos( GetDocPos( rPos ) ); + EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false); + if (!aPaM) + return false; + + if ( aPaM.GetIndex() == 0 ) + { + sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + tools::Rectangle aBulletArea = pEditEngine->GetBulletArea( nPara ); + tools::Long nY = pEditEngine->GetDocPosTopLeft( nPara ).Y(); + const ParaPortion* pParaPortion = pEditEngine->GetParaPortions()[nPara]; + nY += pParaPortion->GetFirstLineOffset(); + if ( ( aDocPos.Y() > ( nY + aBulletArea.Top() ) ) && + ( aDocPos.Y() < ( nY + aBulletArea.Bottom() ) ) && + ( aDocPos.X() > ( aBulletArea.Left() ) ) && + ( aDocPos.X() < ( aBulletArea.Right() ) ) ) + { + if ( pPara ) + *pPara = nPara; + return true; + } + } + + return false; +} + +void ImpEditView::CutCopy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bCut ) +{ + if ( !(rxClipboard.is() && HasSelection()) ) + return; + + uno::Reference<datatransfer::XTransferable> xData = pEditEngine->CreateTransferable( GetEditSelection() ); + + { + SolarMutexReleaser aReleaser; + + try + { + rxClipboard->setContents( xData, nullptr ); + + // #87756# FlushClipboard, but it would be better to become a TerminateListener to the Desktop and flush on demand... + uno::Reference< datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, uno::UNO_QUERY ); + if( xFlushableClipboard.is() ) + xFlushableClipboard->flushClipboard(); + } + catch( const css::uno::Exception& ) + { + } + + } + + if (bCut) + { + pEditEngine->pImpEditEngine->UndoActionStart(EDITUNDO_CUT); + DeleteSelected(); + pEditEngine->pImpEditEngine->UndoActionEnd(); + } +} + +void ImpEditView::Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bUseSpecial ) +{ + if ( !rxClipboard.is() ) + return; + + uno::Reference< datatransfer::XTransferable > xDataObj; + + try + { + SolarMutexReleaser aReleaser; + xDataObj = rxClipboard->getContents(); + } + catch( const css::uno::Exception& ) + { + } + + if ( !xDataObj.is() || !EditEngine::HasValidData( xDataObj ) ) + return; + + pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_PASTE ); + + EditSelection aSel( GetEditSelection() ); + if ( aSel.HasRange() ) + { + DrawSelectionXOR(); + aSel = pEditEngine->DeleteSelection(aSel); + } + + PasteOrDropInfos aPasteOrDropInfos; + aPasteOrDropInfos.nStartPara = pEditEngine->GetEditDoc().GetPos( aSel.Min().GetNode() ); + pEditEngine->HandleBeginPasteOrDrop(aPasteOrDropInfos); + + if ( DoSingleLinePaste() ) + { + datatransfer::DataFlavor aFlavor; + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); + if ( xDataObj->isDataFlavorSupported( aFlavor ) ) + { + try + { + uno::Any aData = xDataObj->getTransferData( aFlavor ); + OUString aTmpText; + aData >>= aTmpText; + OUString aText(convertLineEnd(aTmpText, LINEEND_LF)); + aText = aText.replaceAll( OUStringChar(LINE_SEP), " " ); + aSel = pEditEngine->InsertText(aSel, aText); + } + catch( ... ) + { + ; // #i9286# can happen, even if isDataFlavorSupported returns true... + } + } + } + else + { + // Prevent notifications of paragraph inserts et al that would trigger + // a11y to format content in a half-ready state when obtaining + // paragraphs. Collect and broadcast when done instead. + aSel = pEditEngine->InsertText( + xDataObj, OUString(), aSel.Min(), + bUseSpecial && pEditEngine->GetInternalEditStatus().AllowPasteSpecial()); + } + + aPasteOrDropInfos.nEndPara = pEditEngine->GetEditDoc().GetPos( aSel.Max().GetNode() ); + pEditEngine->HandleEndPasteOrDrop(aPasteOrDropInfos); + + pEditEngine->pImpEditEngine->UndoActionEnd(); + SetEditSelection( aSel ); + pEditEngine->pImpEditEngine->UpdateSelections(); + pEditEngine->pImpEditEngine->FormatAndLayout( GetEditViewPtr() ); + ShowCursor( DoAutoScroll(), true ); +} + + +bool ImpEditView::IsInSelection( const EditPaM& rPaM ) +{ + EditSelection aSel = GetEditSelection(); + if ( !aSel.HasRange() ) + return false; + + aSel.Adjust( pEditEngine->GetEditDoc() ); + + sal_Int32 nStartNode = pEditEngine->GetEditDoc().GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndNode = pEditEngine->GetEditDoc().GetPos( aSel.Max().GetNode() ); + sal_Int32 nCurNode = pEditEngine->GetEditDoc().GetPos( rPaM.GetNode() ); + + if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) ) + return true; + + if ( nStartNode == nEndNode ) + { + if ( nCurNode == nStartNode ) + if ( ( rPaM.GetIndex() >= aSel.Min().GetIndex() ) && ( rPaM.GetIndex() < aSel.Max().GetIndex() ) ) + return true; + } + else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.Min().GetIndex() ) ) + return true; + else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.Max().GetIndex() ) ) + return true; + + return false; +} + +void ImpEditView::CreateAnchor() +{ + pEditEngine->SetInSelectionMode(true); + EditSelection aNewSelection(GetEditSelection()); + aNewSelection.Min() = aNewSelection.Max(); + SetEditSelection(aNewSelection); + // const_cast<EditPaM&>(GetEditSelection().Min()) = GetEditSelection().Max(); +} + +void ImpEditView::DeselectAll() +{ + pEditEngine->SetInSelectionMode(false); + DrawSelectionXOR(); + EditSelection aNewSelection(GetEditSelection()); + aNewSelection.Min() = aNewSelection.Max(); + SetEditSelection(aNewSelection); + // const_cast<EditPaM&>(GetEditSelection().Min()) = GetEditSelection().Max(); + + if (comphelper::LibreOfficeKit::isActive() && mpViewShell && pOutWin) + { + VclPtr<vcl::Window> pParent = pOutWin->GetParentWithLOKNotifier(); + if (pParent && pParent->GetLOKWindowId()) + { + const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier(); + std::vector<vcl::LOKPayloadItem> aItems; + aItems.emplace_back("rectangles", ""); + pNotifier->notifyWindow(pParent->GetLOKWindowId(), "text_selection", aItems); + } + } +} + +bool ImpEditView::IsSelectionAtPoint( const Point& rPosPixel ) +{ + if ( pDragAndDropInfo && pDragAndDropInfo->pField ) + return true; + + // Logical units ... + const OutputDevice& rOutDev = GetOutputDevice(); + Point aMousePos = rOutDev.PixelToLogic(rPosPixel); + + if ( ( !GetOutputArea().Contains( aMousePos ) ) && !pEditEngine->pImpEditEngine->IsInSelectionMode() ) + { + return false; + } + + Point aDocPos( GetDocPos( aMousePos ) ); + EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false); + return IsInSelection( aPaM ); +} + +bool ImpEditView::SetCursorAtPoint( const Point& rPointPixel ) +{ + pEditEngine->CheckIdleFormatter(); + + Point aMousePos( rPointPixel ); + + // Logical units ... + const OutputDevice& rOutDev = GetOutputDevice(); + aMousePos = rOutDev.PixelToLogic( aMousePos ); + + if ( ( !GetOutputArea().Contains( aMousePos ) ) && !pEditEngine->pImpEditEngine->IsInSelectionMode() ) + { + return false; + } + + Point aDocPos( GetDocPos( aMousePos ) ); + + // Can be optimized: first go through the lines within a paragraph for PAM, + // then again with the PaM for the Rect, even though the line is already + // known... This must not be, though! + EditPaM aPaM = pEditEngine->GetPaM(aDocPos); + bool bGotoCursor = DoAutoScroll(); + + // aTmpNewSel: Diff between old and new, not the new selection, unless for tiled rendering + EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? GetEditSelection().Min() : GetEditSelection().Max(), aPaM ); + + // #i27299# + // work on copy of current selection and set new selection, if it has changed. + EditSelection aNewEditSelection( GetEditSelection() ); + + aNewEditSelection.Max() = aPaM; + if (!pEditEngine->GetSelectionEngine().HasAnchor()) + { + if ( aNewEditSelection.Min() != aPaM ) + pEditEngine->CursorMoved(aNewEditSelection.Min().GetNode()); + aNewEditSelection.Min() = aPaM; + } + else + { + DrawSelectionXOR( aTmpNewSel ); + } + + // set changed text selection + if ( GetEditSelection() != aNewEditSelection ) + { + SetEditSelection( aNewEditSelection ); + } + + bool bForceCursor = pDragAndDropInfo == nullptr && !pEditEngine->pImpEditEngine->IsInSelectionMode(); + ShowCursor( bGotoCursor, bForceCursor ); + return true; +} + +void ImpEditView::HideDDCursor() +{ + if ( pDragAndDropInfo && pDragAndDropInfo->bVisCursor ) + { + OutputDevice& rOutDev = GetOutputDevice(); + rOutDev.DrawOutDev( pDragAndDropInfo->aCurSavedCursor.TopLeft(), pDragAndDropInfo->aCurSavedCursor.GetSize(), + Point(0,0), pDragAndDropInfo->aCurSavedCursor.GetSize(),*pDragAndDropInfo->pBackground ); + pDragAndDropInfo->bVisCursor = false; + } +} + +void ImpEditView::ShowDDCursor( const tools::Rectangle& rRect ) +{ + if ( !pDragAndDropInfo || pDragAndDropInfo->bVisCursor ) + return; + + if (pOutWin && pOutWin->GetCursor()) + pOutWin->GetCursor()->Hide(); + + OutputDevice& rOutDev = GetOutputDevice(); + Color aOldFillColor = rOutDev.GetFillColor(); + rOutDev.SetFillColor( Color(4210752) ); // GRAY BRUSH_50, OLDSV, change to DDCursor! + + // Save background ... + tools::Rectangle aSaveRect( rOutDev.LogicToPixel( rRect ) ); + // prefer to save some more ... + aSaveRect.AdjustRight(1 ); + aSaveRect.AdjustBottom(1 ); + + if ( !pDragAndDropInfo->pBackground ) + { + pDragAndDropInfo->pBackground = VclPtr<VirtualDevice>::Create(rOutDev); + MapMode aMapMode( rOutDev.GetMapMode() ); + aMapMode.SetOrigin( Point( 0, 0 ) ); + pDragAndDropInfo->pBackground->SetMapMode( aMapMode ); + + } + + Size aNewSzPx( aSaveRect.GetSize() ); + Size aCurSzPx( pDragAndDropInfo->pBackground->GetOutputSizePixel() ); + if ( ( aCurSzPx.Width() < aNewSzPx.Width() ) ||( aCurSzPx.Height() < aNewSzPx.Height() ) ) + { + bool bDone = pDragAndDropInfo->pBackground->SetOutputSizePixel( aNewSzPx ); + DBG_ASSERT( bDone, "Virtual Device broken?" ); + } + + aSaveRect = rOutDev.PixelToLogic( aSaveRect ); + + pDragAndDropInfo->pBackground->DrawOutDev( Point(0,0), aSaveRect.GetSize(), + aSaveRect.TopLeft(), aSaveRect.GetSize(), rOutDev ); + pDragAndDropInfo->aCurSavedCursor = aSaveRect; + + // Draw Cursor... + rOutDev.DrawRect( rRect ); + + pDragAndDropInfo->bVisCursor = true; + pDragAndDropInfo->aCurCursor = rRect; + + rOutDev.SetFillColor( aOldFillColor ); +} + +void ImpEditView::dragGestureRecognized(const css::datatransfer::dnd::DragGestureEvent& rDGE) +{ + DBG_ASSERT( !pDragAndDropInfo, "dragGestureRecognized - DragAndDropInfo exist!" ); + + SolarMutexGuard aVclGuard; + + pDragAndDropInfo.reset(); + + Point aMousePosPixel( rDGE.DragOriginX, rDGE.DragOriginY ); + + EditSelection aCopySel( GetEditSelection() ); + aCopySel.Adjust( pEditEngine->GetEditDoc() ); + + if ( HasSelection() && bClickedInSelection ) + { + pDragAndDropInfo.reset(new DragAndDropInfo()); + } + else + { + // Field?! + sal_Int32 nPara; + sal_Int32 nPos; + Point aMousePos = GetOutputDevice().PixelToLogic( aMousePosPixel ); + const SvxFieldItem* pField = GetField( aMousePos, &nPara, &nPos ); + if ( pField ) + { + pDragAndDropInfo.reset(new DragAndDropInfo()); + pDragAndDropInfo->pField = pField; + ContentNode* pNode = pEditEngine->GetEditDoc().GetObject( nPara ); + aCopySel = EditSelection( EditPaM( pNode, nPos ), EditPaM( pNode, nPos+1 ) ); + SetEditSelection(aCopySel); + DrawSelectionXOR(); + bool bGotoCursor = DoAutoScroll(); + ShowCursor( bGotoCursor, /*bForceCursor=*/false ); + } + else if ( IsBulletArea( aMousePos, &nPara ) ) + { + pDragAndDropInfo.reset(new DragAndDropInfo()); + pDragAndDropInfo->bOutlinerMode = true; + EditPaM aStartPaM( pEditEngine->GetEditDoc().GetObject( nPara ), 0 ); + EditPaM aEndPaM( aStartPaM ); + const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); + for ( sal_Int32 n = nPara +1; n < pEditEngine->GetEditDoc().Count(); n++ ) + { + const SfxInt16Item& rL = pEditEngine->GetParaAttrib( n, EE_PARA_OUTLLEVEL ); + if ( rL.GetValue() > rLevel.GetValue() ) + { + aEndPaM.SetNode( pEditEngine->GetEditDoc().GetObject( n ) ); + } + else + { + break; + } + } + aEndPaM.SetIndex( aEndPaM.GetNode()->Len() ); + SetEditSelection( EditSelection( aStartPaM, aEndPaM ) ); + } + } + + if ( !pDragAndDropInfo ) + return; + + + pDragAndDropInfo->bStarterOfDD = true; + + // Sensitive area to be scrolled. + Size aSz( 5, 0 ); + aSz = GetOutputDevice().PixelToLogic( aSz ); + pDragAndDropInfo->nSensibleRange = static_cast<sal_uInt16>(aSz.Width()); + pDragAndDropInfo->nCursorWidth = static_cast<sal_uInt16>(aSz.Width()) / 2; + pDragAndDropInfo->aBeginDragSel = pEditEngine->pImpEditEngine->CreateESel( aCopySel ); + + uno::Reference<datatransfer::XTransferable> xData = pEditEngine->CreateTransferable(aCopySel); + + sal_Int8 nActions = bReadOnly ? datatransfer::dnd::DNDConstants::ACTION_COPY : datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE; + + rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, xData, mxDnDListener ); + // If Drag&Move in an Engine, then Copy&Del has to be optional! + GetCursor()->Hide(); +} + +void ImpEditView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& rDSDE ) +{ + SolarMutexGuard aVclGuard; + + DBG_ASSERT( pDragAndDropInfo, "ImpEditView::dragDropEnd: pDragAndDropInfo is NULL!" ); + + // #123688# Shouldn't happen, but seems to happen... + if ( !pDragAndDropInfo ) + return; + + if ( !bReadOnly && rDSDE.DropSuccess && !pDragAndDropInfo->bOutlinerMode && ( rDSDE.DropAction & datatransfer::dnd::DNDConstants::ACTION_MOVE ) ) + { + if ( pDragAndDropInfo->bStarterOfDD && pDragAndDropInfo->bDroppedInMe ) + { + // DropPos: Where was it dropped, irrespective of length. + ESelection aDropPos( pDragAndDropInfo->aDropSel.nStartPara, pDragAndDropInfo->aDropSel.nStartPos, pDragAndDropInfo->aDropSel.nStartPara, pDragAndDropInfo->aDropSel.nStartPos ); + ESelection aToBeDelSel = pDragAndDropInfo->aBeginDragSel; + ESelection aNewSel( pDragAndDropInfo->aDropSel.nEndPara, pDragAndDropInfo->aDropSel.nEndPos, + pDragAndDropInfo->aDropSel.nEndPara, pDragAndDropInfo->aDropSel.nEndPos ); + bool bBeforeSelection = aDropPos < pDragAndDropInfo->aBeginDragSel; + sal_Int32 nParaDiff = pDragAndDropInfo->aBeginDragSel.nEndPara - pDragAndDropInfo->aBeginDragSel.nStartPara; + if ( bBeforeSelection ) + { + // Adjust aToBeDelSel. + DBG_ASSERT( pDragAndDropInfo->aBeginDragSel.nStartPara >= pDragAndDropInfo->aDropSel.nStartPara, "But not before? "); + aToBeDelSel.nStartPara = aToBeDelSel.nStartPara + nParaDiff; + aToBeDelSel.nEndPara = aToBeDelSel.nEndPara + nParaDiff; + // To correct the character? + if ( aToBeDelSel.nStartPara == pDragAndDropInfo->aDropSel.nEndPara ) + { + sal_uInt16 nMoreChars; + if ( pDragAndDropInfo->aDropSel.nStartPara == pDragAndDropInfo->aDropSel.nEndPara ) + nMoreChars = pDragAndDropInfo->aDropSel.nEndPos - pDragAndDropInfo->aDropSel.nStartPos; + else + nMoreChars = pDragAndDropInfo->aDropSel.nEndPos; + aToBeDelSel.nStartPos = + aToBeDelSel.nStartPos + nMoreChars; + if ( aToBeDelSel.nStartPara == aToBeDelSel.nEndPara ) + aToBeDelSel.nEndPos = + aToBeDelSel.nEndPos + nMoreChars; + } + } + else + { + // aToBeDelSel is ok, but the selection of the View + // has to be adapted, if it was deleted before! + DBG_ASSERT( pDragAndDropInfo->aBeginDragSel.nStartPara <= pDragAndDropInfo->aDropSel.nStartPara, "But not before? "); + aNewSel.nStartPara = aNewSel.nStartPara - nParaDiff; + aNewSel.nEndPara = aNewSel.nEndPara - nParaDiff; + // To correct the character? + if ( pDragAndDropInfo->aBeginDragSel.nEndPara == pDragAndDropInfo->aDropSel.nStartPara ) + { + sal_uInt16 nLessChars; + if ( pDragAndDropInfo->aBeginDragSel.nStartPara == pDragAndDropInfo->aBeginDragSel.nEndPara ) + nLessChars = pDragAndDropInfo->aBeginDragSel.nEndPos - pDragAndDropInfo->aBeginDragSel.nStartPos; + else + nLessChars = pDragAndDropInfo->aBeginDragSel.nEndPos; + aNewSel.nStartPos = aNewSel.nStartPos - nLessChars; + if ( aNewSel.nStartPara == aNewSel.nEndPara ) + aNewSel.nEndPos = aNewSel.nEndPos - nLessChars; + } + } + + DrawSelectionXOR(); + EditSelection aDelSel( pEditEngine->pImpEditEngine->CreateSel( aToBeDelSel ) ); + DBG_ASSERT( !aDelSel.DbgIsBuggy( pEditEngine->GetEditDoc() ), "ToBeDel is buggy!" ); + pEditEngine->DeleteSelection(aDelSel); + if ( !bBeforeSelection ) + { + DBG_ASSERT( !pEditEngine->pImpEditEngine->CreateSel( aNewSel ).DbgIsBuggy(pEditEngine->GetEditDoc()), "Bad" ); + SetEditSelection( pEditEngine->pImpEditEngine->CreateSel( aNewSel ) ); + } + pEditEngine->pImpEditEngine->FormatAndLayout( pEditEngine->pImpEditEngine->GetActiveView() ); + DrawSelectionXOR(); + } + else + { + // other EditEngine ... + if (pEditEngine->HasText()) // #88630# SC is removing the content when switching the task + DeleteSelected(); + } + } + + if ( pDragAndDropInfo->bUndoAction ) + pEditEngine->pImpEditEngine->UndoActionEnd(); + + HideDDCursor(); + ShowCursor( DoAutoScroll(), true ); + pDragAndDropInfo.reset(); + pEditEngine->GetEndDropHdl().Call(GetEditViewPtr()); +} + +void ImpEditView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE ) +{ + SolarMutexGuard aVclGuard; + + DBG_ASSERT( pDragAndDropInfo, "Drop - No Drag&Drop info?!" ); + + if ( !(pDragAndDropInfo && pDragAndDropInfo->bDragAccepted) ) + return; + + pEditEngine->GetBeginDropHdl().Call(GetEditViewPtr()); + bool bChanges = false; + + HideDDCursor(); + + if ( pDragAndDropInfo->bStarterOfDD ) + { + pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_DRAGANDDROP ); + pDragAndDropInfo->bUndoAction = true; + } + + if ( pDragAndDropInfo->bOutlinerMode ) + { + bChanges = true; + GetEditViewPtr()->MoveParagraphs( Range( pDragAndDropInfo->aBeginDragSel.nStartPara, pDragAndDropInfo->aBeginDragSel.nEndPara ), pDragAndDropInfo->nOutlinerDropDest ); + } + else + { + uno::Reference< datatransfer::XTransferable > xDataObj = rDTDE.Transferable; + if ( xDataObj.is() ) + { + bChanges = true; + // remove Selection ... + DrawSelectionXOR(); + EditPaM aPaM( pDragAndDropInfo->aDropDest ); + + PasteOrDropInfos aPasteOrDropInfos; + aPasteOrDropInfos.nStartPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + pEditEngine->HandleBeginPasteOrDrop(aPasteOrDropInfos); + + EditSelection aNewSel = pEditEngine->InsertText( + xDataObj, OUString(), aPaM, pEditEngine->GetInternalEditStatus().AllowPasteSpecial()); + + aPasteOrDropInfos.nEndPara = pEditEngine->GetEditDoc().GetPos( aNewSel.Max().GetNode() ); + pEditEngine->HandleEndPasteOrDrop(aPasteOrDropInfos); + + SetEditSelection( aNewSel ); + pEditEngine->pImpEditEngine->FormatAndLayout( pEditEngine->pImpEditEngine->GetActiveView() ); + if ( pDragAndDropInfo->bStarterOfDD ) + { + // Only set if the same engine! + pDragAndDropInfo->aDropSel.nStartPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + pDragAndDropInfo->aDropSel.nStartPos = aPaM.GetIndex(); + pDragAndDropInfo->aDropSel.nEndPara = pEditEngine->GetEditDoc().GetPos( aNewSel.Max().GetNode() ); + pDragAndDropInfo->aDropSel.nEndPos = aNewSel.Max().GetIndex(); + pDragAndDropInfo->bDroppedInMe = true; + } + } + } + + if ( bChanges ) + { + rDTDE.Context->acceptDrop( rDTDE.DropAction ); + } + + if ( !pDragAndDropInfo->bStarterOfDD ) + { + pDragAndDropInfo.reset(); + } + + rDTDE.Context->dropComplete( bChanges ); +} + +void ImpEditView::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& rDTDEE ) +{ + SolarMutexGuard aVclGuard; + + if ( !pDragAndDropInfo ) + pDragAndDropInfo.reset(new DragAndDropInfo()); + + pDragAndDropInfo->bHasValidData = false; + + // Check for supported format... + // Only check for text, will also be there if bin or rtf + datatransfer::DataFlavor aTextFlavor; + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aTextFlavor ); + const css::datatransfer::DataFlavor* pFlavors = rDTDEE.SupportedDataFlavors.getConstArray(); + int nFlavors = rDTDEE.SupportedDataFlavors.getLength(); + for ( int n = 0; n < nFlavors; n++ ) + { + if( TransferableDataHelper::IsEqual( pFlavors[n], aTextFlavor ) ) + { + pDragAndDropInfo->bHasValidData = true; + break; + } + } + + dragOver( rDTDEE ); +} + +void ImpEditView::dragExit( const css::datatransfer::dnd::DropTargetEvent& ) +{ + SolarMutexGuard aVclGuard; + + HideDDCursor(); + + if ( pDragAndDropInfo && !pDragAndDropInfo->bStarterOfDD ) + { + pDragAndDropInfo.reset(); + } +} + +void ImpEditView::dragOver(const css::datatransfer::dnd::DropTargetDragEvent& rDTDE) +{ + SolarMutexGuard aVclGuard; + + const OutputDevice& rOutDev = GetOutputDevice(); + + Point aMousePos( rDTDE.LocationX, rDTDE.LocationY ); + aMousePos = rOutDev.PixelToLogic( aMousePos ); + + bool bAccept = false; + + if ( GetOutputArea().Contains( aMousePos ) && !bReadOnly ) + { + if ( pDragAndDropInfo && pDragAndDropInfo->bHasValidData ) + { + bAccept = true; + + bool bAllowScroll = DoAutoScroll(); + if ( bAllowScroll ) + { + tools::Long nScrollX = 0; + tools::Long nScrollY = 0; + // Check if in the sensitive area + if ( ( (aMousePos.X()-pDragAndDropInfo->nSensibleRange) < GetOutputArea().Left() ) && ( ( aMousePos.X() + pDragAndDropInfo->nSensibleRange ) > GetOutputArea().Left() ) ) + nScrollX = GetOutputArea().GetWidth() / SCRLRANGE; + else if ( ( (aMousePos.X()+pDragAndDropInfo->nSensibleRange) > GetOutputArea().Right() ) && ( ( aMousePos.X() - pDragAndDropInfo->nSensibleRange ) < GetOutputArea().Right() ) ) + nScrollX = -( GetOutputArea().GetWidth() / SCRLRANGE ); + + if ( ( (aMousePos.Y()-pDragAndDropInfo->nSensibleRange) < GetOutputArea().Top() ) && ( ( aMousePos.Y() + pDragAndDropInfo->nSensibleRange ) > GetOutputArea().Top() ) ) + nScrollY = GetOutputArea().GetHeight() / SCRLRANGE; + else if ( ( (aMousePos.Y()+pDragAndDropInfo->nSensibleRange) > GetOutputArea().Bottom() ) && ( ( aMousePos.Y() - pDragAndDropInfo->nSensibleRange ) < GetOutputArea().Bottom() ) ) + nScrollY = -( GetOutputArea().GetHeight() / SCRLRANGE ); + + if ( nScrollX || nScrollY ) + { + HideDDCursor(); + Scroll( nScrollX, nScrollY, ScrollRangeCheck::PaperWidthTextSize ); + } + } + + Point aDocPos( GetDocPos( aMousePos ) ); + EditPaM aPaM = pEditEngine->GetPaM( aDocPos ); + pDragAndDropInfo->aDropDest = aPaM; + if ( pDragAndDropInfo->bOutlinerMode ) + { + sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + ParaPortion* pPPortion = pEditEngine->GetParaPortions().SafeGetObject( nPara ); + if (pPPortion) + { + tools::Long nDestParaStartY = pEditEngine->GetParaPortions().GetYOffset( pPPortion ); + tools::Long nRel = aDocPos.Y() - nDestParaStartY; + if ( nRel < ( pPPortion->GetHeight() / 2 ) ) + { + pDragAndDropInfo->nOutlinerDropDest = nPara; + } + else + { + pDragAndDropInfo->nOutlinerDropDest = nPara+1; + } + + if( ( pDragAndDropInfo->nOutlinerDropDest >= pDragAndDropInfo->aBeginDragSel.nStartPara ) && + ( pDragAndDropInfo->nOutlinerDropDest <= (pDragAndDropInfo->aBeginDragSel.nEndPara+1) ) ) + { + bAccept = false; + } + } + } + else if ( HasSelection() ) + { + // it must not be dropped into a selection + EPaM aP = pEditEngine->pImpEditEngine->CreateEPaM( aPaM ); + ESelection aDestSel( aP.nPara, aP.nIndex, aP.nPara, aP.nIndex); + ESelection aCurSel = pEditEngine->pImpEditEngine->CreateESel( GetEditSelection() ); + aCurSel.Adjust(); + if ( !(aDestSel < aCurSel) && !(aDestSel > aCurSel) ) + { + bAccept = false; + } + } + if ( bAccept ) + { + tools::Rectangle aEditCursor; + if ( pDragAndDropInfo->bOutlinerMode ) + { + tools::Long nDDYPos(0); + if ( pDragAndDropInfo->nOutlinerDropDest < pEditEngine->GetEditDoc().Count() ) + { + ParaPortion* pPPortion = pEditEngine->GetParaPortions().SafeGetObject( pDragAndDropInfo->nOutlinerDropDest ); + if (pPPortion) + nDDYPos = pEditEngine->GetParaPortions().GetYOffset( pPPortion ); + } + else + { + nDDYPos = pEditEngine->pImpEditEngine->GetTextHeight(); + } + Point aStartPos( 0, nDDYPos ); + aStartPos = GetWindowPos( aStartPos ); + Point aEndPos( GetOutputArea().GetWidth(), nDDYPos ); + aEndPos = GetWindowPos( aEndPos ); + aEditCursor = rOutDev.LogicToPixel( tools::Rectangle( aStartPos, aEndPos ) ); + if ( !pEditEngine->IsEffectivelyVertical() ) + { + aEditCursor.AdjustTop( -1 ); + aEditCursor.AdjustBottom( 1 ); + } + else + { + if( IsTopToBottom() ) + { + aEditCursor.AdjustLeft( -1 ); + aEditCursor.AdjustRight( 1 ); + } + else + { + aEditCursor.AdjustLeft( 1 ); + aEditCursor.AdjustRight( -1 ); + } + } + aEditCursor = rOutDev.PixelToLogic( aEditCursor ); + } + else + { + aEditCursor = pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM ); + Point aTopLeft( GetWindowPos( aEditCursor.TopLeft() ) ); + aEditCursor.SetPos( aTopLeft ); + aEditCursor.SetRight( aEditCursor.Left() + pDragAndDropInfo->nCursorWidth ); + aEditCursor = rOutDev.LogicToPixel( aEditCursor ); + aEditCursor = rOutDev.PixelToLogic( aEditCursor ); + } + + bool bCursorChanged = !pDragAndDropInfo->bVisCursor || ( pDragAndDropInfo->aCurCursor != aEditCursor ); + if ( bCursorChanged ) + { + HideDDCursor(); + ShowDDCursor(aEditCursor ); + } + pDragAndDropInfo->bDragAccepted = true; + rDTDE.Context->acceptDrag( rDTDE.DropAction ); + } + } + } + + if ( !bAccept ) + { + HideDDCursor(); + if (pDragAndDropInfo) + pDragAndDropInfo->bDragAccepted = false; + rDTDE.Context->rejectDrag(); + } +} + +void ImpEditView::AddDragAndDropListeners() +{ + if (bActiveDragAndDropListener) + return; + + css::uno::Reference<css::datatransfer::dnd::XDropTarget> xDropTarget; + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + xDropTarget = pCallbacks->GetDropTarget(); + else if (auto xWindow = GetWindow()) + xDropTarget = xWindow->GetDropTarget(); + + if (!xDropTarget.is()) + return; + + mxDnDListener = new vcl::unohelper::DragAndDropWrapper(this); + + css::uno::Reference<css::datatransfer::dnd::XDragGestureRecognizer> xDragGestureRecognizer(xDropTarget, uno::UNO_QUERY); + if (xDragGestureRecognizer.is()) + { + uno::Reference<datatransfer::dnd::XDragGestureListener> xDGL(mxDnDListener, uno::UNO_QUERY); + xDragGestureRecognizer->addDragGestureListener(xDGL); + } + + uno::Reference<datatransfer::dnd::XDropTargetListener> xDTL(mxDnDListener, uno::UNO_QUERY); + xDropTarget->addDropTargetListener(xDTL); + xDropTarget->setActive(true); + xDropTarget->setDefaultActions(datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE); + + bActiveDragAndDropListener = true; +} + +void ImpEditView::RemoveDragAndDropListeners() +{ + if (!bActiveDragAndDropListener) + return; + + css::uno::Reference<css::datatransfer::dnd::XDropTarget> xDropTarget; + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + xDropTarget = pCallbacks->GetDropTarget(); + else if (auto xWindow = GetWindow()) + xDropTarget = xWindow->GetDropTarget(); + + if (xDropTarget.is()) + { + css::uno::Reference<css::datatransfer::dnd::XDragGestureRecognizer> xDragGestureRecognizer(xDropTarget, uno::UNO_QUERY); + if (xDragGestureRecognizer.is()) + { + uno::Reference<datatransfer::dnd::XDragGestureListener> xDGL(mxDnDListener, uno::UNO_QUERY); + xDragGestureRecognizer->removeDragGestureListener(xDGL); + } + + uno::Reference<datatransfer::dnd::XDropTargetListener> xDTL(mxDnDListener, uno::UNO_QUERY); + xDropTarget->removeDropTargetListener(xDTL); + } + + if ( mxDnDListener.is() ) + { + mxDnDListener->disposing( lang::EventObject() ); // #95154# Empty Source means it's the Client + mxDnDListener.clear(); + } + + bActiveDragAndDropListener = false; +} + +void ImpEditView::InitLOKSpecialPositioning(MapUnit eUnit, + const tools::Rectangle& rOutputArea, + const Point& rVisDocStartPos) +{ + if (!mpLOKSpecialPositioning) + mpLOKSpecialPositioning.reset(new LOKSpecialPositioning(*this, eUnit, rOutputArea, rVisDocStartPos)); + else + mpLOKSpecialPositioning->ReInit(eUnit, rOutputArea, rVisDocStartPos); +} + +void ImpEditView::SetLOKSpecialOutputArea(const tools::Rectangle& rOutputArea) +{ + assert(mpLOKSpecialPositioning); + mpLOKSpecialPositioning->SetOutputArea(rOutputArea); +} + +const tools::Rectangle & ImpEditView::GetLOKSpecialOutputArea() const +{ + assert(mpLOKSpecialPositioning); + return mpLOKSpecialPositioning->GetOutputArea(); +} + +void ImpEditView::SetLOKSpecialVisArea(const tools::Rectangle& rVisArea) +{ + assert(mpLOKSpecialPositioning); + mpLOKSpecialPositioning->SetVisDocStartPos(rVisArea.TopLeft()); +} + +tools::Rectangle ImpEditView::GetLOKSpecialVisArea() const +{ + assert(mpLOKSpecialPositioning); + return mpLOKSpecialPositioning->GetVisDocArea(); +} + +bool ImpEditView::HasLOKSpecialPositioning() const +{ + return bool(mpLOKSpecialPositioning); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit.hxx b/editeng/source/editeng/impedit.hxx new file mode 100644 index 000000000..d11ee008c --- /dev/null +++ b/editeng/source/editeng/impedit.hxx @@ -0,0 +1,1356 @@ +/* -*- 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 <eerdll2.hxx> +#include <editdoc.hxx> +#include "editsel.hxx" +#include "editundo.hxx" +#include "editstt2.hxx" +#include <editeng/editdata.hxx> +#include <editeng/SpellPortions.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <svtools/colorcfg.hxx> +#include <editeng/outliner.hxx> +#include <vcl/virdev.hxx> +#include <vcl/cursor.hxx> +#include <vcl/vclptr.hxx> +#include <tools/fract.hxx> +#include <vcl/idle.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/ptrstyle.hxx> + +#include <vcl/dndhelp.hxx> +#include <svl/ondemand.hxx> +#include <svl/languageoptions.hxx> +#include <com/sun/star/linguistic2/XSpellAlternatives.hpp> +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> +#include <com/sun/star/linguistic2/XHyphenator.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XExtendedInputSequenceChecker.hpp> +#include <com/sun/star/uno/Sequence.hxx> + +#include <i18nlangtag/lang.h> +#include <o3tl/deleter.hxx> +#include <o3tl/typed_flags_set.hxx> + +#include <functional> +#include <optional> +#include <memory> +#include <tuple> +#include <string_view> +#include <vector> + +class EditView; +class EditEngine; +class OutlinerSearchable; + +class SvxSearchItem; +class SvxLRSpaceItem; +class TextRanger; +class SvKeyValueIterator; +class SvxForbiddenCharactersTable; +class SvtCTLOptions; +namespace vcl { class Window; } +class SvxNumberFormat; +namespace com::sun::star::datatransfer::clipboard { + class XClipboard; +} + +namespace editeng { + struct MisspellRanges; +} + +#define DEL_LEFT 1 +#define DEL_RIGHT 2 +#define TRAVEL_X_DONTKNOW 0xFFFFFFFF +#define CURSOR_BIDILEVEL_DONTKNOW 0xFFFF +#define MAXCHARSINPARA 0x3FFF-CHARPOSGROW // Max 16K, because WYSIWYG array +#define LINE_SEP '\x0A' + +#define ATTRSPECIAL_WHOLEWORD 1 +#define ATTRSPECIAL_EDGE 2 + +enum class GetCursorFlags { + NONE = 0x0000, + TextOnly = 0x0001, + StartOfLine = 0x0002, + EndOfLine = 0x0004, + PreferPortionStart = 0x0008, +}; +namespace o3tl { + template<> struct typed_flags<GetCursorFlags> : is_typed_flags<GetCursorFlags, 0x0f> {}; +} + + +struct DragAndDropInfo +{ + tools::Rectangle aCurCursor; + tools::Rectangle aCurSavedCursor; + sal_uInt16 nSensibleRange; + sal_uInt16 nCursorWidth; + ESelection aBeginDragSel; + EditPaM aDropDest; + sal_Int32 nOutlinerDropDest; + ESelection aDropSel; + VclPtr<VirtualDevice> pBackground; + const SvxFieldItem* pField; + bool bVisCursor : 1; + bool bDroppedInMe : 1; + bool bStarterOfDD : 1; + bool bHasValidData : 1; + bool bUndoAction : 1; + bool bOutlinerMode : 1; + bool bDragAccepted : 1; + + DragAndDropInfo() + : nSensibleRange(0), nCursorWidth(0), nOutlinerDropDest(0), pBackground(nullptr), + pField(nullptr), bVisCursor(false), bDroppedInMe(false), bStarterOfDD(false), + bHasValidData(false), bUndoAction(false), bOutlinerMode(false), bDragAccepted(false) + { + } + ~DragAndDropInfo() + { + pBackground.disposeAndClear(); + } +}; + +struct ImplIMEInfos +{ + OUString aOldTextAfterStartPos; + std::unique_ptr<ExtTextInputAttr[]> pAttribs; + EditPaM aPos; + sal_Int32 nLen; + bool bWasCursorOverwrite; + + ImplIMEInfos( const EditPaM& rPos, OUString aOldTextAfterStartPos ); + ~ImplIMEInfos(); + + void CopyAttribs( const ExtTextInputAttr* pA, sal_uInt16 nL ); + void DestroyAttribs(); +}; + +// #i18881# to be able to identify the positions of changed words +// the positions of each portion need to be saved +typedef std::vector<EditSelection> SpellContentSelections; + +struct SpellInfo +{ + EditPaM aCurSentenceStart; + svx::SpellPortions aLastSpellPortions; + SpellContentSelections aLastSpellContentSelections; + EESpellState eState; + EPaM aSpellStart; + EPaM aSpellTo; + bool bSpellToEnd; + bool bMultipleDoc; + SpellInfo() : eState(EESpellState::Ok), bSpellToEnd(true), bMultipleDoc(false) + { } +}; + +// used for text conversion +struct ConvInfo +{ + EPaM aConvStart; + EPaM aConvTo; + EPaM aConvContinue; // position to start search for next text portion (word) with + bool bConvToEnd; + bool bMultipleDoc; + + ConvInfo() : bConvToEnd(true), bMultipleDoc(false) {} +}; + +struct FormatterFontMetric +{ + sal_uInt16 nMaxAscent; + sal_uInt16 nMaxDescent; + + FormatterFontMetric() : nMaxAscent(0), nMaxDescent(0) { /* nMinLeading = 0xFFFF; */ } + sal_uInt16 GetHeight() const { return nMaxAscent+nMaxDescent; } +}; + +class IdleFormattter : public Idle +{ +private: + EditView* pView; + int nRestarts; + +public: + IdleFormattter(); + virtual ~IdleFormattter() override; + + void DoIdleFormat( EditView* pV ); + void ForceTimeout(); + void ResetRestarts() { nRestarts = 0; } + EditView* GetView() { return pView; } +}; + +class ImpEditView; +/// This is meant just for Calc, where all positions in logical units (twips for LOK) are computed by +/// doing independent pixel-alignment for each cell's size. LOKSpecialPositioning stores +/// both 'output-area' and 'visible-doc-position' in pure logical unit (twips for LOK). +/// This allows the cursor/selection messages to be in regular(print) twips unit like in Writer. +class LOKSpecialPositioning +{ +public: + LOKSpecialPositioning(const ImpEditView& rImpEditView, MapUnit eUnit, const tools::Rectangle& rOutputArea, + const Point& rVisDocStartPos); + + void ReInit(MapUnit eUnit, const tools::Rectangle& rOutputArea, const Point& rVisDocStartPos); + + void SetOutputArea(const tools::Rectangle& rOutputArea); + const tools::Rectangle& GetOutputArea() const; + void SetVisDocStartPos(const Point& rVisDocStartPos); + + bool IsVertical() const; + bool IsTopToBottom() const; + + tools::Long GetVisDocLeft() const { return maVisDocStartPos.X(); } + tools::Long GetVisDocTop() const { return maVisDocStartPos.Y(); } + tools::Long GetVisDocRight() const { return maVisDocStartPos.X() + (!IsVertical() ? maOutArea.GetWidth() : maOutArea.GetHeight()); } + tools::Long GetVisDocBottom() const { return maVisDocStartPos.Y() + (!IsVertical() ? maOutArea.GetHeight() : maOutArea.GetWidth()); } + tools::Rectangle GetVisDocArea() const; + + Point GetWindowPos(const Point& rDocPos, MapUnit eDocPosUnit) const; + tools::Rectangle GetWindowPos(const tools::Rectangle& rDocRect, MapUnit eDocRectUnit) const; + + Point GetRefPoint() const; + +private: + Point convertUnit(const Point& rPos, MapUnit ePosUnit) const; + tools::Rectangle convertUnit(const tools::Rectangle& rRect, MapUnit eRectUnit) const; + + const ImpEditView& mrImpEditView; + tools::Rectangle maOutArea; + Point maVisDocStartPos; + MapUnit meUnit; +}; + + + +class ImpEditView : public vcl::unohelper::DragAndDropClient +{ + friend class EditView; + friend class EditEngine; + friend class ImpEditEngine; + using vcl::unohelper::DragAndDropClient::dragEnter; + using vcl::unohelper::DragAndDropClient::dragExit; + using vcl::unohelper::DragAndDropClient::dragOver; + +private: + EditView* pEditView; + std::unique_ptr<vcl::Cursor, o3tl::default_delete<vcl::Cursor>> pCursor; + std::optional<Color> mxBackgroundColor; + /// Containing view shell, if any. + OutlinerViewShell* mpViewShell; + /// Another shell, just listening to our state, if any. + OutlinerViewShell* mpOtherShell; + EditEngine* pEditEngine; + VclPtr<vcl::Window> pOutWin; + EditView::OutWindowSet aOutWindowSet; + std::optional<PointerStyle> mxPointer; + std::unique_ptr<DragAndDropInfo> pDragAndDropInfo; + + css::uno::Reference< css::datatransfer::dnd::XDragSourceListener > mxDnDListener; + + + tools::Long nInvMore; + EVControlBits nControl; + sal_uInt32 nTravelXPos; + GetCursorFlags nExtraCursorFlags; + sal_uInt16 nCursorBidiLevel; + sal_uInt16 nScrollDiffX; + bool bReadOnly; + bool bClickedInSelection; + bool bActiveDragAndDropListener; + + Point aAnchorPoint; + tools::Rectangle aOutArea; + Point aVisDocStartPos; + EESelectionMode eSelectionMode; + EditSelection aEditSelection; + EEAnchorMode eAnchorMode; + + /// mechanism to change from the classic refresh mode that simply + // invalidates the area where text was changed. When set, the invalidate + // and the direct repaint of the Window-plugged EditView will be suppressed. + // Instead, a consumer that has registered using an EditViewCallbacks + // incarnation has to handle that. Used e.g. to represent the edited text + // in Draw/Impress in an OverlayObject which avoids evtl. expensive full + // repaints of the EditView(s) + EditViewCallbacks* mpEditViewCallbacks; + std::unique_ptr<LOKSpecialPositioning> mpLOKSpecialPositioning; + bool mbBroadcastLOKViewCursor:1; + bool mbSuppressLOKMessages:1; + bool mbNegativeX:1; + + EditViewCallbacks* getEditViewCallbacks() const + { + return mpEditViewCallbacks; + } + + void lokSelectionCallback(const std::optional<tools::PolyPolygon> &pPolyPoly, bool bStartHandleVisible, bool bEndHandleVisible); + + void setEditViewCallbacks(EditViewCallbacks* pEditViewCallbacks) + { + mpEditViewCallbacks = pEditViewCallbacks; + } + + void InvalidateAtWindow(const tools::Rectangle& rRect); + + css::uno::Reference<css::datatransfer::clipboard::XClipboard> GetClipboard() const; + + void SetBroadcastLOKViewCursor(bool bSet) + { + mbBroadcastLOKViewCursor = bSet; + } + +protected: + + // DragAndDropClient + void dragGestureRecognized(const css::datatransfer::dnd::DragGestureEvent& dge) override; + void dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& dsde ) override; + void drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde) override; + void dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& dtdee ) override; + void dragExit( const css::datatransfer::dnd::DropTargetEvent& dte ) override; + void dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde) override; + + void ShowDDCursor( const tools::Rectangle& rRect ); + void HideDDCursor(); + + void ImplDrawHighlightRect(OutputDevice& rTarget, const Point& rDocPosTopLeft, const Point& rDocPosBottomRight, tools::PolyPolygon* pPolyPoly, bool bLOKCalcRTL); + tools::Rectangle ImplGetEditCursor(EditPaM& aPaM, GetCursorFlags nShowCursorFlags, + sal_Int32& nTextPortionStart, const ParaPortion* pParaPortion) const; + +public: + ImpEditView( EditView* pView, EditEngine* pEng, vcl::Window* pWindow ); + virtual ~ImpEditView() override; + + EditView* GetEditViewPtr() { return pEditView; } + + sal_uInt16 GetScrollDiffX() const { return nScrollDiffX; } + void SetScrollDiffX( sal_uInt16 n ) { nScrollDiffX = n; } + + sal_uInt16 GetCursorBidiLevel() const { return nCursorBidiLevel; } + void SetCursorBidiLevel( sal_uInt16 n ) { nCursorBidiLevel = n; } + + Point GetDocPos( const Point& rWindowPos ) const; + Point GetWindowPos( const Point& rDocPos ) const; + tools::Rectangle GetWindowPos( const tools::Rectangle& rDocPos ) const; + + void SetOutputArea( const tools::Rectangle& rRect ); + void ResetOutputArea( const tools::Rectangle& rRect ); + const tools::Rectangle& GetOutputArea() const { return aOutArea; } + + bool IsVertical() const; + bool IsTopToBottom() const; + + bool PostKeyEvent( const KeyEvent& rKeyEvent, vcl::Window const * pFrameWin ); + + bool MouseButtonUp( const MouseEvent& rMouseEvent ); + bool MouseButtonDown( const MouseEvent& rMouseEvent ); + void ReleaseMouse(); + bool MouseMove( const MouseEvent& rMouseEvent ); + bool Command(const CommandEvent& rCEvt); + + void CutCopy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bCut ); + void Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bUseSpecial = false ); + + void SetVisDocStartPos( const Point& rPos ) { aVisDocStartPos = rPos; } + + tools::Long GetVisDocLeft() const { return aVisDocStartPos.X(); } + tools::Long GetVisDocTop() const { return aVisDocStartPos.Y(); } + tools::Long GetVisDocRight() const { return aVisDocStartPos.X() + ( !IsVertical() ? aOutArea.GetWidth() : aOutArea.GetHeight() ); } + tools::Long GetVisDocBottom() const { return aVisDocStartPos.Y() + ( !IsVertical() ? aOutArea.GetHeight() : aOutArea.GetWidth() ); } + tools::Rectangle GetVisDocArea() const; + + const EditSelection& GetEditSelection() const { return aEditSelection; } + void SetEditSelection( const EditSelection& rEditSelection ); + bool HasSelection() const { return aEditSelection.HasRange(); } + + void SelectionChanged(); + void DrawSelectionXOR() { DrawSelectionXOR( aEditSelection ); } + void DrawSelectionXOR( EditSelection, vcl::Region* pRegion = nullptr, OutputDevice* pTargetDevice = nullptr ); + void GetSelectionRectangles(EditSelection aTmpSel, std::vector<tools::Rectangle>& rLogicRects); + + void ScrollStateChange(); + + OutputDevice& GetOutputDevice() const; + weld::Widget* GetPopupParent(tools::Rectangle& rRect) const; + vcl::Window* GetWindow() const { return pOutWin; } + + void SetSelectionMode( EESelectionMode eMode ); + + inline PointerStyle GetPointer(); + + inline vcl::Cursor* GetCursor(); + + void AddDragAndDropListeners(); + void RemoveDragAndDropListeners(); + + bool IsBulletArea( const Point& rPos, sal_Int32* pPara ); + +// For the Selection Engine... + void CreateAnchor(); + void DeselectAll(); + bool SetCursorAtPoint( const Point& rPointPixel ); + bool IsSelectionAtPoint( const Point& rPosPixel ); + bool IsInSelection( const EditPaM& rPaM ); + + + void SetAnchorMode( EEAnchorMode eMode ); + EEAnchorMode GetAnchorMode() const { return eAnchorMode; } + void CalcAnchorPoint(); + void RecalcOutputArea(); + + tools::Rectangle GetEditCursor() const; + + void ShowCursor( bool bGotoCursor, bool bForceVisCursor ); + Pair Scroll( tools::Long ndX, tools::Long ndY, ScrollRangeCheck nRangeCheck = ScrollRangeCheck::NoNegative ); + + void SetInsertMode( bool bInsert ); + bool IsInsertMode() const { return !( nControl & EVControlBits::OVERWRITE ); } + + bool IsPasteEnabled() const { return bool( nControl & EVControlBits::ENABLEPASTE ); } + + bool DoSingleLinePaste() const { return bool( nControl & EVControlBits::SINGLELINEPASTE ); } + bool DoAutoScroll() const { return bool( nControl & EVControlBits::AUTOSCROLL ); } + bool DoAutoSize() const { return bool( nControl & EVControlBits::AUTOSIZE ); } + bool DoAutoWidth() const { return bool( nControl & EVControlBits::AUTOSIZEX); } + bool DoAutoHeight() const { return bool( nControl & EVControlBits::AUTOSIZEY); } + bool DoInvalidateMore() const { return bool( nControl & EVControlBits::INVONEMORE ); } + + void SetBackgroundColor( const Color& rColor ); + const Color& GetBackgroundColor() const; + + /// Informs this edit view about which view shell contains it. + void RegisterViewShell(OutlinerViewShell* pViewShell); + const OutlinerViewShell* GetViewShell() const; + /// Informs this edit view about which other shell listens to it. + void RegisterOtherShell(OutlinerViewShell* pViewShell); + + bool IsWrongSpelledWord( const EditPaM& rPaM, bool bMarkIfWrong ); + OUString SpellIgnoreWord(); + + const SvxFieldItem* GetField( const Point& rPos, sal_Int32* pPara, sal_Int32* pPos ) const; + void DeleteSelected(); + + // If possible invalidate more than OutputArea, for the DrawingEngine text frame + void SetInvalidateMore( sal_uInt16 nPixel ) { nInvMore = nPixel; } + sal_uInt16 GetInvalidateMore() const { return static_cast<sal_uInt16>(nInvMore); } + + void InitLOKSpecialPositioning(MapUnit eUnit, const tools::Rectangle& rOutputArea, + const Point& rVisDocStartPos); + void SetLOKSpecialOutputArea(const tools::Rectangle& rOutputArea); + const tools::Rectangle & GetLOKSpecialOutputArea() const; + void SetLOKSpecialVisArea(const tools::Rectangle& rVisArea); + tools::Rectangle GetLOKSpecialVisArea() const; + bool HasLOKSpecialPositioning() const; + + void SuppressLOKMessages(bool bSet) { mbSuppressLOKMessages = bSet; } + bool IsSuppressLOKMessages() const { return mbSuppressLOKMessages; } + + void SetNegativeX(bool bSet) { mbNegativeX = bSet; } + bool IsNegativeX() const { return mbNegativeX; } +}; + + +// ImpEditEngine + + +class ImpEditEngine : public SfxListener +{ + friend class EditEngine; + + typedef EditEngine::ViewsType ViewsType; + +private: + std::shared_ptr<editeng::SharedVclResources> pSharedVCL; + + + // Data ... + + + // Document Specific data ... + ParaPortionList aParaPortionList; // Formatting + Size aPaperSize; // Layout + Size aMinAutoPaperSize; // Layout ? + Size aMaxAutoPaperSize; // Layout ? + tools::Long mnMinColumnWrapHeight = 0; // Corresponds to graphic object height + EditDoc aEditDoc; // Document content + + // Engine Specific data ... + EditEngine* pEditEngine; + ViewsType aEditViews; + EditView* pActiveView; + std::unique_ptr<TextRanger> pTextRanger; + + SfxStyleSheetPool* pStylePool; + SfxItemPool* pTextObjectPool; + + VclPtr< VirtualDevice> pVirtDev; + VclPtr< OutputDevice > pRefDev; + VclPtr<VirtualDevice> mpOwnDev; + + svtools::ColorConfig maColorConfig; + mutable std::unique_ptr<SvtCTLOptions> pCTLOptions; + + mutable std::unique_ptr<SfxItemSet> pEmptyItemSet; + EditUndoManager* pUndoManager; + std::unique_ptr<ESelection> pUndoMarkSelection; + + std::unique_ptr<ImplIMEInfos> mpIMEInfos; + + OUString aWordDelimiters; + + EditSelFunctionSet aSelFuncSet; + EditSelectionEngine aSelEngine; + + Color maBackgroundColor; + + sal_uInt16 nStretchX; + sal_uInt16 nStretchY; + + CharCompressType nAsianCompressionMode; + + EEHorizontalTextDirection eDefaultHorizontalTextDirection; + + sal_Int32 nBigTextObjectStart; + css::uno::Reference< css::linguistic2::XSpellChecker1 > xSpeller; + css::uno::Reference< css::linguistic2::XHyphenator > xHyphenator; + std::unique_ptr<SpellInfo> pSpellInfo; + mutable css::uno::Reference < css::i18n::XBreakIterator > xBI; + mutable css::uno::Reference < css::i18n::XExtendedInputSequenceChecker > xISC; + + std::unique_ptr<ConvInfo> pConvInfo; + + OUString aAutoCompleteText; + + InternalEditStatus aStatus; + + LanguageType eDefLanguage; + + OnDemandLocaleDataWrapper xLocaleDataWrapper; + OnDemandTransliterationWrapper xTransliterationWrapper; + + // For Formatting / Update... + std::vector<std::unique_ptr<DeletedNodeInfo> > aDeletedNodes; + tools::Rectangle aInvalidRect; + tools::Long nCurTextHeight; + tools::Long nCurTextHeightNTP; // without trailing empty paragraphs + sal_uInt16 nOnePixelInRef; + + IdleFormattter aIdleFormatter; + + Timer aOnlineSpellTimer; + + // For Chaining + sal_Int32 mnOverflowingPara = -1; + sal_Int32 mnOverflowingLine = -1; + bool mbNeedsChainingHandling = false; + + sal_Int16 mnColumns = 1; + sal_Int32 mnColumnSpacing = 0; + + // If it is detected at one point that the StatusHdl has to be called, but + // this should not happen immediately (critical section): + Timer aStatusTimer; + Size aLOKSpecialPaperSize; + + Link<EditStatus&,void> aStatusHdlLink; + Link<EENotify&,void> aNotifyHdl; + Link<HtmlImportInfo&,void> aHtmlImportHdl; + Link<RtfImportInfo&,void> aRtfImportHdl; + Link<MoveParagraphsInfo&,void> aBeginMovingParagraphsHdl; + Link<MoveParagraphsInfo&,void> aEndMovingParagraphsHdl; + Link<PasteOrDropInfos&,void> aBeginPasteOrDropHdl; + Link<PasteOrDropInfos&,void> aEndPasteOrDropHdl; + Link<LinkParamNone*,void> aModifyHdl; + Link<EditView*,void> maBeginDropHdl; + Link<EditView*,void> maEndDropHdl; + + bool bKernAsianPunctuation:1; + bool bAddExtLeading:1; + bool bIsFormatting:1; + bool bFormatted:1; + bool bInSelection:1; + bool bIsInUndo:1; + bool bUpdateLayout:1; + bool bUndoEnabled:1; + bool bDowning:1; + bool bUseAutoColor:1; + bool bForceAutoColor:1; + bool bCallParaInsertedOrDeleted:1; + bool bFirstWordCapitalization:1; // specifies if auto-correction should capitalize the first word or not + bool mbLastTryMerge:1; + bool mbReplaceLeadingSingleQuotationMark:1; + bool mbSkipOutsideFormat:1; + + bool mbNbspRunNext; // can't be a bitfield as it is passed as bool& + + // Methods... + + + void CursorMoved( const ContentNode* pPrevNode ); + void ParaAttribsChanged( ContentNode const * pNode, bool bIgnoreUndoCheck = false ); + void TextModified(); + void CalcHeight( ParaPortion* pPortion ); + + void InsertUndo( std::unique_ptr<EditUndo> pUndo, bool bTryMerge = false ); + void ResetUndoManager(); + bool HasUndoManager() const { return pUndoManager != nullptr; } + + std::unique_ptr<EditUndoSetAttribs> CreateAttribUndo( EditSelection aSel, const SfxItemSet& rSet ); + + std::unique_ptr<EditTextObject> GetEmptyTextObject(); + + std::tuple<const ParaPortion*, const EditLine*, tools::Long> GetPortionAndLine(Point aDocPos); + EditPaM GetPaM( Point aDocPos, bool bSmart = true ); + bool IsTextPos(const Point& rDocPos, sal_uInt16 nBorder); + tools::Long GetXPos(const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart = false) const; + tools::Long GetPortionXOffset(const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nTextPortion) const; + sal_Int32 GetChar(const ParaPortion* pParaPortion, const EditLine* pLine, tools::Long nX, bool bSmart = true); + Range GetLineXPosStartEnd( const ParaPortion* pParaPortion, const EditLine* pLine ) const; + + void ParaAttribsToCharAttribs( ContentNode* pNode ); + void GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const; + + std::unique_ptr<EditTextObject> + CreateTextObject(EditSelection aSelection, SfxItemPool*, bool bAllowBigObjects = false, sal_Int32 nBigObjStart = 0); + EditSelection InsertTextObject( const EditTextObject&, EditPaM aPaM ); + EditSelection PasteText( css::uno::Reference< css::datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial ); + + void CheckPageOverflow(); + + void Clear(); + EditPaM RemoveText(); + bool CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ); + void CreateAndInsertEmptyLine( ParaPortion* pParaPortion ); + bool FinishCreateLines( ParaPortion* pParaPortion ); + void CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rStartPos /*, sal_Bool bCreateBlockPortions */ ); + void RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars ); + sal_Int32 SplitTextPortion( ParaPortion* pParaPortion, sal_Int32 nPos, EditLine* pCurLine = nullptr ); + void SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont, OutputDevice* pOut = nullptr ); + void RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics, SvxFont& rFont ); + void CheckAutoPageSize(); + + void ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, TextPortion const * pPortion, sal_Int32 nPortionStart, tools::Long nRemainingWidth, bool bCanHyphenate ); + void ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, tools::Long nRemainingSpace ); + EditPaM ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward = false ); + EditPaM ImpDeleteSelection(const EditSelection& rCurSel); + EditPaM ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs = true ); + EditPaM ImpInsertParaBreak( const EditSelection& rEditSelection ); + EditPaM ImpInsertText(const EditSelection& aCurEditSelection, const OUString& rStr); + EditPaM ImpInsertFeature(const EditSelection& rCurSel, const SfxPoolItem& rItem); + void ImpRemoveChars( const EditPaM& rPaM, sal_Int32 nChars ); + void ImpRemoveParagraph( sal_Int32 nPara ); + EditSelection ImpMoveParagraphs( Range aParagraphs, sal_Int32 nNewPos ); + + EditPaM ImpFastInsertText( EditPaM aPaM, const OUString& rStr ); + EditPaM ImpFastInsertParagraph( sal_Int32 nPara ); + + bool ImpCheckRefMapMode(); + + bool ImplHasText() const; + + void ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, std::vector<sal_Int32>& rArray ); + + void InsertContent( ContentNode* pNode, sal_Int32 nPos ); + EditPaM SplitContent( sal_Int32 nNode, sal_Int32 nSepPos ); + EditPaM ConnectContents( sal_Int32 nLeftNode, bool bBackward ); + + void ShowParagraph( sal_Int32 nParagraph, bool bShow ); + + EditPaM PageUp( const EditPaM& rPaM, EditView const * pView); + EditPaM PageDown( const EditPaM& rPaM, EditView const * pView); + EditPaM CursorUp( const EditPaM& rPaM, EditView const * pEditView ); + EditPaM CursorDown( const EditPaM& rPaM, EditView const * pEditView ); + EditPaM CursorLeft( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode = css::i18n::CharacterIteratorMode::SKIPCELL ); + EditPaM CursorRight( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode = css::i18n::CharacterIteratorMode::SKIPCELL ); + EditPaM CursorStartOfLine( const EditPaM& rPaM ); + EditPaM CursorEndOfLine( const EditPaM& rPaM ); + static EditPaM CursorStartOfParagraph( const EditPaM& rPaM ); + static EditPaM CursorEndOfParagraph( const EditPaM& rPaM ); + EditPaM CursorStartOfDoc(); + EditPaM CursorEndOfDoc(); + EditPaM WordLeft( const EditPaM& rPaM ); + EditPaM WordRight( const EditPaM& rPaM, sal_Int16 nWordType = css::i18n::WordType::ANYWORD_IGNOREWHITESPACES ); + EditPaM StartOfWord( const EditPaM& rPaM ); + EditPaM EndOfWord( const EditPaM& rPaM ); + EditSelection SelectWord( const EditSelection& rCurSelection, sal_Int16 nWordType = css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, bool bAcceptStartOfWord = true ); + EditSelection SelectSentence( const EditSelection& rCurSel ) const; + EditPaM CursorVisualLeftRight( EditView const * pEditView, const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode, bool bToLeft ); + EditPaM CursorVisualStartEnd( EditView const * pEditView, const EditPaM& rPaM, bool bStart ); + + + void InitScriptTypes( sal_Int32 nPara ); + sal_uInt16 GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEndPos = nullptr ) const; + SvtScriptType GetItemScriptType( const EditSelection& rSel ) const; + bool IsScriptChange( const EditPaM& rPaM ) const; + bool HasScriptType( sal_Int32 nPara, sal_uInt16 nType ) const; + + bool ImplCalcAsianCompression( ContentNode* pNode, TextPortion* pTextPortion, sal_Int32 nStartPos, + sal_Int32* pDXArray, sal_uInt16 n100thPercentFromMax, bool bManipulateDXArray ); + void ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* pParaPortion, tools::Long nRemainingWidth ); + + void ImplInitLayoutMode(OutputDevice& rOutDev, sal_Int32 nPara, sal_Int32 nIndex); + LanguageType ImplCalcDigitLang(LanguageType eCurLang) const; + void ImplInitDigitMode(OutputDevice& rOutDev, LanguageType eLang); + static OUString convertDigits(std::u16string_view rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang); + + EditPaM ReadText( SvStream& rInput, EditSelection aSel ); + EditPaM ReadRTF( SvStream& rInput, EditSelection aSel ); + EditPaM ReadXML( SvStream& rInput, EditSelection aSel ); + EditPaM ReadHTML( SvStream& rInput, const OUString& rBaseURL, EditSelection aSel, SvKeyValueIterator* pHTTPHeaderAttrs ); + ErrCode WriteText( SvStream& rOutput, EditSelection aSel ); + ErrCode WriteRTF( SvStream& rOutput, EditSelection aSel ); + void WriteXML(SvStream& rOutput, const EditSelection& rSel); + + void WriteItemAsRTF( const SfxPoolItem& rItem, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos, + std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList ); + bool WriteItemListAsRTF( ItemList& rLst, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos, + std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList ); + sal_Int32 LogicToTwips( sal_Int32 n ); + + inline short GetXValue( short nXValue ) const; + inline tools::Long GetXValue( tools::Long nXValue ) const; + + inline short GetYValue( short nYValue ) const; + inline sal_uInt16 GetYValue( sal_uInt16 nYValue ) const; + + ContentNode* GetPrevVisNode( ContentNode const * pCurNode ); + ContentNode* GetNextVisNode( ContentNode const * pCurNode ); + + const ParaPortion* GetPrevVisPortion( const ParaPortion* pCurPortion ) const; + const ParaPortion* GetNextVisPortion( const ParaPortion* pCurPortion ) const; + + void SetBackgroundColor( const Color& rColor ) { maBackgroundColor = rColor; } + const Color& GetBackgroundColor() const { return maBackgroundColor; } + + tools::Long CalcVertLineSpacing(Point& rStartPos) const; + + Color GetAutoColor() const; + void EnableAutoColor( bool b ) { bUseAutoColor = b; } + bool IsAutoColorEnabled() const { return bUseAutoColor; } + void ForceAutoColor( bool b ) { bForceAutoColor = b; } + bool IsForceAutoColor() const { return bForceAutoColor; } + + inline VirtualDevice* GetVirtualDevice( const MapMode& rMapMode, DrawModeFlags nDrawMode ); + void EraseVirtualDevice() { pVirtDev.disposeAndClear(); } + + DECL_LINK( StatusTimerHdl, Timer *, void); + DECL_LINK( IdleFormatHdl, Timer *, void); + DECL_LINK( OnlineSpellHdl, Timer *, void); + DECL_LINK( DocModified, LinkParamNone*, void ); + + void CheckIdleFormatter(); + + inline const ParaPortion* FindParaPortion( const ContentNode* pNode ) const; + inline ParaPortion* FindParaPortion( ContentNode const * pNode ); + + css::uno::Reference< css::datatransfer::XTransferable > CreateTransferable( const EditSelection& rSelection ); + + void SetValidPaperSize( const Size& rSz ); + + css::uno::Reference < css::i18n::XBreakIterator > const & ImplGetBreakIterator() const; + css::uno::Reference < css::i18n::XExtendedInputSequenceChecker > const & ImplGetInputSequenceChecker() const; + + void ImplUpdateOverflowingParaNum(tools::Long); + void ImplUpdateOverflowingLineNum(tools::Long, sal_uInt32, tools::Long); + + void CreateSpellInfo( bool bMultipleDocs ); + /// Obtains a view shell ID from the active EditView. + ViewShellId CreateViewShellId(); + + ImpEditEngine(EditEngine* pEditEngine, SfxItemPool* pPool); + void InitDoc(bool bKeepParaAttribs); + EditDoc& GetEditDoc() { return aEditDoc; } + const EditDoc& GetEditDoc() const { return aEditDoc; } + + const ParaPortionList& GetParaPortions() const { return aParaPortionList; } + ParaPortionList& GetParaPortions() { return aParaPortionList; } + + tools::Long Calc1ColumnTextHeight(tools::Long* pHeightNTP); + + void IdleFormatAndLayout(EditView* pCurView) { aIdleFormatter.DoIdleFormat(pCurView); } + +protected: + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; + +public: + virtual ~ImpEditEngine() override; + ImpEditEngine(const ImpEditEngine&) = delete; + ImpEditEngine& operator=(const ImpEditEngine&) = delete; + + inline EditUndoManager& GetUndoManager(); + inline SfxUndoManager* SetUndoManager(SfxUndoManager* pNew); + + // @return the previous bUpdateLayout state + bool SetUpdateLayout( bool bUpdate, EditView* pCurView = nullptr, bool bForceUpdate = false ); + bool IsUpdateLayout() const { return bUpdateLayout; } + + ViewsType& GetEditViews() { return aEditViews; } + const ViewsType& GetEditViews() const { return aEditViews; } + + const Size& GetPaperSize() const { return aPaperSize; } + void SetPaperSize( const Size& rSz ) { aPaperSize = rSz; } + + void SetVertical( bool bVertical); + bool IsEffectivelyVertical() const { return GetEditDoc().IsEffectivelyVertical(); } + bool IsTopToBottom() const { return GetEditDoc().IsTopToBottom(); } + bool GetVertical() const { return GetEditDoc().GetVertical(); } + void SetRotation( TextRotation nRotation); + TextRotation GetRotation() const { return GetEditDoc().GetRotation(); } + + void SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing); + + bool IsPageOverflow( ) const; + + void SetFixedCellHeight( bool bUseFixedCellHeight ); + bool IsFixedCellHeight() const { return GetEditDoc().IsFixedCellHeight(); } + + void SetDefaultHorizontalTextDirection( EEHorizontalTextDirection eHTextDir ) { eDefaultHorizontalTextDirection = eHTextDir; } + EEHorizontalTextDirection GetDefaultHorizontalTextDirection() const { return eDefaultHorizontalTextDirection; } + + + void InitWritingDirections( sal_Int32 nPara ); + bool IsRightToLeft( sal_Int32 nPara ) const; + sal_uInt8 GetRightToLeft( sal_Int32 nPara, sal_Int32 nChar, sal_Int32* pStart = nullptr, sal_Int32* pEnd = nullptr ); + bool HasDifferentRTLLevels( const ContentNode* pNode ); + + void SetTextRanger( std::unique_ptr<TextRanger> pRanger ); + TextRanger* GetTextRanger() const { return pTextRanger.get(); } + + const Size& GetMinAutoPaperSize() const { return aMinAutoPaperSize; } + void SetMinAutoPaperSize( const Size& rSz ) { aMinAutoPaperSize = rSz; } + + const Size& GetMaxAutoPaperSize() const { return aMaxAutoPaperSize; } + void SetMaxAutoPaperSize( const Size& rSz ) { aMaxAutoPaperSize = rSz; } + + void SetMinColumnWrapHeight(tools::Long nVal) { mnMinColumnWrapHeight = nVal; } + + void FormatDoc(); + void FormatFullDoc(); + void UpdateViews( EditView* pCurView = nullptr ); + void Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice ); + void Paint(OutputDevice& rOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly = false, Degree10 nOrientation = 0_deg10); + + bool MouseButtonUp( const MouseEvent& rMouseEvent, EditView* pView ); + bool MouseButtonDown( const MouseEvent& rMouseEvent, EditView* pView ); + void ReleaseMouse(); + bool MouseMove( const MouseEvent& rMouseEvent, EditView* pView ); + bool Command(const CommandEvent& rCEvt, EditView* pView); + + EditSelectionEngine& GetSelEngine() { return aSelEngine; } + OUString GetSelected( const EditSelection& rSel ) const; + + const SfxItemSet& GetEmptyItemSet() const; + + void UpdateSelections(); + + void EnableUndo( bool bEnable ); + bool IsUndoEnabled() const { return bUndoEnabled; } + void SetUndoMode( bool b ) { bIsInUndo = b; } + bool IsInUndo() const { return bIsInUndo; } + + void SetCallParaInsertedOrDeleted( bool b ) { bCallParaInsertedOrDeleted = b; } + bool IsCallParaInsertedOrDeleted() const { return bCallParaInsertedOrDeleted; } + + bool IsFormatted() const { return bFormatted; } + bool IsFormatting() const { return bIsFormatting; } + + void SetText(const OUString& rText); + EditPaM DeleteSelected(const EditSelection& rEditSelection); + EditPaM InsertTextUserInput( const EditSelection& rCurEditSelection, sal_Unicode c, bool bOverwrite ); + EditPaM InsertText(const EditSelection& aCurEditSelection, const OUString& rStr); + EditPaM AutoCorrect( const EditSelection& rCurEditSelection, sal_Unicode c, bool bOverwrite, vcl::Window const * pFrameWin = nullptr ); + EditPaM DeleteLeftOrRight( const EditSelection& rEditSelection, sal_uInt8 nMode, DeleteMode nDelMode ); + EditPaM InsertParaBreak(const EditSelection& rEditSelection); + EditPaM InsertLineBreak(const EditSelection& aEditSelection); + EditPaM InsertTab(const EditSelection& rEditSelection); + EditPaM InsertField(const EditSelection& rCurSel, const SvxFieldItem& rFld); + bool UpdateFields(); + + EditPaM Read(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, const EditSelection& rSel, SvKeyValueIterator* pHTTPHeaderAttrs = nullptr); + void Write(SvStream& rOutput, EETextFormat eFormat, const EditSelection& rSel); + + std::unique_ptr<EditTextObject> CreateTextObject(); + std::unique_ptr<EditTextObject> CreateTextObject(const EditSelection& rSel); + void SetText( const EditTextObject& rTextObject ); + EditSelection InsertText( const EditTextObject& rTextObject, EditSelection aSel ); + + EditSelection const & MoveCursor( const KeyEvent& rKeyEvent, EditView* pEditView ); + + EditSelection MoveParagraphs( Range aParagraphs, sal_Int32 nNewPos, EditView* pCurView ); + + tools::Long CalcTextHeight( tools::Long* pHeightNTP ); + sal_uInt32 GetTextHeight() const; + sal_uInt32 GetTextHeightNTP() const; + sal_uInt32 CalcTextWidth( bool bIgnoreExtraSpace); + sal_uInt32 CalcParaWidth( sal_Int32 nParagraph, bool bIgnoreExtraSpace ); + sal_uInt32 CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, bool bIgnoreExtraSpace); + sal_Int32 GetLineCount( sal_Int32 nParagraph ) const; + sal_Int32 GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const; + void GetLineBoundaries( /*out*/sal_Int32& rStart, /*out*/sal_Int32& rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const; + sal_Int32 GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const; + sal_uInt16 GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine ); + sal_uInt32 GetParaHeight( sal_Int32 nParagraph ); + + SfxItemSet GetAttribs( sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, GetAttribsFlags nFlags = GetAttribsFlags::ALL ) const; + SfxItemSet GetAttribs( EditSelection aSel, EditEngineAttribs nOnlyHardAttrib = EditEngineAttribs::All ); + void SetAttribs( EditSelection aSel, const SfxItemSet& rSet, SetAttribsMode nSpecial = SetAttribsMode::NONE, bool bSetSelection = true ); + void RemoveCharAttribs( EditSelection aSel, EERemoveParaAttribsMode eMode, sal_uInt16 nWhich ); + void RemoveCharAttribs( sal_Int32 nPara, sal_uInt16 nWhich = 0, bool bRemoveFeatures = false ); + void SetFlatMode( bool bFlat ); + + void SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ); + const SfxItemSet& GetParaAttribs( sal_Int32 nPara ) const; + + bool HasParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const; + const SfxPoolItem& GetParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const; + template<class T> + const T& GetParaAttrib( sal_Int32 nPara, TypedWhichId<T> nWhich ) const + { + return static_cast<const T&>(GetParaAttrib(nPara, sal_uInt16(nWhich))); + } + + tools::Rectangle PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags = GetCursorFlags::NONE ); + tools::Rectangle GetEditCursor(const ParaPortion* pPortion, const EditLine* pLine, + sal_Int32 nIndex, GetCursorFlags nFlags); + + bool IsModified() const { return aEditDoc.IsModified(); } + void SetModifyFlag( bool b ) { aEditDoc.SetModified( b ); } + void SetModifyHdl( const Link<LinkParamNone*,void>& rLink ) { aModifyHdl = rLink; } + const Link<LinkParamNone*,void>& GetModifyHdl() const { return aModifyHdl; } + + bool IsInSelectionMode() const { return bInSelection; } + +// For Undo/Redo + void Undo( EditView* pView ); + void Redo( EditView* pView ); + +// OV-Special + void InvalidateFromParagraph( sal_Int32 nFirstInvPara ); + EditPaM InsertParagraph( sal_Int32 nPara ); + std::optional<EditSelection> SelectParagraph( sal_Int32 nPara ); + + void SetStatusEventHdl( const Link<EditStatus&, void>& rLink ) { aStatusHdlLink = rLink; } + const Link<EditStatus&,void>& GetStatusEventHdl() const { return aStatusHdlLink; } + + void SetNotifyHdl( const Link<EENotify&,void>& rLink ) { aNotifyHdl = rLink; } + const Link<EENotify&,void>& GetNotifyHdl() const { return aNotifyHdl; } + + void FormatAndLayout( EditView* pCurView = nullptr, bool bCalledFromUndo = false ); + + const svtools::ColorConfig& GetColorConfig() const { return maColorConfig; } + bool IsVisualCursorTravelingEnabled(); + bool DoVisualCursorTraveling(); + + EditSelection ConvertSelection( sal_Int32 nStartPara, sal_Int32 nStartPos, sal_Int32 nEndPara, sal_Int32 nEndPos ); + inline EPaM CreateEPaM( const EditPaM& rPaM ) const; + inline EditPaM CreateEditPaM( const EPaM& rEPaM ); + inline ESelection CreateESel( const EditSelection& rSel ) const; + inline EditSelection CreateSel( const ESelection& rSel ); + + void SetStyleSheetPool( SfxStyleSheetPool* pSPool ); + SfxStyleSheetPool* GetStyleSheetPool() const { return pStylePool; } + + void SetStyleSheet( EditSelection aSel, SfxStyleSheet* pStyle ); + void SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle ); + const SfxStyleSheet* GetStyleSheet( sal_Int32 nPara ) const; + SfxStyleSheet* GetStyleSheet( sal_Int32 nPara ); + + void UpdateParagraphsWithStyleSheet( SfxStyleSheet* pStyle ); + void RemoveStyleFromParagraphs( SfxStyleSheet const * pStyle ); + + OutputDevice* GetRefDevice() const { return pRefDev.get(); } + void SetRefDevice( OutputDevice* pRefDef ); + + const MapMode& GetRefMapMode() const { return pRefDev->GetMapMode(); } + void SetRefMapMode( const MapMode& rMapMode ); + + InternalEditStatus& GetStatus() { return aStatus; } + void CallStatusHdl(); + void DelayedCallStatusHdl() { aStatusTimer.Start(); } + + void UndoActionStart( sal_uInt16 nId ); + void UndoActionStart( sal_uInt16 nId, const ESelection& rSel ); + void UndoActionEnd(); + + EditView* GetActiveView() const { return pActiveView; } + void SetActiveView( EditView* pView ); + + css::uno::Reference< css::linguistic2::XSpellChecker1 > const & + GetSpeller(); + void SetSpeller( css::uno::Reference< css::linguistic2::XSpellChecker1 > const &xSpl ) + { xSpeller = xSpl; } + const css::uno::Reference< css::linguistic2::XHyphenator >& + GetHyphenator() const { return xHyphenator; } + void SetHyphenator( css::uno::Reference< css::linguistic2::XHyphenator > const &xHyph ) + { xHyphenator = xHyph; } + + void GetAllMisspellRanges( std::vector<editeng::MisspellRanges>& rRanges ) const; + void SetAllMisspellRanges( const std::vector<editeng::MisspellRanges>& rRanges ); + + SpellInfo* GetSpellInfo() const { return pSpellInfo.get(); } + + void SetDefaultLanguage( LanguageType eLang ) { eDefLanguage = eLang; } + LanguageType GetDefaultLanguage() const { return eDefLanguage; } + + editeng::LanguageSpan GetLanguage( const EditPaM& rPaM, sal_Int32* pEndPos = nullptr ) const; + css::lang::Locale GetLocale( const EditPaM& rPaM ) const; + + void DoOnlineSpelling( ContentNode* pThisNodeOnly = nullptr, bool bSpellAtCursorPos = false, bool bInterruptible = true ); + EESpellState Spell(EditView* pEditView, weld::Widget* pDialogParent, bool bMultipleDoc); + EESpellState HasSpellErrors(); + void ClearSpellErrors(); + EESpellState StartThesaurus(EditView* pEditView, weld::Widget* pDialogParent); + css::uno::Reference< css::linguistic2::XSpellAlternatives > + ImpSpell( EditView* pEditView ); + + // text conversion functions + void Convert(EditView* pEditView, weld::Widget* pDialogParent, LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont, sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc); + void ImpConvert( OUString &rConvTxt, LanguageType &rConvTxtLang, EditView* pEditView, LanguageType nSrcLang, const ESelection &rConvRange, + bool bAllowImplicitChangesForNotConvertibleText, LanguageType nTargetLang, const vcl::Font *pTargetFont ); + ConvInfo * GetConvInfo() const { return pConvInfo.get(); } + bool HasConvertibleTextPortion( LanguageType nLang ); + void SetLanguageAndFont( const ESelection &rESel, + LanguageType nLang, sal_uInt16 nLangWhichId, + const vcl::Font *pFont, sal_uInt16 nFontWhichId ); + + // returns true if input sequence checking should be applied + bool IsInputSequenceCheckingRequired( sal_Unicode nChar, const EditSelection& rCurSel ) const; + + //find the next error within the given selection - forward only! + css::uno::Reference< css::linguistic2::XSpellAlternatives > + ImpFindNextError(EditSelection& rSelection); + //spell and return a sentence + bool SpellSentence(EditView const & rView, svx::SpellPortions& rToFill ); + //put spelling back to start of current sentence - needed after switch of grammar support + void PutSpellingToSentenceStart( EditView const & rEditView ); + //applies a changed sentence + void ApplyChangedSentence(EditView const & rEditView, const svx::SpellPortions& rNewPortions, bool bRecheck ); + //adds one or more portions of text to the SpellPortions depending on language changes + void AddPortionIterated( + EditView const & rEditView, + const EditSelection &rSel, + const css::uno::Reference< css::linguistic2::XSpellAlternatives >& xAlt, + svx::SpellPortions& rToFill); + //adds one portion to the SpellPortions + void AddPortion( + const EditSelection &rSel, + const css::uno::Reference< css::linguistic2::XSpellAlternatives >& xAlt, + svx::SpellPortions& rToFill, + bool bIsField ); + + bool Search( const SvxSearchItem& rSearchItem, EditView* pView ); + bool ImpSearch( const SvxSearchItem& rSearchItem, const EditSelection& rSearchSelection, const EditPaM& rStartPos, EditSelection& rFoundSel ); + sal_Int32 StartSearchAndReplace( EditView* pEditView, const SvxSearchItem& rSearchItem ); + bool HasText( const SvxSearchItem& rSearchItem ); + + void SetEditTextObjectPool( SfxItemPool* pP ) { pTextObjectPool = pP; } + SfxItemPool* GetEditTextObjectPool() const { return pTextObjectPool; } + + const SvxNumberFormat * GetNumberFormat( const ContentNode* pNode ) const; + sal_Int32 GetSpaceBeforeAndMinLabelWidth( const ContentNode *pNode, sal_Int32 *pnSpaceBefore = nullptr, sal_Int32 *pnMinLabelWidth = nullptr ) const; + + const SvxLRSpaceItem& GetLRSpaceItem( ContentNode* pNode ); + SvxAdjust GetJustification( sal_Int32 nPara ) const; + SvxCellJustifyMethod GetJustifyMethod( sal_Int32 nPara ) const; + SvxCellVerJustify GetVerJustification( sal_Int32 nPara ) const; + + void SetCharStretching( sal_uInt16 nX, sal_uInt16 nY ); + inline void GetCharStretching( sal_uInt16& rX, sal_uInt16& rY ) const; + + sal_Int32 GetBigTextObjectStart() const { return nBigTextObjectStart; } + + EditEngine* GetEditEnginePtr() const { return pEditEngine; } + + void StartOnlineSpellTimer() { aOnlineSpellTimer.Start(); } + void StopOnlineSpellTimer() { aOnlineSpellTimer.Stop(); } + + const OUString& GetAutoCompleteText() const { return aAutoCompleteText; } + void SetAutoCompleteText(const OUString& rStr, bool bUpdateTipWindow); + + EditSelection TransliterateText( const EditSelection& rSelection, TransliterationFlags nTransliterationMode ); + short ReplaceTextOnly( ContentNode* pNode, sal_Int32 nCurrentStart, std::u16string_view rText, const css::uno::Sequence< sal_Int32 >& rOffsets ); + + void SetAsianCompressionMode( CharCompressType n ); + CharCompressType GetAsianCompressionMode() const { return nAsianCompressionMode; } + + void SetKernAsianPunctuation( bool b ); + bool IsKernAsianPunctuation() const { return bKernAsianPunctuation; } + + sal_Int32 GetOverflowingParaNum() const { return mnOverflowingPara; } + sal_Int32 GetOverflowingLineNum() const { return mnOverflowingLine; } + void ClearOverflowingParaNum() { mnOverflowingPara = -1; } + + + void SetAddExtLeading( bool b ); + bool IsAddExtLeading() const { return bAddExtLeading; } + + static std::shared_ptr<SvxForbiddenCharactersTable> const & GetForbiddenCharsTable(); + static void SetForbiddenCharsTable( const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars ); + + /** sets a link that is called at the beginning of a drag operation at an edit view */ + void SetBeginDropHdl( const Link<EditView*,void>& rLink ) { maBeginDropHdl = rLink; } + const Link<EditView*,void>& GetBeginDropHdl() const { return maBeginDropHdl; } + + /** sets a link that is called at the end of a drag operation at an edit view */ + void SetEndDropHdl( const Link<EditView*,void>& rLink ) { maEndDropHdl = rLink; } + const Link<EditView*,void>& GetEndDropHdl() const { return maEndDropHdl; } + + /// specifies if auto-correction should capitalize the first word or not (default is on) + void SetFirstWordCapitalization( bool bCapitalize ) { bFirstWordCapitalization = bCapitalize; } + bool IsFirstWordCapitalization() const { return bFirstWordCapitalization; } + + /** specifies if auto-correction should replace a leading single quotation + mark (apostrophe) or not (default is on) */ + void SetReplaceLeadingSingleQuotationMark( bool bReplace ) { mbReplaceLeadingSingleQuotationMark = bReplace; } + bool IsReplaceLeadingSingleQuotationMark() const { return mbReplaceLeadingSingleQuotationMark; } + + /** Whether last AutoCorrect inserted a NO-BREAK SPACE that may need to be removed again. */ + bool IsNbspRunNext() const { return mbNbspRunNext; } + + void EnableSkipOutsideFormat(bool set) { mbSkipOutsideFormat = set; } + + void Dispose(); + void SetLOKSpecialPaperSize(const Size& rSize) { aLOKSpecialPaperSize = rSize; } + const Size& GetLOKSpecialPaperSize() const { return aLOKSpecialPaperSize; } + + enum class CallbackResult + { + Continue, + SkipThisPortion, // Do not call callback until next portion + Stop, // Stop iteration + }; + struct LineAreaInfo + { + ParaPortion& rPortion; // Current ParaPortion + EditLine* pLine; // Current line, or nullptr for paragraph start + tools::Long nHeightNeededToNotWrap; + tools::Rectangle aArea; // The area for the line (or for rPortion's first line offset) + // Bottom coordinate *does not* belong to the area + sal_Int32 nPortion; + sal_Int32 nLine; + sal_Int16 nColumn; // Column number; when overflowing, equal to total number of columns + }; + using IterateLinesAreasFunc = std::function<CallbackResult(const LineAreaInfo&)>; + enum IterFlag // bitmask + { + none = 0, + inclILS = 1, // rArea includes interline space + }; + + void IterateLineAreas(const IterateLinesAreasFunc& f, IterFlag eOptions); + + tools::Long GetColumnWidth(const Size& rPaperSize) const; + Point MoveToNextLine(Point& rMovePos, tools::Long nLineHeight, sal_Int16& nColumn, + Point aOrigin, tools::Long* pnHeightNeededToNotWrap = nullptr) const; + + tools::Long getWidthDirectionAware(const Size& sz) const; + tools::Long getHeightDirectionAware(const Size& sz) const; + void adjustXDirectionAware(Point& pt, tools::Long x) const; + void adjustYDirectionAware(Point& pt, tools::Long y) const; + void setXDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const; + void setYDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const; + tools::Long getYOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const; + bool isXOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const; + // Offset of the rectangle's direction-aware corners in document coordinates + tools::Long getBottomDocOffset(const tools::Rectangle& rect) const; + Size getTopLeftDocOffset(const tools::Rectangle& rect) const; +}; + +inline EPaM ImpEditEngine::CreateEPaM( const EditPaM& rPaM ) const +{ + const ContentNode* pNode = rPaM.GetNode(); + return EPaM( aEditDoc.GetPos( pNode ), rPaM.GetIndex() ); +} + +inline EditPaM ImpEditEngine::CreateEditPaM( const EPaM& rEPaM ) +{ + DBG_ASSERT( rEPaM.nPara < aEditDoc.Count(), "CreateEditPaM: invalid paragraph" ); + DBG_ASSERT( aEditDoc[ rEPaM.nPara ]->Len() >= rEPaM.nIndex, "CreateEditPaM: invalid Index" ); + return EditPaM( aEditDoc[ rEPaM.nPara], rEPaM.nIndex ); +} + +inline ESelection ImpEditEngine::CreateESel( const EditSelection& rSel ) const +{ + const ContentNode* pStartNode = rSel.Min().GetNode(); + const ContentNode* pEndNode = rSel.Max().GetNode(); + ESelection aESel; + aESel.nStartPara = aEditDoc.GetPos( pStartNode ); + aESel.nStartPos = rSel.Min().GetIndex(); + aESel.nEndPara = aEditDoc.GetPos( pEndNode ); + aESel.nEndPos = rSel.Max().GetIndex(); + return aESel; +} + +inline EditSelection ImpEditEngine::CreateSel( const ESelection& rSel ) +{ + DBG_ASSERT( rSel.nStartPara < aEditDoc.Count(), "CreateSel: invalid start paragraph" ); + DBG_ASSERT( rSel.nEndPara < aEditDoc.Count(), "CreateSel: invalid end paragraph" ); + EditSelection aSel; + aSel.Min().SetNode( aEditDoc[ rSel.nStartPara ] ); + aSel.Min().SetIndex( rSel.nStartPos ); + aSel.Max().SetNode( aEditDoc[ rSel.nEndPara ] ); + aSel.Max().SetIndex( rSel.nEndPos ); + DBG_ASSERT( !aSel.DbgIsBuggy( aEditDoc ), "CreateSel: incorrect selection!" ); + return aSel; +} + +inline VirtualDevice* ImpEditEngine::GetVirtualDevice( const MapMode& rMapMode, DrawModeFlags nDrawMode ) +{ + if ( !pVirtDev ) + pVirtDev = VclPtr<VirtualDevice>::Create(); + + if ( ( pVirtDev->GetMapMode().GetMapUnit() != rMapMode.GetMapUnit() ) || + ( pVirtDev->GetMapMode().GetScaleX() != rMapMode.GetScaleX() ) || + ( pVirtDev->GetMapMode().GetScaleY() != rMapMode.GetScaleY() ) ) + { + MapMode aMapMode( rMapMode ); + aMapMode.SetOrigin( Point( 0, 0 ) ); + pVirtDev->SetMapMode( aMapMode ); + } + + pVirtDev->SetDrawMode( nDrawMode ); + + return pVirtDev; +} + +inline EditUndoManager& ImpEditEngine::GetUndoManager() +{ + if ( !pUndoManager ) + { + pUndoManager = new EditUndoManager(); + pUndoManager->SetEditEngine(pEditEngine); + } + return *pUndoManager; +} + +inline SfxUndoManager* ImpEditEngine::SetUndoManager(SfxUndoManager* pNew) +{ + SfxUndoManager* pRetval = pUndoManager; + + if(pUndoManager) + { + pUndoManager->SetEditEngine(nullptr); + } + + pUndoManager = dynamic_cast< EditUndoManager* >(pNew); + + if(pUndoManager) + { + pUndoManager->SetEditEngine(pEditEngine); + } + + return pRetval; +} + +inline const ParaPortion* ImpEditEngine::FindParaPortion( const ContentNode* pNode ) const +{ + sal_Int32 nPos = aEditDoc.GetPos( pNode ); + DBG_ASSERT( nPos < GetParaPortions().Count(), "Portionloser Node?" ); + return GetParaPortions()[ nPos ]; +} + +inline ParaPortion* ImpEditEngine::FindParaPortion( ContentNode const * pNode ) +{ + sal_Int32 nPos = aEditDoc.GetPos( pNode ); + DBG_ASSERT( nPos < GetParaPortions().Count(), "Portionloser Node?" ); + return GetParaPortions()[ nPos ]; +} + +inline void ImpEditEngine::GetCharStretching( sal_uInt16& rX, sal_uInt16& rY ) const +{ + rX = nStretchX; + rY = nStretchY; +} + +inline short ImpEditEngine::GetXValue( short nXValue ) const +{ + if ( !aStatus.DoStretch() || ( nStretchX == 100 ) ) + return nXValue; + + return static_cast<short>(static_cast<tools::Long>(nXValue)*nStretchX/100); +} + + +inline tools::Long ImpEditEngine::GetXValue( tools::Long nXValue ) const +{ + if ( !aStatus.DoStretch() || ( nStretchX == 100 ) ) + return nXValue; + + return nXValue*nStretchX/100; +} + +inline short ImpEditEngine::GetYValue( short nYValue ) const +{ + if ( !aStatus.DoStretch() || ( nStretchY == 100 ) ) + return nYValue; + + return static_cast<short>(static_cast<tools::Long>(nYValue)*nStretchY/100); +} + +inline sal_uInt16 ImpEditEngine::GetYValue( sal_uInt16 nYValue ) const +{ + if ( !aStatus.DoStretch() || ( nStretchY == 100 ) ) + return nYValue; + + return static_cast<sal_uInt16>(static_cast<tools::Long>(nYValue)*nStretchY/100); +} + +inline PointerStyle ImpEditView::GetPointer() +{ + if ( !mxPointer ) + { + mxPointer = IsVertical() ? PointerStyle::TextVertical : PointerStyle::Text; + return *mxPointer; + } + + if(PointerStyle::Text == *mxPointer && IsVertical()) + { + mxPointer = PointerStyle::TextVertical; + } + else if(PointerStyle::TextVertical == *mxPointer && !IsVertical()) + { + mxPointer = PointerStyle::Text; + } + + return *mxPointer; +} + +inline vcl::Cursor* ImpEditView::GetCursor() +{ + if ( !pCursor ) + pCursor.reset( new vcl::Cursor ); + return pCursor.get(); +} + +void ConvertItem( std::unique_ptr<SfxPoolItem>& rPoolItem, MapUnit eSourceUnit, MapUnit eDestUnit ); +void ConvertAndPutItems( SfxItemSet& rDest, const SfxItemSet& rSource, const MapUnit* pSourceUnit = nullptr, const MapUnit* pDestUnit = nullptr ); +AsianCompressionFlags GetCharTypeForCompression( sal_Unicode cChar ); +Point Rotate( const Point& rPoint, Degree10 nOrientation, const Point& rOrigin ); + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit2.cxx b/editeng/source/editeng/impedit2.cxx new file mode 100644 index 000000000..062d596b0 --- /dev/null +++ b/editeng/source/editeng/impedit2.cxx @@ -0,0 +1,4482 @@ +/* -*- 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/window.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/flditem.hxx> +#include "impedit.hxx" +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <eerdll2.hxx> +#include <editeng/eerdll.hxx> +#include <edtspell.hxx> +#include "eeobj.hxx" +#include <editeng/txtrange.hxx> +#include <sfx2/app.hxx> +#include <svtools/colorcfg.hxx> +#include <svl/ctloptions.hxx> +#include <unotools/securityoptions.hxx> +#include <editeng/acorrcfg.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/justifyitem.hxx> + +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/i18n/InputSequenceCheckMode.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/system/XSystemShellExecute.hpp> +#include <com/sun/star/i18n/UnicodeType.hpp> + +#include <rtl/character.hxx> + +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <sot/exchange.hxx> +#include <sot/formats.hxx> +#include <svl/asiancfg.hxx> +#include <i18nutil/unicode.hxx> +#include <tools/diagnose_ex.h> +#include <comphelper/flagguard.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <unotools/configmgr.hxx> + +#include <unicode/ubidi.h> +#include <algorithm> +#include <limits> +#include <memory> +#include <string_view> +#include <fstream> + +using namespace ::com::sun::star; + +static sal_uInt16 lcl_CalcExtraSpace( const SvxLineSpacingItem& rLSItem ) +{ + sal_uInt16 nExtra = 0; + if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) + { + nExtra = rLSItem.GetInterLineSpace(); + } + + return nExtra; +} + +ImpEditEngine::ImpEditEngine( EditEngine* pEE, SfxItemPool* pItemPool ) : + pSharedVCL(EditDLL::Get().GetSharedVclResources()), + aPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ), + aMinAutoPaperSize( 0x0, 0x0 ), + aMaxAutoPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ), + aEditDoc( pItemPool ), + pEditEngine(pEE), + pActiveView(nullptr), + pStylePool(nullptr), + pTextObjectPool(nullptr), + pUndoManager(nullptr), + aWordDelimiters(" .,;:-`'?!_=\"{}()[]"), + maBackgroundColor(COL_AUTO), + nStretchX(100), + nStretchY(100), + nAsianCompressionMode(CharCompressType::NONE), + eDefaultHorizontalTextDirection(EEHorizontalTextDirection::Default), + nBigTextObjectStart(20), + eDefLanguage(LANGUAGE_DONTKNOW), + nCurTextHeight(0), + nCurTextHeightNTP(0), + aOnlineSpellTimer( "editeng::ImpEditEngine aOnlineSpellTimer" ), + aStatusTimer( "editeng::ImpEditEngine aStatusTimer" ), + bKernAsianPunctuation(false), + bAddExtLeading(false), + bIsFormatting(false), + bFormatted(false), + bInSelection(false), + bIsInUndo(false), + bUpdateLayout(true), + bUndoEnabled(true), + bDowning(false), + bUseAutoColor(true), + bForceAutoColor(false), + bCallParaInsertedOrDeleted(false), + bFirstWordCapitalization(true), + mbLastTryMerge(false), + mbReplaceLeadingSingleQuotationMark(true), + mbSkipOutsideFormat(false), + mbNbspRunNext(false) +{ + aStatus.GetControlWord() = EEControlBits::USECHARATTRIBS | EEControlBits::DOIDLEFORMAT | + EEControlBits::PASTESPECIAL | EEControlBits::UNDOATTRIBS | + EEControlBits::ALLOWBIGOBJS | EEControlBits::RTFSTYLESHEETS | + EEControlBits::FORMAT100; + + aSelEngine.SetFunctionSet( &aSelFuncSet ); + + aStatusTimer.SetTimeout( 200 ); + aStatusTimer.SetInvokeHandler( LINK( this, ImpEditEngine, StatusTimerHdl ) ); + + aIdleFormatter.SetPriority( TaskPriority::REPAINT ); + aIdleFormatter.SetInvokeHandler( LINK( this, ImpEditEngine, IdleFormatHdl ) ); + + aOnlineSpellTimer.SetTimeout( 100 ); + aOnlineSpellTimer.SetInvokeHandler( LINK( this, ImpEditEngine, OnlineSpellHdl ) ); + + // Access data already from here on! + SetRefDevice( nullptr ); + InitDoc( false ); + + bCallParaInsertedOrDeleted = true; + + aEditDoc.SetModifyHdl( LINK( this, ImpEditEngine, DocModified ) ); + StartListening(*SfxGetpApp()); +} + +void ImpEditEngine::Dispose() +{ + SolarMutexGuard g; + auto pApp = SfxApplication::Get(); + if(pApp) + EndListening(*pApp); + pVirtDev.disposeAndClear(); + mpOwnDev.disposeAndClear(); + pSharedVCL.reset(); +} + +ImpEditEngine::~ImpEditEngine() +{ + aStatusTimer.Stop(); + aOnlineSpellTimer.Stop(); + aIdleFormatter.Stop(); + + // Destroying templates may otherwise cause unnecessary formatting, + // when a parent template is destroyed. + // And this after the destruction of the data! + bDowning = true; + SetUpdateLayout( false ); + + Dispose(); + // it's only legal to delete the pUndoManager if it was created by + // ImpEditEngine; if it was set by SetUndoManager() it must be cleared + // before destroying the ImpEditEngine! + assert(!pUndoManager || typeid(*pUndoManager) == typeid(EditUndoManager)); + delete pUndoManager; + pTextRanger.reset(); + mpIMEInfos.reset(); + pCTLOptions.reset(); + pSpellInfo.reset(); +} + +void ImpEditEngine::SetRefDevice( OutputDevice* pRef ) +{ + if (pRef) + pRefDev = pRef; + else + pRefDev = pSharedVCL->GetVirtualDevice(); + + nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width()); + + if ( IsFormatted() ) + { + FormatFullDoc(); + UpdateViews(); + } +} + +void ImpEditEngine::SetRefMapMode( const MapMode& rMapMode ) +{ + if ( GetRefDevice()->GetMapMode() == rMapMode ) + return; + + mpOwnDev.disposeAndClear(); + mpOwnDev = VclPtr<VirtualDevice>::Create(); + pRefDev = mpOwnDev; + pRefDev->SetMapMode(MapMode(MapUnit::MapTwip)); + SetRefDevice( pRefDev ); + + pRefDev->SetMapMode( rMapMode ); + nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width()); + if ( IsFormatted() ) + { + FormatFullDoc(); + UpdateViews(); + } +} + +void ImpEditEngine::InitDoc(bool bKeepParaAttribs) +{ + sal_Int32 nParas = aEditDoc.Count(); + for ( sal_Int32 n = bKeepParaAttribs ? 1 : 0; n < nParas; n++ ) + { + if ( aEditDoc[n]->GetStyleSheet() ) + EndListening( *aEditDoc[n]->GetStyleSheet() ); + } + + if ( bKeepParaAttribs ) + aEditDoc.RemoveText(); + else + aEditDoc.Clear(); + + GetParaPortions().Reset(); + + GetParaPortions().Insert(0, std::make_unique<ParaPortion>( aEditDoc[0] )); + + bFormatted = false; + + if ( IsCallParaInsertedOrDeleted() ) + { + GetEditEnginePtr()->ParagraphDeleted( EE_PARA_ALL ); + GetEditEnginePtr()->ParagraphInserted( 0 ); + } + + if ( GetStatus().DoOnlineSpelling() ) + aEditDoc.GetObject( 0 )->CreateWrongList(); +} + +EditPaM ImpEditEngine::DeleteSelected(const EditSelection& rSel) +{ + EditPaM aPaM (ImpDeleteSelection(rSel)); + return aPaM; +} + +OUString ImpEditEngine::GetSelected( const EditSelection& rSel ) const +{ + if ( !rSel.HasRange() ) + return OUString(); + + EditSelection aSel( rSel ); + aSel.Adjust( aEditDoc ); + + ContentNode* pStartNode = aSel.Min().GetNode(); + ContentNode* pEndNode = aSel.Max().GetNode(); + sal_Int32 nStartNode = aEditDoc.GetPos( pStartNode ); + sal_Int32 nEndNode = aEditDoc.GetPos( pEndNode ); + + OSL_ENSURE( nStartNode <= nEndNode, "Selection not sorted ?" ); + + OUStringBuffer aText(256); + const OUString aSep = EditDoc::GetSepStr( LINEEND_LF ); + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + OSL_ENSURE( aEditDoc.GetObject( nNode ), "Node not found: GetSelected" ); + const ContentNode* pNode = aEditDoc.GetObject( nNode ); + + const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; + const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart! + + aText.append(EditDoc::GetParaAsString( pNode, nStartPos, nEndPos )); + if ( nNode < nEndNode ) + aText.append(aSep); + } + return aText.makeStringAndClear(); +} + +bool ImpEditEngine::MouseButtonDown( const MouseEvent& rMEvt, EditView* pView ) +{ + GetSelEngine().SetCurView( pView ); + SetActiveView( pView ); + + if (!GetAutoCompleteText().isEmpty()) + SetAutoCompleteText( OUString(), true ); + + GetSelEngine().SelMouseButtonDown( rMEvt ); + // Special treatment + EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); + if ( rMEvt.IsShift() ) + return true; + + if ( rMEvt.GetClicks() == 2 ) + { + // So that the SelectionEngine knows about the anchor. + aSelEngine.CursorPosChanging( true, false ); + + EditSelection aNewSelection( SelectWord( aCurSel ) ); + pView->pImpEditView->DrawSelectionXOR(); + pView->pImpEditView->SetEditSelection( aNewSelection ); + pView->pImpEditView->DrawSelectionXOR(); + pView->ShowCursor(); + } + else if ( rMEvt.GetClicks() == 3 ) + { + // So that the SelectionEngine knows about the anchor. + aSelEngine.CursorPosChanging( true, false ); + + EditSelection aNewSelection( aCurSel ); + aNewSelection.Min().SetIndex( 0 ); + aNewSelection.Max().SetIndex( aCurSel.Min().GetNode()->Len() ); + pView->pImpEditView->DrawSelectionXOR(); + pView->pImpEditView->SetEditSelection( aNewSelection ); + pView->pImpEditView->DrawSelectionXOR(); + pView->ShowCursor(); + } + return true; +} + +bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) +{ + bool bConsumed = true; + + GetSelEngine().SetCurView( pView ); + SetActiveView( pView ); + if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput ) + { + pView->DeleteSelected(); + mpIMEInfos.reset(); + EditPaM aPaM = pView->GetImpEditView()->GetEditSelection().Max(); + OUString aOldTextAfterStartPos = aPaM.GetNode()->Copy( aPaM.GetIndex() ); + sal_Int32 nMax = aOldTextAfterStartPos.indexOf( CH_FEATURE ); + if ( nMax != -1 ) // don't overwrite features! + aOldTextAfterStartPos = aOldTextAfterStartPos.copy( 0, nMax ); + mpIMEInfos.reset( new ImplIMEInfos( aPaM, aOldTextAfterStartPos ) ); + mpIMEInfos->bWasCursorOverwrite = !pView->IsInsertMode(); + UndoActionStart( EDITUNDO_INSERT ); + } + else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput ) + { + OSL_ENSURE( mpIMEInfos, "CommandEventId::EndExtTextInput => No start ?" ); + if( mpIMEInfos ) + { + // #102812# convert quotes in IME text + // works on the last input character, this is especially in Korean text often done + // quotes that are inside of the string are not replaced! + // Borrowed from sw: edtwin.cxx + if ( mpIMEInfos->nLen ) + { + EditSelection aSel( mpIMEInfos->aPos ); + aSel.Min().SetIndex( aSel.Min().GetIndex() + mpIMEInfos->nLen-1 ); + aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen ); + // #102812# convert quotes in IME text + // works on the last input character, this is especially in Korean text often done + // quotes that are inside of the string are not replaced! + const sal_Unicode nCharCode = aSel.Min().GetNode()->GetChar( aSel.Min().GetIndex() ); + if ( ( GetStatus().DoAutoCorrect() ) && ( ( nCharCode == '\"' ) || ( nCharCode == '\'' ) ) ) + { + aSel = DeleteSelected( aSel ); + aSel = AutoCorrect( aSel, nCharCode, mpIMEInfos->bWasCursorOverwrite ); + pView->pImpEditView->SetEditSelection( aSel ); + } + } + + ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); + if (pPortion) + pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() ); + + bool bWasCursorOverwrite = mpIMEInfos->bWasCursorOverwrite; + + mpIMEInfos.reset(); + + FormatAndLayout( pView ); + + pView->SetInsertMode( !bWasCursorOverwrite ); + } + UndoActionEnd(); + } + else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput ) + { + OSL_ENSURE( mpIMEInfos, "CommandEventId::ExtTextInput => No Start ?" ); + if( mpIMEInfos ) + { + const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData(); + + if ( !pData->IsOnlyCursorChanged() ) + { + EditSelection aSel( mpIMEInfos->aPos ); + aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen ); + aSel = DeleteSelected( aSel ); + aSel = ImpInsertText( aSel, pData->GetText() ); + + if ( mpIMEInfos->bWasCursorOverwrite ) + { + sal_Int32 nOldIMETextLen = mpIMEInfos->nLen; + sal_Int32 nNewIMETextLen = pData->GetText().getLength(); + + if ( ( nOldIMETextLen > nNewIMETextLen ) && + ( nNewIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) ) + { + // restore old characters + sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen; + EditPaM aPaM( mpIMEInfos->aPos ); + aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen ); + ImpInsertText( aPaM, mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) ); + } + else if ( ( nOldIMETextLen < nNewIMETextLen ) && + ( nOldIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) ) + { + // overwrite + sal_Int32 nOverwrite = nNewIMETextLen - nOldIMETextLen; + if ( ( nOldIMETextLen + nOverwrite ) > mpIMEInfos->aOldTextAfterStartPos.getLength() ) + nOverwrite = mpIMEInfos->aOldTextAfterStartPos.getLength() - nOldIMETextLen; + OSL_ENSURE( nOverwrite && (nOverwrite < 0xFF00), "IME Overwrite?!" ); + EditPaM aPaM( mpIMEInfos->aPos ); + aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen ); + EditSelection _aSel( aPaM ); + _aSel.Max().SetIndex( _aSel.Max().GetIndex() + nOverwrite ); + DeleteSelected( _aSel ); + } + } + if ( pData->GetTextAttr() ) + { + mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() ); + } + else + { + mpIMEInfos->DestroyAttribs(); + mpIMEInfos->nLen = pData->GetText().getLength(); + } + + ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); + pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() ); + FormatAndLayout( pView ); + } + + EditSelection aNewSel = EditPaM( mpIMEInfos->aPos.GetNode(), mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() ); + pView->SetSelection( CreateESel( aNewSel ) ); + pView->SetInsertMode( !pData->IsCursorOverwrite() ); + + if ( pData->IsCursorVisible() ) + pView->ShowCursor(); + else + pView->HideCursor(); + } + } + else if ( rCEvt.GetCommand() == CommandEventId::InputContextChange ) + { + } + else if ( rCEvt.GetCommand() == CommandEventId::CursorPos ) + { + if (mpIMEInfos) + { + EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() ); + tools::Rectangle aR1 = PaMtoEditCursor( aPaM ); + + sal_Int32 nInputEnd = mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen; + + if ( !IsFormatted() ) + FormatDoc(); + + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( GetEditDoc().GetPos( aPaM.GetNode() ) ); + if (pParaPortion) + { + sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), true ); + const EditLine& rLine = pParaPortion->GetLines()[nLine]; + if ( nInputEnd > rLine.GetEnd() ) + nInputEnd = rLine.GetEnd(); + tools::Rectangle aR2 = PaMtoEditCursor( EditPaM( aPaM.GetNode(), nInputEnd ), GetCursorFlags::EndOfLine ); + tools::Rectangle aRect = pView->GetImpEditView()->GetWindowPos( aR1 ); + auto nExtTextInputWidth = aR2.Left() - aR1.Right(); + if (EditViewCallbacks* pEditViewCallbacks = pView->getEditViewCallbacks()) + pEditViewCallbacks->EditViewCursorRect(aRect, nExtTextInputWidth); + else if (vcl::Window* pWindow = pView->GetWindow()) + pWindow->SetCursorRect(&aRect, nExtTextInputWidth); + } + } + else + { + if (vcl::Window* pWindow = pView->GetWindow()) + pWindow->SetCursorRect(); + } + } + else if ( rCEvt.GetCommand() == CommandEventId::SelectionChange ) + { + const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData(); + + ESelection aSelection = pView->GetSelection(); + aSelection.Adjust(); + + if( pView->HasSelection() ) + { + aSelection.nEndPos = aSelection.nStartPos; + aSelection.nStartPos += pData->GetStart(); + aSelection.nEndPos += pData->GetEnd(); + } + else + { + aSelection.nStartPos = pData->GetStart(); + aSelection.nEndPos = pData->GetEnd(); + } + pView->SetSelection( aSelection ); + } + else if ( rCEvt.GetCommand() == CommandEventId::PrepareReconversion ) + { + if ( pView->HasSelection() ) + { + ESelection aSelection = pView->GetSelection(); + aSelection.Adjust(); + + if ( aSelection.nStartPara != aSelection.nEndPara ) + { + sal_Int32 aParaLen = pEditEngine->GetTextLen( aSelection.nStartPara ); + aSelection.nEndPara = aSelection.nStartPara; + aSelection.nEndPos = aParaLen; + pView->SetSelection( aSelection ); + } + } + } + else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition ) + { + if (mpIMEInfos) + { + EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() ); + if ( !IsFormatted() ) + FormatDoc(); + + sal_Int32 nPortionPos = GetEditDoc().GetPos(aPaM.GetNode()); + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject(nPortionPos); + if (pParaPortion) + { + const sal_Int32 nMinPos = mpIMEInfos->aPos.GetIndex(); + const sal_Int32 nMaxPos = nMinPos + mpIMEInfos->nLen - 1; + std::vector<tools::Rectangle> aRects(mpIMEInfos->nLen); + + auto CollectCharPositions = [&](const LineAreaInfo& rInfo) { + if (!rInfo.pLine) // Start of ParaPortion + { + if (rInfo.nPortion < nPortionPos) + return CallbackResult::SkipThisPortion; + if (rInfo.nPortion > nPortionPos) + return CallbackResult::Stop; + assert(&rInfo.rPortion == pParaPortion); + } + else // This is the needed ParaPortion + { + if (rInfo.pLine->GetStart() > nMaxPos) + return CallbackResult::Stop; + if (rInfo.pLine->GetEnd() < nMinPos) + return CallbackResult::Continue; + for (sal_Int32 n = nMinPos; n <= nMaxPos; ++n) + { + if (rInfo.pLine->IsIn(n)) + { + tools::Rectangle aR = GetEditCursor(pParaPortion, rInfo.pLine, n, + GetCursorFlags::NONE); + aR.Move(getTopLeftDocOffset(rInfo.aArea)); + aRects[n - nMinPos] = pView->GetImpEditView()->GetWindowPos(aR); + } + } + } + return CallbackResult::Continue; + }; + IterateLineAreas(CollectCharPositions, IterFlag::none); + + if (vcl::Window* pWindow = pView->GetWindow()) + pWindow->SetCompositionCharRect(aRects.data(), aRects.size()); + } + } + } + else + bConsumed = false; + + return GetSelEngine().Command(rCEvt) || bConsumed; +} + +bool ImpEditEngine::MouseButtonUp( const MouseEvent& rMEvt, EditView* pView ) +{ + GetSelEngine().SetCurView( pView ); + GetSelEngine().SelMouseButtonUp( rMEvt ); + + // in the tiled rendering case, setting bInSelection here has unexpected + // consequences - further tiles painting removes the selection + // FIXME I believe resetting bInSelection should not be here even in the + // non-tiled-rendering case, but it has been here since 2000 (and before) + // so who knows what corner case it was supposed to solve back then + if (!comphelper::LibreOfficeKit::isActive()) + bInSelection = false; + + // Special treatments + EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); + if ( aCurSel.HasRange() ) + return true; + + if ( ( rMEvt.GetClicks() != 1 ) || !rMEvt.IsLeft() || rMEvt.IsMod2() ) + return true; + + const OutputDevice& rOutDev = pView->getEditViewCallbacks() ? pView->getEditViewCallbacks()->EditViewOutputDevice() : *pView->GetWindow()->GetOutDev(); + Point aLogicClick = rOutDev.PixelToLogic(rMEvt.GetPosPixel()); + const SvxFieldItem* pFld = pView->GetField(aLogicClick); + if (!pFld) + return true; + + // tdf#121039 When in edit mode, editeng is responsible for opening the URL on mouse click + bool bUrlOpened = GetEditEnginePtr()->FieldClicked( *pFld ); + if (bUrlOpened) + return true; + + if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFld->GetField())) + { + bool bCtrlClickHappened = rMEvt.IsMod1(); + bool bCtrlClickSecOption + = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink); + if ((bCtrlClickHappened && bCtrlClickSecOption) + || (!bCtrlClickHappened && !bCtrlClickSecOption)) + { + css::uno::Reference<css::system::XSystemShellExecute> exec( + css::system::SystemShellExecute::create( + comphelper::getProcessComponentContext())); + exec->execute(pUrlField->GetURL(), OUString(), + css::system::SystemShellExecuteFlags::DEFAULTS); + } + } + return true; +} + +void ImpEditEngine::ReleaseMouse() +{ + GetSelEngine().ReleaseMouse(); +} + +bool ImpEditEngine::MouseMove( const MouseEvent& rMEvt, EditView* pView ) +{ + // MouseMove is called directly after ShowQuickHelp()! + GetSelEngine().SetCurView( pView ); + GetSelEngine().SelMouseMove( rMEvt ); + return true; +} + +EditPaM ImpEditEngine::InsertText(const EditSelection& aSel, const OUString& rStr) +{ + EditPaM aPaM = ImpInsertText( aSel, rStr ); + return aPaM; +} + +void ImpEditEngine::Clear() +{ + InitDoc( false ); + + EditPaM aPaM = aEditDoc.GetStartPaM(); + EditSelection aSel( aPaM ); + + nCurTextHeight = 0; + nCurTextHeightNTP = 0; + + ResetUndoManager(); + + for (size_t nView = aEditViews.size(); nView; ) + { + EditView* pView = aEditViews[--nView]; + pView->pImpEditView->SetEditSelection( aSel ); + } + + // Related: tdf#82115 Fix crash when handling input method events. + // The nodes in mpIMEInfos may be deleted in ImpEditEngine::Clear() which + // causes a crash in the CommandEventId::ExtTextInput and + // CommandEventId::EndExtTextInput event handlers. + mpIMEInfos.reset(); +} + +EditPaM ImpEditEngine::RemoveText() +{ + InitDoc( true ); + + EditPaM aStartPaM = aEditDoc.GetStartPaM(); + EditSelection aEmptySel( aStartPaM, aStartPaM ); + for (EditView* pView : aEditViews) + { + pView->pImpEditView->SetEditSelection( aEmptySel ); + } + ResetUndoManager(); + return aEditDoc.GetStartPaM(); +} + + +void ImpEditEngine::SetText(const OUString& rText) +{ + // RemoveText deletes the undo list! + EditPaM aStartPaM = RemoveText(); + bool bUndoCurrentlyEnabled = IsUndoEnabled(); + // The text inserted manually can not be made reversible by the user + EnableUndo( false ); + + EditSelection aEmptySel( aStartPaM, aStartPaM ); + EditPaM aPaM = aStartPaM; + if (!rText.isEmpty()) + aPaM = ImpInsertText( aEmptySel, rText ); + + for (EditView* pView : aEditViews) + { + pView->pImpEditView->SetEditSelection( EditSelection( aPaM, aPaM ) ); + // If no text then also no Format&Update + // => The text remains. + if (rText.isEmpty() && IsUpdateLayout()) + { + tools::Rectangle aTmpRect( pView->GetOutputArea().TopLeft(), + Size( aPaperSize.Width(), nCurTextHeight ) ); + aTmpRect.Intersection( pView->GetOutputArea() ); + pView->InvalidateWindow( aTmpRect ); + } + } + if (rText.isEmpty()) { // otherwise it must be invalidated later, !bFormatted is enough. + nCurTextHeight = 0; + nCurTextHeightNTP = 0; + } + EnableUndo( bUndoCurrentlyEnabled ); + OSL_ENSURE( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "Undo after SetText?" ); +} + + +const SfxItemSet& ImpEditEngine::GetEmptyItemSet() const +{ + if ( !pEmptyItemSet ) + { + pEmptyItemSet = std::make_unique<SfxItemSetFixed<EE_ITEMS_START, EE_ITEMS_END>>(const_cast<SfxItemPool&>(aEditDoc.GetItemPool())); + for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++) + { + pEmptyItemSet->ClearItem( nWhich ); + } + } + return *pEmptyItemSet; +} + + +// MISC + +void ImpEditEngine::CursorMoved( const ContentNode* pPrevNode ) +{ + // Delete empty attributes, but only if paragraph is not empty! + if (pPrevNode->GetCharAttribs().HasEmptyAttribs() && pPrevNode->Len()) + { + const_cast<ContentNode*>(pPrevNode)->GetCharAttribs().DeleteEmptyAttribs(aEditDoc.GetItemPool()); + } +} + +void ImpEditEngine::TextModified() +{ + bFormatted = false; + + if ( GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_TEXTMODIFIED ); + GetNotifyHdl().Call( aNotify ); + } +} + + +void ImpEditEngine::ParaAttribsChanged( ContentNode const * pNode, bool bIgnoreUndoCheck ) +{ + assert(pNode && "ParaAttribsChanged: Which one?"); + + aEditDoc.SetModified( true ); + bFormatted = false; + + ParaPortion* pPortion = FindParaPortion( pNode ); + OSL_ENSURE( pPortion, "ParaAttribsChanged: Portion?" ); + pPortion->MarkSelectionInvalid( 0 ); + + sal_Int32 nPara = aEditDoc.GetPos( pNode ); + if ( bIgnoreUndoCheck || pEditEngine->IsInUndo() ) + pEditEngine->ParaAttribsChanged( nPara ); + + ParaPortion* pNextPortion = GetParaPortions().SafeGetObject( nPara+1 ); + // => is formatted again anyway, if Invalid. + if ( pNextPortion && !pNextPortion->IsInvalid() ) + CalcHeight( pNextPortion ); +} + + +// Cursor movements + + +EditSelection const & ImpEditEngine::MoveCursor( const KeyEvent& rKeyEvent, EditView* pEditView ) +{ + // Actually, only necessary for up/down, but whatever. + CheckIdleFormatter(); + + EditPaM aPaM( pEditView->pImpEditView->GetEditSelection().Max() ); + + EditPaM aOldPaM( aPaM ); + + TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom; + if (IsEffectivelyVertical() && IsTopToBottom()) + eTextDirection = TextDirectionality::TopToBottom_RightToLeft; + else if (IsEffectivelyVertical() && !IsTopToBottom()) + eTextDirection = TextDirectionality::BottomToTop_LeftToRight; + else if ( IsRightToLeft( GetEditDoc().GetPos( aPaM.GetNode() ) ) ) + eTextDirection = TextDirectionality::RightToLeft_TopToBottom; + + KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection ); + + bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1(); + sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode(); + + if ( DoVisualCursorTraveling() ) + { + // Only for simple cursor movement... + if ( !bCtrl && ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) ) ) + { + aPaM = CursorVisualLeftRight( pEditView, aPaM, rKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL, rKeyEvent.GetKeyCode().GetCode() == KEY_LEFT ); + nCode = 0; // skip switch statement + } + } + + bool bKeyModifySelection = aTranslatedKeyEvent.GetKeyCode().IsShift(); + switch ( nCode ) + { + case KEY_UP: aPaM = CursorUp( aPaM, pEditView ); + break; + case KEY_DOWN: aPaM = CursorDown( aPaM, pEditView ); + break; + case KEY_LEFT: aPaM = bCtrl ? WordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL ); + break; + case KEY_RIGHT: aPaM = bCtrl ? WordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL ); + break; + case KEY_HOME: aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM ); + break; + case KEY_END: aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM ); + break; + case KEY_PAGEUP: aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM, pEditView ); + break; + case KEY_PAGEDOWN: aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM, pEditView ); + break; + case css::awt::Key::MOVE_TO_BEGIN_OF_LINE: + aPaM = CursorStartOfLine( aPaM ); + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_TO_END_OF_LINE: + aPaM = CursorEndOfLine( aPaM ); + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_WORD_BACKWARD: + aPaM = WordLeft( aPaM ); + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_WORD_FORWARD: + aPaM = WordRight( aPaM ); + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH: + aPaM = CursorStartOfParagraph( aPaM ); + if( aPaM == aOldPaM ) + { + aPaM = CursorLeft( aPaM ); + aPaM = CursorStartOfParagraph( aPaM ); + } + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH: + aPaM = CursorEndOfParagraph( aPaM ); + if( aPaM == aOldPaM ) + { + aPaM = CursorRight( aPaM ); + aPaM = CursorEndOfParagraph( aPaM ); + } + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT: + aPaM = CursorStartOfDoc(); + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_TO_END_OF_DOCUMENT: + aPaM = CursorEndOfDoc(); + bKeyModifySelection = false; + break; + case css::awt::Key::SELECT_TO_BEGIN_OF_LINE: + aPaM = CursorStartOfLine( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_TO_END_OF_LINE: + aPaM = CursorEndOfLine( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_BACKWARD: + aPaM = CursorLeft( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_FORWARD: + aPaM = CursorRight( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_WORD_BACKWARD: + aPaM = WordLeft( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_WORD_FORWARD: + aPaM = WordRight( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH: + aPaM = CursorStartOfParagraph( aPaM ); + if( aPaM == aOldPaM ) + { + aPaM = CursorLeft( aPaM ); + aPaM = CursorStartOfParagraph( aPaM ); + } + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH: + aPaM = CursorEndOfParagraph( aPaM ); + if( aPaM == aOldPaM ) + { + aPaM = CursorRight( aPaM ); + aPaM = CursorEndOfParagraph( aPaM ); + } + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT: + aPaM = CursorStartOfDoc(); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_TO_END_OF_DOCUMENT: + aPaM = CursorEndOfDoc(); + bKeyModifySelection = true; + break; + } + + if ( aOldPaM != aPaM ) + { + CursorMoved( aOldPaM.GetNode() ); + } + + // May cause, a CreateAnchor or deselection all + aSelEngine.SetCurView( pEditView ); + aSelEngine.CursorPosChanging( bKeyModifySelection, aTranslatedKeyEvent.GetKeyCode().IsMod1() ); + EditPaM aOldEnd( pEditView->pImpEditView->GetEditSelection().Max() ); + + { + EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection()); + aNewSelection.Max() = aPaM; + pEditView->pImpEditView->SetEditSelection(aNewSelection); + // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Max()) = aPaM; + } + + if ( bKeyModifySelection ) + { + // Then the selection is expanded ... or the whole selection is painted in case of tiled rendering. + EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? pEditView->pImpEditView->GetEditSelection().Min() : aOldEnd, aPaM ); + pEditView->pImpEditView->DrawSelectionXOR( aTmpNewSel ); + } + else + { + EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection()); + aNewSelection.Min() = aPaM; + pEditView->pImpEditView->SetEditSelection(aNewSelection); + // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Min()) = aPaM; + } + + return pEditView->pImpEditView->GetEditSelection(); +} + +EditPaM ImpEditEngine::CursorVisualStartEnd( EditView const * pEditView, const EditPaM& rPaM, bool bStart ) +{ + EditPaM aPaM( rPaM ); + + sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() ); + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (!pParaPortion) + return aPaM; + + sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false ); + const EditLine& rLine = pParaPortion->GetLines()[nLine]; + bool bEmptyLine = rLine.GetStart() == rLine.GetEnd(); + + pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE; + + if ( !bEmptyLine ) + { + OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()); + + UErrorCode nError = U_ZERO_ERROR; + UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError ); + + const UBiDiLevel nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/; + ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError ); + + sal_Int32 nVisPos = bStart ? 0 : aLine.getLength()-1; + const sal_Int32 nLogPos = ubidi_getLogicalIndex( pBidi, nVisPos, &nError ); + + ubidi_close( pBidi ); + + aPaM.SetIndex( nLogPos + rLine.GetStart() ); + + sal_Int32 nTmp; + sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTmp, true ); + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; + bool bPortionRTL = rTextPortion.IsRightToLeft(); + + if ( bStart ) + { + pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 0 : 1 ); + // Maybe we must be *behind* the character + if ( bPortionRTL && pEditView->IsInsertMode() ) + aPaM.SetIndex( aPaM.GetIndex()+1 ); + } + else + { + pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 1 : 0 ); + if ( !bPortionRTL && pEditView->IsInsertMode() ) + aPaM.SetIndex( aPaM.GetIndex()+1 ); + } + } + + return aPaM; +} + +EditPaM ImpEditEngine::CursorVisualLeftRight( EditView const * pEditView, const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode, bool bVisualToLeft ) +{ + EditPaM aPaM( rPaM ); + + sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() ); + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (!pParaPortion) + return aPaM; + + sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false ); + const EditLine& rLine = pParaPortion->GetLines()[nLine]; + bool bEmptyLine = rLine.GetStart() == rLine.GetEnd(); + + pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE; + + bool bParaRTL = IsRightToLeft( nPara ); + + bool bDone = false; + + if ( bEmptyLine ) + { + if ( bVisualToLeft ) + { + aPaM = CursorUp( aPaM, pEditView ); + if ( aPaM != rPaM ) + aPaM = CursorVisualStartEnd( pEditView, aPaM, false ); + } + else + { + aPaM = CursorDown( aPaM, pEditView ); + if ( aPaM != rPaM ) + aPaM = CursorVisualStartEnd( pEditView, aPaM, true ); + } + + bDone = true; + } + + bool bLogicalBackward = bParaRTL ? !bVisualToLeft : bVisualToLeft; + + if ( !bDone && pEditView->IsInsertMode() ) + { + // Check if we are within a portion and don't have overwrite mode, then it's easy... + sal_Int32 nPortionStart; + sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart ); + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; + + bool bPortionBoundary = ( aPaM.GetIndex() == nPortionStart ) || ( aPaM.GetIndex() == (nPortionStart+rTextPortion.GetLen()) ); + sal_uInt16 nRTLLevel = rTextPortion.GetRightToLeftLevel(); + + // Portion boundary doesn't matter if both have same RTL level + sal_Int32 nRTLLevelNextPortion = -1; + if ( bPortionBoundary && aPaM.GetIndex() && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ) ) + { + sal_Int32 nTmp; + sal_Int32 nNextTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex()+1, nTmp, !bLogicalBackward ); + const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nNextTextPortion]; + nRTLLevelNextPortion = rNextTextPortion.GetRightToLeftLevel(); + } + + if ( !bPortionBoundary || ( nRTLLevel == nRTLLevelNextPortion ) ) + { + if (bVisualToLeft != bool(nRTLLevel % 2)) + { + aPaM = CursorLeft( aPaM, nCharacterIteratorMode ); + pEditView->pImpEditView->SetCursorBidiLevel( 1 ); + } + else + { + aPaM = CursorRight( aPaM, nCharacterIteratorMode ); + pEditView->pImpEditView->SetCursorBidiLevel( 0 ); + } + bDone = true; + } + } + + if ( !bDone ) + { + bool bGotoStartOfNextLine = false; + bool bGotoEndOfPrevLine = false; + + OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()); + const sal_Int32 nPosInLine = aPaM.GetIndex() - rLine.GetStart(); + + UErrorCode nError = U_ZERO_ERROR; + UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError ); + + const UBiDiLevel nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/; + ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError ); + + if ( !pEditView->IsInsertMode() ) + { + bool bEndOfLine = nPosInLine == aLine.getLength(); + sal_Int32 nVisPos = ubidi_getVisualIndex( pBidi, !bEndOfLine ? nPosInLine : nPosInLine-1, &nError ); + if ( bVisualToLeft ) + { + bGotoEndOfPrevLine = nVisPos == 0; + if ( !bEndOfLine ) + nVisPos--; + } + else + { + bGotoStartOfNextLine = nVisPos == (aLine.getLength() - 1); + if ( !bEndOfLine ) + nVisPos++; + } + + if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine ) + { + aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) ); + pEditView->pImpEditView->SetCursorBidiLevel( 0 ); + } + } + else + { + bool bWasBehind = false; + bool bBeforePortion = !nPosInLine || pEditView->pImpEditView->GetCursorBidiLevel() == 1; + if ( nPosInLine && ( !bBeforePortion ) ) // before the next portion + bWasBehind = true; // step one back, otherwise visual will be unusable when rtl portion follows. + + sal_Int32 nPortionStart; + sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, bBeforePortion ); + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; + bool bRTLPortion = rTextPortion.IsRightToLeft(); + + // -1: We are 'behind' the character + tools::Long nVisPos = static_cast<tools::Long>(ubidi_getVisualIndex( pBidi, bWasBehind ? nPosInLine-1 : nPosInLine, &nError )); + if ( bVisualToLeft ) + { + if ( !bWasBehind || bRTLPortion ) + nVisPos--; + } + else + { + if ( bWasBehind || bRTLPortion || bBeforePortion ) + nVisPos++; + } + + bGotoEndOfPrevLine = nVisPos < 0; + bGotoStartOfNextLine = nVisPos >= aLine.getLength(); + + if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine ) + { + aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) ); + + // RTL portion, stay visually on the left side. + sal_Int32 _nPortionStart; + // sal_uInt16 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, !bRTLPortion ); + sal_Int32 _nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), _nPortionStart, true ); + const TextPortion& _rTextPortion = pParaPortion->GetTextPortions()[_nTextPortion]; + if ( bVisualToLeft && !bRTLPortion && _rTextPortion.IsRightToLeft() ) + aPaM.SetIndex( aPaM.GetIndex()+1 ); + else if ( !bVisualToLeft && bRTLPortion && ( bWasBehind || !_rTextPortion.IsRightToLeft() ) ) + aPaM.SetIndex( aPaM.GetIndex()+1 ); + + pEditView->pImpEditView->SetCursorBidiLevel( _nPortionStart ); + } + } + + ubidi_close( pBidi ); + + if ( bGotoEndOfPrevLine ) + { + aPaM = CursorUp( aPaM, pEditView ); + if ( aPaM != rPaM ) + aPaM = CursorVisualStartEnd( pEditView, aPaM, false ); + } + else if ( bGotoStartOfNextLine ) + { + aPaM = CursorDown( aPaM, pEditView ); + if ( aPaM != rPaM ) + aPaM = CursorVisualStartEnd( pEditView, aPaM, true ); + } + } + return aPaM; +} + + +EditPaM ImpEditEngine::CursorLeft( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode ) +{ + EditPaM aCurPaM( rPaM ); + EditPaM aNewPaM( aCurPaM ); + + if ( aCurPaM.GetIndex() ) + { + sal_Int32 nCount = 1; + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + aNewPaM.SetIndex( + _xBI->previousCharacters( + aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount)); + } + else + { + ContentNode* pNode = aCurPaM.GetNode(); + pNode = GetPrevVisNode( pNode ); + if ( pNode ) + { + aNewPaM.SetNode( pNode ); + aNewPaM.SetIndex( pNode->Len() ); + } + } + + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorRight( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode ) +{ + EditPaM aCurPaM( rPaM ); + EditPaM aNewPaM( aCurPaM ); + + if ( aCurPaM.GetIndex() < aCurPaM.GetNode()->Len() ) + { + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + sal_Int32 nCount = 1; + aNewPaM.SetIndex( + _xBI->nextCharacters( + aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount)); + } + else + { + ContentNode* pNode = aCurPaM.GetNode(); + pNode = GetNextVisNode( pNode ); + if ( pNode ) + { + aNewPaM.SetNode( pNode ); + aNewPaM.SetIndex( 0 ); + } + } + + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorUp( const EditPaM& rPaM, EditView const * pView ) +{ + assert(pView && "No View - No Cursor Movement!"); + + const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() ); + OSL_ENSURE( pPPortion, "No matching portion found: CursorUp "); + sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() ); + const EditLine& rLine = pPPortion->GetLines()[nLine]; + + tools::Long nX; + if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW ) + { + nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() ); + pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef; + } + else + nX = pView->pImpEditView->nTravelXPos; + + EditPaM aNewPaM( rPaM ); + if ( nLine ) // same paragraph + { + const EditLine& rPrevLine = pPPortion->GetLines()[nLine-1]; + aNewPaM.SetIndex( GetChar( pPPortion, &rPrevLine, nX ) ); + // If a previous automatically wrapped line, and one has to be exactly + // at the end of this line, the cursor lands on the current line at the + // beginning. See Problem: Last character of an automatically wrapped + // Row = cursor + if ( aNewPaM.GetIndex() && ( aNewPaM.GetIndex() == rLine.GetStart() ) ) + aNewPaM = CursorLeft( aNewPaM ); + } + else // previous paragraph + { + const ParaPortion* pPrevPortion = GetPrevVisPortion( pPPortion ); + if ( pPrevPortion ) + { + const EditLine& rLine2 = pPrevPortion->GetLines()[pPrevPortion->GetLines().Count()-1]; + aNewPaM.SetNode( pPrevPortion->GetNode() ); + aNewPaM.SetIndex( GetChar( pPrevPortion, &rLine2, nX+nOnePixelInRef ) ); + } + } + + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorDown( const EditPaM& rPaM, EditView const * pView ) +{ + OSL_ENSURE( pView, "No View - No Cursor Movement!" ); + + const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() ); + OSL_ENSURE( pPPortion, "No matching portion found: CursorDown" ); + sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() ); + + tools::Long nX; + if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW ) + { + const EditLine& rLine = pPPortion->GetLines()[nLine]; + nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() ); + pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef; + } + else + nX = pView->pImpEditView->nTravelXPos; + + EditPaM aNewPaM( rPaM ); + if ( nLine < pPPortion->GetLines().Count()-1 ) + { + const EditLine& rNextLine = pPPortion->GetLines()[nLine+1]; + aNewPaM.SetIndex( GetChar( pPPortion, &rNextLine, nX ) ); + // Special treatment, see CursorUp ... + if ( ( aNewPaM.GetIndex() == rNextLine.GetEnd() ) && ( aNewPaM.GetIndex() > rNextLine.GetStart() ) && ( aNewPaM.GetIndex() < pPPortion->GetNode()->Len() ) ) + aNewPaM = CursorLeft( aNewPaM ); + } + else // next paragraph + { + const ParaPortion* pNextPortion = GetNextVisPortion( pPPortion ); + if ( pNextPortion ) + { + const EditLine& rLine = pNextPortion->GetLines()[0]; + aNewPaM.SetNode( pNextPortion->GetNode() ); + // Never at the very end when several lines, because then a line + // below the cursor appears. + aNewPaM.SetIndex( GetChar( pNextPortion, &rLine, nX+nOnePixelInRef ) ); + if ( ( aNewPaM.GetIndex() == rLine.GetEnd() ) && ( aNewPaM.GetIndex() > rLine.GetStart() ) && ( pNextPortion->GetLines().Count() > 1 ) ) + aNewPaM = CursorLeft( aNewPaM ); + } + } + + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorStartOfLine( const EditPaM& rPaM ) +{ + const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() ); + OSL_ENSURE( pCurPortion, "No Portion for the PaM ?" ); + sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() ); + const EditLine& rLine = pCurPortion->GetLines()[nLine]; + + EditPaM aNewPaM( rPaM ); + aNewPaM.SetIndex( rLine.GetStart() ); + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorEndOfLine( const EditPaM& rPaM ) +{ + const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() ); + OSL_ENSURE( pCurPortion, "No Portion for the PaM ?" ); + sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() ); + const EditLine& rLine = pCurPortion->GetLines()[nLine]; + + EditPaM aNewPaM( rPaM ); + aNewPaM.SetIndex( rLine.GetEnd() ); + if ( rLine.GetEnd() > rLine.GetStart() ) + { + if ( aNewPaM.GetNode()->IsFeature( aNewPaM.GetIndex() - 1 ) ) + { + // When a soft break, be in front of it! + const EditCharAttrib* pNextFeature = aNewPaM.GetNode()->GetCharAttribs().FindFeature( aNewPaM.GetIndex()-1 ); + if ( pNextFeature && ( pNextFeature->GetItem()->Which() == EE_FEATURE_LINEBR ) ) + aNewPaM = CursorLeft( aNewPaM ); + } + else if ( ( aNewPaM.GetNode()->GetChar( aNewPaM.GetIndex() - 1 ) == ' ' ) && ( aNewPaM.GetIndex() != aNewPaM.GetNode()->Len() ) ) + { + // For a Blank in an auto wrapped line, it makes sense, to stand + // in front of it, since the user wants to be after the word. + // If this is changed, special treatment for Pos1 to End! + aNewPaM = CursorLeft( aNewPaM ); + } + } + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorStartOfParagraph( const EditPaM& rPaM ) +{ + EditPaM aPaM(rPaM); + aPaM.SetIndex(0); + return aPaM; +} + +EditPaM ImpEditEngine::CursorEndOfParagraph( const EditPaM& rPaM ) +{ + EditPaM aPaM(rPaM); + aPaM.SetIndex(rPaM.GetNode()->Len()); + return aPaM; +} + +EditPaM ImpEditEngine::CursorStartOfDoc() +{ + EditPaM aPaM( aEditDoc.GetObject( 0 ), 0 ); + return aPaM; +} + +EditPaM ImpEditEngine::CursorEndOfDoc() +{ + ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 ); + ParaPortion* pLastPortion = GetParaPortions().SafeGetObject( aEditDoc.Count()-1 ); + OSL_ENSURE( pLastNode && pLastPortion, "CursorEndOfDoc: Node or Portion not found" ); + if (!(pLastNode && pLastPortion)) + return EditPaM(); + + if ( !pLastPortion->IsVisible() ) + { + pLastNode = GetPrevVisNode( pLastPortion->GetNode() ); + OSL_ENSURE( pLastNode, "No visible paragraph?" ); + if ( !pLastNode ) + pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 ); + } + + EditPaM aPaM( pLastNode, pLastNode->Len() ); + return aPaM; +} + +EditPaM ImpEditEngine::PageUp( const EditPaM& rPaM, EditView const * pView ) +{ + tools::Rectangle aRect = PaMtoEditCursor( rPaM ); + Point aTopLeft = aRect.TopLeft(); + aTopLeft.AdjustY( -(pView->GetVisArea().GetHeight() *9/10) ); + aTopLeft.AdjustX(nOnePixelInRef ); + if ( aTopLeft.Y() < 0 ) + { + aTopLeft.setY( 0 ); + } + return GetPaM( aTopLeft ); +} + +EditPaM ImpEditEngine::PageDown( const EditPaM& rPaM, EditView const * pView ) +{ + tools::Rectangle aRect = PaMtoEditCursor( rPaM ); + Point aBottomRight = aRect.BottomRight(); + aBottomRight.AdjustY(pView->GetVisArea().GetHeight() *9/10 ); + aBottomRight.AdjustX(nOnePixelInRef ); + tools::Long nHeight = GetTextHeight(); + if ( aBottomRight.Y() > nHeight ) + { + aBottomRight.setY( nHeight-2 ); + } + return GetPaM( aBottomRight ); +} + +EditPaM ImpEditEngine::WordLeft( const EditPaM& rPaM ) +{ + const sal_Int32 nCurrentPos = rPaM.GetIndex(); + EditPaM aNewPaM( rPaM ); + if ( nCurrentPos == 0 ) + { + // Previous paragraph... + sal_Int32 nCurPara = aEditDoc.GetPos( aNewPaM.GetNode() ); + ContentNode* pPrevNode = aEditDoc.GetObject( --nCurPara ); + if ( pPrevNode ) + { + aNewPaM.SetNode( pPrevNode ); + aNewPaM.SetIndex( pPrevNode->Len() ); + } + } + else + { + // we need to increase the position by 1 when retrieving the locale + // since the attribute for the char left to the cursor position is returned + EditPaM aTmpPaM( aNewPaM ); + if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() ) + aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); + lang::Locale aLocale( GetLocale( aTmpPaM ) ); + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + i18n::Boundary aBoundary = + _xBI->getWordBoundary(aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); + if ( aBoundary.startPos >= nCurrentPos ) + aBoundary = _xBI->previousWord( + aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES); + aNewPaM.SetIndex( ( aBoundary.startPos != -1 ) ? aBoundary.startPos : 0 ); + } + + return aNewPaM; +} + +EditPaM ImpEditEngine::WordRight( const EditPaM& rPaM, sal_Int16 nWordType ) +{ + const sal_Int32 nMax = rPaM.GetNode()->Len(); + EditPaM aNewPaM( rPaM ); + if ( aNewPaM.GetIndex() < nMax ) + { + // we need to increase the position by 1 when retrieving the locale + // since the attribute for the char left to the cursor position is returned + EditPaM aTmpPaM( aNewPaM ); + aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); + lang::Locale aLocale( GetLocale( aTmpPaM ) ); + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + i18n::Boundary aBoundary = _xBI->nextWord( + aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), aLocale, nWordType); + aNewPaM.SetIndex( aBoundary.startPos ); + } + // not 'else', maybe the index reached nMax now... + if ( aNewPaM.GetIndex() >= nMax ) + { + // Next paragraph ... + sal_Int32 nCurPara = aEditDoc.GetPos( aNewPaM.GetNode() ); + ContentNode* pNextNode = aEditDoc.GetObject( ++nCurPara ); + if ( pNextNode ) + { + aNewPaM.SetNode( pNextNode ); + aNewPaM.SetIndex( 0 ); + } + } + return aNewPaM; +} + +EditPaM ImpEditEngine::StartOfWord( const EditPaM& rPaM ) +{ + EditPaM aNewPaM( rPaM ); + + // we need to increase the position by 1 when retrieving the locale + // since the attribute for the char left to the cursor position is returned + EditPaM aTmpPaM( aNewPaM ); + if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() ) + aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); + lang::Locale aLocale( GetLocale( aTmpPaM ) ); + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + // tdf#135761 - since this function is only used when a selection is deleted at the left, + // change the search preference of the word boundary from forward to backward. + // For further details of a deletion of a selection check ImpEditEngine::DeleteLeftOrRight. + i18n::Boundary aBoundary = _xBI->getWordBoundary( + rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, false); + + aNewPaM.SetIndex( aBoundary.startPos ); + return aNewPaM; +} + +EditPaM ImpEditEngine::EndOfWord( const EditPaM& rPaM ) +{ + EditPaM aNewPaM( rPaM ); + + // we need to increase the position by 1 when retrieving the locale + // since the attribute for the char left to the cursor position is returned + EditPaM aTmpPaM( aNewPaM ); + if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() ) + aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); + lang::Locale aLocale( GetLocale( aTmpPaM ) ); + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + i18n::Boundary aBoundary = _xBI->getWordBoundary( + rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); + + aNewPaM.SetIndex( aBoundary.endPos ); + return aNewPaM; +} + +EditSelection ImpEditEngine::SelectWord( const EditSelection& rCurSel, sal_Int16 nWordType, bool bAcceptStartOfWord ) +{ + EditSelection aNewSel( rCurSel ); + EditPaM aPaM( rCurSel.Max() ); + + // we need to increase the position by 1 when retrieving the locale + // since the attribute for the char left to the cursor position is returned + EditPaM aTmpPaM( aPaM ); + if ( aTmpPaM.GetIndex() < aPaM.GetNode()->Len() ) + aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); + lang::Locale aLocale( GetLocale( aTmpPaM ) ); + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + sal_Int16 nType = _xBI->getWordType( + aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale); + + if ( nType == i18n::WordType::ANY_WORD ) + { + i18n::Boundary aBoundary = _xBI->getWordBoundary( + aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale, nWordType, true); + + // don't select when cursor at end of word + if ( ( aBoundary.endPos > aPaM.GetIndex() ) && + ( ( aBoundary.startPos < aPaM.GetIndex() ) || ( bAcceptStartOfWord && ( aBoundary.startPos == aPaM.GetIndex() ) ) ) ) + { + aNewSel.Min().SetIndex( aBoundary.startPos ); + aNewSel.Max().SetIndex( aBoundary.endPos ); + } + } + + return aNewSel; +} + +EditSelection ImpEditEngine::SelectSentence( const EditSelection& rCurSel ) + const +{ + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + const EditPaM& rPaM = rCurSel.Min(); + const ContentNode* pNode = rPaM.GetNode(); + // #i50710# line breaks are marked with 0x01 - the break iterator prefers 0x0a for that + const OUString sParagraph = pNode->GetString().replaceAll("\x01", "\x0a"); + //return Null if search starts at the beginning of the string + sal_Int32 nStart = rPaM.GetIndex() ? _xBI->beginOfSentence( sParagraph, rPaM.GetIndex(), GetLocale( rPaM ) ) : 0; + + sal_Int32 nEnd = _xBI->endOfSentence( + pNode->GetString(), rPaM.GetIndex(), GetLocale(rPaM)); + + EditSelection aNewSel( rCurSel ); + OSL_ENSURE(pNode->Len() ? (nStart < pNode->Len()) : (nStart == 0), "sentence start index out of range"); + OSL_ENSURE(nEnd <= pNode->Len(), "sentence end index out of range"); + aNewSel.Min().SetIndex( nStart ); + aNewSel.Max().SetIndex( nEnd ); + return aNewSel; +} + +bool ImpEditEngine::IsInputSequenceCheckingRequired( sal_Unicode nChar, const EditSelection& rCurSel ) const +{ + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + if (!pCTLOptions) + pCTLOptions.reset( new SvtCTLOptions ); + + // get the index that really is first + const sal_Int32 nFirstPos = std::min(rCurSel.Min().GetIndex(), rCurSel.Max().GetIndex()); + + bool bIsSequenceChecking = + pCTLOptions->IsCTLFontEnabled() && + pCTLOptions->IsCTLSequenceChecking() && + nFirstPos != 0 && /* first char needs not to be checked */ + _xBI.is() && i18n::ScriptType::COMPLEX == _xBI->getScriptType( OUString( nChar ), 0 ); + + return bIsSequenceChecking; +} + +static bool lcl_HasStrongLTR ( std::u16string_view rTxt, sal_Int32 nStart, sal_Int32 nEnd ) + { + for( sal_Int32 nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx ) + { + const UCharDirection nCharDir = u_charDirection ( rTxt[ nCharIdx ] ); + if ( nCharDir == U_LEFT_TO_RIGHT || + nCharDir == U_LEFT_TO_RIGHT_EMBEDDING || + nCharDir == U_LEFT_TO_RIGHT_OVERRIDE ) + return true; + } + return false; + } + + +void ImpEditEngine::InitScriptTypes( sal_Int32 nPara ) +{ + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (!pParaPortion) + return; + + ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + rTypes.clear(); + + ContentNode* pNode = pParaPortion->GetNode(); + if ( !pNode->Len() ) + return; + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + + OUString aText = pNode->GetString(); + + // To handle fields put the character from the field in the string, + // because endOfScript( ... ) will skip the CH_FEATURE, because this is WEAK + const EditCharAttrib* pField = pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, 0 ); + while ( pField ) + { + const OUString aFldText = static_cast<const EditCharAttribField*>(pField)->GetFieldValue(); + if ( !aFldText.isEmpty() ) + { + aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(0,1) ); + short nFldScriptType = _xBI->getScriptType( aFldText, 0 ); + + for ( sal_Int32 nCharInField = 1; nCharInField < aFldText.getLength(); nCharInField++ ) + { + short nTmpType = _xBI->getScriptType( aFldText, nCharInField ); + + // First char from field wins... + if ( nFldScriptType == i18n::ScriptType::WEAK ) + { + nFldScriptType = nTmpType; + aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) ); + } + + // ... but if the first one is LATIN, and there are CJK or CTL chars too, + // we prefer that ScriptType because we need another font. + if ( ( nTmpType == i18n::ScriptType::ASIAN ) || ( nTmpType == i18n::ScriptType::COMPLEX ) ) + { + aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) ); + break; + } + } + } + // #112831# Last Field might go from 0xffff to 0x0000 + pField = pField->GetEnd() ? pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, pField->GetEnd() ) : nullptr; + } + + sal_Int32 nTextLen = aText.getLength(); + + sal_Int32 nPos = 0; + short nScriptType = _xBI->getScriptType( aText, nPos ); + rTypes.emplace_back( nScriptType, nPos, nTextLen ); + nPos = _xBI->endOfScript( aText, nPos, nScriptType ); + while ( ( nPos != -1 ) && ( nPos < nTextLen ) ) + { + rTypes.back().nEndPos = nPos; + + nScriptType = _xBI->getScriptType( aText, nPos ); + tools::Long nEndPos = _xBI->endOfScript( aText, nPos, nScriptType ); + + if ( ( nScriptType == i18n::ScriptType::WEAK ) || ( nScriptType == rTypes.back().nScriptType ) ) + { + // Expand last ScriptTypePosInfo, don't create weak or unnecessary portions + rTypes.back().nEndPos = nEndPos; + } + else + { + if ( _xBI->getScriptType( aText, nPos - 1 ) == i18n::ScriptType::WEAK ) + { + switch ( u_charType(aText.iterateCodePoints(&nPos, 0) ) ) { + case U_NON_SPACING_MARK: + case U_ENCLOSING_MARK: + case U_COMBINING_SPACING_MARK: + --nPos; + rTypes.back().nEndPos--; + break; + } + } + rTypes.emplace_back( nScriptType, nPos, nTextLen ); + } + + nPos = nEndPos; + } + + if ( rTypes[0].nScriptType == i18n::ScriptType::WEAK ) + rTypes[0].nScriptType = ( rTypes.size() > 1 ) ? rTypes[1].nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() ); + + // create writing direction information: + if ( pParaPortion->aWritingDirectionInfos.empty() ) + InitWritingDirections( nPara ); + + // i89825: Use CTL font for numbers embedded into an RTL run: + WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos; + for (const WritingDirectionInfo & rDirInfo : rDirInfos) + { + const sal_Int32 nStart = rDirInfo.nStartPos; + const sal_Int32 nEnd = rDirInfo.nEndPos; + const sal_uInt8 nCurrDirType = rDirInfo.nType; + + if ( nCurrDirType % 2 == UBIDI_RTL || // text in RTL run + ( nCurrDirType > UBIDI_LTR && !lcl_HasStrongLTR( aText, nStart, nEnd ) ) ) // non-strong text in embedded LTR run + { + size_t nIdx = 0; + + // Skip entries in ScriptArray which are not inside the RTL run: + while ( nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart ) + ++nIdx; + + // Remove any entries *inside* the current run: + while (nIdx < rTypes.size() && rTypes[nIdx].nEndPos <= nEnd) + { + // coverity[use_iterator] - we're protected from a bad iterator by the above condition + rTypes.erase(rTypes.begin() + nIdx); + } + + // special case: + if(nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart && rTypes[nIdx].nEndPos > nEnd) + { + rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( rTypes[nIdx].nScriptType, nEnd, rTypes[nIdx].nEndPos ) ); + rTypes[nIdx].nEndPos = nStart; + } + + if( nIdx ) + rTypes[nIdx - 1].nEndPos = nStart; + + rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( i18n::ScriptType::COMPLEX, nStart, nEnd) ); + ++nIdx; + + if( nIdx < rTypes.size() ) + rTypes[nIdx].nStartPos = nEnd; + } + } +} + +namespace { + +struct FindByPos +{ + explicit FindByPos(sal_Int32 nPos) + : mnPos(nPos) + { + } + + bool operator()(const ScriptTypePosInfos::value_type& rValue) + { + return rValue.nStartPos <= mnPos && rValue.nEndPos >= mnPos; + } + +private: + sal_Int32 mnPos; +}; + +} + +sal_uInt16 ImpEditEngine::GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEndPos ) const +{ + sal_uInt16 nScriptType = 0; + + if ( pEndPos ) + *pEndPos = rPaM.GetNode()->Len(); + + if ( rPaM.GetNode()->Len() ) + { + sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() ); + const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (pParaPortion) + { + if ( pParaPortion->aScriptInfos.empty() ) + const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); + + const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + + const sal_Int32 nPos = rPaM.GetIndex(); + ScriptTypePosInfos::const_iterator itr = std::find_if(rTypes.begin(), rTypes.end(), FindByPos(nPos)); + if(itr != rTypes.end()) + { + nScriptType = itr->nScriptType; + if( pEndPos ) + *pEndPos = itr->nEndPos; + } + } + } + return nScriptType ? nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() ); +} + +SvtScriptType ImpEditEngine::GetItemScriptType( const EditSelection& rSel ) const +{ + EditSelection aSel( rSel ); + aSel.Adjust( aEditDoc ); + + SvtScriptType nScriptType = SvtScriptType::NONE; + + sal_Int32 nStartPara = GetEditDoc().GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndPara = GetEditDoc().GetPos( aSel.Max().GetNode() ); + + for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ ) + { + const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (!pParaPortion) + continue; + + if ( pParaPortion->aScriptInfos.empty() ) + const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); + + const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + + // find all the scripts of this range + sal_Int32 nS = ( nPara == nStartPara ) ? aSel.Min().GetIndex() : 0; + sal_Int32 nE = ( nPara == nEndPara ) ? aSel.Max().GetIndex() : pParaPortion->GetNode()->Len(); + + //no selection, just bare cursor + if (nStartPara == nEndPara && nS == nE) + { + //If we are not at the start of the paragraph we want the properties of the + //preceding character. Otherwise get the properties of the next (or what the + //next would have if it existed) + if (nS != 0) + --nS; + else + ++nE; + } + + for (const ScriptTypePosInfo & rType : rTypes) + { + bool bStartInRange = rType.nStartPos <= nS && nS < rType.nEndPos; + bool bEndInRange = rType.nStartPos < nE && nE <= rType.nEndPos; + + if (bStartInRange || bEndInRange) + { + if ( rType.nScriptType != i18n::ScriptType::WEAK ) + nScriptType |= SvtLanguageOptions::FromI18NToSvtScriptType( rType.nScriptType ); + } + } + } + return bool(nScriptType) ? nScriptType : SvtLanguageOptions::GetScriptTypeOfLanguage( GetDefaultLanguage() ); +} + +bool ImpEditEngine::IsScriptChange( const EditPaM& rPaM ) const +{ + bool bScriptChange = false; + + if ( rPaM.GetNode()->Len() ) + { + sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() ); + const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (pParaPortion) + { + if ( pParaPortion->aScriptInfos.empty() ) + const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); + + const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + const sal_Int32 nPos = rPaM.GetIndex(); + for (const ScriptTypePosInfo & rType : rTypes) + { + if ( rType.nStartPos == nPos ) + { + bScriptChange = true; + break; + } + } + } + } + return bScriptChange; +} + +bool ImpEditEngine::HasScriptType( sal_Int32 nPara, sal_uInt16 nType ) const +{ + bool bTypeFound = false; + + const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (pParaPortion) + { + if ( pParaPortion->aScriptInfos.empty() ) + const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); + + const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + for ( size_t n = rTypes.size(); n && !bTypeFound; ) + { + if ( rTypes[--n].nScriptType == nType ) + bTypeFound = true; + } + } + return bTypeFound; +} + +void ImpEditEngine::InitWritingDirections( sal_Int32 nPara ) +{ + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (!pParaPortion) + return; + + WritingDirectionInfos& rInfos = pParaPortion->aWritingDirectionInfos; + rInfos.clear(); + + if (pParaPortion->GetNode()->Len()) + { + const OUString aText = pParaPortion->GetNode()->GetString(); + + // Bidi functions from icu 2.0 + + UErrorCode nError = U_ZERO_ERROR; + UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError ); + nError = U_ZERO_ERROR; + + const UBiDiLevel nBidiLevel = IsRightToLeft(nPara) ? 1 /*RTL*/ : 0 /*LTR*/; + ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError ); + nError = U_ZERO_ERROR; + + int32_t nCount = ubidi_countRuns( pBidi, &nError ); + + /* ubidi_countRuns can return -1 in case of error */ + if (nCount > 0) + { + int32_t nStart = 0; + int32_t nEnd; + UBiDiLevel nCurrDir; + + for (int32_t nIdx = 0; nIdx < nCount; ++nIdx) + { + ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir ); + rInfos.emplace_back( nCurrDir, nStart, nEnd ); + nStart = nEnd; + } + } + + ubidi_close( pBidi ); + } + + // No infos mean ubidi error, default to LTR + if ( rInfos.empty() ) + rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->Len() ); + +} + +bool ImpEditEngine::IsRightToLeft( sal_Int32 nPara ) const +{ + bool bR2L = false; + const SvxFrameDirectionItem* pFrameDirItem = nullptr; + + if ( !IsEffectivelyVertical() ) + { + bR2L = GetDefaultHorizontalTextDirection() == EEHorizontalTextDirection::R2L; + pFrameDirItem = &GetParaAttrib( nPara, EE_PARA_WRITINGDIR ); + if ( pFrameDirItem->GetValue() == SvxFrameDirection::Environment ) + { + // #103045# if DefaultHorizontalTextDirection is set, use that value, otherwise pool default. + if ( GetDefaultHorizontalTextDirection() != EEHorizontalTextDirection::Default ) + { + pFrameDirItem = nullptr; // bR2L already set to default horizontal text direction + } + else + { + // Use pool default + pFrameDirItem = &GetEmptyItemSet().Get(EE_PARA_WRITINGDIR); + } + } + } + + if ( pFrameDirItem ) + bR2L = pFrameDirItem->GetValue() == SvxFrameDirection::Horizontal_RL_TB; + + return bR2L; +} + +bool ImpEditEngine::HasDifferentRTLLevels( const ContentNode* pNode ) +{ + bool bHasDifferentRTLLevels = false; + + sal_Int32 nPara = GetEditDoc().GetPos( pNode ); + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (pParaPortion) + { + sal_uInt16 nRTLLevel = IsRightToLeft( nPara ) ? 1 : 0; + for ( sal_Int32 n = 0; n < pParaPortion->GetTextPortions().Count(); n++ ) + { + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n]; + if ( rTextPortion.GetRightToLeftLevel() != nRTLLevel ) + { + bHasDifferentRTLLevels = true; + break; + } + } + } + return bHasDifferentRTLLevels; +} + + +sal_uInt8 ImpEditEngine::GetRightToLeft( sal_Int32 nPara, sal_Int32 nPos, sal_Int32* pStart, sal_Int32* pEnd ) +{ + sal_uInt8 nRightToLeft = 0; + + ContentNode* pNode = aEditDoc.GetObject( nPara ); + if ( pNode && pNode->Len() ) + { + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (pParaPortion) + { + if ( pParaPortion->aWritingDirectionInfos.empty() ) + InitWritingDirections( nPara ); + + WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos; + for (const WritingDirectionInfo & rDirInfo : rDirInfos) + { + if ( ( rDirInfo.nStartPos <= nPos ) && ( rDirInfo.nEndPos >= nPos ) ) + { + nRightToLeft = rDirInfo.nType; + if ( pStart ) + *pStart = rDirInfo.nStartPos; + if ( pEnd ) + *pEnd = rDirInfo.nEndPos; + break; + } + } + } + } + return nRightToLeft; +} + +SvxAdjust ImpEditEngine::GetJustification( sal_Int32 nPara ) const +{ + SvxAdjust eJustification = SvxAdjust::Left; + + if ( !aStatus.IsOutliner() ) + { + eJustification = GetParaAttrib( nPara, EE_PARA_JUST ).GetAdjust(); + + if ( IsRightToLeft( nPara ) ) + { + if ( eJustification == SvxAdjust::Left ) + eJustification = SvxAdjust::Right; + else if ( eJustification == SvxAdjust::Right ) + eJustification = SvxAdjust::Left; + } + } + return eJustification; +} + +SvxCellJustifyMethod ImpEditEngine::GetJustifyMethod( sal_Int32 nPara ) const +{ + const SvxJustifyMethodItem& rItem = GetParaAttrib(nPara, EE_PARA_JUST_METHOD); + return static_cast<SvxCellJustifyMethod>(rItem.GetEnumValue()); +} + +SvxCellVerJustify ImpEditEngine::GetVerJustification( sal_Int32 nPara ) const +{ + const SvxVerJustifyItem& rItem = GetParaAttrib(nPara, EE_PARA_VER_JUST); + return static_cast<SvxCellVerJustify>(rItem.GetEnumValue()); +} + +// Text changes +void ImpEditEngine::ImpRemoveChars( const EditPaM& rPaM, sal_Int32 nChars ) +{ + if ( IsUndoEnabled() && !IsInUndo() ) + { + const OUString aStr( rPaM.GetNode()->Copy( rPaM.GetIndex(), nChars ) ); + + // Check whether attributes are deleted or changed: + const sal_Int32 nStart = rPaM.GetIndex(); + const sal_Int32 nEnd = nStart + nChars; + const CharAttribList::AttribsType& rAttribs = rPaM.GetNode()->GetCharAttribs().GetAttribs(); + for (const auto & rAttrib : rAttribs) + { + const EditCharAttrib& rAttr = *rAttrib; + if (rAttr.GetEnd() >= nStart && rAttr.GetStart() < nEnd) + { + EditSelection aSel( rPaM ); + aSel.Max().SetIndex( aSel.Max().GetIndex() + nChars ); + InsertUndo( CreateAttribUndo( aSel, GetEmptyItemSet() ) ); + break; // for + } + } + InsertUndo(std::make_unique<EditUndoRemoveChars>(pEditEngine, CreateEPaM(rPaM), aStr)); + } + + aEditDoc.RemoveChars( rPaM, nChars ); +} + +EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 nNewPos ) +{ + aOldPositions.Justify(); + bool bValidAction = ( static_cast<tools::Long>(nNewPos) < aOldPositions.Min() ) || ( static_cast<tools::Long>(nNewPos) > aOldPositions.Max() ); + OSL_ENSURE( bValidAction, "Move in itself?" ); + OSL_ENSURE( aOldPositions.Max() <= static_cast<tools::Long>(GetParaPortions().Count()), "totally over it: MoveParagraphs" ); + + EditSelection aSelection; + + if ( !bValidAction ) + { + aSelection = aEditDoc.GetStartPaM(); + return aSelection; + } + + sal_Int32 nParaCount = GetParaPortions().Count(); + + if ( nNewPos >= nParaCount ) + nNewPos = nParaCount; + + // Height may change when moving first or last Paragraph + ParaPortion* pRecalc1 = nullptr; + ParaPortion* pRecalc2 = nullptr; + ParaPortion* pRecalc3 = nullptr; + ParaPortion* pRecalc4 = nullptr; + + if ( nNewPos == 0 ) // Move to Start + { + pRecalc1 = GetParaPortions()[0]; + pRecalc2 = GetParaPortions()[aOldPositions.Min()]; + + } + else if ( nNewPos == nParaCount ) + { + pRecalc1 = GetParaPortions()[nParaCount-1]; + pRecalc2 = GetParaPortions()[aOldPositions.Max()]; + } + + if ( aOldPositions.Min() == 0 ) // Move from Start + { + pRecalc3 = GetParaPortions()[0]; + pRecalc4 = GetParaPortions()[aOldPositions.Max()+1]; + } + else if ( aOldPositions.Max() == (nParaCount-1) ) + { + pRecalc3 = GetParaPortions()[aOldPositions.Max()]; + pRecalc4 = GetParaPortions()[aOldPositions.Min()-1]; + } + + MoveParagraphsInfo aMoveParagraphsInfo( aOldPositions.Min(), aOldPositions.Max(), nNewPos ); + aBeginMovingParagraphsHdl.Call( aMoveParagraphsInfo ); + + if ( IsUndoEnabled() && !IsInUndo()) + InsertUndo(std::make_unique<EditUndoMoveParagraphs>(pEditEngine, aOldPositions, nNewPos)); + + // do not lose sight of the Position ! + ParaPortion* pDestPortion = GetParaPortions().SafeGetObject( nNewPos ); + + ParaPortionList aTmpPortionList; + for (tools::Long i = aOldPositions.Min(); i <= aOldPositions.Max(); i++ ) + { + // always aOldPositions.Min(), since Remove(). + std::unique_ptr<ParaPortion> pTmpPortion = GetParaPortions().Release(aOldPositions.Min()); + aEditDoc.Release( aOldPositions.Min() ); + aTmpPortionList.Append(std::move(pTmpPortion)); + } + + sal_Int32 nRealNewPos = pDestPortion ? GetParaPortions().GetPos( pDestPortion ) : GetParaPortions().Count(); + assert( nRealNewPos != EE_PARA_NOT_FOUND && "ImpMoveParagraphs: Invalid Position!" ); + + sal_Int32 i = 0; + while( aTmpPortionList.Count() > 0 ) + { + std::unique_ptr<ParaPortion> pTmpPortion = aTmpPortionList.Release(0); + if ( i == 0 ) + aSelection.Min().SetNode( pTmpPortion->GetNode() ); + + aSelection.Max().SetNode( pTmpPortion->GetNode() ); + aSelection.Max().SetIndex( pTmpPortion->GetNode()->Len() ); + + ContentNode* pN = pTmpPortion->GetNode(); + aEditDoc.Insert(nRealNewPos+i, pN); + + GetParaPortions().Insert(nRealNewPos+i, std::move(pTmpPortion)); + ++i; + } + + aEndMovingParagraphsHdl.Call( aMoveParagraphsInfo ); + + if ( GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_PARAGRAPHSMOVED ); + aNotify.nParagraph = nNewPos; + aNotify.nParam1 = aOldPositions.Min(); + aNotify.nParam2 = aOldPositions.Max(); + GetNotifyHdl().Call( aNotify ); + } + + aEditDoc.SetModified( true ); + + if ( pRecalc1 ) + CalcHeight( pRecalc1 ); + if ( pRecalc2 ) + CalcHeight( pRecalc2 ); + if ( pRecalc3 ) + CalcHeight( pRecalc3 ); + if ( pRecalc4 ) + CalcHeight( pRecalc4 ); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + ParaPortionList::DbgCheck(GetParaPortions(), aEditDoc); +#endif + return aSelection; +} + + +EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward ) +{ + OSL_ENSURE( pLeft != pRight, "Join together the same paragraph ?" ); + OSL_ENSURE( aEditDoc.GetPos( pLeft ) != EE_PARA_NOT_FOUND, "Inserted node not found (1)" ); + OSL_ENSURE( aEditDoc.GetPos( pRight ) != EE_PARA_NOT_FOUND, "Inserted node not found (2)" ); + + // #i120020# it is possible that left and right are *not* in the desired order (left/right) + // so correct it. This correction is needed, else an invalid SfxLinkUndoAction will be + // created from ConnectParagraphs below. Assert this situation, it should be corrected by the + // caller. + if(aEditDoc.GetPos( pLeft ) > aEditDoc.GetPos( pRight )) + { + OSL_ENSURE(false, "ImpConnectParagraphs with wrong order of pLeft/pRight nodes (!)"); + std::swap(pLeft, pRight); + } + + sal_Int32 nParagraphTobeDeleted = aEditDoc.GetPos( pRight ); + aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pRight, nParagraphTobeDeleted )); + + GetEditEnginePtr()->ParagraphConnected( aEditDoc.GetPos( pLeft ), aEditDoc.GetPos( pRight ) ); + + if ( IsUndoEnabled() && !IsInUndo() ) + { + InsertUndo( std::make_unique<EditUndoConnectParas>(pEditEngine, + aEditDoc.GetPos( pLeft ), pLeft->Len(), + pLeft->GetContentAttribs().GetItems(), pRight->GetContentAttribs().GetItems(), + pLeft->GetStyleSheet(), pRight->GetStyleSheet(), bBackward ) ); + } + + if ( bBackward ) + { + pLeft->SetStyleSheet( pRight->GetStyleSheet() ); + // it feels wrong to set pLeft's attribs if pRight is empty, tdf#128046 + if ( pRight->Len() ) + pLeft->GetContentAttribs().GetItems().Set( pRight->GetContentAttribs().GetItems() ); + pLeft->GetCharAttribs().GetDefFont() = pRight->GetCharAttribs().GetDefFont(); + } + + ParaAttribsChanged( pLeft, true ); + + // First search for Portions since pRight is gone after ConnectParagraphs. + ParaPortion* pLeftPortion = FindParaPortion( pLeft ); + OSL_ENSURE( pLeftPortion, "Blind Portion in ImpConnectParagraphs(1)" ); + + if ( GetStatus().DoOnlineSpelling() ) + { + sal_Int32 nEnd = pLeft->Len(); + sal_Int32 nInv = nEnd ? nEnd-1 : nEnd; + pLeft->GetWrongList()->ClearWrongs( nInv, static_cast<size_t>(-1), pLeft ); // Possibly remove one + pLeft->GetWrongList()->SetInvalidRange(nInv, nEnd+1); + // Take over misspelled words + WrongList* pRWrongs = pRight->GetWrongList(); + for (auto & elem : *pRWrongs) + { + if (elem.mnStart != 0) // Not a subsequent + { + elem.mnStart = elem.mnStart + nEnd; + elem.mnEnd = elem.mnEnd + nEnd; + pLeft->GetWrongList()->push_back(elem); + } + } + } + + if ( IsCallParaInsertedOrDeleted() ) + GetEditEnginePtr()->ParagraphDeleted( nParagraphTobeDeleted ); + + EditPaM aPaM = aEditDoc.ConnectParagraphs( pLeft, pRight ); + GetParaPortions().Remove( nParagraphTobeDeleted ); + + pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() ); + + // the right node is deleted by EditDoc:ConnectParagraphs(). + if ( GetTextRanger() ) + { + // By joining together the two, the left is although reformatted, + // however if its height does not change then the formatting receives + // the change of the total text height too late... + for ( sal_Int32 n = nParagraphTobeDeleted; n < GetParaPortions().Count(); n++ ) + { + ParaPortion* pPP = GetParaPortions()[n]; + pPP->MarkSelectionInvalid( 0 ); + pPP->GetLines().Reset(); + } + } + + TextModified(); + + return aPaM; +} + +EditPaM ImpEditEngine::DeleteLeftOrRight( const EditSelection& rSel, sal_uInt8 nMode, DeleteMode nDelMode ) +{ + OSL_ENSURE( !rSel.DbgIsBuggy( aEditDoc ), "Index out of range in DeleteLeftOrRight" ); + + if ( rSel.HasRange() ) // only then Delete Selection + return ImpDeleteSelection( rSel ); + + EditPaM aCurPos( rSel.Max() ); + EditPaM aDelStart( aCurPos ); + EditPaM aDelEnd( aCurPos ); + if ( nMode == DEL_LEFT ) + { + if ( nDelMode == DeleteMode::Simple ) + { + sal_uInt16 nCharMode = i18n::CharacterIteratorMode::SKIPCHARACTER; + // Check if we are deleting a CJK ideograph variance sequence (IVS). + sal_Int32 nIndex = aCurPos.GetIndex(); + if (nIndex > 0) + { + const OUString& rString = aCurPos.GetNode()->GetString(); + sal_Int32 nCode = rString.iterateCodePoints(&nIndex, -1); + if (unicode::isIVSSelector(nCode) && nIndex > 0 && + unicode::isCJKIVSCharacter(rString.iterateCodePoints(&nIndex, -1))) + { + nCharMode = i18n::CharacterIteratorMode::SKIPCELL; + } + } + aDelStart = CursorLeft(aCurPos, nCharMode); + } + else if ( nDelMode == DeleteMode::RestOfWord ) + { + aDelStart = StartOfWord( aCurPos ); + if ( aDelStart.GetIndex() == aCurPos.GetIndex() ) + aDelStart = WordLeft( aCurPos ); + } + else // DELMODE_RESTOFCONTENT + { + aDelStart.SetIndex( 0 ); + if ( aDelStart == aCurPos ) + { + // Complete paragraph previous + ContentNode* pPrev = GetPrevVisNode( aCurPos.GetNode() ); + if ( pPrev ) + aDelStart = EditPaM( pPrev, 0 ); + } + } + } + else + { + if ( nDelMode == DeleteMode::Simple ) + { + aDelEnd = CursorRight( aCurPos ); + } + else if ( nDelMode == DeleteMode::RestOfWord ) + { + aDelEnd = EndOfWord( aCurPos ); + if (aDelEnd.GetIndex() == aCurPos.GetIndex()) + { + const sal_Int32 nLen(aCurPos.GetNode()->Len()); + // end of para? + if (aDelEnd.GetIndex() == nLen) + { + ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() ); + if ( pNext ) + aDelEnd = EditPaM( pNext, 0 ); + } + else // there's still something to delete on the right + { + aDelEnd = EndOfWord( WordRight( aCurPos ) ); + } + } + } + else // DELMODE_RESTOFCONTENT + { + aDelEnd.SetIndex( aCurPos.GetNode()->Len() ); + if ( aDelEnd == aCurPos ) + { + // Complete paragraph next + ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() ); + if ( pNext ) + aDelEnd = EditPaM( pNext, pNext->Len() ); + } + } + } + + // ConnectParagraphs not enough for different Nodes when + // DeleteMode::RestOfContent. + if ( ( nDelMode == DeleteMode::RestOfContent ) || ( aDelStart.GetNode() == aDelEnd.GetNode() ) ) + return ImpDeleteSelection( EditSelection( aDelStart, aDelEnd ) ); + + return ImpConnectParagraphs(aDelStart.GetNode(), aDelEnd.GetNode()); +} + +EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) +{ + if ( !rCurSel.HasRange() ) + return rCurSel.Min(); + + EditSelection aCurSel(rCurSel); + aCurSel.Adjust( aEditDoc ); + EditPaM aStartPaM(aCurSel.Min()); + EditPaM aEndPaM(aCurSel.Max()); + + CursorMoved( aStartPaM.GetNode() ); // only so that newly set Attributes disappear... + CursorMoved( aEndPaM.GetNode() ); // only so that newly set Attributes disappear... + + OSL_ENSURE( aStartPaM.GetIndex() <= aStartPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" ); + OSL_ENSURE( aEndPaM.GetIndex() <= aEndPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" ); + + sal_Int32 nStartNode = aEditDoc.GetPos( aStartPaM.GetNode() ); + sal_Int32 nEndNode = aEditDoc.GetPos( aEndPaM.GetNode() ); + + OSL_ENSURE( nEndNode != EE_PARA_NOT_FOUND, "Start > End ?!" ); + OSL_ENSURE( nStartNode <= nEndNode, "Start > End ?!" ); + + // Remove all nodes in between... + for ( sal_Int32 z = nStartNode+1; z < nEndNode; z++ ) + { + // Always nStartNode+1, due to Remove()! + ImpRemoveParagraph( nStartNode+1 ); + } + + if ( aStartPaM.GetNode() != aEndPaM.GetNode() ) + { + // The Rest of the StartNodes... + ImpRemoveChars( aStartPaM, aStartPaM.GetNode()->Len() - aStartPaM.GetIndex() ); + ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() ); + OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(3)" ); + pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() ); + + // The beginning of the EndNodes... + const sal_Int32 nChars = aEndPaM.GetIndex(); + aEndPaM.SetIndex( 0 ); + ImpRemoveChars( aEndPaM, nChars ); + pPortion = FindParaPortion( aEndPaM.GetNode() ); + OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(4)" ); + pPortion->MarkSelectionInvalid( 0 ); + // Join together... + aStartPaM = ImpConnectParagraphs( aStartPaM.GetNode(), aEndPaM.GetNode() ); + } + else + { + ImpRemoveChars( aStartPaM, aEndPaM.GetIndex() - aStartPaM.GetIndex() ); + ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() ); + OSL_ENSURE( pPortion, "Blind Portion in ImpDeleteSelection(5)" ); + pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() ); + } + + UpdateSelections(); + TextModified(); + return aStartPaM; +} + +void ImpEditEngine::ImpRemoveParagraph( sal_Int32 nPara ) +{ + ContentNode* pNode = aEditDoc.GetObject( nPara ); + ContentNode* pNextNode = aEditDoc.GetObject( nPara+1 ); + + OSL_ENSURE( pNode, "Blind Node in ImpRemoveParagraph" ); + + aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pNode, nPara )); + + // The node is managed by the undo and possibly destroyed! + aEditDoc.Release( nPara ); + GetParaPortions().Remove( nPara ); + + if ( IsCallParaInsertedOrDeleted() ) + { + GetEditEnginePtr()->ParagraphDeleted( nPara ); + } + + // Extra-Space may be determined again in the following. For + // ParaAttribsChanged the paragraph is unfortunately formatted again, + // however this method should not be time critical! + if ( pNextNode ) + ParaAttribsChanged( pNextNode ); + + if ( IsUndoEnabled() && !IsInUndo() ) + InsertUndo(std::make_unique<EditUndoDelContent>(pEditEngine, pNode, nPara)); + else + { + aEditDoc.RemoveItemsFromPool(*pNode); + if ( pNode->GetStyleSheet() ) + EndListening( *pNode->GetStyleSheet() ); + delete pNode; + } +} + +EditPaM ImpEditEngine::AutoCorrect( const EditSelection& rCurSel, sal_Unicode c, + bool bOverwrite, vcl::Window const * pFrameWin ) +{ + // i.e. Calc has special needs regarding a leading single quotation mark + // when starting cell input. + if (c == '\'' && !IsReplaceLeadingSingleQuotationMark() && + rCurSel.Min() == rCurSel.Max() && rCurSel.Max().GetIndex() == 0) + { + return InsertTextUserInput( rCurSel, c, bOverwrite ); + } + + EditSelection aSel( rCurSel ); + SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect(); + if ( pAutoCorrect ) + { + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( rCurSel ); + + // #i78661 allow application to turn off capitalization of + // start sentence explicitly. + // (This is done by setting IsFirstWordCapitalization to sal_False.) + bool bOldCapitalStartSentence = pAutoCorrect->IsAutoCorrFlag( ACFlags::CapitalStartSentence ); + if (!IsFirstWordCapitalization()) + { + ESelection aESel( CreateESel(aSel) ); + EditSelection aFirstWordSel; + EditSelection aSecondWordSel; + if (aESel.nEndPara == 0) // is this the first para? + { + // select first word... + // start by checking if para starts with word. + aFirstWordSel = SelectWord( CreateSel(ESelection()) ); + if (aFirstWordSel.Min().GetIndex() == 0 && aFirstWordSel.Max().GetIndex() == 0) + { + // para does not start with word -> select next/first word + EditPaM aRightWord( WordRight( aFirstWordSel.Max() ) ); + aFirstWordSel = SelectWord( EditSelection( aRightWord ) ); + } + + // select second word + // (sometimes aSel might not point to the end of the first word + // but to some following char like '.'. ':', ... + // In those cases we need aSecondWordSel to see if aSel + // will actually effect the first word.) + EditPaM aRight2Word( WordRight( aFirstWordSel.Max() ) ); + aSecondWordSel = SelectWord( EditSelection( aRight2Word ) ); + } + bool bIsFirstWordInFirstPara = aESel.nEndPara == 0 && + aFirstWordSel.Max().GetIndex() <= aSel.Max().GetIndex() && + aSel.Max().GetIndex() <= aSecondWordSel.Min().GetIndex(); + + if (bIsFirstWordInFirstPara) + pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, IsFirstWordCapitalization() ); + } + + ContentNode* pNode = aSel.Max().GetNode(); + const sal_Int32 nIndex = aSel.Max().GetIndex(); + EdtAutoCorrDoc aAuto(pEditEngine, pNode, nIndex, c); + // FIXME: this _must_ be called with reference to the actual node text! + OUString const& rNodeString(pNode->GetString()); + pAutoCorrect->DoAutoCorrect( + aAuto, rNodeString, nIndex, c, !bOverwrite, mbNbspRunNext, pFrameWin ); + aSel.Max().SetIndex( aAuto.GetCursor() ); + + // #i78661 since the SvxAutoCorrect object used here is + // shared we need to reset the value to its original state. + pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, bOldCapitalStartSentence ); + } + return aSel.Max(); +} + + +EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel, + sal_Unicode c, bool bOverwrite ) +{ + OSL_ENSURE( c != '\t', "Tab for InsertText ?" ); + OSL_ENSURE( c != '\n', "Word wrapping for InsertText ?"); + + EditPaM aPaM( rCurSel.Min() ); + + bool bDoOverwrite = bOverwrite && + ( aPaM.GetIndex() < aPaM.GetNode()->Len() ); + + bool bUndoAction = ( rCurSel.HasRange() || bDoOverwrite ); + + if ( bUndoAction ) + UndoActionStart( EDITUNDO_INSERT ); + + if ( rCurSel.HasRange() ) + { + aPaM = ImpDeleteSelection( rCurSel ); + } + else if ( bDoOverwrite ) + { + // If selected, then do not also overwrite a character! + EditSelection aTmpSel( aPaM ); + aTmpSel.Max().SetIndex( aTmpSel.Max().GetIndex()+1 ); + OSL_ENSURE( !aTmpSel.DbgIsBuggy( aEditDoc ), "Overwrite: Wrong selection! "); + ImpDeleteSelection( aTmpSel ); + } + + if ( aPaM.GetNode()->Len() < MAXCHARSINPARA ) + { + if (IsInputSequenceCheckingRequired( c, rCurSel )) + { + uno::Reference < i18n::XExtendedInputSequenceChecker > _xISC( ImplGetInputSequenceChecker() ); + if (!pCTLOptions) + pCTLOptions.reset( new SvtCTLOptions ); + + if (_xISC) + { + const sal_Int32 nTmpPos = aPaM.GetIndex(); + sal_Int16 nCheckMode = pCTLOptions->IsCTLSequenceCheckingRestricted() ? + i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC; + + // the text that needs to be checked is only the one + // before the current cursor position + const OUString aOldText( aPaM.GetNode()->Copy(0, nTmpPos) ); + OUString aNewText( aOldText ); + if (pCTLOptions->IsCTLSequenceCheckingTypeAndReplace()) + { + _xISC->correctInputSequence(aNewText, nTmpPos - 1, c, nCheckMode); + + // find position of first character that has changed + sal_Int32 nOldLen = aOldText.getLength(); + sal_Int32 nNewLen = aNewText.getLength(); + const sal_Unicode *pOldTxt = aOldText.getStr(); + const sal_Unicode *pNewTxt = aNewText.getStr(); + sal_Int32 nChgPos = 0; + while ( nChgPos < nOldLen && nChgPos < nNewLen && + pOldTxt[nChgPos] == pNewTxt[nChgPos] ) + ++nChgPos; + + const OUString aChgText( aNewText.copy( nChgPos ) ); + + // select text from first pos to be changed to current pos + EditSelection aSel( EditPaM( aPaM.GetNode(), nChgPos ), aPaM ); + + if (!aChgText.isEmpty()) + return InsertText( aSel, aChgText ); // implicitly handles undo + else + return aPaM; + } + else + { + // should the character be ignored (i.e. not get inserted) ? + if (!_xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode )) + return aPaM; // nothing to be done -> no need for undo + } + } + + // at this point now we will insert the character 'normally' some lines below... + } + + if ( IsUndoEnabled() && !IsInUndo() ) + { + std::unique_ptr<EditUndoInsertChars> pNewUndo(new EditUndoInsertChars(pEditEngine, CreateEPaM(aPaM), OUString(c))); + bool bTryMerge = !bDoOverwrite && ( c != ' ' ); + InsertUndo( std::move(pNewUndo), bTryMerge ); + } + + aEditDoc.InsertText( aPaM, OUString(c) ); + ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); + OSL_ENSURE( pPortion, "Blind Portion in InsertText" ); + pPortion->MarkInvalid( aPaM.GetIndex(), 1 ); + aPaM.SetIndex( aPaM.GetIndex()+1 ); // does not do EditDoc-Method anymore + } + + TextModified(); + + if ( bUndoAction ) + UndoActionEnd(); + + return aPaM; +} + +EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUString& rStr) +{ + UndoActionStart( EDITUNDO_INSERT ); + + EditPaM aPaM; + if ( aCurSel.HasRange() ) + aPaM = ImpDeleteSelection( aCurSel ); + else + aPaM = aCurSel.Max(); + + EditPaM aCurPaM( aPaM ); // for the Invalidate + + // get word boundaries in order to clear possible WrongList entries + // and invalidate all the necessary text (everything after and including the + // start of the word) + // #i107201# do the expensive SelectWord call only if online spelling is active + EditSelection aCurWord; + if ( GetStatus().DoOnlineSpelling() ) + aCurWord = SelectWord( aCurPaM, i18n::WordType::DICTIONARY_WORD ); + + OUString aText(convertLineEnd(rStr, LINEEND_LF)); + if (utl::ConfigManager::IsFuzzing()) //tab expansion performance in editeng is appalling + aText = aText.replaceAll("\t","-"); + SfxVoidItem aTabItem( EE_FEATURE_TAB ); + + // Converts to linesep = \n + // Token LINE_SEP query, + // since the MAC-Compiler makes something else from \n ! + + sal_Int32 nStart = 0; + while ( nStart < aText.getLength() ) + { + sal_Int32 nEnd = aText.indexOf( LINE_SEP, nStart ); + if ( nEnd == -1 ) + nEnd = aText.getLength(); // not dereference! + + // Start == End => empty line + if ( nEnd > nStart ) + { + OUString aLine = aText.copy( nStart, nEnd-nStart ); + sal_Int32 nExistingChars = aPaM.GetNode()->Len(); + sal_Int32 nChars = nExistingChars + aLine.getLength(); + if (nChars > MAXCHARSINPARA) + { + sal_Int32 nMaxNewChars = std::max<sal_Int32>(0, MAXCHARSINPARA - nExistingChars); + // Wherever we break, it may be wrong. However, try to find the + // previous non-alnum/non-letter character. Note this is only + // in the to be appended data, otherwise already existing + // characters would have to be moved and PaM to be updated. + // Restrict to 2*42, if not found by then assume other data or + // language-script uses only letters or idiographs. + sal_Int32 nPos = nMaxNewChars; + while (nPos-- > 0 && (nMaxNewChars - nPos) <= 84) + { + switch (unicode::getUnicodeType(aLine[nPos])) + { + case css::i18n::UnicodeType::UPPERCASE_LETTER: + case css::i18n::UnicodeType::LOWERCASE_LETTER: + case css::i18n::UnicodeType::TITLECASE_LETTER: + case css::i18n::UnicodeType::MODIFIER_LETTER: + case css::i18n::UnicodeType::OTHER_LETTER: + case css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER: + case css::i18n::UnicodeType::LETTER_NUMBER: + case css::i18n::UnicodeType::OTHER_NUMBER: + case css::i18n::UnicodeType::CURRENCY_SYMBOL: + break; + default: + { + const sal_Unicode c = aLine[nPos]; + // Ignore NO-BREAK spaces, NBSP, NNBSP, ZWNBSP. + if (c == 0x00A0 || c == 0x202F || c == 0xFEFF) + break; + if (c == '-' && nPos + 1 < nMaxNewChars) + { + // Keep HYPHEN-MINUS with a number to the right. + const sal_Int16 t = unicode::getUnicodeType(aLine[nPos+1]); + if ( t == css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER || + t == css::i18n::UnicodeType::LETTER_NUMBER || + t == css::i18n::UnicodeType::OTHER_NUMBER) + nMaxNewChars = nPos; // line break before + else + nMaxNewChars = nPos + 1; // line break after + } + else + { + nMaxNewChars = nPos + 1; // line break after + } + nPos = 0; // will break loop + } + } + } + // Remaining characters end up in the next paragraph. Note that + // new nStart will be nEnd+1 below so decrement by one more. + nEnd -= (aLine.getLength() - nMaxNewChars + 1); + aLine = aLine.copy( 0, nMaxNewChars ); // Delete the Rest... + } + if ( IsUndoEnabled() && !IsInUndo() ) + InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), aLine)); + // Tabs ? + if ( aLine.indexOf( '\t' ) == -1 ) + aPaM = aEditDoc.InsertText( aPaM, aLine ); + else + { + sal_Int32 nStart2 = 0; + while ( nStart2 < aLine.getLength() ) + { + sal_Int32 nEnd2 = aLine.indexOf( "\t", nStart2 ); + if ( nEnd2 == -1 ) + nEnd2 = aLine.getLength(); // not dereference! + + if ( nEnd2 > nStart2 ) + aPaM = aEditDoc.InsertText( aPaM, aLine.copy( nStart2, nEnd2-nStart2 ) ); + if ( nEnd2 < aLine.getLength() ) + { + aPaM = aEditDoc.InsertFeature( aPaM, aTabItem ); + } + nStart2 = nEnd2+1; + } + } + ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); + OSL_ENSURE( pPortion, "Blind Portion in InsertText" ); + + if ( GetStatus().DoOnlineSpelling() ) + { + // now remove the Wrongs (red spell check marks) from both words... + WrongList *pWrongs = aCurPaM.GetNode()->GetWrongList(); + if (pWrongs && !pWrongs->empty()) + pWrongs->ClearWrongs( aCurWord.Min().GetIndex(), aPaM.GetIndex(), aPaM.GetNode() ); + // ... and mark both words as 'to be checked again' + pPortion->MarkInvalid( aCurWord.Min().GetIndex(), aLine.getLength() ); + } + else + pPortion->MarkInvalid( aCurPaM.GetIndex(), aLine.getLength() ); + } + if ( nEnd < aText.getLength() ) + aPaM = ImpInsertParaBreak( aPaM ); + + nStart = nEnd+1; + } + + UndoActionEnd(); + + TextModified(); + return aPaM; +} + +EditPaM ImpEditEngine::ImpFastInsertText( EditPaM aPaM, const OUString& rStr ) +{ + OSL_ENSURE( rStr.indexOf( 0x0A ) == -1, "FastInsertText: Newline not allowed! "); + OSL_ENSURE( rStr.indexOf( 0x0D ) == -1, "FastInsertText: Newline not allowed! "); + OSL_ENSURE( rStr.indexOf( '\t' ) == -1, "FastInsertText: Newline not allowed! "); + + if ( ( aPaM.GetNode()->Len() + rStr.getLength() ) < MAXCHARSINPARA ) + { + if ( IsUndoEnabled() && !IsInUndo() ) + InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), rStr)); + + aPaM = aEditDoc.InsertText( aPaM, rStr ); + TextModified(); + } + else + { + aPaM = ImpInsertText( aPaM, rStr ); + } + + return aPaM; +} + +EditPaM ImpEditEngine::ImpInsertFeature(const EditSelection& rCurSel, const SfxPoolItem& rItem) +{ + EditPaM aPaM; + if ( rCurSel.HasRange() ) + aPaM = ImpDeleteSelection( rCurSel ); + else + aPaM = rCurSel.Max(); + + if ( aPaM.GetIndex() >= SAL_MAX_INT32-1 ) + return aPaM; + + if ( IsUndoEnabled() && !IsInUndo() ) + InsertUndo(std::make_unique<EditUndoInsertFeature>(pEditEngine, CreateEPaM(aPaM), rItem)); + aPaM = aEditDoc.InsertFeature( aPaM, rItem ); + UpdateFields(); + + ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); + OSL_ENSURE( pPortion, "Blind Portion in InsertFeature" ); + pPortion->MarkInvalid( aPaM.GetIndex()-1, 1 ); + + TextModified(); + + return aPaM; +} + +EditPaM ImpEditEngine::ImpInsertParaBreak( const EditSelection& rCurSel ) +{ + EditPaM aPaM; + if ( rCurSel.HasRange() ) + aPaM = ImpDeleteSelection( rCurSel ); + else + aPaM = rCurSel.Max(); + + return ImpInsertParaBreak( aPaM ); +} + +EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs ) +{ + if ( aEditDoc.Count() >= EE_PARA_MAX_COUNT ) + { + SAL_WARN( "editeng", "ImpEditEngine::ImpInsertParaBreak - can't process more than " + << EE_PARA_MAX_COUNT << " paragraphs!"); + return rPaM; + } + + if ( IsUndoEnabled() && !IsInUndo() ) + InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, aEditDoc.GetPos(rPaM.GetNode()), rPaM.GetIndex())); + + EditPaM aPaM( aEditDoc.InsertParaBreak( rPaM, bKeepEndingAttribs ) ); + + if ( GetStatus().DoOnlineSpelling() ) + { + sal_Int32 nEnd = rPaM.GetNode()->Len(); + aPaM.GetNode()->CreateWrongList(); + WrongList* pLWrongs = rPaM.GetNode()->GetWrongList(); + WrongList* pRWrongs = aPaM.GetNode()->GetWrongList(); + // take over misspelled words: + for (auto & elem : *pLWrongs) + { + // Correct only if really a word gets overlapped in the process of + // Spell checking + if (elem.mnStart > o3tl::make_unsigned(nEnd)) + { + pRWrongs->push_back(elem); + editeng::MisspellRange& rRWrong = pRWrongs->back(); + rRWrong.mnStart = rRWrong.mnStart - nEnd; + rRWrong.mnEnd = rRWrong.mnEnd - nEnd; + } + else if (elem.mnStart < o3tl::make_unsigned(nEnd) && elem.mnEnd > o3tl::make_unsigned(nEnd)) + elem.mnEnd = nEnd; + } + sal_Int32 nInv = nEnd ? nEnd-1 : nEnd; + if ( nEnd ) + pLWrongs->SetInvalidRange(nInv, nEnd); + else + pLWrongs->SetValid(); + pRWrongs->SetValid(); + pRWrongs->SetInvalidRange(0, 1); // Only test the first word + } + + ParaPortion* pPortion = FindParaPortion( rPaM.GetNode() ); + OSL_ENSURE( pPortion, "Blind Portion in ImpInsertParaBreak" ); + pPortion->MarkInvalid( rPaM.GetIndex(), 0 ); + + // Optimization: Do not place unnecessarily many getPos to Listen! + // Here, as in undo, but also in all other methods. + sal_Int32 nPos = GetParaPortions().GetPos( pPortion ); + ParaPortion* pNewPortion = new ParaPortion( aPaM.GetNode() ); + GetParaPortions().Insert(nPos+1, std::unique_ptr<ParaPortion>(pNewPortion)); + ParaAttribsChanged( pNewPortion->GetNode() ); + if ( IsCallParaInsertedOrDeleted() ) + GetEditEnginePtr()->ParagraphInserted( nPos+1 ); + + CursorMoved( rPaM.GetNode() ); // if empty Attributes have emerged. + TextModified(); + return aPaM; +} + +EditPaM ImpEditEngine::ImpFastInsertParagraph( sal_Int32 nPara ) +{ + if ( IsUndoEnabled() && !IsInUndo() ) + { + if ( nPara ) + { + OSL_ENSURE( aEditDoc.GetObject( nPara-1 ), "FastInsertParagraph: Prev does not exist" ); + InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, nPara-1, aEditDoc.GetObject( nPara-1 )->Len())); + } + else + InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, 0, 0)); + } + + ContentNode* pNode = new ContentNode( aEditDoc.GetItemPool() ); + // If flat mode, then later no Font is set: + pNode->GetCharAttribs().GetDefFont() = aEditDoc.GetDefFont(); + + if ( GetStatus().DoOnlineSpelling() ) + pNode->CreateWrongList(); + + aEditDoc.Insert(nPara, pNode); + + GetParaPortions().Insert(nPara, std::make_unique<ParaPortion>( pNode )); + if ( IsCallParaInsertedOrDeleted() ) + GetEditEnginePtr()->ParagraphInserted( nPara ); + + return EditPaM( pNode, 0 ); +} + +EditPaM ImpEditEngine::InsertParaBreak(const EditSelection& rCurSel) +{ + EditPaM aPaM(ImpInsertParaBreak(rCurSel)); + if ( aStatus.DoAutoIndenting() ) + { + sal_Int32 nPara = aEditDoc.GetPos( aPaM.GetNode() ); + OSL_ENSURE( nPara > 0, "AutoIndenting: Error!" ); + const OUString aPrevParaText( GetEditDoc().GetParaAsString( nPara-1 ) ); + sal_Int32 n = 0; + while ( ( n < aPrevParaText.getLength() ) && + ( ( aPrevParaText[n] == ' ' ) || ( aPrevParaText[n] == '\t' ) ) ) + { + if ( aPrevParaText[n] == '\t' ) + aPaM = ImpInsertFeature( aPaM, SfxVoidItem( EE_FEATURE_TAB ) ); + else + aPaM = ImpInsertText( aPaM, OUString(aPrevParaText[n]) ); + n++; + } + + } + return aPaM; +} + +EditPaM ImpEditEngine::InsertTab(const EditSelection& rCurSel) +{ + EditPaM aPaM( ImpInsertFeature(rCurSel, SfxVoidItem(EE_FEATURE_TAB ))); + return aPaM; +} + +EditPaM ImpEditEngine::InsertField(const EditSelection& rCurSel, const SvxFieldItem& rFld) +{ + return ImpInsertFeature(rCurSel, rFld); +} + +bool ImpEditEngine::UpdateFields() +{ + bool bChanges = false; + sal_Int32 nParas = GetEditDoc().Count(); + for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) + { + bool bChangesInPara = false; + ContentNode* pNode = GetEditDoc().GetObject( nPara ); + OSL_ENSURE( pNode, "NULL-Pointer in Doc" ); + CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); + for (std::unique_ptr<EditCharAttrib> & rAttrib : rAttribs) + { + EditCharAttrib& rAttr = *rAttrib; + if (rAttr.Which() == EE_FEATURE_FIELD) + { + EditCharAttribField& rField = static_cast<EditCharAttribField&>(rAttr); + EditCharAttribField aCurrent(rField); + rField.Reset(); + + if (!aStatus.MarkNonUrlFields() && !aStatus.MarkUrlFields()) + ; // nothing marked + else if (aStatus.MarkNonUrlFields() && aStatus.MarkUrlFields()) + rField.GetFieldColor() = GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor; + else + { + bool bURL = false; + if (const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(rField.GetItem())) + { + if (const SvxFieldData* pFieldData = pFieldItem->GetField()) + bURL = (dynamic_cast<const SvxURLField* >(pFieldData) != nullptr); + } + if ((bURL && aStatus.MarkUrlFields()) || (!bURL && aStatus.MarkNonUrlFields())) + rField.GetFieldColor() = GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor; + } + + const OUString aFldValue = + GetEditEnginePtr()->CalcFieldValue( + static_cast<const SvxFieldItem&>(*rField.GetItem()), + nPara, rField.GetStart(), rField.GetTextColor(), rField.GetFieldColor()); + + rField.SetFieldValue(aFldValue); + if (rField != aCurrent) + { + bChanges = true; + bChangesInPara = true; + } + } + } + if ( bChangesInPara ) + { + // If possible be more precise when invalidate. + ParaPortion* pPortion = GetParaPortions()[nPara]; + OSL_ENSURE( pPortion, "NULL-Pointer in Doc" ); + pPortion->MarkSelectionInvalid( 0 ); + } + } + return bChanges; +} + +EditPaM ImpEditEngine::InsertLineBreak(const EditSelection& aCurSel) +{ + EditPaM aPaM( ImpInsertFeature( aCurSel, SfxVoidItem( EE_FEATURE_LINEBR ) ) ); + return aPaM; +} + + +// Helper functions + +tools::Rectangle ImpEditEngine::GetEditCursor(const ParaPortion* pPortion, const EditLine* pLine, + sal_Int32 nIndex, GetCursorFlags nFlags) +{ + assert(pPortion && pLine); + // nIndex might be not in the line + // Search within the line... + tools::Long nX; + + if ((nIndex == pLine->GetStart()) && (nFlags & GetCursorFlags::StartOfLine)) + { + Range aXRange = GetLineXPosStartEnd(pPortion, pLine); + nX = !IsRightToLeft(GetEditDoc().GetPos(pPortion->GetNode())) ? aXRange.Min() + : aXRange.Max(); + } + else if ((nIndex == pLine->GetEnd()) && (nFlags & GetCursorFlags::EndOfLine)) + { + Range aXRange = GetLineXPosStartEnd(pPortion, pLine); + nX = !IsRightToLeft(GetEditDoc().GetPos(pPortion->GetNode())) ? aXRange.Max() + : aXRange.Min(); + } + else + { + nX = GetXPos(pPortion, pLine, nIndex, bool(nFlags & GetCursorFlags::PreferPortionStart)); + } + + tools::Rectangle aEditCursor; + aEditCursor.SetLeft(nX); + aEditCursor.SetRight(nX); + + aEditCursor.SetBottom(pLine->GetHeight() - 1); + if (nFlags & GetCursorFlags::TextOnly) + aEditCursor.SetTop(aEditCursor.Bottom() - pLine->GetTxtHeight() + 1); + else + aEditCursor.SetTop(aEditCursor.Bottom() + - std::min(pLine->GetTxtHeight(), pLine->GetHeight()) + 1); + return aEditCursor; +} + +tools::Rectangle ImpEditEngine::PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags ) +{ + assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: PaMtoEditCursor" ); + + tools::Rectangle aEditCursor; + const sal_Int32 nIndex = aPaM.GetIndex(); + const ParaPortion* pPortion = nullptr; + const EditLine* pLastLine = nullptr; + tools::Rectangle aLineArea; + + auto FindPortionLineAndArea + = [&, bEOL(bool(nFlags & GetCursorFlags::EndOfLine))](const LineAreaInfo& rInfo) { + if (!rInfo.pLine) // start of ParaPortion + { + ContentNode* pNode = rInfo.rPortion.GetNode(); + OSL_ENSURE(pNode, "Invalid Node in Portion!"); + if (pNode != aPaM.GetNode()) + return CallbackResult::SkipThisPortion; + pPortion = &rInfo.rPortion; + } + else // guaranteed that this is the correct ParaPortion + { + pLastLine = rInfo.pLine; + aLineArea = rInfo.aArea; + if ((rInfo.pLine->GetStart() == nIndex) || (rInfo.pLine->IsIn(nIndex, bEOL))) + return CallbackResult::Stop; + } + return CallbackResult::Continue; + }; + IterateLineAreas(FindPortionLineAndArea, IterFlag::none); + + if (pLastLine) + { + aEditCursor = GetEditCursor(pPortion, pLastLine, nIndex, nFlags); + aEditCursor.Move(getTopLeftDocOffset(aLineArea)); + } + else + OSL_FAIL("Line not found!"); + + return aEditCursor; +} + +void ImpEditEngine::IterateLineAreas(const IterateLinesAreasFunc& f, IterFlag eOptions) +{ + const Point aOrigin(0, 0); + Point aLineStart(aOrigin); + const tools::Long nVertLineSpacing = CalcVertLineSpacing(aLineStart); + const tools::Long nColumnWidth = GetColumnWidth(aPaperSize); + sal_Int16 nColumn = 0; + for (sal_Int32 n = 0, nPortions = GetParaPortions().Count(); n < nPortions; ++n) + { + ParaPortion* pPortion = GetParaPortions()[n]; + bool bSkipThis = true; + if (pPortion->IsVisible()) + { + // when typing idle formatting, asynchronous Paint. Invisible Portions may be invalid. + if (pPortion->IsInvalid()) + return; + + LineAreaInfo aInfo{ + *pPortion, // pPortion + nullptr, // pLine + 0, // nHeightNeededToNotWrap + { aLineStart, Size{ nColumnWidth, pPortion->GetFirstLineOffset() } }, // aArea + n, // nPortion + 0, // nLine + nColumn // nColumn + }; + auto eResult = f(aInfo); + if (eResult == CallbackResult::Stop) + return; + bSkipThis = eResult == CallbackResult::SkipThisPortion; + + sal_uInt16 nSBL = 0; + if (!aStatus.IsOutliner()) + { + const SvxLineSpacingItem& rLSItem + = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL); + nSBL = (rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix) + ? GetYValue(rLSItem.GetInterLineSpace()) + : 0; + } + + adjustYDirectionAware(aLineStart, pPortion->GetFirstLineOffset()); + for (sal_Int32 nLine = 0, nLines = pPortion->GetLines().Count(); nLine < nLines; nLine++) + { + EditLine& rLine = pPortion->GetLines()[nLine]; + tools::Long nLineHeight = rLine.GetHeight(); + if (nLine != nLines - 1) + nLineHeight += nVertLineSpacing; + MoveToNextLine(aLineStart, nLineHeight, nColumn, aOrigin, + &aInfo.nHeightNeededToNotWrap); + const bool bInclILS = eOptions & IterFlag::inclILS; + if (bInclILS && (nLine != nLines - 1) && !aStatus.IsOutliner()) + { + adjustYDirectionAware(aLineStart, nSBL); + nLineHeight += nSBL; + } + + if (!bSkipThis) + { + Point aOtherCorner(aLineStart); + adjustXDirectionAware(aOtherCorner, nColumnWidth); + adjustYDirectionAware(aOtherCorner, -nLineHeight); + + // Calls to f() for each line + aInfo.nColumn = nColumn; + aInfo.pLine = &rLine; + aInfo.nLine = nLine; + aInfo.aArea = tools::Rectangle::Justify(aLineStart, aOtherCorner); + eResult = f(aInfo); + if (eResult == CallbackResult::Stop) + return; + bSkipThis = eResult == CallbackResult::SkipThisPortion; + } + + if (!bInclILS && (nLine != nLines - 1) && !aStatus.IsOutliner()) + adjustYDirectionAware(aLineStart, nSBL); + } + if (!aStatus.IsOutliner()) + { + const SvxULSpaceItem& rULItem + = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE); + tools::Long nUL = GetYValue(rULItem.GetLower()); + adjustYDirectionAware(aLineStart, nUL); + } + } + // Invisible ParaPortion has no height (see ParaPortion::GetHeight), don't handle it + } +} + +std::tuple<const ParaPortion*, const EditLine*, tools::Long> +ImpEditEngine::GetPortionAndLine(Point aDocPos) +{ + // First find the column from the point + sal_Int32 nClickColumn = 0; + for (tools::Long nColumnStart = 0, nColumnWidth = GetColumnWidth(aPaperSize);; + nColumnStart += mnColumnSpacing + nColumnWidth, ++nClickColumn) + { + if (aDocPos.X() <= nColumnStart + nColumnWidth + mnColumnSpacing / 2) + break; + if (nClickColumn >= mnColumns - 1) + break; + } + + const ParaPortion* pLastPortion = nullptr; + const EditLine* pLastLine = nullptr; + tools::Long nLineStartX = 0; + Point aPos; + adjustYDirectionAware(aPos, aDocPos.Y()); + + auto FindLastMatchingPortionAndLine = [&](const LineAreaInfo& rInfo) { + if (rInfo.pLine) // Only handle lines, not ParaPortion starts + { + if (rInfo.nColumn > nClickColumn) + return CallbackResult::Stop; + pLastPortion = &rInfo.rPortion; // Candidate paragraph + pLastLine = rInfo.pLine; // Last visible line not later than click position + nLineStartX = getTopLeftDocOffset(rInfo.aArea).Width(); + if (rInfo.nColumn == nClickColumn && getYOverflowDirectionAware(aPos, rInfo.aArea) == 0) + return CallbackResult::Stop; // Found it + } + return CallbackResult::Continue; + }; + IterateLineAreas(FindLastMatchingPortionAndLine, IterFlag::inclILS); + + return { pLastPortion, pLastLine, nLineStartX }; +} + +EditPaM ImpEditEngine::GetPaM( Point aDocPos, bool bSmart ) +{ + assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: GetPaM" ); + + if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(aDocPos); pPortion) + { + sal_Int32 nCurIndex + = GetChar(pPortion, pLine, aDocPos.X() - nLineStartX, bSmart); + EditPaM aPaM(pPortion->GetNode(), nCurIndex); + + if (nCurIndex && (nCurIndex == pLine->GetEnd()) + && (pLine != &pPortion->GetLines()[pPortion->GetLines().Count() - 1])) + { + aPaM = CursorLeft(aPaM); + } + + return aPaM; + } + return {}; +} + +bool ImpEditEngine::IsTextPos(const Point& rDocPos, sal_uInt16 nBorder) +{ + if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(rDocPos); pPortion) + { + Range aLineXPosStartEnd = GetLineXPosStartEnd(pPortion, pLine); + if ((rDocPos.X() >= nLineStartX + aLineXPosStartEnd.Min() - nBorder) + && (rDocPos.X() <= nLineStartX + aLineXPosStartEnd.Max() + nBorder)) + return true; + } + return false; +} + +sal_uInt32 ImpEditEngine::GetTextHeight() const +{ + assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" ); + OSL_ENSURE( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" ); + return nCurTextHeight; +} + +sal_uInt32 ImpEditEngine::CalcTextWidth( bool bIgnoreExtraSpace ) +{ + // If still not formatted and not in the process. + // Will be brought in the formatting for AutoPageSize. + if ( !IsFormatted() && !IsFormatting() ) + FormatDoc(); + + sal_uInt32 nMaxWidth = 0; + + // Over all the paragraphs ... + + sal_Int32 nParas = GetParaPortions().Count(); + for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) + { + nMaxWidth = std::max(nMaxWidth, CalcParaWidth(nPara, bIgnoreExtraSpace)); + } + + return nMaxWidth; +} + +sal_uInt32 ImpEditEngine::CalcParaWidth( sal_Int32 nPara, bool bIgnoreExtraSpace ) +{ + // If still not formatted and not in the process. + // Will be brought in the formatting for AutoPageSize. + if ( !IsFormatted() && !IsFormatting() ) + FormatDoc(); + + tools::Long nMaxWidth = 0; + + // Over all the paragraphs ... + + OSL_ENSURE( 0 <= nPara && nPara < GetParaPortions().Count(), "CalcParaWidth: Out of range" ); + ParaPortion* pPortion = GetParaPortions()[nPara]; + if ( pPortion && pPortion->IsVisible() ) + { + const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pPortion->GetNode() ); + sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pPortion->GetNode() ); + + + // On the lines of the paragraph ... + + sal_Int32 nLines = pPortion->GetLines().Count(); + for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ ) + { + EditLine& rLine = pPortion->GetLines()[nLine]; + // nCurWidth = pLine->GetStartPosX(); + // For Center- or Right- alignment it depends on the paper + // width, here not preferred. I general, it is best not leave it + // to StartPosX, also the right indents have to be taken into + // account! + tools::Long nCurWidth = GetXValue( rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth ); + if ( nLine == 0 ) + { + tools::Long nFI = GetXValue( rLRItem.GetTextFirstLineOffset() ); + nCurWidth -= nFI; + if ( pPortion->GetBulletX() > nCurWidth ) + { + nCurWidth += nFI; // LI? + if ( pPortion->GetBulletX() > nCurWidth ) + nCurWidth = pPortion->GetBulletX(); + } + } + nCurWidth += GetXValue( rLRItem.GetRight() ); + nCurWidth += CalcLineWidth( pPortion, &rLine, bIgnoreExtraSpace ); + if ( nCurWidth > nMaxWidth ) + { + nMaxWidth = nCurWidth; + } + } + } + + nMaxWidth++; // widen it, because in CreateLines for >= is wrapped. + return static_cast<sal_uInt32>(nMaxWidth); +} + +sal_uInt32 ImpEditEngine::CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, bool bIgnoreExtraSpace ) +{ + sal_Int32 nPara = GetEditDoc().GetPos( pPortion->GetNode() ); + + // #114278# Saving both layout mode and language (since I'm + // potentially changing both) + GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE ); + + ImplInitLayoutMode(*GetRefDevice(), nPara, -1); + + SvxAdjust eJustification = GetJustification( nPara ); + + // Calculation of the width without the Indents ... + sal_uInt32 nWidth = 0; + sal_Int32 nPos = pLine->GetStart(); + for ( sal_Int32 nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ ) + { + const TextPortion& rTextPortion = pPortion->GetTextPortions()[nTP]; + switch ( rTextPortion.GetKind() ) + { + case PortionKind::FIELD: + case PortionKind::HYPHENATOR: + case PortionKind::TAB: + { + nWidth += rTextPortion.GetSize().Width(); + } + break; + case PortionKind::TEXT: + { + if ( ( eJustification != SvxAdjust::Block ) || ( !bIgnoreExtraSpace ) ) + { + nWidth += rTextPortion.GetSize().Width(); + } + else + { + SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() ); + SeekCursor( pPortion->GetNode(), nPos+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + nWidth += aTmpFont.QuickGetTextSize( GetRefDevice(), + pPortion->GetNode()->GetString(), nPos, rTextPortion.GetLen(), nullptr ).Width(); + } + } + break; + case PortionKind::LINEBREAK: break; + } + nPos = nPos + rTextPortion.GetLen(); + } + + GetRefDevice()->Pop(); + + return nWidth; +} + +sal_uInt32 ImpEditEngine::GetTextHeightNTP() const +{ + assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" ); + DBG_ASSERT( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" ); + return nCurTextHeightNTP; +} + +tools::Long ImpEditEngine::Calc1ColumnTextHeight(tools::Long* pHeightNTP) +{ + tools::Long nHeight = 0; + if (pHeightNTP) + *pHeightNTP = 0; + // Pretend that we have ~infinite height to get total height + comphelper::ValueRestorationGuard aGuard(nCurTextHeight, + std::numeric_limits<tools::Long>::max()); + + IterateLinesAreasFunc FindLastLineBottom = [&](const LineAreaInfo& rInfo) { + if (rInfo.pLine) + { + // bottom coordinate does not belong to area, so no need to do +1 + nHeight = getBottomDocOffset(rInfo.aArea); + if (pHeightNTP && !rInfo.rPortion.IsEmpty()) + *pHeightNTP = nHeight; + } + return CallbackResult::Continue; + }; + IterateLineAreas(FindLastLineBottom, IterFlag::none); + return nHeight; +} + +tools::Long ImpEditEngine::CalcTextHeight(tools::Long* pHeightNTP) +{ + assert( IsUpdateLayout() && "Should not be used when Update=FALSE: CalcTextHeight" ); + + if (mnColumns <= 1) + return Calc1ColumnTextHeight(pHeightNTP); // All text fits into a single column - done! + + // The final column height can be smaller than total height divided by number of columns (taking + // into account first line offset and interline spacing, that aren't considered in positioning + // after the wrap). The wrap should only happen after the minimal height is exceeded. + tools::Long nTentativeColHeight = mnMinColumnWrapHeight; + tools::Long nWantedIncrease = 0; + tools::Long nCurrentTextHeight; + + // This does the necessary column balancing for the case when the text does not fit min height. + // When the height of column (taken from nCurTextHeight) is too small, the last column will + // overflow, so the resulting height of the text will exceed the set column height. Increasing + // the column height step by step by the minimal value that allows one of columns to accommodate + // one line more, we finally get to the point where all the text fits. At each iteration, the + // height is only increased, so it's impossible to have infinite layout loops. The found value + // is the global minimum. + // + // E.g., given the following four line heights: + // Line 1: 10; + // Line 2: 12; + // Line 3: 10; + // Line 4: 10; + // number of columns 3, and the minimal paper height of 5, the iterations would be: + // * Tentative column height is set to 5 + // <ITERATION 1> + // * Line 1 is attempted to go to column 0. Overflow is 5 => moved to column 1. + // * Line 2 is attempted to go to column 1 after Line 1; overflow is 17 => moved to column 2. + // * Line 3 is attempted to go to column 2 after Line 2; overflow is 17, stays in max column 2. + // * Line 4 goes to column 2 after Line 3. + // * Final iteration columns are: {empty}, {Line 1}, {Line 2, Line 3, Line 4} + // * Total text height is max({0, 10, 32}) == 32 > Tentative column height 5 => NEXT ITERATION + // * Minimal height increase that allows at least one column to accommodate one more line is + // min({5, 17, 17}) = 5. + // * Tentative column height is set to 5 + 5 = 10. + // <ITERATION 2> + // * Line 1 goes to column 0, no overflow. + // * Line 2 is attempted to go to column 0 after Line 1; overflow is 12 => moved to column 1. + // * Line 3 is attempted to go to column 1 after Line 2; overflow is 12 => moved to column 2. + // * Line 4 is attempted to go to column 2 after Line 3; overflow is 10, stays in max column 2. + // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4} + // * Total text height is max({10, 12, 20}) == 20 > Tentative column height 10 => NEXT ITERATION + // * Minimal height increase that allows at least one column to accommodate one more line is + // min({12, 12, 10}) = 10. + // * Tentative column height is set to 10 + 10 == 20. + // <ITERATION 3> + // * Line 1 goes to column 0, no overflow. + // * Line 2 is attempted to go to column 0 after Line 1; overflow is 2 => moved to column 1. + // * Line 3 is attempted to go to column 1 after Line 2; overflow is 2 => moved to column 2. + // * Line 4 is attempted to go to column 2 after Line 3; no overflow. + // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4} + // * Total text height is max({10, 12, 20}) == 20 == Tentative column height 20 => END. + do + { + nTentativeColHeight += nWantedIncrease; + nWantedIncrease = std::numeric_limits<tools::Long>::max(); + nCurrentTextHeight = 0; + if (pHeightNTP) + *pHeightNTP = 0; + auto GetHeightAndWantedIncrease = [&, minHeight = tools::Long(0), lastCol = sal_Int16(0)]( + const LineAreaInfo& rInfo) mutable { + if (rInfo.pLine) + { + if (lastCol != rInfo.nColumn) + { + minHeight = std::max(nCurrentTextHeight, + minHeight); // total height can't be less than previous columns + nWantedIncrease = std::min(rInfo.nHeightNeededToNotWrap, nWantedIncrease); + lastCol = rInfo.nColumn; + } + // bottom coordinate does not belong to area, so no need to do +1 + nCurrentTextHeight = std::max(getBottomDocOffset(rInfo.aArea), minHeight); + if (pHeightNTP) + { + if (rInfo.rPortion.IsEmpty()) + *pHeightNTP = std::max(*pHeightNTP, minHeight); + else + *pHeightNTP = nCurrentTextHeight; + } + } + return CallbackResult::Continue; + }; + comphelper::ValueRestorationGuard aGuard(nCurTextHeight, nTentativeColHeight); + IterateLineAreas(GetHeightAndWantedIncrease, IterFlag::none); + } while (nCurrentTextHeight > nTentativeColHeight && nWantedIncrease > 0 + && nWantedIncrease != std::numeric_limits<tools::Long>::max()); + return nCurrentTextHeight; +} + +sal_Int32 ImpEditEngine::GetLineCount( sal_Int32 nParagraph ) const +{ + OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); + const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + OSL_ENSURE( pPPortion, "Paragraph not found: GetLineCount" ); + if ( pPPortion ) + return pPPortion->GetLines().Count(); + + return -1; +} + +sal_Int32 ImpEditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineLen: Out of range" ); + const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + OSL_ENSURE( pPPortion, "Paragraph not found: GetLineLen" ); + if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) ) + { + const EditLine& rLine = pPPortion->GetLines()[nLine]; + return rLine.GetLen(); + } + + return -1; +} + +void ImpEditEngine::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); + const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + OSL_ENSURE( pPPortion, "Paragraph not found: GetLineBoundaries" ); + rStart = rEnd = -1; // default values in case of error + if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) ) + { + const EditLine& rLine = pPPortion->GetLines()[nLine]; + rStart = rLine.GetStart(); + rEnd = rLine.GetEnd(); + } +} + +sal_Int32 ImpEditEngine::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + sal_Int32 nLineNo = -1; + const ContentNode* pNode = GetEditDoc().GetObject( nPara ); + OSL_ENSURE( pNode, "GetLineNumberAtIndex: invalid paragraph index" ); + if (pNode) + { + // we explicitly allow for the index to point at the character right behind the text + const bool bValidIndex = /*0 <= nIndex &&*/ nIndex <= pNode->Len(); + OSL_ENSURE( bValidIndex, "GetLineNumberAtIndex: invalid index" ); + const sal_Int32 nLineCount = GetLineCount( nPara ); + if (nIndex == pNode->Len()) + nLineNo = nLineCount > 0 ? nLineCount - 1 : 0; + else if (bValidIndex) // nIndex < pNode->Len() + { + sal_Int32 nStart = -1, nEnd = -1; + for (sal_Int32 i = 0; i < nLineCount && nLineNo == -1; ++i) + { + GetLineBoundaries( nStart, nEnd, nPara, i ); + if (nStart >= 0 && nStart <= nIndex && nEnd >= 0 && nIndex < nEnd) + nLineNo = i; + } + } + } + return nLineNo; +} + +sal_uInt16 ImpEditEngine::GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine ) +{ + OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); + ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + OSL_ENSURE( pPPortion, "Paragraph not found: GetLineHeight" ); + if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) ) + { + const EditLine& rLine = pPPortion->GetLines()[nLine]; + return rLine.GetHeight(); + } + + return 0xFFFF; +} + +sal_uInt32 ImpEditEngine::GetParaHeight( sal_Int32 nParagraph ) +{ + sal_uInt32 nHeight = 0; + + ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + OSL_ENSURE( pPPortion, "Paragraph not found: GetParaHeight" ); + + if ( pPPortion ) + nHeight = pPPortion->GetHeight(); + + return nHeight; +} + +void ImpEditEngine::UpdateSelections() +{ + // Check whether one of the selections is at a deleted node... + // If the node is valid, the index has yet to be examined! + for (EditView* pView : aEditViews) + { + EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); + bool bChanged = false; + for (const std::unique_ptr<DeletedNodeInfo> & aDeletedNode : aDeletedNodes) + { + const DeletedNodeInfo& rInf = *aDeletedNode; + if ( ( aCurSel.Min().GetNode() == rInf.GetNode() ) || + ( aCurSel.Max().GetNode() == rInf.GetNode() ) ) + { + // Use ParaPortions, as now also hidden paragraphs have to be + // taken into account! + sal_Int32 nPara = rInf.GetPosition(); + if (!GetParaPortions().SafeGetObject(nPara)) // Last paragraph + { + nPara = GetParaPortions().Count()-1; + } + assert(GetParaPortions()[nPara] && "Empty Document in UpdateSelections ?"); + // Do not end up from a hidden paragraph: + sal_Int32 nCurPara = nPara; + sal_Int32 nLastPara = GetParaPortions().Count()-1; + while ( nPara <= nLastPara && !GetParaPortions()[nPara]->IsVisible() ) + nPara++; + if ( nPara > nLastPara ) // then also backwards ... + { + nPara = nCurPara; + while ( nPara && !GetParaPortions()[nPara]->IsVisible() ) + nPara--; + } + OSL_ENSURE( GetParaPortions()[nPara]->IsVisible(), "No visible paragraph found: UpdateSelections" ); + + ParaPortion* pParaPortion = GetParaPortions()[nPara]; + EditSelection aTmpSelection( EditPaM( pParaPortion->GetNode(), 0 ) ); + pView->pImpEditView->SetEditSelection( aTmpSelection ); + bChanged=true; + break; // for loop + } + } + if ( !bChanged ) + { + // Check Index if node shrunk. + if ( aCurSel.Min().GetIndex() > aCurSel.Min().GetNode()->Len() ) + { + aCurSel.Min().SetIndex( aCurSel.Min().GetNode()->Len() ); + pView->pImpEditView->SetEditSelection( aCurSel ); + } + if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() ) + { + aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() ); + pView->pImpEditView->SetEditSelection( aCurSel ); + } + } + } + aDeletedNodes.clear(); +} + +EditSelection ImpEditEngine::ConvertSelection( + sal_Int32 nStartPara, sal_Int32 nStartPos, sal_Int32 nEndPara, sal_Int32 nEndPos ) +{ + EditSelection aNewSelection; + + // Start... + ContentNode* pNode = aEditDoc.GetObject( nStartPara ); + sal_Int32 nIndex = nStartPos; + if ( !pNode ) + { + pNode = aEditDoc[ aEditDoc.Count()-1 ]; + nIndex = pNode->Len(); + } + else if ( nIndex > pNode->Len() ) + nIndex = pNode->Len(); + + aNewSelection.Min().SetNode( pNode ); + aNewSelection.Min().SetIndex( nIndex ); + + // End... + pNode = aEditDoc.GetObject( nEndPara ); + nIndex = nEndPos; + if ( !pNode ) + { + pNode = aEditDoc[ aEditDoc.Count()-1 ]; + nIndex = pNode->Len(); + } + else if ( nIndex > pNode->Len() ) + nIndex = pNode->Len(); + + aNewSelection.Max().SetNode( pNode ); + aNewSelection.Max().SetIndex( nIndex ); + + return aNewSelection; +} + +void ImpEditEngine::SetActiveView( EditView* pView ) +{ + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Actually, now bHasVisSel and HideSelection would be necessary !!! + + if ( pView == pActiveView ) + return; + + if ( pActiveView && pActiveView->HasSelection() ) + pActiveView->pImpEditView->DrawSelectionXOR(); + + pActiveView = pView; + + if ( pActiveView && pActiveView->HasSelection() ) + pActiveView->pImpEditView->DrawSelectionXOR(); + + // NN: Quick fix for #78668#: + // When editing of a cell in Calc is ended, the edit engine is not deleted, + // only the edit views are removed. If mpIMEInfos is still set in that case, + // mpIMEInfos->aPos points to an invalid selection. + // -> reset mpIMEInfos now + // (probably something like this is necessary whenever the content is modified + // from the outside) + + if ( !pView && mpIMEInfos ) + { + mpIMEInfos.reset(); + } +} + +uno::Reference< datatransfer::XTransferable > ImpEditEngine::CreateTransferable( const EditSelection& rSelection ) +{ + EditSelection aSelection( rSelection ); + aSelection.Adjust( GetEditDoc() ); + + rtl::Reference<EditDataObject> pDataObj = new EditDataObject; + + pDataObj->GetString() = convertLineEnd(GetSelected(aSelection), GetSystemLineEnd()); // System specific + + WriteRTF( pDataObj->GetRTFStream(), aSelection ); + pDataObj->GetRTFStream().Seek( 0 ); + + WriteXML( pDataObj->GetODFStream(), aSelection ); + pDataObj->GetODFStream().Seek( 0 ); + + //Dumping the ODFStream to a XML file for testing purpose + /* + std::filebuf afilebuf; + afilebuf.open ("gsoc17_clipboard_test.xml",std::ios::out); + std::ostream os(&afilebuf); + os.write((const char*)(pDataObj->GetODFStream().GetData()), pDataObj->GetODFStream().remainingSize()); + afilebuf.close(); + */ + //dumping ends + + if ( ( aSelection.Min().GetNode() == aSelection.Max().GetNode() ) + && ( aSelection.Max().GetIndex() == (aSelection.Min().GetIndex()+1) ) ) + { + const EditCharAttrib* pAttr = aSelection.Min().GetNode()->GetCharAttribs(). + FindFeature( aSelection.Min().GetIndex() ); + if ( pAttr && + ( pAttr->GetStart() == aSelection.Min().GetIndex() ) && + ( pAttr->Which() == EE_FEATURE_FIELD ) ) + { + const SvxFieldItem* pField = static_cast<const SvxFieldItem*>(pAttr->GetItem()); + const SvxFieldData* pFld = pField->GetField(); + if ( auto pUrlField = dynamic_cast<const SvxURLField* >(pFld) ) + { + // Office-Bookmark + pDataObj->GetURL() = pUrlField->GetURL(); + } + } + } + + return pDataObj; +} + +EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial ) +{ + EditSelection aNewSelection( rPaM ); + + if ( !rxDataObj.is() ) + return aNewSelection; + + datatransfer::DataFlavor aFlavor; + bool bDone = false; + + if ( bUseSpecial ) + { + // XML + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT, aFlavor ); + if ( rxDataObj->isDataFlavorSupported( aFlavor ) ) + { + try + { + uno::Any aData = rxDataObj->getTransferData( aFlavor ); + uno::Sequence< sal_Int8 > aSeq; + aData >>= aSeq; + { + SvMemoryStream aODFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ ); + aNewSelection = Read( aODFStream, rBaseURL, EETextFormat::Xml, rPaM ); + } + bDone = true; + } + catch( const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "editeng", "Unable to paste EDITENGINE_ODF_TEXT_FLAT" ); + } + } + + if ( !bDone ) + { + // RTF + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RTF, aFlavor ); + // RICHTEXT + datatransfer::DataFlavor aFlavorRichtext; + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RICHTEXT, aFlavorRichtext ); + bool bRtfSupported = rxDataObj->isDataFlavorSupported( aFlavor ); + bool bRichtextSupported = rxDataObj->isDataFlavorSupported( aFlavorRichtext ); + if ( bRtfSupported || bRichtextSupported ) + { + if(bRichtextSupported) + { + aFlavor = aFlavorRichtext; + } + try + { + uno::Any aData = rxDataObj->getTransferData( aFlavor ); + uno::Sequence< sal_Int8 > aSeq; + aData >>= aSeq; + { + SvMemoryStream aRTFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ ); + aNewSelection = Read( aRTFStream, rBaseURL, EETextFormat::Rtf, rPaM ); + } + bDone = true; + } + catch( const css::uno::Exception& ) + { + } + } + } + } + if ( !bDone ) + { + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); + if ( rxDataObj->isDataFlavorSupported( aFlavor ) ) + { + try + { + uno::Any aData = rxDataObj->getTransferData( aFlavor ); + OUString aText; + aData >>= aText; + aNewSelection = ImpInsertText( rPaM, aText ); + } + catch( ... ) + { + ; // #i9286# can happen, even if isDataFlavorSupported returns true... + } + } + } + + return aNewSelection; +} + +sal_Int32 ImpEditEngine::GetChar( + const ParaPortion* pParaPortion, const EditLine* pLine, tools::Long nXPos, bool bSmart) +{ + OSL_ENSURE( pLine, "No line received: GetChar" ); + + sal_Int32 nChar = -1; + sal_Int32 nCurIndex = pLine->GetStart(); + + + // Search best matching portion with GetPortionXOffset() + for ( sal_Int32 i = pLine->GetStartPortion(); i <= pLine->GetEndPortion(); i++ ) + { + const TextPortion& rPortion = pParaPortion->GetTextPortions()[i]; + tools::Long nXLeft = GetPortionXOffset( pParaPortion, pLine, i ); + tools::Long nXRight = nXLeft + rPortion.GetSize().Width(); + if ( ( nXLeft <= nXPos ) && ( nXRight >= nXPos ) ) + { + nChar = nCurIndex; + + // Search within Portion... + + // Don't search within special portions... + if ( rPortion.GetKind() != PortionKind::TEXT ) + { + // ...but check on which side + if ( bSmart ) + { + tools::Long nLeftDiff = nXPos-nXLeft; + tools::Long nRightDiff = nXRight-nXPos; + if ( nRightDiff < nLeftDiff ) + nChar++; + } + } + else + { + sal_Int32 nMax = rPortion.GetLen(); + sal_Int32 nOffset = -1; + sal_Int32 nTmpCurIndex = nChar - pLine->GetStart(); + + tools::Long nXInPortion = nXPos - nXLeft; + if ( rPortion.IsRightToLeft() ) + nXInPortion = nXRight - nXPos; + + // Search in Array... + for ( sal_Int32 x = 0; x < nMax; x++ ) + { + tools::Long nTmpPosMax = pLine->GetCharPosArray()[nTmpCurIndex+x]; + if ( nTmpPosMax > nXInPortion ) + { + // Check whether this or the previous... + tools::Long nTmpPosMin = x ? pLine->GetCharPosArray()[nTmpCurIndex+x-1] : 0; + tools::Long nDiffLeft = nXInPortion - nTmpPosMin; + tools::Long nDiffRight = nTmpPosMax - nXInPortion; + OSL_ENSURE( nDiffLeft >= 0, "DiffLeft negative" ); + OSL_ENSURE( nDiffRight >= 0, "DiffRight negative" ); + + if (bSmart && nDiffRight < nDiffLeft) + { + // I18N: If there are character position with the length of 0, + // they belong to the same character, we can not use this position as an index. + // Skip all 0-positions, cheaper than using XBreakIterator: + tools::Long nX = pLine->GetCharPosArray()[nTmpCurIndex + x]; + while(x < nMax && pLine->GetCharPosArray()[nTmpCurIndex + x] == nX) + ++x; + } + nOffset = x; + break; + } + } + + // There should not be any inaccuracies when using the + // CharPosArray! Maybe for kerning? + // 0xFFF happens for example for Outline-Font when at the very end. + if ( nOffset < 0 ) + nOffset = nMax; + + OSL_ENSURE( nOffset <= nMax, "nOffset > nMax" ); + + nChar = nChar + nOffset; + + // Check if index is within a cell: + if ( nChar && ( nChar < pParaPortion->GetNode()->Len() ) ) + { + EditPaM aPaM( pParaPortion->GetNode(), nChar+1 ); + sal_uInt16 nScriptType = GetI18NScriptType( aPaM ); + if ( nScriptType == i18n::ScriptType::COMPLEX ) + { + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + sal_Int32 nCount = 1; + lang::Locale aLocale = GetLocale( aPaM ); + sal_Int32 nRight = _xBI->nextCharacters( + pParaPortion->GetNode()->GetString(), nChar, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount ); + sal_Int32 nLeft = _xBI->previousCharacters( + pParaPortion->GetNode()->GetString(), nRight, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount ); + if ( ( nLeft != nChar ) && ( nRight != nChar ) ) + { + nChar = ( std::abs( nRight - nChar ) < std::abs( nLeft - nChar ) ) ? nRight : nLeft; + } + } + else + { + OUString aStr(pParaPortion->GetNode()->GetString()); + // tdf#102625: don't select middle of a pair of surrogates with mouse cursor + if (rtl::isSurrogate(aStr[nChar])) + --nChar; + } + } + } + } + + nCurIndex = nCurIndex + rPortion.GetLen(); + } + + if ( nChar == -1 ) + { + nChar = ( nXPos <= pLine->GetStartPosX() ) ? pLine->GetStart() : pLine->GetEnd(); + } + + return nChar; +} + +Range ImpEditEngine::GetLineXPosStartEnd( const ParaPortion* pParaPortion, const EditLine* pLine ) const +{ + Range aLineXPosStartEnd; + + sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() ); + if ( !IsRightToLeft( nPara ) ) + { + aLineXPosStartEnd.Min() = pLine->GetStartPosX(); + aLineXPosStartEnd.Max() = pLine->GetStartPosX() + pLine->GetTextWidth(); + } + else + { + aLineXPosStartEnd.Min() = GetPaperSize().Width() - ( pLine->GetStartPosX() + pLine->GetTextWidth() ); + aLineXPosStartEnd.Max() = GetPaperSize().Width() - pLine->GetStartPosX(); + } + + + return aLineXPosStartEnd; +} + +tools::Long ImpEditEngine::GetPortionXOffset( + const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nTextPortion) const +{ + tools::Long nX = pLine->GetStartPosX(); + + for ( sal_Int32 i = pLine->GetStartPortion(); i < nTextPortion; i++ ) + { + const TextPortion& rPortion = pParaPortion->GetTextPortions()[i]; + switch ( rPortion.GetKind() ) + { + case PortionKind::FIELD: + case PortionKind::TEXT: + case PortionKind::HYPHENATOR: + case PortionKind::TAB: + { + nX += rPortion.GetSize().Width(); + } + break; + case PortionKind::LINEBREAK: break; + } + } + + sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() ); + bool bR2LPara = IsRightToLeft( nPara ); + + const TextPortion& rDestPortion = pParaPortion->GetTextPortions()[nTextPortion]; + if ( rDestPortion.GetKind() != PortionKind::TAB ) + { + if ( !bR2LPara && rDestPortion.GetRightToLeftLevel() ) + { + // Portions behind must be added, visual before this portion + sal_Int32 nTmpPortion = nTextPortion+1; + while ( nTmpPortion <= pLine->GetEndPortion() ) + { + const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + if ( rNextTextPortion.GetRightToLeftLevel() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) ) + nX += rNextTextPortion.GetSize().Width(); + else + break; + nTmpPortion++; + } + // Portions before must be removed, visual behind this portion + nTmpPortion = nTextPortion; + while ( nTmpPortion > pLine->GetStartPortion() ) + { + --nTmpPortion; + const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + if ( rPrevTextPortion.GetRightToLeftLevel() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) ) + nX -= rPrevTextPortion.GetSize().Width(); + else + break; + } + } + else if ( bR2LPara && !rDestPortion.IsRightToLeft() ) + { + // Portions behind must be removed, visual behind this portion + sal_Int32 nTmpPortion = nTextPortion+1; + while ( nTmpPortion <= pLine->GetEndPortion() ) + { + const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) ) + nX += rNextTextPortion.GetSize().Width(); + else + break; + nTmpPortion++; + } + // Portions before must be added, visual before this portion + nTmpPortion = nTextPortion; + while ( nTmpPortion > pLine->GetStartPortion() ) + { + --nTmpPortion; + const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) ) + nX -= rPrevTextPortion.GetSize().Width(); + else + break; + } + } + } + if ( bR2LPara ) + { + // Switch X positions... + OSL_ENSURE( GetTextRanger() || GetPaperSize().Width(), "GetPortionXOffset - paper size?!" ); + OSL_ENSURE( GetTextRanger() || (nX <= GetPaperSize().Width()), "GetPortionXOffset - position out of paper size!" ); + nX = GetPaperSize().Width() - nX; + nX -= rDestPortion.GetSize().Width(); + } + + return nX; +} + +tools::Long ImpEditEngine::GetXPos( + const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart) const +{ + OSL_ENSURE( pLine, "No line received: GetXPos" ); + OSL_ENSURE( ( nIndex >= pLine->GetStart() ) && ( nIndex <= pLine->GetEnd() ) , "GetXPos has to be called properly!" ); + + bool bDoPreferPortionStart = bPreferPortionStart; + // Assure that the portion belongs to this line: + if ( nIndex == pLine->GetStart() ) + bDoPreferPortionStart = true; + else if ( nIndex == pLine->GetEnd() ) + bDoPreferPortionStart = false; + + sal_Int32 nTextPortionStart = 0; + sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart ); + + OSL_ENSURE( ( nTextPortion >= pLine->GetStartPortion() ) && ( nTextPortion <= pLine->GetEndPortion() ), "GetXPos: Portion not in current line! " ); + + const TextPortion& rPortion = pParaPortion->GetTextPortions()[nTextPortion]; + + tools::Long nX = GetPortionXOffset( pParaPortion, pLine, nTextPortion ); + + // calc text width, portion size may include CJK/CTL spacing... + // But the array might not be init yet, if using text ranger this method is called within CreateLines()... + tools::Long nPortionTextWidth = rPortion.GetSize().Width(); + if ( ( rPortion.GetKind() == PortionKind::TEXT ) && rPortion.GetLen() && !GetTextRanger() ) + nPortionTextWidth = pLine->GetCharPosArray()[nTextPortionStart + rPortion.GetLen() - 1 - pLine->GetStart()]; + + if ( nTextPortionStart != nIndex ) + { + // Search within portion... + if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) ) + { + // End of Portion + if ( rPortion.GetKind() == PortionKind::TAB ) + { + if ( nTextPortion+1 < pParaPortion->GetTextPortions().Count() ) + { + const TextPortion& rNextPortion = pParaPortion->GetTextPortions()[nTextPortion+1]; + if ( rNextPortion.GetKind() != PortionKind::TAB ) + { + if ( !bPreferPortionStart ) + nX = GetXPos( pParaPortion, pLine, nIndex, true ); + else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) ) + nX += nPortionTextWidth; + } + } + else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) ) + { + nX += nPortionTextWidth; + } + } + else if ( !rPortion.IsRightToLeft() ) + { + nX += nPortionTextWidth; + } + } + else if ( rPortion.GetKind() == PortionKind::TEXT ) + { + OSL_ENSURE( nIndex != pLine->GetStart(), "Strange behavior in new GetXPos()" ); + OSL_ENSURE( pLine && !pLine->GetCharPosArray().empty(), "svx::ImpEditEngine::GetXPos(), portion in an empty line?" ); + + if( !pLine->GetCharPosArray().empty() ) + { + sal_Int32 nPos = nIndex - 1 - pLine->GetStart(); + if (nPos < 0 || o3tl::make_unsigned(nPos) >= pLine->GetCharPosArray().size()) + { + nPos = pLine->GetCharPosArray().size()-1; + OSL_FAIL("svx::ImpEditEngine::GetXPos(), index out of range!"); + } + + // old code restored see #i112788 (which leaves #i74188 unfixed again) + tools::Long nPosInPortion = pLine->GetCharPosArray()[nPos]; + + if ( !rPortion.IsRightToLeft() ) + { + nX += nPosInPortion; + } + else + { + nX += nPortionTextWidth - nPosInPortion; + } + + if ( rPortion.GetExtraInfos() && rPortion.GetExtraInfos()->bCompressed ) + { + nX += rPortion.GetExtraInfos()->nPortionOffsetX; + if ( rPortion.GetExtraInfos()->nAsianCompressionTypes & AsianCompressionFlags::PunctuationRight ) + { + AsianCompressionFlags nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex ) ); + if ( nType == AsianCompressionFlags::PunctuationRight && !pLine->GetCharPosArray().empty() ) + { + sal_Int32 n = nIndex - nTextPortionStart; + const sal_Int32* pDXArray = pLine->GetCharPosArray().data()+( nTextPortionStart-pLine->GetStart() ); + sal_Int32 nCharWidth = ( ( (n+1) < rPortion.GetLen() ) ? pDXArray[n] : rPortion.GetSize().Width() ) + - ( n ? pDXArray[n-1] : 0 ); + if ( (n+1) < rPortion.GetLen() ) + { + // smaller, when char behind is AsianCompressionFlags::PunctuationRight also + nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex+1 ) ); + if ( nType == AsianCompressionFlags::PunctuationRight ) + { + sal_Int32 nNextCharWidth = ( ( (n+2) < rPortion.GetLen() ) ? pDXArray[n+1] : rPortion.GetSize().Width() ) + - pDXArray[n]; + sal_Int32 nCompressed = nNextCharWidth/2; + nCompressed *= rPortion.GetExtraInfos()->nMaxCompression100thPercent; + nCompressed /= 10000; + nCharWidth += nCompressed; + } + } + else + { + nCharWidth *= 2; // last char pos to portion end is only compressed size + } + nX += nCharWidth/2; // 50% compression + } + } + } + } + } + } + else // if ( nIndex == pLine->GetStart() ) + { + if ( rPortion.IsRightToLeft() ) + { + nX += nPortionTextWidth; + } + } + + return nX; +} + +void ImpEditEngine::CalcHeight( ParaPortion* pPortion ) +{ + pPortion->nHeight = 0; + pPortion->nFirstLineOffset = 0; + + if ( !pPortion->IsVisible() ) + return; + + OSL_ENSURE( pPortion->GetLines().Count(), "Paragraph with no lines in ParaPortion::CalcHeight" ); + for (sal_Int32 nLine = 0; nLine < pPortion->GetLines().Count(); ++nLine) + pPortion->nHeight += pPortion->GetLines()[nLine].GetHeight(); + + if ( aStatus.IsOutliner() ) + return; + + const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); + const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + sal_Int32 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; + + if ( nSBL ) + { + if ( pPortion->GetLines().Count() > 1 ) + pPortion->nHeight += ( pPortion->GetLines().Count() - 1 ) * nSBL; + if ( aStatus.ULSpaceSummation() ) + pPortion->nHeight += nSBL; + } + + sal_Int32 nPortion = GetParaPortions().GetPos( pPortion ); + if ( nPortion ) + { + sal_uInt16 nUpper = GetYValue( rULItem.GetUpper() ); + pPortion->nHeight += nUpper; + pPortion->nFirstLineOffset = nUpper; + } + + if ( nPortion != (GetParaPortions().Count()-1) ) + { + pPortion->nHeight += GetYValue( rULItem.GetLower() ); // not in the last + } + + + if ( !nPortion || aStatus.ULSpaceSummation() ) + return; + + ParaPortion* pPrev = GetParaPortions().SafeGetObject( nPortion-1 ); + if (!pPrev) + return; + + const SvxULSpaceItem& rPrevULItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); + const SvxLineSpacingItem& rPrevLSItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + + // In relation between WinWord6/Writer3: + // With a proportional line spacing the paragraph spacing is + // also manipulated. + // Only Writer3: Do not add up, but minimum distance. + + // check if distance by LineSpacing > Upper: + sal_uInt16 nExtraSpace = GetYValue( lcl_CalcExtraSpace( rLSItem ) ); + if ( nExtraSpace > pPortion->nFirstLineOffset ) + { + // Paragraph becomes 'bigger': + pPortion->nHeight += ( nExtraSpace - pPortion->nFirstLineOffset ); + pPortion->nFirstLineOffset = nExtraSpace; + } + + // Determine nFirstLineOffset now f(pNode) => now f(pNode, pPrev): + sal_uInt16 nPrevLower = GetYValue( rPrevULItem.GetLower() ); + + // This PrevLower is still in the height of PrevPortion ... + if ( nPrevLower > pPortion->nFirstLineOffset ) + { + // Paragraph is 'small': + pPortion->nHeight -= pPortion->nFirstLineOffset; + pPortion->nFirstLineOffset = 0; + } + else if ( nPrevLower ) + { + // Paragraph becomes 'somewhat smaller': + pPortion->nHeight -= nPrevLower; + pPortion->nFirstLineOffset = + pPortion->nFirstLineOffset - nPrevLower; + } + // I find it not so good, but Writer3 feature: + // Check if distance by LineSpacing > Lower: this value is not + // stuck in the height of PrevPortion. + if ( pPrev->IsInvalid() ) + return; + + nExtraSpace = GetYValue( lcl_CalcExtraSpace( rPrevLSItem ) ); + if ( nExtraSpace > nPrevLower ) + { + sal_uInt16 nMoreLower = nExtraSpace - nPrevLower; + // Paragraph becomes 'bigger', 'grows' downwards: + if ( nMoreLower > pPortion->nFirstLineOffset ) + { + pPortion->nHeight += ( nMoreLower - pPortion->nFirstLineOffset ); + pPortion->nFirstLineOffset = nMoreLower; + } + } +} + +void ImpEditEngine::SetValidPaperSize( const Size& rNewSz ) +{ + aPaperSize = rNewSz; + + tools::Long nMinWidth = aStatus.AutoPageWidth() ? aMinAutoPaperSize.Width() : 0; + tools::Long nMaxWidth = aStatus.AutoPageWidth() ? aMaxAutoPaperSize.Width() : 0x7FFFFFFF; + tools::Long nMinHeight = aStatus.AutoPageHeight() ? aMinAutoPaperSize.Height() : 0; + tools::Long nMaxHeight = aStatus.AutoPageHeight() ? aMaxAutoPaperSize.Height() : 0x7FFFFFFF; + + // Minimum/Maximum width: + if ( aPaperSize.Width() < nMinWidth ) + aPaperSize.setWidth( nMinWidth ); + else if ( aPaperSize.Width() > nMaxWidth ) + aPaperSize.setWidth( nMaxWidth ); + + // Minimum/Maximum height: + if ( aPaperSize.Height() < nMinHeight ) + aPaperSize.setHeight( nMinHeight ); + else if ( aPaperSize.Height() > nMaxHeight ) + aPaperSize.setHeight( nMaxHeight ); +} + +std::shared_ptr<SvxForbiddenCharactersTable> const & ImpEditEngine::GetForbiddenCharsTable() +{ + return EditDLL::Get().GetGlobalData()->GetForbiddenCharsTable(); +} + +void ImpEditEngine::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars) +{ + EditDLL::Get().GetGlobalData()->SetForbiddenCharsTable( xForbiddenChars ); +} + +bool ImpEditEngine::IsVisualCursorTravelingEnabled() +{ + bool bVisualCursorTravaling = false; + + if( !pCTLOptions ) + pCTLOptions.reset( new SvtCTLOptions ); + + if ( pCTLOptions->IsCTLFontEnabled() && ( pCTLOptions->GetCTLCursorMovement() == SvtCTLOptions::MOVEMENT_VISUAL ) ) + { + bVisualCursorTravaling = true; + } + + return bVisualCursorTravaling; + +} + +bool ImpEditEngine::DoVisualCursorTraveling() +{ + // Don't check if it's necessary, because we also need it when leaving the paragraph + return IsVisualCursorTravelingEnabled(); +} + +IMPL_LINK_NOARG(ImpEditEngine, DocModified, LinkParamNone*, void) +{ + aModifyHdl.Call( nullptr /*GetEditEnginePtr()*/ ); // NULL, because also used for Outliner +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx new file mode 100644 index 000000000..db6f71b6d --- /dev/null +++ b/editeng/source/editeng/impedit3.cxx @@ -0,0 +1,4698 @@ +/* -*- 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/metaact.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/settings.hxx> +#include <vcl/window.hxx> + +#include <editeng/tstpitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include "impedit.hxx" +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/txtrange.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/numitem.hxx> + +#include <svtools/colorcfg.hxx> +#include <svl/ctloptions.hxx> +#include <svl/asiancfg.hxx> + +#include <editeng/hngpnctitem.hxx> +#include <editeng/forbiddencharacterstable.hxx> + +#include <unotools/configmgr.hxx> + +#include <math.h> +#include <vcl/metric.hxx> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/InputSequenceChecker.hpp> +#include <vcl/pdfextoutdevdata.hxx> +#include <i18nlangtag/mslangid.hxx> + +#include <comphelper/processfactory.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/sorted_vector.hxx> +#include <osl/diagnose.h> +#include <comphelper/string.hxx> +#include <cstddef> +#include <memory> +#include <set> + +#include <vcl/outdev/ScopedStates.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; + +constexpr OUStringLiteral CH_HYPH = u"-"; + +constexpr tools::Long WRONG_SHOW_MIN = 5; + +namespace { + +struct TabInfo +{ + bool bValid; + + SvxTabStop aTabStop; + sal_Int32 nTabPortion; + tools::Long nStartPosX; + tools::Long nTabPos; + + TabInfo() + : bValid(false) + , nTabPortion(0) + , nStartPosX(0) + , nTabPos(0) + { } + +}; + +} + +Point Rotate( const Point& rPoint, Degree10 nOrientation, const Point& rOrigin ) +{ + double nRealOrientation = toRadians(nOrientation); + double nCos = cos( nRealOrientation ); + double nSin = sin( nRealOrientation ); + + Point aRotatedPos; + Point aTranslatedPos( rPoint ); + + // Translation + aTranslatedPos -= rOrigin; + + // Rotation... + aRotatedPos.setX( static_cast<tools::Long>( nCos*aTranslatedPos.X() + nSin*aTranslatedPos.Y() ) ); + aRotatedPos.setY( static_cast<tools::Long>(- ( nSin*aTranslatedPos.X() - nCos*aTranslatedPos.Y() )) ); + aTranslatedPos = aRotatedPos; + + // Translation... + aTranslatedPos += rOrigin; + return aTranslatedPos; +} + +AsianCompressionFlags GetCharTypeForCompression( sal_Unicode cChar ) +{ + switch ( cChar ) + { + case 0x3008: case 0x300A: case 0x300C: case 0x300E: + case 0x3010: case 0x3014: case 0x3016: case 0x3018: + case 0x301A: case 0x301D: + { + return AsianCompressionFlags::PunctuationRight; + } + case 0x3001: case 0x3002: case 0x3009: case 0x300B: + case 0x300D: case 0x300F: case 0x3011: case 0x3015: + case 0x3017: case 0x3019: case 0x301B: case 0x301E: + case 0x301F: + { + return AsianCompressionFlags::PunctuationLeft; + } + default: + { + return ( ( 0x3040 <= cChar ) && ( 0x3100 > cChar ) ) ? AsianCompressionFlags::Kana : AsianCompressionFlags::Normal; + } + } +} + +static void lcl_DrawRedLines( OutputDevice& rOutDev, + tools::Long nFontHeight, + const Point& rPoint, + size_t nIndex, + size_t nMaxEnd, + o3tl::span<const sal_Int32> pDXArray, + WrongList const * pWrongs, + Degree10 nOrientation, + const Point& rOrigin, + bool bVertical, + bool bIsRightToLeft ) +{ + // But only if font is not too small... + tools::Long nHeight = rOutDev.LogicToPixel(Size(0, nFontHeight)).Height(); + if (WRONG_SHOW_MIN >= nHeight) + return; + + size_t nEnd, nStart = nIndex; + bool bWrong = pWrongs->NextWrong(nStart, nEnd); + + while (bWrong) + { + if (nStart >= nMaxEnd) + break; + + if (nStart < nIndex) // Corrected + nStart = nIndex; + + if (nEnd > nMaxEnd) + nEnd = nMaxEnd; + + Point aPoint1(rPoint); + if (bVertical) + { + // VCL doesn't know that the text is vertical, and is manipulating + // the positions a little bit in y direction... + tools::Long nOnePixel = rOutDev.PixelToLogic(Size(0, 1)).Height(); + tools::Long nCorrect = 2 * nOnePixel; + aPoint1.AdjustY(-nCorrect); + aPoint1.AdjustX(-nCorrect); + } + if (nStart > nIndex) + { + if (!bVertical) + { + // since for RTL portions rPoint is on the visual right end of the portion + // (i.e. at the start of the first RTL char) we need to subtract the offset + // for RTL portions... + aPoint1.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nStart - nIndex - 1]); + } + else + aPoint1.AdjustY(pDXArray[nStart - nIndex - 1]); + } + Point aPoint2(rPoint); + assert(nEnd > nIndex && "RedLine: aPnt2?"); + if (!bVertical) + { + // since for RTL portions rPoint is on the visual right end of the portion + // (i.e. at the start of the first RTL char) we need to subtract the offset + // for RTL portions... + aPoint2.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nEnd - nIndex - 1]); + } + else + { + aPoint2.AdjustY(pDXArray[nEnd - nIndex - 1]); + } + + if (nOrientation) + { + aPoint1 = Rotate(aPoint1, nOrientation, rOrigin); + aPoint2 = Rotate(aPoint2, nOrientation, rOrigin); + } + + { + vcl::ScopedAntialiasing a(rOutDev, true); + rOutDev.DrawWaveLine(aPoint1, aPoint2); + } + + nStart = nEnd + 1; + if (nEnd < nMaxEnd) + bWrong = pWrongs->NextWrong(nStart, nEnd); + else + bWrong = false; + } +} + +static Point lcl_ImplCalcRotatedPos( Point rPos, Point rOrigin, double nSin, double nCos ) +{ + Point aRotatedPos; + // Translation... + Point aTranslatedPos( rPos); + aTranslatedPos -= rOrigin; + + aRotatedPos.setX( static_cast<tools::Long>( nCos*aTranslatedPos.X() + nSin*aTranslatedPos.Y() ) ); + aRotatedPos.setY( static_cast<tools::Long>(- ( nSin*aTranslatedPos.X() - nCos*aTranslatedPos.Y() )) ); + aTranslatedPos = aRotatedPos; + // Translation... + aTranslatedPos += rOrigin; + + return aTranslatedPos; +} + +static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh ) // For Kashidas from sw/source/core/text/porlay.txt +{ + // Lam + Alef + return ( 0x644 == cCh && 0x627 == cNextCh ) || + // Beh + Reh + ( 0x628 == cCh && 0x631 == cNextCh ); +} + +static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh ) // For Kashidas from sw/source/core/text/porlay.txt +{ + // Alef, Dal, Thal, Reh, Zain, and Waw do not connect to the left + bool bRet = 0x627 != cPrevCh && 0x62F != cPrevCh && 0x630 != cPrevCh && + 0x631 != cPrevCh && 0x632 != cPrevCh && 0x648 != cPrevCh; + + // check for ligatures cPrevChar + cChar + if ( bRet ) + bRet = ! lcl_IsLigature( cPrevCh, cCh ); + + return bRet; +} + + + +void ImpEditEngine::UpdateViews( EditView* pCurView ) +{ + if ( !IsUpdateLayout() || IsFormatting() || aInvalidRect.IsEmpty() ) + return; + + DBG_ASSERT( IsFormatted(), "UpdateViews: Doc not formatted!" ); + + for (EditView* pView : aEditViews) + { + pView->HideCursor(); + + tools::Rectangle aClipRect( aInvalidRect ); + tools::Rectangle aVisArea( pView->GetVisArea() ); + aClipRect.Intersection( aVisArea ); + + if ( !aClipRect.IsEmpty() ) + { + // convert to window coordinates... + aClipRect = pView->pImpEditView->GetWindowPos( aClipRect ); + + // moved to one executing method to allow finer control + pView->InvalidateWindow(aClipRect); + + pView->InvalidateOtherViewWindows( aClipRect ); + } + } + + if ( pCurView ) + { + bool bGotoCursor = pCurView->pImpEditView->DoAutoScroll(); + pCurView->ShowCursor( bGotoCursor ); + } + + aInvalidRect = tools::Rectangle(); + CallStatusHdl(); +} + +IMPL_LINK_NOARG(ImpEditEngine, OnlineSpellHdl, Timer *, void) +{ + if ( !Application::AnyInput( VclInputFlags::KEYBOARD ) && IsUpdateLayout() && IsFormatted() ) + DoOnlineSpelling(); + else + aOnlineSpellTimer.Start(); +} + +IMPL_LINK_NOARG(ImpEditEngine, IdleFormatHdl, Timer *, void) +{ + aIdleFormatter.ResetRestarts(); + + // #i97146# check if that view is still available + // else probably the idle format timer fired while we're already + // downing + EditView* pView = aIdleFormatter.GetView(); + for (EditView* aEditView : aEditViews) + { + if( aEditView == pView ) + { + FormatAndLayout( pView ); + break; + } + } +} + +void ImpEditEngine::CheckIdleFormatter() +{ + aIdleFormatter.ForceTimeout(); + // If not idle, but still not formatted: + if ( !IsFormatted() ) + FormatDoc(); +} + +bool ImpEditEngine::IsPageOverflow( ) const +{ + return mbNeedsChainingHandling; +} + + +void ImpEditEngine::FormatFullDoc() +{ + for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) + GetParaPortions()[nPortion]->MarkSelectionInvalid( 0 ); + FormatDoc(); +} + +void ImpEditEngine::FormatDoc() +{ + if (!IsUpdateLayout() || IsFormatting()) + return; + + bIsFormatting = true; + + // Then I can also start the spell-timer... + if ( GetStatus().DoOnlineSpelling() ) + StartOnlineSpellTimer(); + + tools::Long nY = 0; + bool bGrow = false; + + // Here already, so that not always in CreateLines... + bool bMapChanged = ImpCheckRefMapMode(); + sal_Int32 nParaCount = GetParaPortions().Count(); + o3tl::sorted_vector<sal_Int32> aRepaintParas; + aRepaintParas.reserve(nParaCount); + + for ( sal_Int32 nPara = 0; nPara < nParaCount; nPara++ ) + { + ParaPortion* pParaPortion = GetParaPortions()[nPara]; + if ( pParaPortion->MustRepaint() || ( pParaPortion->IsInvalid() && pParaPortion->IsVisible() ) ) + { + // No formatting should be necessary for MustRepaint()! + if ( !pParaPortion->IsInvalid() || CreateLines( nPara, nY ) ) + { + if ( !bGrow && GetTextRanger() ) + { + // For a change in height all below must be reformatted... + for ( sal_Int32 n = nPara+1; n < GetParaPortions().Count(); n++ ) + { + ParaPortion* pPP = GetParaPortions()[n]; + pPP->MarkSelectionInvalid( 0 ); + pPP->GetLines().Reset(); + } + } + bGrow = true; + if ( IsCallParaInsertedOrDeleted() ) + { + GetEditEnginePtr()->ParagraphHeightChanged( nPara ); + + for (EditView* pView : aEditViews) + { + ImpEditView* pImpView = pView->pImpEditView.get(); + pImpView->ScrollStateChange(); + } + + } + pParaPortion->SetMustRepaint( false ); + } + + aRepaintParas.insert(nPara); + } + nY += pParaPortion->GetHeight(); + } + + aInvalidRect = tools::Rectangle(); // make empty + + // One can also get into the formatting through UpdateMode ON=>OFF=>ON... + // enable optimization first after Vobis delivery... + { + tools::Long nNewHeightNTP; + tools::Long nNewHeight = CalcTextHeight(&nNewHeightNTP); + tools::Long nDiff = nNewHeight - nCurTextHeight; + if ( nDiff ) + { + aInvalidRect.Union(tools::Rectangle::Justify( + { 0, nNewHeight }, { getWidthDirectionAware(aPaperSize), nCurTextHeight })); + aStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED; + } + + nCurTextHeight = nNewHeight; + nCurTextHeightNTP = nNewHeightNTP; + + if ( aStatus.AutoPageSize() ) + CheckAutoPageSize(); + else if ( nDiff ) + { + for (EditView* pView : aEditViews) + { + ImpEditView* pImpView = pView->pImpEditView.get(); + if ( pImpView->DoAutoHeight() ) + { + Size aSz( pImpView->GetOutputArea().GetWidth(), nCurTextHeight ); + if ( aSz.Height() > aMaxAutoPaperSize.Height() ) + aSz.setHeight( aMaxAutoPaperSize.Height() ); + else if ( aSz.Height() < aMinAutoPaperSize.Height() ) + aSz.setHeight( aMinAutoPaperSize.Height() ); + pImpView->ResetOutputArea( tools::Rectangle( + pImpView->GetOutputArea().TopLeft(), aSz ) ); + } + } + } + + if (!aRepaintParas.empty()) + { + auto CombineRepaintParasAreas = [&](const LineAreaInfo& rInfo) { + if (aRepaintParas.count(rInfo.nPortion)) + aInvalidRect.Union(rInfo.aArea); + return CallbackResult::Continue; + }; + IterateLineAreas(CombineRepaintParasAreas, IterFlag::inclILS); + } + } + + bIsFormatting = false; + bFormatted = true; + + if ( bMapChanged ) + GetRefDevice()->Pop(); + + CallStatusHdl(); // If Modified... +} + +bool ImpEditEngine::ImpCheckRefMapMode() +{ + bool bChange = false; + + if ( aStatus.DoFormat100() ) + { + MapMode aMapMode( GetRefDevice()->GetMapMode() ); + if ( aMapMode.GetScaleX().GetNumerator() != aMapMode.GetScaleX().GetDenominator() ) + bChange = true; + else if ( aMapMode.GetScaleY().GetNumerator() != aMapMode.GetScaleY().GetDenominator() ) + bChange = true; + + if ( bChange ) + { + Fraction Scale1( 1, 1 ); + aMapMode.SetScaleX( Scale1 ); + aMapMode.SetScaleY( Scale1 ); + GetRefDevice()->Push(); + GetRefDevice()->SetMapMode( aMapMode ); + } + } + + return bChange; +} + +void ImpEditEngine::CheckAutoPageSize() +{ + Size aPrevPaperSize( GetPaperSize() ); + if ( GetStatus().AutoPageWidth() ) + aPaperSize.setWidth( !IsEffectivelyVertical() ? CalcTextWidth( true ) : GetTextHeight() ); + if ( GetStatus().AutoPageHeight() ) + aPaperSize.setHeight( !IsEffectivelyVertical() ? GetTextHeight() : CalcTextWidth( true ) ); + + SetValidPaperSize( aPaperSize ); // consider Min, Max + + if ( aPaperSize == aPrevPaperSize ) + return; + + if ( ( !IsEffectivelyVertical() && ( aPaperSize.Width() != aPrevPaperSize.Width() ) ) + || ( IsEffectivelyVertical() && ( aPaperSize.Height() != aPrevPaperSize.Height() ) ) ) + { + // If ahead is centered / right or tabs... + aStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TEXTWIDTHCHANGED : EditStatusFlags::TextHeightChanged; + for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) + { + // Only paragraphs which are not aligned to the left need to be + // reformatted, the height can not be changed here anymore. + ParaPortion* pParaPortion = GetParaPortions()[nPara]; + SvxAdjust eJustification = GetJustification( nPara ); + if ( eJustification != SvxAdjust::Left ) + { + pParaPortion->MarkSelectionInvalid( 0 ); + CreateLines( nPara, 0 ); // 0: For AutoPageSize no TextRange! + } + } + } + + Size aInvSize = aPaperSize; + if ( aPaperSize.Width() < aPrevPaperSize.Width() ) + aInvSize.setWidth( aPrevPaperSize.Width() ); + if ( aPaperSize.Height() < aPrevPaperSize.Height() ) + aInvSize.setHeight( aPrevPaperSize.Height() ); + + Size aSz( aInvSize ); + if ( IsEffectivelyVertical() ) + { + aSz.setWidth( aInvSize.Height() ); + aSz.setHeight( aInvSize.Width() ); + } + aInvalidRect = tools::Rectangle( Point(), aSz ); + + + for (EditView* pView : aEditViews) + { + pView->pImpEditView->RecalcOutputArea(); + } +} + +void ImpEditEngine::CheckPageOverflow() +{ + SAL_INFO("editeng.chaining", "[CONTROL_STATUS] AutoPageSize is " << (( aStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") ); + + tools::Long nBoxHeight = GetMaxAutoPaperSize().Height(); + SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current MaxAutoPaperHeight is " << nBoxHeight); + + tools::Long nTxtHeight = CalcTextHeight(nullptr); + SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current Text Height is " << nTxtHeight); + + sal_uInt32 nParaCount = GetParaPortions().Count(); + sal_uInt32 nFirstLineCount = GetLineCount(0); + bool bOnlyOneEmptyPara = (nParaCount == 1) && + (nFirstLineCount == 1) && + (GetLineLen(0,0) == 0); + + if (nTxtHeight > nBoxHeight && !bOnlyOneEmptyPara) + { + // which paragraph is the first to cause higher size of the box? + ImplUpdateOverflowingParaNum( nBoxHeight); // XXX: currently only for horizontal text + //aStatus.SetPageOverflow(true); + mbNeedsChainingHandling = true; + } else + { + // No overflow if within box boundaries + //aStatus.SetPageOverflow(false); + mbNeedsChainingHandling = false; + } + +} + +static sal_Int32 ImplCalculateFontIndependentLineSpacing( const sal_Int32 nFontHeight ) +{ + return ( nFontHeight * 12 ) / 10; // + 20% +} + +tools::Long ImpEditEngine::GetColumnWidth(const Size& rPaperSize) const +{ + assert(mnColumns >= 1); + tools::Long nWidth = IsEffectivelyVertical() ? rPaperSize.Height() : rPaperSize.Width(); + return (nWidth - mnColumnSpacing * (mnColumns - 1)) / mnColumns; +} + +bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) +{ + ParaPortion* pParaPortion = GetParaPortions()[nPara]; + + // sal_Bool: Changes in the height of paragraph Yes / No - sal_True/sal_False + assert( pParaPortion->GetNode() && "Portion without Node in CreateLines" ); + DBG_ASSERT( pParaPortion->IsVisible(), "Invisible paragraphs not formatted!" ); + DBG_ASSERT( pParaPortion->IsInvalid(), "CreateLines: Portion not invalid!" ); + + bool bProcessingEmptyLine = ( pParaPortion->GetNode()->Len() == 0 ); + bool bEmptyNodeWithPolygon = ( pParaPortion->GetNode()->Len() == 0 ) && GetTextRanger(); + + + // Fast special treatment for empty paragraphs... + + if ( ( pParaPortion->GetNode()->Len() == 0 ) && !GetTextRanger() ) + { + // fast special treatment... + if ( pParaPortion->GetTextPortions().Count() ) + pParaPortion->GetTextPortions().Reset(); + if ( pParaPortion->GetLines().Count() ) + pParaPortion->GetLines().Reset(); + CreateAndInsertEmptyLine( pParaPortion ); + return FinishCreateLines( pParaPortion ); + } + + sal_Int64 nCurrentPosY = nStartPosY; + // If we're allowed to skip parts outside and this cannot possibly fit in the given height, + // bail out to avoid possibly formatting a lot of text that will not be used. For the first + // paragraph still format at least a bit. + if( mbSkipOutsideFormat && nPara != 0 + && !aStatus.AutoPageHeight() && aPaperSize.Height() < nCurrentPosY ) + { + return false; + } + + // Initialization... + + // Always format for 100%: + bool bMapChanged = ImpCheckRefMapMode(); + + if ( pParaPortion->GetLines().Count() == 0 ) + { + EditLine* pL = new EditLine; + pParaPortion->GetLines().Append(pL); + } + + + // Get Paragraph attributes... + + ContentNode* const pNode = pParaPortion->GetNode(); + + bool bRightToLeftPara = IsRightToLeft( nPara ); + + SvxAdjust eJustification = GetJustification( nPara ); + bool bHyphenatePara = pNode->GetContentAttribs().GetItem( EE_PARA_HYPHENATE ).GetValue(); + sal_Int32 nSpaceBefore = 0; + sal_Int32 nMinLabelWidth = 0; + sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pNode, &nSpaceBefore, &nMinLabelWidth ); + const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pNode ); + const SvxLineSpacingItem& rLSItem = pNode->GetContentAttribs().GetItem( EE_PARA_SBL ); + const bool bScriptSpace = pNode->GetContentAttribs().GetItem( EE_PARA_ASIANCJKSPACING ).GetValue(); + + const short nInvalidDiff = pParaPortion->GetInvalidDiff(); + const sal_Int32 nInvalidStart = pParaPortion->GetInvalidPosStart(); + const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff ); + + bool bQuickFormat = false; + if ( !bEmptyNodeWithPolygon && !HasScriptType( nPara, i18n::ScriptType::COMPLEX ) ) + { + if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff > 0 ) && + ( pNode->GetString().indexOf( CH_FEATURE, nInvalidStart ) > nInvalidEnd ) ) + { + bQuickFormat = true; + } + else if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff < 0 ) ) + { + // check if delete over the portion boundaries was done... + sal_Int32 nStart = nInvalidStart; // DOUBLE !!!!!!!!!!!!!!! + sal_Int32 nEnd = nStart - nInvalidDiff; // negative + bQuickFormat = true; + sal_Int32 nPos = 0; + sal_Int32 nPortions = pParaPortion->GetTextPortions().Count(); + for ( sal_Int32 nTP = 0; nTP < nPortions; nTP++ ) + { + // There must be no start / end in the deleted area. + const TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ]; + nPos = nPos + rTP.GetLen(); + if ( ( nPos > nStart ) && ( nPos < nEnd ) ) + { + bQuickFormat = false; + break; + } + } + } + } + + // Saving both layout mode and language (since I'm potentially changing both) + GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE ); + + ImplInitLayoutMode(*GetRefDevice(), nPara, -1); + + sal_Int32 nRealInvalidStart = nInvalidStart; + + if ( bEmptyNodeWithPolygon ) + { + TextPortion* pDummyPortion = new TextPortion( 0 ); + pParaPortion->GetTextPortions().Reset(); + pParaPortion->GetTextPortions().Append(pDummyPortion); + } + else if ( bQuickFormat ) + { + // faster Method: + RecalcTextPortion( pParaPortion, nInvalidStart, nInvalidDiff ); + } + else // nRealInvalidStart can be before InvalidStart, since Portions were deleted... + { + CreateTextPortions( pParaPortion, nRealInvalidStart ); + } + + + // Search for line with InvalidPos, start one line before + // Flag the line => do not remove it ! + + + sal_Int32 nLine = pParaPortion->GetLines().Count()-1; + for ( sal_Int32 nL = 0; nL <= nLine; nL++ ) + { + EditLine& rLine = pParaPortion->GetLines()[nL]; + if ( rLine.GetEnd() > nRealInvalidStart ) // not nInvalidStart! + { + nLine = nL; + break; + } + rLine.SetValid(); + } + // Begin one line before... + // If it is typed at the end, the line in front cannot change. + if ( nLine && ( !pParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->Len() ) || ( nInvalidDiff <= 0 ) ) ) + nLine--; + + EditLine* pLine = &pParaPortion->GetLines()[nLine]; + + static const tools::Rectangle aZeroArea { Point(), Point() }; + tools::Rectangle aBulletArea( aZeroArea ); + if ( !nLine ) + { + aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) ); + if ( !aBulletArea.IsWidthEmpty() && aBulletArea.Right() > 0 ) + pParaPortion->SetBulletX( static_cast<sal_Int32>(GetXValue( aBulletArea.Right() )) ); + else + pParaPortion->SetBulletX( 0 ); // if Bullet is set incorrectly + } + + + // Reformat all lines from here... + + sal_Int32 nDelFromLine = -1; + bool bLineBreak = false; + + sal_Int32 nIndex = pLine->GetStart(); + EditLine aSaveLine( *pLine ); + SvxFont aTmpFont( pNode->GetCharAttribs().GetDefFont() ); + + std::vector<sal_Int32> aBuf( pNode->Len() ); + + bool bSameLineAgain = false; // For TextRanger, if the height changes. + TabInfo aCurrentTab; + + bool bForceOneRun = bEmptyNodeWithPolygon; + bool bCompressedChars = false; + + while ( ( nIndex < pNode->Len() ) || bForceOneRun ) + { + bForceOneRun = false; + + bool bEOL = false; + bool bEOC = false; + sal_Int32 nPortionStart = 0; + sal_Int32 nPortionEnd = 0; + + tools::Long nStartX = GetXValue( rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth ); + if ( nIndex == 0 ) + { + tools::Long nFI = GetXValue( rLRItem.GetTextFirstLineOffset() ); + nStartX += nFI; + + if ( !nLine && ( pParaPortion->GetBulletX() > nStartX ) ) + { + nStartX = pParaPortion->GetBulletX(); + } + } + + const bool bAutoSize = IsEffectivelyVertical() ? aStatus.AutoPageHeight() : aStatus.AutoPageWidth(); + tools::Long nMaxLineWidth = GetColumnWidth(bAutoSize ? aMaxAutoPaperSize : aPaperSize); + + nMaxLineWidth -= GetXValue( rLRItem.GetRight() ); + nMaxLineWidth -= nStartX; + + // If PaperSize == long_max, one cannot take away any negative + // first line indent. (Overflow) + if ( ( nMaxLineWidth < 0 ) && ( nStartX < 0 ) ) + nMaxLineWidth = GetColumnWidth(aPaperSize) - GetXValue(rLRItem.GetRight()); + + // If still less than 0, it may be just the right edge. + if ( nMaxLineWidth <= 0 ) + nMaxLineWidth = 1; + + // Problem: + // Since formatting starts a line _before_ the invalid position, + // the positions unfortunately have to be redefined... + // Solution: + // The line before can only become longer, not smaller + // =>... + pLine->GetCharPosArray().clear(); + + sal_Int32 nTmpPos = nIndex; + sal_Int32 nTmpPortion = pLine->GetStartPortion(); + tools::Long nTmpWidth = 0; + tools::Long nXWidth = nMaxLineWidth; + + std::deque<tools::Long>* pTextRanges = nullptr; + tools::Long nTextExtraYOffset = 0; + tools::Long nTextXOffset = 0; + tools::Long nTextLineHeight = 0; + if ( GetTextRanger() ) + { + GetTextRanger()->SetVertical( IsEffectivelyVertical() ); + + tools::Long nTextY = nStartPosY + GetEditCursor( pParaPortion, pLine, pLine->GetStart(), GetCursorFlags::NONE ).Top(); + if ( !bSameLineAgain ) + { + SeekCursor( pNode, nTmpPos+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + + if ( IsFixedCellHeight() ) + nTextLineHeight = ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ); + else + nTextLineHeight = aTmpFont.GetPhysTxtSize( GetRefDevice() ).Height(); + // Metrics can be greater + FormatterFontMetric aTempFormatterMetrics; + RecalcFormatterFontMetrics( aTempFormatterMetrics, aTmpFont ); + sal_uInt16 nLineHeight = aTempFormatterMetrics.GetHeight(); + if ( nLineHeight > nTextLineHeight ) + nTextLineHeight = nLineHeight; + } + else + nTextLineHeight = pLine->GetHeight(); + + nXWidth = 0; + while ( !nXWidth ) + { + tools::Long nYOff = nTextY + nTextExtraYOffset; + tools::Long nYDiff = nTextLineHeight; + if ( IsEffectivelyVertical() ) + { + tools::Long nMaxPolygonX = GetTextRanger()->GetBoundRect().Right(); + nYOff = nMaxPolygonX-nYOff; + nYDiff = -nTextLineHeight; + } + pTextRanges = GetTextRanger()->GetTextRanges( Range( nYOff, nYOff + nYDiff ) ); + assert( pTextRanges && "GetTextRanges?!" ); + tools::Long nMaxRangeWidth = 0; + // Use the widest range... + // The widest range could be a bit confusing, so normally it + // is the first one. Best with gaps. + assert(pTextRanges->size() % 2 == 0 && "textranges are always in pairs"); + if (!pTextRanges->empty()) + { + tools::Long nA = pTextRanges->at(0); + tools::Long nB = pTextRanges->at(1); + DBG_ASSERT( nA <= nB, "TextRange distorted?" ); + tools::Long nW = nB - nA; + if ( nW > nMaxRangeWidth ) + { + nMaxRangeWidth = nW; + nTextXOffset = nA; + } + } + nXWidth = nMaxRangeWidth; + if ( nXWidth ) + nMaxLineWidth = nXWidth - nStartX - GetXValue( rLRItem.GetRight() ); + else + { + // Try further down in the polygon. + // Below the polygon use the Paper Width. + nTextExtraYOffset += std::max( static_cast<tools::Long>(nTextLineHeight / 10), tools::Long(1) ); + if ( ( nTextY + nTextExtraYOffset ) > GetTextRanger()->GetBoundRect().Bottom() ) + { + nXWidth = getWidthDirectionAware(GetPaperSize()); + if ( !nXWidth ) // AutoPaperSize + nXWidth = 0x7FFFFFFF; + } + } + } + } + + // search for Portion that no longer fits in line... + TextPortion* pPortion = nullptr; + sal_Int32 nPortionLen = 0; + bool bContinueLastPortion = false; + bool bBrokenLine = false; + bLineBreak = false; + const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature( pLine->GetStart() ); + while ( ( nTmpWidth < nXWidth ) && !bEOL ) + { + const sal_Int32 nTextPortions = pParaPortion->GetTextPortions().Count(); + assert(nTextPortions > 0); + bContinueLastPortion = (nTmpPortion >= nTextPortions); + if (bContinueLastPortion) + { + if (nTmpPos >= pNode->Len()) + break; // while + + // Continue with remainder. This only to have *some* valid + // X-values and not endlessly create new lines until DOOM... + // Happened in the scenario of tdf#104152 where inserting a + // paragraph lead to a11y attempting to format the doc to + // obtain content when notified. + nTmpPortion = nTextPortions - 1; + SAL_WARN("editeng","ImpEditEngine::CreateLines - continuation of a broken portion"); + } + + nPortionStart = nTmpPos; + pPortion = &pParaPortion->GetTextPortions()[nTmpPortion]; + if ( !bContinueLastPortion && pPortion->GetKind() == PortionKind::HYPHENATOR ) + { + // Throw away a Portion, if necessary correct the one before, + // if the Hyph portion has swallowed a character... + sal_Int32 nTmpLen = pPortion->GetLen(); + pParaPortion->GetTextPortions().Remove( nTmpPortion ); + if (nTmpPortion && nTmpLen) + { + nTmpPortion--; + TextPortion& rPrev = pParaPortion->GetTextPortions()[nTmpPortion]; + DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" ); + nTmpWidth -= rPrev.GetSize().Width(); + nTmpPos = nTmpPos - rPrev.GetLen(); + rPrev.SetLen(rPrev.GetLen() + nTmpLen); + rPrev.GetSize().setWidth( -1 ); + } + + assert( nTmpPortion < pParaPortion->GetTextPortions().Count() && "No more Portions left!" ); + pPortion = &pParaPortion->GetTextPortions()[nTmpPortion]; + } + + if (bContinueLastPortion) + { + // Note that this may point behind the portion and is only to + // be used with the node's string offsets to generate X-values. + nPortionLen = pNode->Len() - nPortionStart; + } + else + { + nPortionLen = pPortion->GetLen(); + } + + DBG_ASSERT( pPortion->GetKind() != PortionKind::HYPHENATOR, "CreateLines: Hyphenator-Portion!" ); + DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion in CreateLines ?!" ); + if ( pNextFeature && ( pNextFeature->GetStart() == nTmpPos ) ) + { + SAL_WARN_IF( bContinueLastPortion, + "editeng","ImpEditEngine::CreateLines - feature in continued portion will be wrong"); + sal_uInt16 nWhich = pNextFeature->GetItem()->Which(); + switch ( nWhich ) + { + case EE_FEATURE_TAB: + { + tools::Long nOldTmpWidth = nTmpWidth; + + // Search for Tab-Pos... + tools::Long nCurPos = nTmpWidth+nStartX; + // consider scaling + if ( aStatus.DoStretch() && ( nStretchX != 100 ) ) + nCurPos = nCurPos*100/std::max(static_cast<sal_Int32>(nStretchX), static_cast<sal_Int32>(1)); + + short nAllSpaceBeforeText = static_cast< short >(rLRItem.GetTextLeft()/* + rLRItem.GetTextLeft()*/ + nSpaceBeforeAndMinLabelWidth); + aCurrentTab.aTabStop = pNode->GetContentAttribs().FindTabStop( nCurPos - nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/, aEditDoc.GetDefTab() ); + aCurrentTab.nTabPos = GetXValue( static_cast<tools::Long>( aCurrentTab.aTabStop.GetTabPos() + nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/ ) ); + aCurrentTab.bValid = false; + + // Switch direction in R2L para... + if ( bRightToLeftPara ) + { + if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) + aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Left; + else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Left ) + aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Right; + } + + if ( ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) || + ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) || + ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) ) + { + // For LEFT / DEFAULT this tab is not considered. + aCurrentTab.bValid = true; + aCurrentTab.nStartPosX = nTmpWidth; + aCurrentTab.nTabPortion = nTmpPortion; + } + + pPortion->SetKind(PortionKind::TAB); + pPortion->SetExtraValue( aCurrentTab.aTabStop.GetFill() ); + pPortion->GetSize().setWidth( aCurrentTab.nTabPos - (nTmpWidth+nStartX) ); + + // Height needed... + SeekCursor( pNode, nTmpPos+1, aTmpFont ); + pPortion->GetSize().setHeight( + aTmpFont.QuickGetTextSize( GetRefDevice(), OUString(), 0, 0, nullptr ).Height() ); + + DBG_ASSERT( pPortion->GetSize().Width() >= 0, "Tab incorrectly calculated!" ); + + nTmpWidth = aCurrentTab.nTabPos-nStartX; + + // If this is the first token on the line, + // and nTmpWidth > aPaperSize.Width, => infinite loop! + if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) ) + { + // What now? + // make the tab fitting + pPortion->GetSize().setWidth( nXWidth-nOldTmpWidth ); + nTmpWidth = nXWidth-1; + bEOL = true; + bBrokenLine = true; + } + EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); + size_t nPos = nTmpPos - pLine->GetStart(); + rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width()); + bCompressedChars = false; + } + break; + case EE_FEATURE_LINEBR: + { + assert( pPortion ); + pPortion->GetSize().setWidth( 0 ); + bEOL = true; + bLineBreak = true; + pPortion->SetKind( PortionKind::LINEBREAK ); + bCompressedChars = false; + EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); + size_t nPos = nTmpPos - pLine->GetStart(); + rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width()); + } + break; + case EE_FEATURE_FIELD: + { + SeekCursor( pNode, nTmpPos+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + + OUString aFieldValue = static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue(); + // get size, but also DXArray to allow length information in line breaking below + std::vector<sal_Int32> aTmpDXArray; + pPortion->GetSize() = aTmpFont.QuickGetTextSize(GetRefDevice(), + aFieldValue, 0, aFieldValue.getLength(), &aTmpDXArray); + + // So no scrolling for oversized fields + if ( pPortion->GetSize().Width() > nXWidth ) + { + // create ExtraPortionInfo on-demand, flush lineBreaksList + ExtraPortionInfo *pExtraInfo = pPortion->GetExtraInfos(); + + if(nullptr == pExtraInfo) + { + pExtraInfo = new ExtraPortionInfo(); + pExtraInfo->nOrgWidth = nXWidth; + pPortion->SetExtraInfos(pExtraInfo); + } + else + { + pExtraInfo->lineBreaksList.clear(); + } + + // iterate over CellBreaks using XBreakIterator to be on the + // safe side with international texts/charSets + Reference < i18n::XBreakIterator > xBreakIterator(ImplGetBreakIterator()); + const sal_Int32 nTextLength(aFieldValue.getLength()); + const lang::Locale aLocale(GetLocale(EditPaM(pNode, nPortionStart))); + sal_Int32 nDone(0); + sal_Int32 nNextCellBreak( + xBreakIterator->nextCharacters( + aFieldValue, + 0, + aLocale, + css::i18n::CharacterIteratorMode::SKIPCELL, + 0, + nDone)); + sal_Int32 nLastCellBreak(0); + sal_Int32 nLineStartX(0); + + // always add 1st line break (safe, we already know we are larger than nXWidth) + pExtraInfo->lineBreaksList.push_back(0); + + for(sal_Int32 a(0); a < nTextLength; a++) + { + if(a == nNextCellBreak) + { + // check width + if(aTmpDXArray[a] - nLineStartX > nXWidth) + { + // new CellBreak does not fit in current line, need to + // create a break at LastCellBreak - but do not add 1st + // line break twice for very tall frames + if(0 != a) + { + pExtraInfo->lineBreaksList.push_back(a); + } + + // moveLineStart forward in X + nLineStartX = aTmpDXArray[nLastCellBreak]; + } + + // update CellBreak iteration values + nLastCellBreak = a; + nNextCellBreak = xBreakIterator->nextCharacters( + aFieldValue, + a, + aLocale, + css::i18n::CharacterIteratorMode::SKIPCELL, + 1, + nDone); + } + } + } + nTmpWidth += pPortion->GetSize().Width(); + EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); + size_t nPos = nTmpPos - pLine->GetStart(); + rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width()); + pPortion->SetKind(PortionKind::FIELD); + // If this is the first token on the line, + // and nTmpWidth > aPaperSize.Width, => infinite loop! + if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) ) + { + nTmpWidth = nXWidth-1; + bEOL = true; + bBrokenLine = true; + } + // Compression in Fields???? + // I think this could be a little bit difficult and is not very useful + bCompressedChars = false; + } + break; + default: OSL_FAIL( "What feature?" ); + } + pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1 ); + } + else + { + DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion - Extra Space?!" ); + SeekCursor( pNode, nTmpPos+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + + if (!bContinueLastPortion) + pPortion->SetRightToLeftLevel( GetRightToLeft( nPara, nTmpPos+1 ) ); + + if (bContinueLastPortion) + { + Size aSize( aTmpFont.QuickGetTextSize( GetRefDevice(), + pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, &aBuf )); + pPortion->GetSize().AdjustWidth(aSize.Width() ); + if (pPortion->GetSize().Height() < aSize.Height()) + pPortion->GetSize().setHeight( aSize.Height() ); + } + else + { + pPortion->GetSize() = aTmpFont.QuickGetTextSize( GetRefDevice(), + pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, &aBuf ); + } + + // #i9050# Do Kerning also behind portions... + if ( ( aTmpFont.GetFixKerning() > 0 ) && ( ( nTmpPos + nPortionLen ) < pNode->Len() ) ) + pPortion->GetSize().AdjustWidth(aTmpFont.GetFixKerning() ); + if ( IsFixedCellHeight() ) + pPortion->GetSize().setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); + // The array is generally flattened at the beginning + // => Always simply quick inserts. + size_t nPos = nTmpPos - pLine->GetStart(); + EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); + rArray.insert( rArray.begin() + nPos, aBuf.data(), aBuf.data() + nPortionLen); + + // And now check for Compression: + if ( !bContinueLastPortion && nPortionLen && GetAsianCompressionMode() != CharCompressType::NONE ) + { + sal_Int32* pDXArray = rArray.data() + nTmpPos - pLine->GetStart(); + bCompressedChars |= ImplCalcAsianCompression( + pNode, pPortion, nTmpPos, pDXArray, 10000, false); + } + + nTmpWidth += pPortion->GetSize().Width(); + + sal_Int32 _nPortionEnd = nTmpPos + nPortionLen; + if( bScriptSpace && ( _nPortionEnd < pNode->Len() ) && ( nTmpWidth < nXWidth ) && IsScriptChange( EditPaM( pNode, _nPortionEnd ) ) ) + { + bool bAllow = false; + sal_uInt16 nScriptTypeLeft = GetI18NScriptType( EditPaM( pNode, _nPortionEnd ) ); + sal_uInt16 nScriptTypeRight = GetI18NScriptType( EditPaM( pNode, _nPortionEnd+1 ) ); + if ( ( nScriptTypeLeft == i18n::ScriptType::ASIAN ) || ( nScriptTypeRight == i18n::ScriptType::ASIAN ) ) + bAllow = true; + + // No spacing within L2R/R2L nesting + if ( bAllow ) + { + tools::Long nExtraSpace = pPortion->GetSize().Height()/5; + nExtraSpace = GetXValue( nExtraSpace ); + pPortion->GetSize().AdjustWidth(nExtraSpace ); + nTmpWidth += nExtraSpace; + } + } + } + + if ( aCurrentTab.bValid && ( nTmpPortion != aCurrentTab.nTabPortion ) ) + { + tools::Long nWidthAfterTab = 0; + for ( sal_Int32 n = aCurrentTab.nTabPortion+1; n <= nTmpPortion; n++ ) + { + const TextPortion& rTP = pParaPortion->GetTextPortions()[n]; + nWidthAfterTab += rTP.GetSize().Width(); + } + tools::Long nW = nWidthAfterTab; // Length before tab position + if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) + { + } + else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) + { + nW = nWidthAfterTab/2; + } + else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) + { + OUString aText = GetSelected( EditSelection( EditPaM( pParaPortion->GetNode(), nTmpPos ), + EditPaM( pParaPortion->GetNode(), nTmpPos + nPortionLen ) ) ); + sal_Int32 nDecPos = aText.indexOf( aCurrentTab.aTabStop.GetDecimal() ); + if ( nDecPos != -1 ) + { + nW -= pParaPortion->GetTextPortions()[nTmpPortion].GetSize().Width(); + nW += aTmpFont.QuickGetTextSize( GetRefDevice(), pParaPortion->GetNode()->GetString(), + nTmpPos, nDecPos, nullptr ).Width(); + aCurrentTab.bValid = false; + } + } + else + { + OSL_FAIL( "CreateLines: Tab not handled!" ); + } + tools::Long nMaxW = aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nStartX; + if ( nW >= nMaxW ) + { + nW = nMaxW; + aCurrentTab.bValid = false; + } + TextPortion& rTabPortion = pParaPortion->GetTextPortions()[aCurrentTab.nTabPortion]; + rTabPortion.GetSize().setWidth( aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nW - nStartX ); + nTmpWidth = aCurrentTab.nStartPosX + rTabPortion.GetSize().Width() + nWidthAfterTab; + } + + nTmpPos = nTmpPos + nPortionLen; + nPortionEnd = nTmpPos; + nTmpPortion++; + if ( aStatus.OneCharPerLine() ) + bEOL = true; + } + + DBG_ASSERT( pPortion, "no portion!?" ); + + aCurrentTab.bValid = false; + + assert(pLine); + + // this was possibly a portion too far: + bool bFixedEnd = false; + if ( aStatus.OneCharPerLine() ) + { + // State before Portion (apart from nTmpWidth): + nTmpPos -= pPortion ? nPortionLen : 0; + nPortionStart = nTmpPos; + nTmpPortion--; + + bEOL = true; + bEOC = false; + + // And now just one character: + nTmpPos++; + nTmpPortion++; + nPortionEnd = nTmpPortion; + // one Non-Feature-Portion has to be wrapped + if ( pPortion && nPortionLen > 1 ) + { + DBG_ASSERT( pPortion->GetKind() == PortionKind::TEXT, "Len>1, but no TextPortion?" ); + nTmpWidth -= pPortion->GetSize().Width(); + sal_Int32 nP = SplitTextPortion( pParaPortion, nTmpPos, pLine ); + nTmpWidth += pParaPortion->GetTextPortions()[nP].GetSize().Width(); + } + } + else if ( nTmpWidth >= nXWidth ) + { + nPortionEnd = nTmpPos; + nTmpPos -= pPortion ? nPortionLen : 0; + nPortionStart = nTmpPos; + nTmpPortion--; + bEOL = false; + bEOC = false; + if( pPortion ) switch ( pPortion->GetKind() ) + { + case PortionKind::TEXT: + { + nTmpWidth -= pPortion->GetSize().Width(); + } + break; + case PortionKind::FIELD: + case PortionKind::TAB: + { + nTmpWidth -= pPortion->GetSize().Width(); + bEOL = true; + bFixedEnd = true; + } + break; + default: + { + // A feature is not wrapped: + DBG_ASSERT( ( pPortion->GetKind() == PortionKind::LINEBREAK ), "What Feature ?" ); + bEOL = true; + bFixedEnd = true; + } + } + } + else + { + bEOL = true; + bEOC = true; + pLine->SetEnd( nPortionEnd ); + assert( pParaPortion->GetTextPortions().Count() && "No TextPortions?" ); + pLine->SetEndPortion( pParaPortion->GetTextPortions().Count() - 1 ); + } + + if ( aStatus.OneCharPerLine() ) + { + pLine->SetEnd( nPortionEnd ); + pLine->SetEndPortion( nTmpPortion-1 ); + } + else if ( bFixedEnd ) + { + pLine->SetEnd( nPortionStart ); + pLine->SetEndPortion( nTmpPortion-1 ); + } + else if ( bLineBreak || bBrokenLine ) + { + pLine->SetEnd( nPortionStart+1 ); + pLine->SetEndPortion( nTmpPortion-1 ); + bEOC = false; // was set above, maybe change the sequence of the if's? + } + else if ( !bEOL && !bContinueLastPortion ) + { + DBG_ASSERT( pPortion && ((nPortionEnd-nPortionStart) == pPortion->GetLen()), "However, another portion?!" ); + tools::Long nRemainingWidth = nMaxLineWidth - nTmpWidth; + bool bCanHyphenate = ( aTmpFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL ); + if ( bCompressedChars && pPortion && ( pPortion->GetLen() > 1 ) && pPortion->GetExtraInfos() && pPortion->GetExtraInfos()->bCompressed ) + { + // I need the manipulated DXArray for determining the break position... + sal_Int32* pDXArray = pLine->GetCharPosArray().data() + (nPortionStart - pLine->GetStart()); + ImplCalcAsianCompression( + pNode, pPortion, nPortionStart, pDXArray, 10000, true); + } + if( pPortion ) + ImpBreakLine( pParaPortion, pLine, pPortion, nPortionStart, + nRemainingWidth, bCanHyphenate && bHyphenatePara ); + } + + + // Line finished => adjust + + + // CalcTextSize should be replaced by a continuous registering! + Size aTextSize = pLine->CalcTextSize( *pParaPortion ); + + if ( aTextSize.Height() == 0 ) + { + SeekCursor( pNode, pLine->GetStart()+1, aTmpFont ); + aTmpFont.SetPhysFont(*pRefDev); + ImplInitDigitMode(*pRefDev, aTmpFont.GetLanguage()); + + if ( IsFixedCellHeight() ) + aTextSize.setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); + else + aTextSize.setHeight( aTmpFont.GetPhysTxtSize( pRefDev ).Height() ); + pLine->SetHeight( static_cast<sal_uInt16>(aTextSize.Height()) ); + } + + // The font metrics can not be calculated continuously, if the font is + // set anyway, because a large font only after wrapping suddenly ends + // up in the next line => Font metrics too big. + FormatterFontMetric aFormatterMetrics; + sal_Int32 nTPos = pLine->GetStart(); + for ( sal_Int32 nP = pLine->GetStartPortion(); nP <= pLine->GetEndPortion(); nP++ ) + { + const TextPortion& rTP = pParaPortion->GetTextPortions()[nP]; + // problem with hard font height attribute, when everything but the line break has this attribute + if ( rTP.GetKind() != PortionKind::LINEBREAK ) + { + SeekCursor( pNode, nTPos+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont ); + } + nTPos = nTPos + rTP.GetLen(); + } + sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight(); + if ( nLineHeight > pLine->GetHeight() ) + pLine->SetHeight( nLineHeight ); + pLine->SetMaxAscent( aFormatterMetrics.nMaxAscent ); + + bSameLineAgain = false; + if ( GetTextRanger() && ( pLine->GetHeight() > nTextLineHeight ) ) + { + // put down with the other size! + bSameLineAgain = true; + } + + + if ( !bSameLineAgain && !aStatus.IsOutliner() ) + { + if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min ) + { + sal_uInt16 nMinHeight = GetYValue( rLSItem.GetLineHeight() ); + sal_uInt16 nTxtHeight = pLine->GetHeight(); + if ( nTxtHeight < nMinHeight ) + { + // The Ascent has to be adjusted for the difference: + tools::Long nDiff = nMinHeight - nTxtHeight; + pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + nDiff) ); + pLine->SetHeight( nMinHeight, nTxtHeight ); + } + } + else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix ) + { + sal_uInt16 nFixHeight = GetYValue( rLSItem.GetLineHeight() ); + sal_uInt16 nTxtHeight = pLine->GetHeight(); + pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) ); + pLine->SetHeight( nFixHeight, nTxtHeight ); + } + else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) + { + // There are documents with PropLineSpace 0, why? + // (cmc: re above question :-) such documents can be seen by importing a .ppt + if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() < 100 ) ) + { + // Adapted code from sw/source/core/text/itrform2.cxx + sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace(); + sal_uInt16 nAscent = pLine->GetMaxAscent(); + sal_uInt16 nNewAscent = pLine->GetTxtHeight() * nPropLineSpace / 100 * 4 / 5; // 80% + if ( !nAscent || nAscent > nNewAscent ) + { + pLine->SetMaxAscent( nNewAscent ); + } + sal_uInt16 nHeight = pLine->GetHeight() * nPropLineSpace / 100; + pLine->SetHeight( nHeight, pLine->GetTxtHeight() ); + } + else if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) ) + { + sal_uInt16 nTxtHeight = pLine->GetHeight(); + sal_Int32 nPropTextHeight = nTxtHeight * rLSItem.GetPropLineSpace() / 100; + // The Ascent has to be adjusted for the difference: + tools::Long nDiff = pLine->GetHeight() - nPropTextHeight; + pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() - nDiff ) ); + pLine->SetHeight( static_cast<sal_uInt16>( nPropTextHeight ), nTxtHeight ); + } + } + } + + if ( ( !IsEffectivelyVertical() && aStatus.AutoPageWidth() ) || + ( IsEffectivelyVertical() && aStatus.AutoPageHeight() ) ) + { + // If the row fits within the current paper width, then this width + // has to be used for the Alignment. If it does not fit or if it + // will change the paper width, it will be formatted again for + // Justification! = LEFT anyway. + tools::Long nMaxLineWidthFix = GetColumnWidth(aPaperSize) + - GetXValue( rLRItem.GetRight() ) - nStartX; + if ( aTextSize.Width() < nMaxLineWidthFix ) + nMaxLineWidth = nMaxLineWidthFix; + } + + if ( bCompressedChars ) + { + tools::Long nRemainingWidth = nMaxLineWidth - aTextSize.Width(); + if ( nRemainingWidth > 0 ) + { + ImplExpandCompressedPortions( pLine, pParaPortion, nRemainingWidth ); + aTextSize = pLine->CalcTextSize( *pParaPortion ); + } + } + + if ( pLine->IsHangingPunctuation() ) + { + // Width from HangingPunctuation was set to 0 in ImpBreakLine, + // check for rel width now, maybe create compression... + tools::Long n = nMaxLineWidth - aTextSize.Width(); + TextPortion& rTP = pParaPortion->GetTextPortions()[pLine->GetEndPortion()]; + sal_Int32 nPosInArray = pLine->GetEnd()-1-pLine->GetStart(); + tools::Long nNewValue = ( nPosInArray ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ) + n; + if (o3tl::make_unsigned(nPosInArray) < pLine->GetCharPosArray().size()) + { + pLine->GetCharPosArray()[ nPosInArray ] = nNewValue; + } + rTP.GetSize().AdjustWidth(n ); + } + + pLine->SetTextWidth( aTextSize.Width() ); + switch ( eJustification ) + { + case SvxAdjust::Center: + { + tools::Long n = ( nMaxLineWidth - aTextSize.Width() ) / 2; + n += nStartX; // Indentation is kept. + pLine->SetStartPosX( n ); + } + break; + case SvxAdjust::Right: + { + // For automatically wrapped lines, which has a blank at the end + // the blank must not be displayed! + tools::Long n = nMaxLineWidth - aTextSize.Width(); + n += nStartX; // Indentation is kept. + pLine->SetStartPosX( n ); + } + break; + case SvxAdjust::Block: + { + bool bDistLastLine = (GetJustifyMethod(nPara) == SvxCellJustifyMethod::Distribute); + tools::Long nRemainingSpace = nMaxLineWidth - aTextSize.Width(); + pLine->SetStartPosX( nStartX ); + if ( nRemainingSpace > 0 && (!bEOC || bDistLastLine) ) + ImpAdjustBlocks( pParaPortion, pLine, nRemainingSpace ); + } + break; + default: + { + pLine->SetStartPosX( nStartX ); // FI, LI + } + break; + } + + + // Check whether the line must be re-issued... + + pLine->SetInvalid(); + + // If a portion was wrapped there may be far too many positions in + // CharPosArray: + EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); + size_t nLen = pLine->GetLen(); + if (rArray.size() > nLen) + rArray.erase(rArray.begin()+nLen, rArray.end()); + + if ( GetTextRanger() ) + { + if ( nTextXOffset ) + pLine->SetStartPosX( pLine->GetStartPosX() + nTextXOffset ); + if ( nTextExtraYOffset ) + { + pLine->SetHeight( static_cast<sal_uInt16>( pLine->GetHeight() + nTextExtraYOffset ), 0 ); + pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() + nTextExtraYOffset ) ); + } + } + + // for <0 think over ! + if ( pParaPortion->IsSimpleInvalid() ) + { + // Change through simple Text changes... + // Do not cancel formatting since Portions possibly have to be split + // again! If at some point cancelable, then validate the following + // line! But if applicable, mark as valid, so there is less output... + if ( pLine->GetEnd() < nInvalidStart ) + { + if ( *pLine == aSaveLine ) + { + pLine->SetValid(); + } + } + else + { + sal_Int32 nStart = pLine->GetStart(); + sal_Int32 nEnd = pLine->GetEnd(); + + if ( nStart > nInvalidEnd ) + { + if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) && + ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) ) + { + pLine->SetValid(); + if (bQuickFormat) + { + bLineBreak = false; + pParaPortion->CorrectValuesBehindLastFormattedLine( nLine ); + break; + } + } + } + else if (bQuickFormat && (nEnd > nInvalidEnd)) + { + // If the invalid line ends so that the next begins on the + // 'same' passage as before, i.e. not wrapped differently, + // then the text width does not have to be determined anew: + if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) ) + { + bLineBreak = false; + pParaPortion->CorrectValuesBehindLastFormattedLine( nLine ); + break; + } + } + } + } + + if ( !bSameLineAgain ) + { + nIndex = pLine->GetEnd(); // next line start = last line end + // as nEnd points to the last character! + + sal_Int32 nEndPortion = pLine->GetEndPortion(); + nCurrentPosY += pLine->GetHeight(); + + // Next line or maybe a new line... + pLine = nullptr; + if ( nLine < pParaPortion->GetLines().Count()-1 ) + pLine = &pParaPortion->GetLines()[++nLine]; + if ( pLine && ( nIndex >= pNode->Len() ) ) + { + nDelFromLine = nLine; + break; + } + // Stop processing if allowed and this is outside of the paper size height. + // Format at least two lines though, in case something detects whether + // the text has been wrapped or something similar. + if( mbSkipOutsideFormat && nLine > 2 + && !aStatus.AutoPageHeight() && aPaperSize.Height() < nCurrentPosY ) + { + if ( pLine && ( nIndex >= pNode->Len()) ) + nDelFromLine = nLine; + break; + } + if ( !pLine ) + { + if ( nIndex < pNode->Len() ) + { + pLine = new EditLine; + pParaPortion->GetLines().Insert(++nLine, pLine); + } + else if ( nIndex && bLineBreak && GetTextRanger() ) + { + // normally CreateAndInsertEmptyLine would be called, but I want to use + // CreateLines, so I need Polygon code only here... + TextPortion* pDummyPortion = new TextPortion( 0 ); + pParaPortion->GetTextPortions().Append(pDummyPortion); + pLine = new EditLine; + pParaPortion->GetLines().Insert(++nLine, pLine); + bForceOneRun = true; + bProcessingEmptyLine = true; + } + } + if ( pLine ) + { + aSaveLine = *pLine; + pLine->SetStart( nIndex ); + pLine->SetEnd( nIndex ); + pLine->SetStartPortion( nEndPortion+1 ); + pLine->SetEndPortion( nEndPortion+1 ); + } + } + } // while ( Index < Len ) + + if ( nDelFromLine >= 0 ) + pParaPortion->GetLines().DeleteFromLine( nDelFromLine ); + + DBG_ASSERT( pParaPortion->GetLines().Count(), "No line after CreateLines!" ); + + if ( bLineBreak ) + CreateAndInsertEmptyLine( pParaPortion ); + + bool bHeightChanged = FinishCreateLines( pParaPortion ); + + if ( bMapChanged ) + GetRefDevice()->Pop(); + + GetRefDevice()->Pop(); + + return bHeightChanged; +} + +void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion ) +{ + DBG_ASSERT( !GetTextRanger(), "Don't use CreateAndInsertEmptyLine with a polygon!" ); + + EditLine* pTmpLine = new EditLine; + pTmpLine->SetStart( pParaPortion->GetNode()->Len() ); + pTmpLine->SetEnd( pParaPortion->GetNode()->Len() ); + pParaPortion->GetLines().Append(pTmpLine); + + bool bLineBreak = pParaPortion->GetNode()->Len() > 0; + sal_Int32 nSpaceBefore = 0; + sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pParaPortion->GetNode(), &nSpaceBefore ); + const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pParaPortion->GetNode() ); + const SvxLineSpacingItem& rLSItem = pParaPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + tools::Long nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBefore ); + + tools::Rectangle aBulletArea { Point(), Point() }; + if ( bLineBreak ) + { + nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth ); + } + else + { + aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) ); + if ( !aBulletArea.IsEmpty() && aBulletArea.Right() > 0 ) + pParaPortion->SetBulletX( static_cast<sal_Int32>(GetXValue( aBulletArea.Right() )) ); + else + pParaPortion->SetBulletX( 0 ); // If Bullet set incorrectly. + if ( pParaPortion->GetBulletX() > nStartX ) + { + nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth ); + if ( pParaPortion->GetBulletX() > nStartX ) + nStartX = pParaPortion->GetBulletX(); + } + } + + SvxFont aTmpFont; + SeekCursor( pParaPortion->GetNode(), bLineBreak ? pParaPortion->GetNode()->Len() : 0, aTmpFont ); + aTmpFont.SetPhysFont(*pRefDev); + + TextPortion* pDummyPortion = new TextPortion( 0 ); + pDummyPortion->GetSize() = aTmpFont.GetPhysTxtSize( pRefDev ); + if ( IsFixedCellHeight() ) + pDummyPortion->GetSize().setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); + pParaPortion->GetTextPortions().Append(pDummyPortion); + FormatterFontMetric aFormatterMetrics; + RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont ); + pTmpLine->SetMaxAscent( aFormatterMetrics.nMaxAscent ); + pTmpLine->SetHeight( static_cast<sal_uInt16>(pDummyPortion->GetSize().Height()) ); + sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight(); + if ( nLineHeight > pTmpLine->GetHeight() ) + pTmpLine->SetHeight( nLineHeight ); + + if ( !aStatus.IsOutliner() ) + { + sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion ); + SvxAdjust eJustification = GetJustification( nPara ); + tools::Long nMaxLineWidth = GetColumnWidth(aPaperSize); + nMaxLineWidth -= GetXValue( rLRItem.GetRight() ); + if ( nMaxLineWidth < 0 ) + nMaxLineWidth = 1; + if ( eJustification == SvxAdjust::Center ) + nStartX = nMaxLineWidth / 2; + else if ( eJustification == SvxAdjust::Right ) + nStartX = nMaxLineWidth; + } + + pTmpLine->SetStartPosX( nStartX ); + + if ( !aStatus.IsOutliner() ) + { + if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min ) + { + sal_uInt16 nMinHeight = rLSItem.GetLineHeight(); + sal_uInt16 nTxtHeight = pTmpLine->GetHeight(); + if ( nTxtHeight < nMinHeight ) + { + // The Ascent has to be adjusted for the difference: + tools::Long nDiff = nMinHeight - nTxtHeight; + pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff) ); + pTmpLine->SetHeight( nMinHeight, nTxtHeight ); + } + } + else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix ) + { + sal_uInt16 nFixHeight = rLSItem.GetLineHeight(); + sal_uInt16 nTxtHeight = pTmpLine->GetHeight(); + + pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) ); + pTmpLine->SetHeight( nFixHeight, nTxtHeight ); + } + else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) + { + sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion ); + if ( nPara || pTmpLine->GetStartPortion() ) // Not the very first line + { + // There are documents with PropLineSpace 0, why? + // (cmc: re above question :-) such documents can be seen by importing a .ppt + if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) ) + { + sal_uInt16 nTxtHeight = pTmpLine->GetHeight(); + sal_Int32 nH = nTxtHeight; + nH *= rLSItem.GetPropLineSpace(); + nH /= 100; + // The Ascent has to be adjusted for the difference: + tools::Long nDiff = pTmpLine->GetHeight() - nH; + if ( nDiff > pTmpLine->GetMaxAscent() ) + nDiff = pTmpLine->GetMaxAscent(); + pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() - nDiff) ); + pTmpLine->SetHeight( static_cast<sal_uInt16>(nH), nTxtHeight ); + } + } + } + } + + if ( !bLineBreak ) + { + tools::Long nMinHeight = aBulletArea.GetHeight(); + if ( nMinHeight > static_cast<tools::Long>(pTmpLine->GetHeight()) ) + { + tools::Long nDiff = nMinHeight - static_cast<tools::Long>(pTmpLine->GetHeight()); + // distribute nDiff upwards and downwards + pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff/2) ); + pTmpLine->SetHeight( static_cast<sal_uInt16>(nMinHeight) ); + } + } + else + { + // -2: The new one is already inserted. +#ifdef DBG_UTIL + EditLine& rLastLine = pParaPortion->GetLines()[pParaPortion->GetLines().Count()-2]; + DBG_ASSERT( rLastLine.GetEnd() == pParaPortion->GetNode()->Len(), "different anyway?" ); +#endif + sal_Int32 nPos = pParaPortion->GetTextPortions().Count() - 1 ; + pTmpLine->SetStartPortion( nPos ); + pTmpLine->SetEndPortion( nPos ); + } +} + +bool ImpEditEngine::FinishCreateLines( ParaPortion* pParaPortion ) +{ +// CalcCharPositions( pParaPortion ); + pParaPortion->SetValid(); + tools::Long nOldHeight = pParaPortion->GetHeight(); + CalcHeight( pParaPortion ); + + DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "FinishCreateLines: No Text-Portion?" ); + bool bRet = ( pParaPortion->GetHeight() != nOldHeight ); + return bRet; +} + +void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, TextPortion const * pPortion, sal_Int32 nPortionStart, tools::Long nRemainingWidth, bool bCanHyphenate ) +{ + ContentNode* const pNode = pParaPortion->GetNode(); + + sal_Int32 nBreakInLine = nPortionStart - pLine->GetStart(); + sal_Int32 nMax = nBreakInLine + pPortion->GetLen(); + while ( ( nBreakInLine < nMax ) && ( pLine->GetCharPosArray()[nBreakInLine] < nRemainingWidth ) ) + nBreakInLine++; + + sal_Int32 nMaxBreakPos = nBreakInLine + pLine->GetStart(); + sal_Int32 nBreakPos = SAL_MAX_INT32; + + bool bCompressBlank = false; + bool bHyphenated = false; + bool bHangingPunctuation = false; + sal_Unicode cAlternateReplChar = 0; + sal_Unicode cAlternateExtraChar = 0; + bool bAltFullLeft = false; + bool bAltFullRight = false; + sal_uInt32 nAltDelChar = 0; + + if ( ( nMaxBreakPos < ( nMax + pLine->GetStart() ) ) && ( pNode->GetChar( nMaxBreakPos ) == ' ' ) ) + { + // Break behind the blank, blank will be compressed... + nBreakPos = nMaxBreakPos + 1; + bCompressBlank = true; + } + else + { + sal_Int32 nMinBreakPos = pLine->GetStart(); + const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); + for (size_t nAttr = rAttrs.size(); nAttr; ) + { + const EditCharAttrib& rAttr = *rAttrs[--nAttr]; + if (rAttr.IsFeature() && rAttr.GetEnd() > nMinBreakPos && rAttr.GetEnd() <= nMaxBreakPos) + { + nMinBreakPos = rAttr.GetEnd(); + break; + } + } + assert(nMinBreakPos <= nMaxBreakPos); + + lang::Locale aLocale = GetLocale( EditPaM( pNode, nMaxBreakPos ) ); + + Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + const bool bAllowPunctuationOutsideMargin = static_cast<const SfxBoolItem&>( + pNode->GetContentAttribs().GetItem( EE_PARA_HANGINGPUNCTUATION )).GetValue(); + + if (nMinBreakPos == nMaxBreakPos) + { + nBreakPos = nMinBreakPos; + } + else + { + Reference< XHyphenator > xHyph; + if ( bCanHyphenate ) + xHyph = GetHyphenator(); + i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, Sequence< PropertyValue >(), 1 ); + i18n::LineBreakUserOptions aUserOptions; + + const i18n::ForbiddenCharacters* pForbidden = GetForbiddenCharsTable()->GetForbiddenCharacters( LanguageTag::convertToLanguageType( aLocale ), true ); + aUserOptions.forbiddenBeginCharacters = pForbidden->beginLine; + aUserOptions.forbiddenEndCharacters = pForbidden->endLine; + aUserOptions.applyForbiddenRules = static_cast<const SfxBoolItem&>(pNode->GetContentAttribs().GetItem( EE_PARA_FORBIDDENRULES )).GetValue(); + aUserOptions.allowPunctuationOutsideMargin = bAllowPunctuationOutsideMargin; + aUserOptions.allowHyphenateEnglish = false; + + i18n::LineBreakResults aLBR = _xBI->getLineBreak( + pNode->GetString(), nMaxBreakPos, aLocale, nMinBreakPos, aHyphOptions, aUserOptions ); + nBreakPos = aLBR.breakIndex; + + // BUG in I18N - under special condition (break behind field, #87327#) breakIndex is < nMinBreakPos + if ( nBreakPos < nMinBreakPos ) + { + nBreakPos = nMinBreakPos; + } + else if ( ( nBreakPos > nMaxBreakPos ) && !aUserOptions.allowPunctuationOutsideMargin ) + { + OSL_FAIL( "I18N: XBreakIterator::getLineBreak returns position > Max" ); + nBreakPos = nMaxBreakPos; + } + // Hanging punctuation is the only case that increases nBreakPos and makes + // nBreakPos > nMaxBreakPos. It's expected that the hanging punctuation goes over + // the border of the object. + } + + // BUG in I18N - the japanese dot is in the next line! + // !!! Test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + if ( (nBreakPos + ( bAllowPunctuationOutsideMargin ? 0 : 1 ) ) <= nMaxBreakPos ) + { + sal_Unicode cFirstInNextLine = ( (nBreakPos+1) < pNode->Len() ) ? pNode->GetChar( nBreakPos ) : 0; + if ( cFirstInNextLine == 12290 ) + nBreakPos++; + } + + bHangingPunctuation = nBreakPos > nMaxBreakPos; + pLine->SetHangingPunctuation( bHangingPunctuation ); + + // Whether a separator or not, push the word after the separator through + // hyphenation... NMaxBreakPos is the last character that fits into + // the line, nBreakPos is the beginning of the word. + // There is a problem if the Doc is so narrow that a word is broken + // into more than two lines... + if ( !bHangingPunctuation && bCanHyphenate && GetHyphenator().is() ) + { + i18n::Boundary aBoundary = _xBI->getWordBoundary( + pNode->GetString(), nBreakPos, GetLocale( EditPaM( pNode, nBreakPos ) ), css::i18n::WordType::DICTIONARY_WORD, true); + sal_Int32 nWordStart = nBreakPos; + sal_Int32 nWordEnd = aBoundary.endPos; + DBG_ASSERT( nWordEnd >= nWordStart, "Start >= End?" ); + + sal_Int32 nWordLen = nWordEnd - nWordStart; + if ( ( nWordEnd >= nMaxBreakPos ) && ( nWordLen > 3 ) ) + { + // May happen, because getLineBreak may differ from getWordBoundary with DICTIONARY_WORD + const OUString aWord = pNode->GetString().copy(nWordStart, nWordLen); + sal_Int32 nMinTrail = nWordEnd-nMaxBreakPos+1; //+1: Before the dickey letter + Reference< XHyphenatedWord > xHyphWord; + if (xHyphenator.is()) + xHyphWord = xHyphenator->hyphenate( aWord, aLocale, aWord.getLength() - nMinTrail, Sequence< PropertyValue >() ); + if (xHyphWord.is()) + { + bool bAlternate = xHyphWord->isAlternativeSpelling(); + sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos(); + + if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= (pLine->GetStart() + 2 ) ) ) + { + if ( !bAlternate ) + { + bHyphenated = true; + nBreakPos = nWordStart + _nWordLen; + } + else + { + // TODO: handle all alternative hyphenations (see hyphen-1.2.8/tests/unicode.*) + OUString aAlt( xHyphWord->getHyphenatedWord() ); + std::u16string_view aAltLeft(aAlt.subView(0, _nWordLen)); + std::u16string_view aAltRight(aAlt.subView(_nWordLen)); + bAltFullLeft = aWord.startsWith(aAltLeft); + bAltFullRight = aWord.endsWith(aAltRight); + nAltDelChar = aWord.getLength() - aAlt.getLength() + static_cast<int>(!bAltFullLeft) + static_cast<int>(!bAltFullRight); + + // NOTE: improved for other cases, see fdo#63711 + + // We expect[ed] the two cases: + // 1) packen becomes pak-ken + // 2) Schiffahrt becomes Schiff-fahrt + // In case 1, a character has to be replaced + // in case 2 a character is added. + // The identification is complicated by long + // compound words because the Hyphenator separates + // all position of the word. [This is not true for libhyphen.] + // "Schiffahrtsbrennesseln" -> "Schifffahrtsbrennnesseln" + // We can thus actually not directly connect the index of the + // AlternativeWord to aWord. The whole issue will be simplified + // by a function in the Hyphenator as soon as AMA builds this in... + sal_Int32 nAltStart = _nWordLen - 1; + sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength()); + sal_Int32 nTxtEnd = nTxtStart; + sal_Int32 nAltEnd = nAltStart; + + // The regions between the nStart and nEnd is the + // difference between alternative and original string. + while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() && + aWord[nTxtEnd] != aAlt[nAltEnd] ) + { + ++nTxtEnd; + ++nAltEnd; + } + + // If a character is added, then we notice it now: + if( nAltEnd > nTxtEnd && nAltStart == nAltEnd && + aWord[ nTxtEnd ] == aAlt[nAltEnd] ) + { + ++nAltEnd; + ++nTxtStart; + ++nTxtEnd; + } + + DBG_ASSERT( ( nAltEnd - nAltStart ) == 1, "Alternate: Wrong assumption!" ); + + if ( nTxtEnd > nTxtStart ) + cAlternateReplChar = aAlt[nAltStart]; + else + cAlternateExtraChar = aAlt[nAltStart]; + + bHyphenated = true; + nBreakPos = nWordStart + nTxtStart; + if ( cAlternateReplChar || aAlt.getLength() < aWord.getLength() || !bAltFullRight) // also for "oma-tje", "re-eel" + nBreakPos++; + } + } + } + } + } + + if ( nBreakPos <= pLine->GetStart() ) + { + // No separator in line => Chop! + nBreakPos = nMaxBreakPos; + // I18N nextCharacters ! + if ( nBreakPos <= pLine->GetStart() ) + nBreakPos = pLine->GetStart() + 1; // Otherwise infinite loop! + } + } + + // the dickey portion is the end portion + pLine->SetEnd( nBreakPos ); + + sal_Int32 nEndPortion = SplitTextPortion( pParaPortion, nBreakPos, pLine ); + + if ( !bCompressBlank && !bHangingPunctuation ) + { + // When justification is not SvxAdjust::Left, it's important to compress + // the trailing space even if there is enough room for the space... + // Don't check for SvxAdjust::Left, doesn't matter to compress in this case too... + assert( nBreakPos > pLine->GetStart() && "ImpBreakLines - BreakPos not expected!" ); + if ( pNode->GetChar( nBreakPos-1 ) == ' ' ) + bCompressBlank = true; + } + + if ( bCompressBlank || bHangingPunctuation ) + { + TextPortion& rTP = pParaPortion->GetTextPortions()[nEndPortion]; + DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "BlankRubber: No TextPortion!" ); + DBG_ASSERT( nBreakPos > pLine->GetStart(), "SplitTextPortion at the beginning of the line?" ); + sal_Int32 nPosInArray = nBreakPos - 1 - pLine->GetStart(); + rTP.GetSize().setWidth( ( nPosInArray && ( rTP.GetLen() > 1 ) ) ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ); + if (o3tl::make_unsigned(nPosInArray) < pLine->GetCharPosArray().size()) + { + pLine->GetCharPosArray()[ nPosInArray ] = rTP.GetSize().Width(); + } + } + else if ( bHyphenated ) + { + // A portion for inserting the separator... + TextPortion* pHyphPortion = new TextPortion( 0 ); + pHyphPortion->SetKind( PortionKind::HYPHENATOR ); + if ( (cAlternateReplChar || cAlternateExtraChar) && bAltFullRight ) // alternation after the break doesn't supported + { + TextPortion& rPrev = pParaPortion->GetTextPortions()[nEndPortion]; + DBG_ASSERT( rPrev.GetLen(), "Hyphenate: Prev portion?!" ); + rPrev.SetLen( rPrev.GetLen() - nAltDelChar ); + pHyphPortion->SetLen( nAltDelChar ); + if (cAlternateReplChar && !bAltFullLeft) pHyphPortion->SetExtraValue( cAlternateReplChar ); + // Correct width of the portion above: + rPrev.GetSize().setWidth( + pLine->GetCharPosArray()[ nBreakPos-1 - pLine->GetStart() - nAltDelChar ] ); + } + + // Determine the width of the Hyph-Portion: + SvxFont aFont; + SeekCursor( pParaPortion->GetNode(), nBreakPos, aFont ); + aFont.SetPhysFont(*GetRefDevice()); + pHyphPortion->GetSize().setHeight( GetRefDevice()->GetTextHeight() ); + pHyphPortion->GetSize().setWidth( GetRefDevice()->GetTextWidth( CH_HYPH ) ); + + pParaPortion->GetTextPortions().Insert(++nEndPortion, pHyphPortion); + } + pLine->SetEndPortion( nEndPortion ); +} + +void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, tools::Long nRemainingSpace ) +{ + DBG_ASSERT( nRemainingSpace > 0, "AdjustBlocks: Somewhat too little..." ); + assert( pLine && "AdjustBlocks: Line ?!" ); + if ( ( nRemainingSpace < 0 ) || pLine->IsEmpty() ) + return ; + + const sal_Int32 nFirstChar = pLine->GetStart(); + const sal_Int32 nLastChar = pLine->GetEnd() -1; // Last points behind + ContentNode* pNode = pParaPortion->GetNode(); + + DBG_ASSERT( nLastChar < pNode->Len(), "AdjustBlocks: Out of range!" ); + + // Search blanks or Kashidas... + std::vector<sal_Int32> aPositions; + sal_uInt16 nLastScript = i18n::ScriptType::LATIN; + for ( sal_Int32 nChar = nFirstChar; nChar <= nLastChar; nChar++ ) + { + EditPaM aPaM( pNode, nChar+1 ); + LanguageType eLang = GetLanguage(aPaM).nLang; + sal_uInt16 nScript = GetI18NScriptType(aPaM); + if ( MsLangId::getPrimaryLanguage( eLang) == LANGUAGE_ARABIC_PRIMARY_ONLY ) + // Arabic script is handled later. + continue; + + if ( pNode->GetChar(nChar) == ' ' ) + { + // Normal latin script. + aPositions.push_back( nChar ); + } + else if (nChar > nFirstChar) + { + if (nLastScript == i18n::ScriptType::ASIAN) + { + // Set break position between this and the last character if + // the last character is asian script. + aPositions.push_back( nChar-1 ); + } + else if (nScript == i18n::ScriptType::ASIAN) + { + // Set break position between a latin script and asian script. + aPositions.push_back( nChar-1 ); + } + } + + nLastScript = nScript; + } + + // Kashidas ? + ImpFindKashidas( pNode, nFirstChar, nLastChar, aPositions ); + + if ( aPositions.empty() ) + return; + + // If the last character is a blank, it is rejected! + // The width must be distributed to the blockers in front... + // But not if it is the only one. + if ( ( pNode->GetChar( nLastChar ) == ' ' ) && ( aPositions.size() > 1 ) && + ( MsLangId::getPrimaryLanguage( GetLanguage( EditPaM( pNode, nLastChar ) ).nLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) ) + { + aPositions.pop_back(); + sal_Int32 nPortionStart, nPortion; + nPortion = pParaPortion->GetTextPortions().FindPortion( nLastChar+1, nPortionStart ); + TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ]; + tools::Long nRealWidth = pLine->GetCharPosArray()[nLastChar-nFirstChar]; + tools::Long nBlankWidth = nRealWidth; + if ( nLastChar > nPortionStart ) + nBlankWidth -= pLine->GetCharPosArray()[nLastChar-nFirstChar-1]; + // Possibly the blank has already been deducted in ImpBreakLine: + if ( nRealWidth == rLastPortion.GetSize().Width() ) + { + // For the last character the portion must stop behind the blank + // => Simplify correction: + DBG_ASSERT( ( nPortionStart + rLastPortion.GetLen() ) == ( nLastChar+1 ), "Blank actually not at the end of the portion!?"); + rLastPortion.GetSize().AdjustWidth( -nBlankWidth ); + nRemainingSpace += nBlankWidth; + } + pLine->GetCharPosArray()[nLastChar-nFirstChar] -= nBlankWidth; + } + + size_t nGaps = aPositions.size(); + const tools::Long nMore4Everyone = nRemainingSpace / nGaps; + tools::Long nSomeExtraSpace = nRemainingSpace - nMore4Everyone*nGaps; + + DBG_ASSERT( nSomeExtraSpace < static_cast<tools::Long>(nGaps), "AdjustBlocks: ExtraSpace too large" ); + DBG_ASSERT( nSomeExtraSpace >= 0, "AdjustBlocks: ExtraSpace < 0 " ); + + // Correct the positions in the Array and the portion widths: + // Last character won't be considered... + for (auto const& nChar : aPositions) + { + if ( nChar < nLastChar ) + { + sal_Int32 nPortionStart, nPortion; + nPortion = pParaPortion->GetTextPortions().FindPortion( nChar, nPortionStart, true ); + TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ]; + + // The width of the portion: + rLastPortion.GetSize().AdjustWidth(nMore4Everyone ); + if ( nSomeExtraSpace ) + rLastPortion.GetSize().AdjustWidth( 1 ); + + // Correct positions in array + // Even for kashidas just change positions, VCL will then draw the kashida automatically + sal_Int32 nPortionEnd = nPortionStart + rLastPortion.GetLen(); + for ( sal_Int32 _n = nChar; _n < nPortionEnd; _n++ ) + { + pLine->GetCharPosArray()[_n-nFirstChar] += nMore4Everyone; + if ( nSomeExtraSpace ) + pLine->GetCharPosArray()[_n-nFirstChar]++; + } + + if ( nSomeExtraSpace ) + nSomeExtraSpace--; + } + } + + // Now the text width contains the extra width... + pLine->SetTextWidth( pLine->GetTextWidth() + nRemainingSpace ); +} + +void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, std::vector<sal_Int32>& rArray ) +{ + // the search has to be performed on a per word base + + EditSelection aWordSel( EditPaM( pNode, nStart ) ); + aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD ); + if ( aWordSel.Min().GetIndex() < nStart ) + aWordSel.Min().SetIndex( nStart ); + + while ( ( aWordSel.Min().GetNode() == pNode ) && ( aWordSel.Min().GetIndex() < nEnd ) ) + { + const sal_Int32 nSavPos = aWordSel.Max().GetIndex(); + if ( aWordSel.Max().GetIndex() > nEnd ) + aWordSel.Max().SetIndex( nEnd ); + + OUString aWord = GetSelected( aWordSel ); + + // restore selection for proper iteration at the end of the function + aWordSel.Max().SetIndex( nSavPos ); + + sal_Int32 nIdx = 0; + sal_Int32 nKashidaPos = -1; + sal_Unicode cCh; + sal_Unicode cPrevCh = 0; + + while ( nIdx < aWord.getLength() ) + { + cCh = aWord[ nIdx ]; + + // 1. Priority: + // after user inserted kashida + if ( 0x640 == cCh ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nIdx; + break; + } + + // 2. Priority: + // after a Seen or Sad + if ( nIdx + 1 < aWord.getLength() && + ( 0x633 == cCh || 0x635 == cCh ) ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nIdx; + break; + } + + // 3. Priority: + // before final form of the Marbuta, Hah, Dal + // 4. Priority: + // before final form of Alef, Lam or Kaf + if ( nIdx && nIdx + 1 == aWord.getLength() && + ( 0x629 == cCh || 0x62D == cCh || 0x62F == cCh || + 0x627 == cCh || 0x644 == cCh || 0x643 == cCh ) ) + { + DBG_ASSERT( 0 != cPrevCh, "No previous character" ); + + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1; + break; + } + } + + // 5. Priority: + // before media Bah + if ( nIdx && nIdx + 1 < aWord.getLength() && 0x628 == cCh ) + { + DBG_ASSERT( 0 != cPrevCh, "No previous character" ); + + // check if next character is Reh, Yeh or Alef Maksura + sal_Unicode cNextCh = aWord[ nIdx + 1 ]; + + if ( 0x631 == cNextCh || 0x64A == cNextCh || + 0x649 == cNextCh ) + { + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1; + } + } + + // 6. Priority: + // other connecting possibilities + if ( nIdx && nIdx + 1 == aWord.getLength() && + 0x60C <= cCh && 0x6FE >= cCh ) + { + DBG_ASSERT( 0 != cPrevCh, "No previous character" ); + + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + // only choose this position if we did not find + // a better one: + if ( nKashidaPos<0 ) + nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1; + break; + } + } + + // Do not consider Fathatan, Dammatan, Kasratan, Fatha, + // Damma, Kasra, Shadda and Sukun when checking if + // a character can be connected to previous character. + if ( cCh < 0x64B || cCh > 0x652 ) + cPrevCh = cCh; + + ++nIdx; + } // end of current word + + if ( nKashidaPos>=0 ) + rArray.push_back( nKashidaPos ); + + aWordSel = WordRight( aWordSel.Max(), css::i18n::WordType::DICTIONARY_WORD ); + aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD ); + } +} + +sal_Int32 ImpEditEngine::SplitTextPortion( ParaPortion* pPortion, sal_Int32 nPos, EditLine* pCurLine ) +{ + // The portion at nPos is split, if there is not a transition at nPos anyway + if ( nPos == 0 ) + return 0; + + assert( pPortion && "SplitTextPortion: Which ?" ); + + sal_Int32 nSplitPortion; + sal_Int32 nTmpPos = 0; + TextPortion* pTextPortion = nullptr; + sal_Int32 nPortions = pPortion->GetTextPortions().Count(); + for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ ) + { + TextPortion& rTP = pPortion->GetTextPortions()[nSplitPortion]; + nTmpPos = nTmpPos + rTP.GetLen(); + if ( nTmpPos >= nPos ) + { + if ( nTmpPos == nPos ) // then nothing needs to be split + { + return nSplitPortion; + } + pTextPortion = &rTP; + break; + } + } + + DBG_ASSERT( pTextPortion, "Position outside the area!" ); + + if (!pTextPortion) + return 0; + + DBG_ASSERT( pTextPortion->GetKind() == PortionKind::TEXT, "SplitTextPortion: No TextPortion!" ); + + sal_Int32 nOverlapp = nTmpPos - nPos; + pTextPortion->SetLen( pTextPortion->GetLen() - nOverlapp ); + TextPortion* pNewPortion = new TextPortion( nOverlapp ); + pPortion->GetTextPortions().Insert(nSplitPortion+1, pNewPortion); + // Set sizes + if ( pCurLine ) + { + // No new GetTextSize, instead use values from the Array: + assert( nPos > pCurLine->GetStart() && "SplitTextPortion at the beginning of the line?" ); + pTextPortion->GetSize().setWidth( pCurLine->GetCharPosArray()[ nPos-pCurLine->GetStart()-1 ] ); + + if ( pTextPortion->GetExtraInfos() && pTextPortion->GetExtraInfos()->bCompressed ) + { + // We need the original size from the portion + sal_Int32 nTxtPortionStart = pPortion->GetTextPortions().GetStartPos( nSplitPortion ); + SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() ); + SeekCursor( pPortion->GetNode(), nTxtPortionStart+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + GetRefDevice()->Push( vcl::PushFlags::TEXTLANGUAGE ); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + Size aSz = aTmpFont.QuickGetTextSize( GetRefDevice(), pPortion->GetNode()->GetString(), + nTxtPortionStart, pTextPortion->GetLen(), nullptr ); + GetRefDevice()->Pop(); + pTextPortion->GetExtraInfos()->nOrgWidth = aSz.Width(); + } + } + else + pTextPortion->GetSize().setWidth( -1 ); + + return nSplitPortion; +} + +void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rStart ) +{ + sal_Int32 nStartPos = rStart; + ContentNode* pNode = pParaPortion->GetNode(); + DBG_ASSERT( pNode->Len(), "CreateTextPortions should not be used for empty paragraphs!" ); + + o3tl::sorted_vector< sal_Int32 > aPositions; + aPositions.insert( 0 ); + + for (std::size_t nAttr = 0;; ++nAttr) + { + // Insert Start and End into the Array... + // The Insert method does not allow for duplicate values... + EditCharAttrib* pAttrib = GetAttrib(pNode->GetCharAttribs().GetAttribs(), nAttr); + if (!pAttrib) + break; + aPositions.insert( pAttrib->GetStart() ); + aPositions.insert( pAttrib->GetEnd() ); + } + aPositions.insert( pNode->Len() ); + + if ( pParaPortion->aScriptInfos.empty() ) + InitScriptTypes( GetParaPortions().GetPos( pParaPortion ) ); + + const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + for (const ScriptTypePosInfo& rType : rTypes) + aPositions.insert( rType.nStartPos ); + + const WritingDirectionInfos& rWritingDirections = pParaPortion->aWritingDirectionInfos; + for (const WritingDirectionInfo & rWritingDirection : rWritingDirections) + aPositions.insert( rWritingDirection.nStartPos ); + + if ( mpIMEInfos && mpIMEInfos->nLen && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) ) + { + ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xFFFF); + for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ ) + { + if ( mpIMEInfos->pAttribs[n] != nLastAttr ) + { + aPositions.insert( mpIMEInfos->aPos.GetIndex() + n ); + nLastAttr = mpIMEInfos->pAttribs[n]; + } + } + aPositions.insert( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ); + } + + // From ... Delete: + // Unfortunately, the number of text portions does not have to match + // aPositions.Count(), since there might be line breaks... + sal_Int32 nPortionStart = 0; + sal_Int32 nInvPortion = 0; + sal_Int32 nP; + for ( nP = 0; nP < pParaPortion->GetTextPortions().Count(); nP++ ) + { + const TextPortion& rTmpPortion = pParaPortion->GetTextPortions()[nP]; + nPortionStart = nPortionStart + rTmpPortion.GetLen(); + if ( nPortionStart >= nStartPos ) + { + nPortionStart = nPortionStart - rTmpPortion.GetLen(); + rStart = nPortionStart; + nInvPortion = nP; + break; + } + } + DBG_ASSERT( nP < pParaPortion->GetTextPortions().Count() || !pParaPortion->GetTextPortions().Count(), "Nothing to delete: CreateTextPortions" ); + if ( nInvPortion && ( nPortionStart+pParaPortion->GetTextPortions()[nInvPortion].GetLen() > nStartPos ) ) + { + // prefer one in front... + // But only if it was in the middle of the portion of, otherwise it + // might be the only one in the row in front! + nInvPortion--; + nPortionStart = nPortionStart - pParaPortion->GetTextPortions()[nInvPortion].GetLen(); + } + pParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion ); + + // A portion may also have been formed by a line break: + aPositions.insert( nPortionStart ); + + auto nInvPos = aPositions.find( nPortionStart ); + DBG_ASSERT( (nInvPos != aPositions.end()), "InvPos ?!" ); + + auto i = nInvPos; + ++i; + while ( i != aPositions.end() ) + { + TextPortion* pNew = new TextPortion( (*i++) - *nInvPos++ ); + pParaPortion->GetTextPortions().Append(pNew); + } + + DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions?!" ); +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portion is broken?" ); +#endif +} + +void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars ) +{ + DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions!" ); + DBG_ASSERT( nNewChars, "RecalcTextPortion with Diff == 0" ); + + ContentNode* const pNode = pParaPortion->GetNode(); + if ( nNewChars > 0 ) + { + // If an Attribute begins/ends at nStartPos, then a new portion starts + // otherwise the portion is extended at nStartPos. + if ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) || IsScriptChange( EditPaM( pNode, nStartPos ) ) ) + { + sal_Int32 nNewPortionPos = 0; + if ( nStartPos ) + nNewPortionPos = SplitTextPortion( pParaPortion, nStartPos ) + 1; + + // A blank portion may be here, if the paragraph was empty, + // or if a line was created by a hard line break. + if ( ( nNewPortionPos < pParaPortion->GetTextPortions().Count() ) && + !pParaPortion->GetTextPortions()[nNewPortionPos].GetLen() ) + { + TextPortion& rTP = pParaPortion->GetTextPortions()[nNewPortionPos]; + DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "the empty portion was no TextPortion!" ); + rTP.SetLen( rTP.GetLen() + nNewChars ); + } + else + { + TextPortion* pNewPortion = new TextPortion( nNewChars ); + pParaPortion->GetTextPortions().Insert(nNewPortionPos, pNewPortion); + } + } + else + { + sal_Int32 nPortionStart; + const sal_Int32 nTP = pParaPortion->GetTextPortions(). + FindPortion( nStartPos, nPortionStart ); + TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ]; + rTP.SetLen( rTP.GetLen() + nNewChars ); + rTP.GetSize().setWidth( -1 ); + } + } + else + { + // Shrink or remove portion if necessary. + // Before calling this method it must be ensured that no portions were + // in the deleted area! + + // There must be no portions extending into the area or portions starting in + // the area, so it must be: + // nStartPos <= nPos <= nStartPos - nNewChars(neg.) + sal_Int32 nPortion = 0; + sal_Int32 nPos = 0; + sal_Int32 nEnd = nStartPos-nNewChars; + sal_Int32 nPortions = pParaPortion->GetTextPortions().Count(); + TextPortion* pTP = nullptr; + for ( nPortion = 0; nPortion < nPortions; nPortion++ ) + { + pTP = &pParaPortion->GetTextPortions()[ nPortion ]; + if ( ( nPos+pTP->GetLen() ) > nStartPos ) + { + DBG_ASSERT( nPos <= nStartPos, "Wrong Start!" ); + DBG_ASSERT( nPos+pTP->GetLen() >= nEnd, "Wrong End!" ); + break; + } + nPos = nPos + pTP->GetLen(); + } + assert( pTP && "RecalcTextPortion: Portion not found" ); + if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) ) + { + // Remove portion; + PortionKind nType = pTP->GetKind(); + pParaPortion->GetTextPortions().Remove( nPortion ); + if ( nType == PortionKind::LINEBREAK ) + { + TextPortion& rNext = pParaPortion->GetTextPortions()[ nPortion ]; + if ( !rNext.GetLen() ) + { + // Remove dummy portion + pParaPortion->GetTextPortions().Remove( nPortion ); + } + } + } + else + { + DBG_ASSERT( pTP->GetLen() > (-nNewChars), "Portion too small to shrink! "); + pTP->SetLen( pTP->GetLen() + nNewChars ); + } + + sal_Int32 nPortionCount = pParaPortion->GetTextPortions().Count(); + assert( nPortionCount ); + if (nPortionCount) + { + // No HYPHENATOR portion is allowed to get stuck right at the end... + sal_Int32 nLastPortion = nPortionCount - 1; + pTP = &pParaPortion->GetTextPortions()[nLastPortion]; + if ( pTP->GetKind() == PortionKind::HYPHENATOR ) + { + // Discard portion; if possible, correct the ones before, + // if the Hyphenator portion has swallowed one character... + if ( nLastPortion && pTP->GetLen() ) + { + TextPortion& rPrev = pParaPortion->GetTextPortions()[nLastPortion - 1]; + DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" ); + rPrev.SetLen( rPrev.GetLen() + pTP->GetLen() ); + rPrev.GetSize().setWidth( -1 ); + } + pParaPortion->GetTextPortions().Remove( nLastPortion ); + } + } + } +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portions are broken?" ); +#endif +} + +void ImpEditEngine::SetTextRanger( std::unique_ptr<TextRanger> pRanger ) +{ + pTextRanger = std::move(pRanger); + + for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) + { + ParaPortion* pParaPortion = GetParaPortions()[nPara]; + pParaPortion->MarkSelectionInvalid( 0 ); + pParaPortion->GetLines().Reset(); + } + + FormatFullDoc(); + UpdateViews( GetActiveView() ); + if ( IsUpdateLayout() && GetActiveView() ) + pActiveView->ShowCursor(false, false); +} + +void ImpEditEngine::SetVertical( bool bVertical) +{ + if ( IsEffectivelyVertical() != bVertical) + { + GetEditDoc().SetVertical(bVertical); + bool bUseCharAttribs = bool(aStatus.GetControlWord() & EEControlBits::USECHARATTRIBS); + GetEditDoc().CreateDefFont( bUseCharAttribs ); + if ( IsFormatted() ) + { + FormatFullDoc(); + UpdateViews( GetActiveView() ); + } + } +} + +void ImpEditEngine::SetRotation(TextRotation nRotation) +{ + if (GetEditDoc().GetRotation() == nRotation) + return; // not modified + GetEditDoc().SetRotation(nRotation); + bool bUseCharAttribs = bool(aStatus.GetControlWord() & EEControlBits::USECHARATTRIBS); + GetEditDoc().CreateDefFont( bUseCharAttribs ); + if ( IsFormatted() ) + { + FormatFullDoc(); + UpdateViews( GetActiveView() ); + } +} + +void ImpEditEngine::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing) +{ + assert(nColumns >= 1); + if (mnColumns != nColumns || mnColumnSpacing != nSpacing) + { + if (nColumns == 0) + { + SAL_WARN("editeng", "bad nColumns value, ignoring"); + nColumns = 1; + } + mnColumns = nColumns; + mnColumnSpacing = nSpacing; + if (IsFormatted()) + { + FormatFullDoc(); + UpdateViews(GetActiveView()); + } + } +} + +void ImpEditEngine::SetFixedCellHeight( bool bUseFixedCellHeight ) +{ + if ( IsFixedCellHeight() != bUseFixedCellHeight ) + { + GetEditDoc().SetFixedCellHeight( bUseFixedCellHeight ); + if ( IsFormatted() ) + { + FormatFullDoc(); + UpdateViews( GetActiveView() ); + } + } +} + +void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont, OutputDevice* pOut ) +{ + // It was planned, SeekCursor( nStartPos, nEndPos,... ), so that it would + // only be searched anew at the StartPosition. + // Problem: There would be two lists to consider/handle: + // OrderedByStart,OrderedByEnd. + + if ( nPos > pNode->Len() ) + nPos = pNode->Len(); + + rFont = pNode->GetCharAttribs().GetDefFont(); + + /* + * Set attributes for script types Asian and Complex + */ + short nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nPos ) ); + SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N); + if ( ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) || ( nScriptTypeI18N == i18n::ScriptType::COMPLEX ) ) + { + const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) )); + rFont.SetFamilyName( rFontItem.GetFamilyName() ); + rFont.SetFamily( rFontItem.GetFamily() ); + rFont.SetPitch( rFontItem.GetPitch() ); + rFont.SetCharSet( rFontItem.GetCharSet() ); + Size aSz( rFont.GetFontSize() ); + aSz.setHeight( static_cast<const SvxFontHeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) ).GetHeight() ); + rFont.SetFontSize( aSz ); + rFont.SetWeight( static_cast<const SvxWeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ))).GetWeight() ); + rFont.SetItalic( static_cast<const SvxPostureItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ))).GetPosture() ); + rFont.SetLanguage( static_cast<const SvxLanguageItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ))).GetLanguage() ); + } + + sal_uInt16 nRelWidth = pNode->GetContentAttribs().GetItem( EE_CHAR_FONTWIDTH).GetValue(); + + /* + * Set output device's line and overline colors + */ + if ( pOut ) + { + const SvxUnderlineItem& rTextLineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_UNDERLINE ); + if ( rTextLineColor.GetColor() != COL_TRANSPARENT ) + pOut->SetTextLineColor( rTextLineColor.GetColor() ); + else + pOut->SetTextLineColor(); + + const SvxOverlineItem& rOverlineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_OVERLINE ); + if ( rOverlineColor.GetColor() != COL_TRANSPARENT ) + pOut->SetOverlineColor( rOverlineColor.GetColor() ); + else + pOut->SetOverlineColor(); + } + + const SvxLanguageItem* pCJKLanguageItem = nullptr; + + /* + * Scan through char attributes of pNode + */ + if ( aStatus.UseCharAttribs() ) + { + CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); + size_t nAttr = 0; + EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr); + while ( pAttrib && ( pAttrib->GetStart() <= nPos ) ) + { + // when seeking, ignore attributes which start there! Empty attributes + // are considered (used) as these are just set. But do not use empty + // attributes: When just set and empty => no effect on font + // In a blank paragraph, set characters take effect immediately. + if ( ( pAttrib->Which() != 0 ) && + ( ( ( pAttrib->GetStart() < nPos ) && ( pAttrib->GetEnd() >= nPos ) ) + || ( !pNode->Len() ) ) ) + { + DBG_ASSERT( ( pAttrib->Which() >= EE_CHAR_START ) && ( pAttrib->Which() <= EE_FEATURE_END ), "Invalid Attribute in Seek() " ); + if ( IsScriptItemValid( pAttrib->Which(), nScriptTypeI18N ) ) + { + pAttrib->SetFont( rFont, pOut ); + // #i1550# hard color attrib should win over text color from field + if ( pAttrib->Which() == EE_FEATURE_FIELD ) + { + EditCharAttrib* pColorAttr = pNode->GetCharAttribs().FindAttrib( EE_CHAR_COLOR, nPos ); + if ( pColorAttr ) + pColorAttr->SetFont( rFont, pOut ); + } + } + if ( pAttrib->Which() == EE_CHAR_FONTWIDTH ) + nRelWidth = static_cast<const SvxCharScaleWidthItem*>(pAttrib->GetItem())->GetValue(); + if ( pAttrib->Which() == EE_CHAR_LANGUAGE_CJK ) + pCJKLanguageItem = static_cast<const SvxLanguageItem*>( pAttrib->GetItem() ); + } + pAttrib = GetAttrib( rAttribs, ++nAttr ); + } + } + + if ( !pCJKLanguageItem ) + pCJKLanguageItem = &pNode->GetContentAttribs().GetItem( EE_CHAR_LANGUAGE_CJK ); + + rFont.SetCJKContextLanguage( pCJKLanguageItem->GetLanguage() ); + + if ( (rFont.GetKerning() != FontKerning::NONE) && IsKernAsianPunctuation() && ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) ) + rFont.SetKerning( rFont.GetKerning() | FontKerning::Asian ); + + if ( aStatus.DoNotUseColors() ) + { + rFont.SetColor( /* rColorItem.GetValue() */ COL_BLACK ); + } + + if ( aStatus.DoStretch() || ( nRelWidth != 100 ) ) + { + // For the current Output device, because otherwise if RefDev=Printer its looks + // ugly on the screen! + OutputDevice* pDev = pOut ? pOut : GetRefDevice(); + rFont.SetPhysFont(*pDev); + FontMetric aMetric( pDev->GetFontMetric() ); + + // before forcing nPropr to 100%, calculate a new escapement relative to this fake size. + sal_uInt8 nPropr = rFont.GetPropr(); + sal_Int16 nEsc = rFont.GetEscapement(); + if ( nPropr && nEsc && nPropr != 100 && abs(nEsc) != DFLT_ESC_AUTO_SUPER ) + rFont.SetEscapement( 100.0/nPropr * nEsc ); + + // Set the font as we want it to look like & reset the Propr attribute + // so that it is not counted twice. + Size aRealSz( aMetric.GetFontSize() ); + rFont.SetPropr( 100 ); + + if ( aStatus.DoStretch() ) + { + if ( nStretchY != 100 ) + { + aRealSz.setHeight( aRealSz.Height() * nStretchY ); + aRealSz.setHeight( aRealSz.Height() / 100 ); + } + if ( nStretchX != 100 ) + { + if ( nStretchX == nStretchY && + nRelWidth == 100 ) + { + aRealSz.setWidth( 0 ); + } + else + { + aRealSz.setWidth( aRealSz.Width() * nStretchX ); + aRealSz.setWidth( aRealSz.Width() / 100 ); + + // Also the Kerning: (long due to handle Interim results) + tools::Long nKerning = rFont.GetFixKerning(); +/* + The consideration was: If negative kerning, but StretchX = 200 + => Do not double the kerning, thus pull the letters closer together + --------------------------- + Kern StretchX =>Kern + --------------------------- + >0 <100 < (Proportional) + <0 <100 < (Proportional) + >0 >100 > (Proportional) + <0 >100 < (The amount, thus disproportional) +*/ + if ( ( nKerning < 0 ) && ( nStretchX > 100 ) ) + { + // disproportional + nKerning *= 100; + nKerning /= nStretchX; + } + else if ( nKerning ) + { + // Proportional + nKerning *= nStretchX; + nKerning /= 100; + } + rFont.SetFixKerning( static_cast<short>(nKerning) ); + } + } + } + if ( nRelWidth != 100 ) + { + aRealSz.setWidth( aRealSz.Width() * nRelWidth ); + aRealSz.setWidth( aRealSz.Width() / 100 ); + } + rFont.SetFontSize( aRealSz ); + // Font is not restored... + } + + if ( ( ( rFont.GetColor() == COL_AUTO ) || ( IsForceAutoColor() ) ) && pOut ) + { + // #i75566# Do not use AutoColor when printing OR Pdf export + const bool bPrinting(OUTDEV_PRINTER == pOut->GetOutDevType()); + const bool bPDFExporting(OUTDEV_PDF == pOut->GetOutDevType()); + + if ( IsAutoColorEnabled() && !bPrinting && !bPDFExporting) + { + // Never use WindowTextColor on the printer + rFont.SetColor( GetAutoColor() ); + } + else + { + if ( ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() ) + rFont.SetColor( COL_WHITE ); + else + rFont.SetColor( COL_BLACK ); + } + } + + if ( !(mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) && + ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) )) ) + return; + + ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ]; + if ( nAttr & ExtTextInputAttr::Underline ) + rFont.SetUnderline( LINESTYLE_SINGLE ); + else if ( nAttr & ExtTextInputAttr::DoubleUnderline ) + rFont.SetUnderline( LINESTYLE_DOUBLE ); + else if ( nAttr & ExtTextInputAttr::BoldUnderline ) + rFont.SetUnderline( LINESTYLE_BOLD ); + else if ( nAttr & ExtTextInputAttr::DottedUnderline ) + rFont.SetUnderline( LINESTYLE_DOTTED ); + else if ( nAttr & ExtTextInputAttr::DashDotUnderline ) + rFont.SetUnderline( LINESTYLE_DOTTED ); + else if ( nAttr & ExtTextInputAttr::RedText ) + rFont.SetColor( COL_RED ); + else if ( nAttr & ExtTextInputAttr::HalfToneText ) + rFont.SetColor( COL_LIGHTGRAY ); + if ( nAttr & ExtTextInputAttr::Highlight ) + { + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + rFont.SetColor( rStyleSettings.GetHighlightTextColor() ); + rFont.SetFillColor( rStyleSettings.GetHighlightColor() ); + rFont.SetTransparent( false ); + } + else if ( nAttr & ExtTextInputAttr::GrayWaveline ) + { + rFont.SetUnderline( LINESTYLE_WAVE ); + if( pOut ) + pOut->SetTextLineColor( COL_LIGHTGRAY ); + } +} + +void ImpEditEngine::RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics, SvxFont& rFont ) +{ + // for line height at high / low first without Propr! + sal_uInt16 nPropr = rFont.GetPropr(); + DBG_ASSERT( ( nPropr == 100 ) || rFont.GetEscapement(), "Propr without Escape?!" ); + if ( nPropr != 100 ) + { + rFont.SetPropr( 100 ); + rFont.SetPhysFont(*pRefDev); + } + sal_uInt16 nAscent, nDescent; + + FontMetric aMetric( pRefDev->GetFontMetric() ); + nAscent = static_cast<sal_uInt16>(aMetric.GetAscent()); + if ( IsAddExtLeading() ) + nAscent = sal::static_int_cast< sal_uInt16 >( + nAscent + aMetric.GetExternalLeading() ); + nDescent = static_cast<sal_uInt16>(aMetric.GetDescent()); + + if ( IsFixedCellHeight() ) + { + nAscent = sal::static_int_cast< sal_uInt16 >( rFont.GetFontHeight() ); + nDescent= sal::static_int_cast< sal_uInt16 >( ImplCalculateFontIndependentLineSpacing( rFont.GetFontHeight() ) - nAscent ); + } + else + { + sal_uInt16 nIntLeading = ( aMetric.GetInternalLeading() > 0 ) ? static_cast<sal_uInt16>(aMetric.GetInternalLeading()) : 0; + // Fonts without leading cause problems + if ( ( nIntLeading == 0 ) && ( pRefDev->GetOutDevType() == OUTDEV_PRINTER ) ) + { + // Lets see what Leading one gets on the screen + VclPtr<VirtualDevice> pVDev = GetVirtualDevice( pRefDev->GetMapMode(), pRefDev->GetDrawMode() ); + rFont.SetPhysFont(*pVDev); + aMetric = pVDev->GetFontMetric(); + + // This is so that the Leading does not count itself out again, + // if the whole line has the font, nTmpLeading. + nAscent = static_cast<sal_uInt16>(aMetric.GetAscent()); + nDescent = static_cast<sal_uInt16>(aMetric.GetDescent()); + } + } + if ( nAscent > rCurMetrics.nMaxAscent ) + rCurMetrics.nMaxAscent = nAscent; + if ( nDescent > rCurMetrics.nMaxDescent ) + rCurMetrics.nMaxDescent= nDescent; + // Special treatment of high/low: + if ( !rFont.GetEscapement() ) + return; + + // Now in consideration of Escape/Propr + // possibly enlarge Ascent or Descent + short nDiff = static_cast<short>(rFont.GetFontSize().Height()*rFont.GetEscapement()/100); + if ( rFont.GetEscapement() > 0 ) + { + nAscent = static_cast<sal_uInt16>(static_cast<tools::Long>(nAscent)*nPropr/100 + nDiff); + if ( nAscent > rCurMetrics.nMaxAscent ) + rCurMetrics.nMaxAscent = nAscent; + } + else // has to be < 0 + { + nDescent = static_cast<sal_uInt16>(static_cast<tools::Long>(nDescent)*nPropr/100 - nDiff); + if ( nDescent > rCurMetrics.nMaxDescent ) + rCurMetrics.nMaxDescent= nDescent; + } +} + +tools::Long ImpEditEngine::getWidthDirectionAware(const Size& sz) const +{ + return !IsEffectivelyVertical() ? sz.Width() : sz.Height(); +} + +tools::Long ImpEditEngine::getHeightDirectionAware(const Size& sz) const +{ + return !IsEffectivelyVertical() ? sz.Height() : sz.Width(); +} + +void ImpEditEngine::adjustXDirectionAware(Point& pt, tools::Long x) const +{ + if (!IsEffectivelyVertical()) + pt.AdjustX(x); + else + pt.AdjustY(IsTopToBottom() ? x : -x); +} + +void ImpEditEngine::adjustYDirectionAware(Point& pt, tools::Long y) const +{ + if (!IsEffectivelyVertical()) + pt.AdjustY(y); + else + pt.AdjustX(IsTopToBottom() ? -y : y); +} + +void ImpEditEngine::setXDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const +{ + if (!IsEffectivelyVertical()) + ptDest.setX(ptSrc.X()); + else + ptDest.setY(ptSrc.Y()); +} + +void ImpEditEngine::setYDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const +{ + if (!IsEffectivelyVertical()) + ptDest.setY(ptSrc.Y()); + else + ptDest.setX(ptSrc.Y()); +} + +tools::Long ImpEditEngine::getYOverflowDirectionAware(const Point& pt, + const tools::Rectangle& rectMax) const +{ + tools::Long nRes; + if (!IsEffectivelyVertical()) + nRes = pt.Y() - rectMax.Bottom(); + else if (IsTopToBottom()) + nRes = rectMax.Left() - pt.X(); + else + nRes = pt.X() - rectMax.Right(); + return std::max(nRes, tools::Long(0)); +} + +bool ImpEditEngine::isXOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const +{ + if (!IsEffectivelyVertical()) + return pt.X() > rectMax.Right(); + + if (IsTopToBottom()) + return pt.Y() > rectMax.Bottom(); + else + return pt.Y() < rectMax.Top(); +} + +tools::Long ImpEditEngine::getBottomDocOffset(const tools::Rectangle& rect) const +{ + if (!IsEffectivelyVertical()) + return rect.Bottom(); + + if (IsTopToBottom()) + return -rect.Left(); + else + return rect.Right(); +} + +Size ImpEditEngine::getTopLeftDocOffset(const tools::Rectangle& rect) const +{ + if (!IsEffectivelyVertical()) + return { rect.Left(), rect.Top() }; + + if (IsTopToBottom()) + return { rect.Top(), -rect.Right() }; + else + return { -rect.Bottom(), rect.Left() }; +} + +// Returns the resulting shift for the point; allows to apply the same shift to other points +Point ImpEditEngine::MoveToNextLine( + Point& rMovePos, // [in, out] Point that will move to the next line + tools::Long nLineHeight, // [in] Y-direction move distance (direction-aware) + sal_Int16& rColumn, // [in, out] current column number + Point aOrigin, // [in] Origin point to calculate limits and initial Y position in a new column + tools::Long* pnHeightNeededToNotWrap // On column wrap, returns how much more height is needed +) const +{ + const Point aOld = rMovePos; + + // Move the point by the requested distance in Y direction + adjustYDirectionAware(rMovePos, nLineHeight); + // Check if the resulting position has moved beyond the limits, and more columns left. + // The limits are defined by a rectangle starting from aOrigin with width of aPaperSize + // and height of nCurTextHeight + Point aOtherCorner = aOrigin; + adjustXDirectionAware(aOtherCorner, getWidthDirectionAware(aPaperSize)); + adjustYDirectionAware(aOtherCorner, nCurTextHeight); + tools::Long nNeeded + = getYOverflowDirectionAware(rMovePos, tools::Rectangle::Justify(aOrigin, aOtherCorner)); + if (pnHeightNeededToNotWrap) + *pnHeightNeededToNotWrap = nNeeded; + if (nNeeded && rColumn < mnColumns) + { + ++rColumn; + // If we didn't fit into the last column, indicate that only by setting the column number + // to the total number of columns; do not adjust + if (rColumn < mnColumns) + { + // Set Y position of the point to that of aOrigin + setYDirectionAwareFrom(rMovePos, aOrigin); + // Move the point by the requested distance in Y direction + adjustYDirectionAware(rMovePos, nLineHeight); + // Move the point by the column+spacing distance in X direction + adjustXDirectionAware(rMovePos, GetColumnWidth(aPaperSize) + mnColumnSpacing); + } + } + + return rMovePos - aOld; +} + +// TODO: use IterateLineAreas in ImpEditEngine::Paint, to avoid algorithm duplication + +void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly, Degree10 nOrientation ) +{ + if ( !IsUpdateLayout() && !bStripOnly ) + return; + + if ( !IsFormatted() ) + FormatDoc(); + + tools::Long nFirstVisXPos = - rOutDev.GetMapMode().GetOrigin().X(); + tools::Long nFirstVisYPos = - rOutDev.GetMapMode().GetOrigin().Y(); + + DBG_ASSERT( GetParaPortions().Count(), "No ParaPortion?!" ); + SvxFont aTmpFont( GetParaPortions()[0]->GetNode()->GetCharAttribs().GetDefFont() ); + vcl::PDFExtOutDevData* const pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData* >( rOutDev.GetExtOutDevData() ); + + // In the case of rotated text is aStartPos considered TopLeft because + // other information is missing, and since the whole object is shown anyway + // un-scrolled. + // The rectangle is infinite. + const Point aOrigin( aStartPos ); + double nCos = 0.0, nSin = 0.0; + if ( nOrientation ) + { + double nRealOrientation = toRadians(nOrientation); + nCos = cos( nRealOrientation ); + nSin = sin( nRealOrientation ); + } + + // #110496# Added some more optional metafile comments. This + // change: factored out some duplicated code. + GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile(); + const bool bMetafileValid( pMtf != nullptr ); + + const tools::Long nVertLineSpacing = CalcVertLineSpacing(aStartPos); + + sal_Int16 nColumn = 0; + + // Over all the paragraphs... + + for ( sal_Int32 n = 0; n < GetParaPortions().Count(); n++ ) + { + const ParaPortion* const pPortion = GetParaPortions()[n]; + assert( pPortion && "NULL-Pointer in TokenList in Paint" ); + // if when typing idle formatting, asynchronous Paint. + // Invisible Portions may be invalid. + if ( pPortion->IsVisible() && pPortion->IsInvalid() ) + return; + + if ( pPDFExtOutDevData ) + pPDFExtOutDevData->BeginStructureElement( vcl::PDFWriter::Paragraph ); + + const tools::Long nParaHeight = pPortion->GetHeight(); + if ( pPortion->IsVisible() && ( + ( !IsEffectivelyVertical() && ( ( aStartPos.Y() + nParaHeight ) > aClipRect.Top() ) ) || + ( IsEffectivelyVertical() && IsTopToBottom() && ( ( aStartPos.X() - nParaHeight ) < aClipRect.Right() ) ) || + ( IsEffectivelyVertical() && !IsTopToBottom() && ( ( aStartPos.X() + nParaHeight ) > aClipRect.Left() ) ) ) ) + + { + Point aTmpPos; + + // Over the lines of the paragraph... + + const sal_Int32 nLines = pPortion->GetLines().Count(); + const sal_Int32 nLastLine = nLines-1; + + bool bEndOfParagraphWritten(false); + + adjustYDirectionAware(aStartPos, pPortion->GetFirstLineOffset()); + + const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) + ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; + bool bPaintBullet (false); + + for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ ) + { + const EditLine* const pLine = &pPortion->GetLines()[nLine]; + assert( pLine && "NULL-Pointer in the line iterator in UpdateViews" ); + sal_Int32 nIndex = pLine->GetStart(); + tools::Long nLineHeight = pLine->GetHeight(); + if (nLine != nLastLine) + nLineHeight += nVertLineSpacing; + MoveToNextLine(aStartPos, nLineHeight, nColumn, aOrigin); + aTmpPos = aStartPos; + adjustXDirectionAware(aTmpPos, pLine->GetStartPosX()); + adjustYDirectionAware(aTmpPos, pLine->GetMaxAscent() - nLineHeight); + + if ( ( !IsEffectivelyVertical() && ( aStartPos.Y() > aClipRect.Top() ) ) + || ( IsEffectivelyVertical() && IsTopToBottom() && aStartPos.X() < aClipRect.Right() ) + || ( IsEffectivelyVertical() && !IsTopToBottom() && aStartPos.X() > aClipRect.Left() ) ) + { + bPaintBullet = false; + + // Why not just also call when stripping portions? This will give the correct values + // and needs no position corrections in OutlinerEditEng::DrawingText which tries to call + // PaintBullet correctly; exactly what GetEditEnginePtr()->PaintingFirstLine + // does, too. No change for not-layouting (painting). + if(0 == nLine) // && !bStripOnly) + { + Point aLineStart(aStartPos); + adjustYDirectionAware(aLineStart, -nLineHeight); + GetEditEnginePtr()->PaintingFirstLine(n, aLineStart, aOrigin, nOrientation, rOutDev); + + // Remember whether a bullet was painted. + const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib(n, EE_PARA_BULLETSTATE); + bPaintBullet = rBulletState.GetValue(); + } + + + // Over the Portions of the line... + + bool bParsingFields = false; + std::vector< sal_Int32 >::iterator itSubLines; + + for ( sal_Int32 nPortion = pLine->GetStartPortion(); nPortion <= pLine->GetEndPortion(); nPortion++ ) + { + DBG_ASSERT( pPortion->GetTextPortions().Count(), "Line without Textportion in Paint!" ); + const TextPortion& rTextPortion = pPortion->GetTextPortions()[nPortion]; + + const tools::Long nPortionXOffset = GetPortionXOffset( pPortion, pLine, nPortion ); + setXDirectionAwareFrom(aTmpPos, aStartPos); + adjustXDirectionAware(aTmpPos, nPortionXOffset); + if (isXOverflowDirectionAware(aTmpPos, aClipRect)) + break; // No further output in line necessary + + switch ( rTextPortion.GetKind() ) + { + case PortionKind::TEXT: + case PortionKind::FIELD: + case PortionKind::HYPHENATOR: + { + SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, &rOutDev ); + + bool bDrawFrame = false; + + if ( ( rTextPortion.GetKind() == PortionKind::FIELD ) && !aTmpFont.IsTransparent() && + ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() && + ( IsAutoColorEnabled() && ( rOutDev.GetOutDevType() != OUTDEV_PRINTER ) ) ) + { + aTmpFont.SetTransparent( true ); + rOutDev.SetFillColor(); + rOutDev.SetLineColor( GetAutoColor() ); + bDrawFrame = true; + } + +#if OSL_DEBUG_LEVEL > 2 + // Do we really need this if statement? + if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR ) + { + aTmpFont.SetFillColor( COL_LIGHTGRAY ); + aTmpFont.SetTransparent( sal_False ); + } + if ( rTextPortion.IsRightToLeft() ) + { + aTmpFont.SetFillColor( COL_LIGHTGRAY ); + aTmpFont.SetTransparent( sal_False ); + } + else if ( GetI18NScriptType( EditPaM( pPortion->GetNode(), nIndex+1 ) ) == i18n::ScriptType::COMPLEX ) + { + aTmpFont.SetFillColor( COL_LIGHTCYAN ); + aTmpFont.SetTransparent( sal_False ); + } +#endif + aTmpFont.SetPhysFont(rOutDev); + + // #114278# Saving both layout mode and language (since I'm + // potentially changing both) + rOutDev.Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE ); + ImplInitLayoutMode(rOutDev, n, nIndex); + ImplInitDigitMode(rOutDev, aTmpFont.GetLanguage()); + + OUString aText; + sal_Int32 nTextStart = 0; + sal_Int32 nTextLen = 0; + o3tl::span<const sal_Int32> pDXArray; + std::vector<sal_Int32> aTmpDXArray; + + if ( rTextPortion.GetKind() == PortionKind::TEXT ) + { + aText = pPortion->GetNode()->GetString(); + nTextStart = nIndex; + nTextLen = rTextPortion.GetLen(); + pDXArray = o3tl::span(pLine->GetCharPosArray().data() + (nIndex - pLine->GetStart()), + pLine->GetCharPosArray().size() - (nIndex - pLine->GetStart())); + + // Paint control characters (#i55716#) + /* XXX: Given that there's special handling + * only for some specific characters + * (U+200B ZERO WIDTH SPACE and U+2060 WORD + * JOINER) it is assumed to be not relevant + * for MarkUrlFields(). */ + if ( aStatus.MarkNonUrlFields() ) + { + sal_Int32 nTmpIdx; + const sal_Int32 nTmpEnd = nTextStart + rTextPortion.GetLen(); + + for ( nTmpIdx = nTextStart; nTmpIdx <= nTmpEnd ; ++nTmpIdx ) + { + const sal_Unicode cChar = ( nTmpIdx != aText.getLength() && ( nTmpIdx != nTextStart || 0 == nTextStart ) ) ? + aText[nTmpIdx] : + 0; + + if ( 0x200B == cChar || 0x2060 == cChar ) + { + tools::Long nHalfBlankWidth = aTmpFont.QuickGetTextSize( &rOutDev, + " ", 0, 1, nullptr ).Width() / 2; + + const tools::Long nAdvanceX = ( nTmpIdx == nTmpEnd ? + rTextPortion.GetSize().Width() : + pDXArray[ nTmpIdx - nTextStart ] ) - nHalfBlankWidth; + const tools::Long nAdvanceY = -pLine->GetMaxAscent(); + + Point aTopLeftRectPos( aTmpPos ); + adjustXDirectionAware(aTopLeftRectPos, nAdvanceX); + adjustYDirectionAware(aTopLeftRectPos, nAdvanceY); + + Point aBottomRightRectPos( aTopLeftRectPos ); + adjustXDirectionAware(aBottomRightRectPos, 2 * nHalfBlankWidth); + adjustYDirectionAware(aBottomRightRectPos, pLine->GetHeight()); + + rOutDev.Push( vcl::PushFlags::FILLCOLOR ); + rOutDev.Push( vcl::PushFlags::LINECOLOR ); + rOutDev.SetFillColor( COL_LIGHTGRAY ); + rOutDev.SetLineColor( COL_LIGHTGRAY ); + + const tools::Rectangle aBackRect( aTopLeftRectPos, aBottomRightRectPos ); + rOutDev.DrawRect( aBackRect ); + + rOutDev.Pop(); + rOutDev.Pop(); + + if ( 0x200B == cChar ) + { + const OUString aSlash( '/' ); + const short nOldEscapement = aTmpFont.GetEscapement(); + const sal_uInt8 nOldPropr = aTmpFont.GetPropr(); + + aTmpFont.SetEscapement( -20 ); + aTmpFont.SetPropr( 25 ); + aTmpFont.SetPhysFont(rOutDev); + + const Size aSlashSize = aTmpFont.QuickGetTextSize( &rOutDev, + aSlash, 0, 1, nullptr ); + Point aSlashPos( aTmpPos ); + const tools::Long nAddX = nHalfBlankWidth - aSlashSize.Width() / 2; + setXDirectionAwareFrom(aSlashPos, aTopLeftRectPos); + adjustXDirectionAware(aSlashPos, nAddX); + + aTmpFont.QuickDrawText( &rOutDev, aSlashPos, aSlash, 0, 1, {} ); + + aTmpFont.SetEscapement( nOldEscapement ); + aTmpFont.SetPropr( nOldPropr ); + aTmpFont.SetPhysFont(rOutDev); + } + } + } + } + } + else if ( rTextPortion.GetKind() == PortionKind::FIELD ) + { + const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); + assert( pAttr && "Field not found"); + DBG_ASSERT( dynamic_cast< const SvxFieldItem* >( pAttr->GetItem() ) != nullptr, "Field of the wrong type! "); + aText = static_cast<const EditCharAttribField*>(pAttr)->GetFieldValue(); + nTextStart = 0; + nTextLen = aText.getLength(); + ExtraPortionInfo *pExtraInfo = rTextPortion.GetExtraInfos(); + // Do not split the Fields into different lines while editing + // With EditView on Overlay bStripOnly is now set for stripping to + // primitives. To stay compatible in EditMode use pActiveView to detect + // when we are in EditMode. For whatever reason URLs are drawn as single + // line in edit mode, originally clipped against edit area (which is no + // longer done in Overlay mode and allows to *read* the URL). + // It would be difficult to change this due to needed adaptations in + // EditEngine (look for lineBreaksList creation) + if( nullptr == pActiveView && bStripOnly && !bParsingFields && pExtraInfo && !pExtraInfo->lineBreaksList.empty() ) + { + bParsingFields = true; + itSubLines = pExtraInfo->lineBreaksList.begin(); + } + + if( bParsingFields ) + { + if( itSubLines != pExtraInfo->lineBreaksList.begin() ) + { + // only use GetMaxAscent(), pLine->GetHeight() will not + // proceed as needed (see PortionKind::TEXT above and nAdvanceY) + // what will lead to a compressed look with multiple lines + const sal_uInt16 nMaxAscent(pLine->GetMaxAscent()); + + aTmpPos += MoveToNextLine(aStartPos, nMaxAscent, + nColumn, aOrigin); + } + std::vector< sal_Int32 >::iterator curIt = itSubLines; + ++itSubLines; + if( itSubLines != pExtraInfo->lineBreaksList.end() ) + { + nTextStart = *curIt; + nTextLen = *itSubLines - nTextStart; + } + else + { + nTextStart = *curIt; + nTextLen = nTextLen - nTextStart; + bParsingFields = false; + } + } + + aTmpFont.SetPhysFont(*GetRefDevice()); + aTmpFont.QuickGetTextSize( GetRefDevice(), aText, nTextStart, nTextLen, + &aTmpDXArray ); + pDXArray = aTmpDXArray; + + // add a meta file comment if we record to a metafile + if( bMetafileValid ) + { + const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); + if( pFieldItem ) + { + const SvxFieldData* pFieldData = pFieldItem->GetField(); + if( pFieldData ) + pMtf->AddAction( pFieldData->createBeginComment() ); + } + } + + } + else if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR ) + { + if ( rTextPortion.GetExtraValue() ) + aText = OUString(rTextPortion.GetExtraValue()); + aText += CH_HYPH; + nTextStart = 0; + nTextLen = aText.getLength(); + + // crash when accessing 0 pointer in pDXArray + aTmpFont.SetPhysFont(*GetRefDevice()); + aTmpFont.QuickGetTextSize( GetRefDevice(), aText, 0, aText.getLength(), + &aTmpDXArray ); + pDXArray = aTmpDXArray; + } + + tools::Long nTxtWidth = rTextPortion.GetSize().Width(); + + Point aOutPos( aTmpPos ); + Point aRedLineTmpPos = aTmpPos; + // In RTL portions spell markup pos should be at the start of the + // first chara as well. That is on the right end of the portion + if (rTextPortion.IsRightToLeft()) + aRedLineTmpPos.AdjustX(rTextPortion.GetSize().Width() ); + + if ( bStripOnly ) + { + EEngineData::WrongSpellVector aWrongSpellVector; + + if(GetStatus().DoOnlineSpelling() && rTextPortion.GetLen()) + { + WrongList* pWrongs = pPortion->GetNode()->GetWrongList(); + + if(pWrongs && !pWrongs->empty()) + { + size_t nStart = nIndex, nEnd = 0; + bool bWrong = pWrongs->NextWrong(nStart, nEnd); + const size_t nMaxEnd(nIndex + rTextPortion.GetLen()); + + while(bWrong) + { + if(nStart >= nMaxEnd) + { + break; + } + + if(nStart < o3tl::make_unsigned(nIndex)) + { + nStart = nIndex; + } + + if(nEnd > nMaxEnd) + { + nEnd = nMaxEnd; + } + + // add to vector + aWrongSpellVector.emplace_back(nStart, nEnd); + + // goto next index + nStart = nEnd + 1; + + if(nEnd < nMaxEnd) + { + bWrong = pWrongs->NextWrong(nStart, nEnd); + } + else + { + bWrong = false; + } + } + } + } + + const SvxFieldData* pFieldData = nullptr; + + if(PortionKind::FIELD == rTextPortion.GetKind()) + { + const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); + const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); + + if(pFieldItem) + { + pFieldData = pFieldItem->GetField(); + } + } + + // support for EOC, EOW, EOS TEXT comments. To support that, + // the locale is needed. With the locale and a XBreakIterator it is + // possible to re-create the text marking info on primitive level + const lang::Locale aLocale(GetLocale(EditPaM(pPortion->GetNode(), nIndex + 1))); + + // create EOL and EOP bools + const bool bEndOfLine(nPortion == pLine->GetEndPortion()); + const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines); + + // get Overline color (from ((const SvxOverlineItem*)GetItem())->GetColor() in + // consequence, but also already set at rOutDev) + const Color aOverlineColor(rOutDev.GetOverlineColor()); + + // get TextLine color (from ((const SvxUnderlineItem*)GetItem())->GetColor() in + // consequence, but also already set at rOutDev) + const Color aTextLineColor(rOutDev.GetTextLineColor()); + + // Unicode code points conversion according to ctl text numeral setting + aText = convertDigits(aText, nTextStart, nTextLen, + ImplCalcDigitLang(aTmpFont.GetLanguage())); + + // StripPortions() data callback + GetEditEnginePtr()->DrawingText( aOutPos, aText, nTextStart, nTextLen, pDXArray, + aTmpFont, n, rTextPortion.GetRightToLeftLevel(), + !aWrongSpellVector.empty() ? &aWrongSpellVector : nullptr, + pFieldData, + bEndOfLine, bEndOfParagraph, // support for EOL/EOP TEXT comments + &aLocale, + aOverlineColor, + aTextLineColor); + + // #108052# remember that EOP is written already for this ParaPortion + if(bEndOfParagraph) + { + bEndOfParagraphWritten = true; + } + } + else + { + short nEsc = aTmpFont.GetEscapement(); + if ( nOrientation ) + { + // In case of high/low do it yourself: + if ( aTmpFont.GetEscapement() ) + { + tools::Long nDiff = aTmpFont.GetFontSize().Height() * aTmpFont.GetEscapement() / 100L; + adjustYDirectionAware(aOutPos, -nDiff); + aRedLineTmpPos = aOutPos; + aTmpFont.SetEscapement( 0 ); + } + + aOutPos = lcl_ImplCalcRotatedPos( aOutPos, aOrigin, nSin, nCos ); + aTmpFont.SetOrientation( aTmpFont.GetOrientation()+nOrientation ); + aTmpFont.SetPhysFont(rOutDev); + + } + + // Take only what begins in the visible range: + // Important, because of a bug in some graphic cards + // when transparent font, output when negative + if ( nOrientation || ( !IsEffectivelyVertical() && ( ( aTmpPos.X() + nTxtWidth ) >= nFirstVisXPos ) ) + || ( IsEffectivelyVertical() && ( ( aTmpPos.Y() + nTxtWidth ) >= nFirstVisYPos ) ) ) + { + if ( nEsc && ( aTmpFont.GetUnderline() != LINESTYLE_NONE ) ) + { + // Paint the high/low without underline, + // Display the Underline on the + // base line of the original font height... + // But only if there was something underlined before! + bool bSpecialUnderline = false; + EditCharAttrib* pPrev = pPortion->GetNode()->GetCharAttribs().FindAttrib( EE_CHAR_ESCAPEMENT, nIndex ); + if ( pPrev ) + { + SvxFont aDummy; + // Underscore in front? + if ( pPrev->GetStart() ) + { + SeekCursor( pPortion->GetNode(), pPrev->GetStart(), aDummy ); + if ( aDummy.GetUnderline() != LINESTYLE_NONE ) + bSpecialUnderline = true; + } + if ( !bSpecialUnderline && ( pPrev->GetEnd() < pPortion->GetNode()->Len() ) ) + { + SeekCursor( pPortion->GetNode(), pPrev->GetEnd()+1, aDummy ); + if ( aDummy.GetUnderline() != LINESTYLE_NONE ) + bSpecialUnderline = true; + } + } + if ( bSpecialUnderline ) + { + Size aSz = aTmpFont.GetPhysTxtSize( &rOutDev, aText, nTextStart, nTextLen ); + sal_uInt8 nProp = aTmpFont.GetPropr(); + aTmpFont.SetEscapement( 0 ); + aTmpFont.SetPropr( 100 ); + aTmpFont.SetPhysFont(rOutDev); + OUStringBuffer aBlanks(nTextLen); + comphelper::string::padToLength( aBlanks, nTextLen, ' ' ); + Point aUnderlinePos( aOutPos ); + if ( nOrientation ) + aUnderlinePos = lcl_ImplCalcRotatedPos( aTmpPos, aOrigin, nSin, nCos ); + rOutDev.DrawStretchText( aUnderlinePos, aSz.Width(), aBlanks.makeStringAndClear(), 0, nTextLen ); + + aTmpFont.SetUnderline( LINESTYLE_NONE ); + if ( !nOrientation ) + aTmpFont.SetEscapement( nEsc ); + aTmpFont.SetPropr( nProp ); + aTmpFont.SetPhysFont(rOutDev); + } + } + Point aRealOutPos( aOutPos ); + if ( ( rTextPortion.GetKind() == PortionKind::TEXT ) + && rTextPortion.GetExtraInfos() && rTextPortion.GetExtraInfos()->bCompressed + && rTextPortion.GetExtraInfos()->bFirstCharIsRightPunktuation ) + { + aRealOutPos.AdjustX(rTextPortion.GetExtraInfos()->nPortionOffsetX ); + } + + // RTL portions with (#i37132#) + // compressed blank should not paint this blank: + if ( rTextPortion.IsRightToLeft() && nTextLen >= 2 && + pDXArray[ nTextLen - 1 ] == + pDXArray[ nTextLen - 2 ] && + ' ' == aText[nTextStart + nTextLen - 1] ) + --nTextLen; + + // output directly + aTmpFont.QuickDrawText( &rOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray ); + + if ( bDrawFrame ) + { + Point aTopLeft( aTmpPos ); + aTopLeft.AdjustY( -(pLine->GetMaxAscent()) ); + if ( nOrientation ) + aTopLeft = lcl_ImplCalcRotatedPos( aTopLeft, aOrigin, nSin, nCos ); + tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() ); + rOutDev.DrawRect( aRect ); + } + + // PDF export: + if ( pPDFExtOutDevData ) + { + if ( rTextPortion.GetKind() == PortionKind::FIELD ) + { + const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); + const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); + if( pFieldItem ) + { + const SvxFieldData* pFieldData = pFieldItem->GetField(); + if ( auto pUrlField = dynamic_cast< const SvxURLField* >( pFieldData ) ) + { + Point aTopLeft( aTmpPos ); + aTopLeft.AdjustY( -(pLine->GetMaxAscent()) ); + + tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() ); + vcl::PDFExtOutDevBookmarkEntry aBookmark; + aBookmark.nLinkId = pPDFExtOutDevData->CreateLink(aRect, pUrlField->GetRepresentation()); + aBookmark.aBookmark = pUrlField->GetURL(); + std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks(); + rBookmarks.push_back( aBookmark ); + } + } + } + } + } + + const WrongList* const pWrongList = pPortion->GetNode()->GetWrongList(); + if ( GetStatus().DoOnlineSpelling() && pWrongList && !pWrongList->empty() && rTextPortion.GetLen() ) + { + {//#105750# adjust LinePos for superscript or subscript text + short _nEsc = aTmpFont.GetEscapement(); + if( _nEsc ) + { + tools::Long nShift = (_nEsc * aTmpFont.GetFontSize().Height()) / 100L; + adjustYDirectionAware(aRedLineTmpPos, -nShift); + } + } + Color aOldColor( rOutDev.GetLineColor() ); + rOutDev.SetLineColor( GetColorConfig().GetColorValue( svtools::SPELL ).nColor ); + lcl_DrawRedLines( rOutDev, aTmpFont.GetFontSize().Height(), aRedLineTmpPos, static_cast<size_t>(nIndex), static_cast<size_t>(nIndex) + rTextPortion.GetLen(), pDXArray, pPortion->GetNode()->GetWrongList(), nOrientation, aOrigin, IsEffectivelyVertical(), rTextPortion.IsRightToLeft() ); + rOutDev.SetLineColor( aOldColor ); + } + } + + rOutDev.Pop(); + + if ( rTextPortion.GetKind() == PortionKind::FIELD ) + { + // add a meta file comment if we record to a metafile + if( bMetafileValid ) + { + const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); + assert( pAttr && "Field not found" ); + + const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); + DBG_ASSERT( pFieldItem != nullptr, "Wrong type of field!" ); + + if( pFieldItem ) + { + const SvxFieldData* pFieldData = pFieldItem->GetField(); + if( pFieldData ) + pMtf->AddAction( SvxFieldData::createEndComment() ); + } + } + + } + + } + break; + case PortionKind::TAB: + { + if ( rTextPortion.GetExtraValue() && ( rTextPortion.GetExtraValue() != ' ' ) ) + { + SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, &rOutDev ); + aTmpFont.SetTransparent( false ); + aTmpFont.SetEscapement( 0 ); + aTmpFont.SetPhysFont(rOutDev); + tools::Long nCharWidth = aTmpFont.QuickGetTextSize( &rOutDev, + OUString(rTextPortion.GetExtraValue()), 0, 1, {} ).Width(); + sal_Int32 nChars = 2; + if( nCharWidth ) + nChars = rTextPortion.GetSize().Width() / nCharWidth; + if ( nChars < 2 ) + nChars = 2; // is compressed by DrawStretchText. + else if ( nChars == 2 ) + nChars = 3; // looks better + + OUStringBuffer aBuf(nChars); + comphelper::string::padToLength(aBuf, nChars, rTextPortion.GetExtraValue()); + OUString aText(aBuf.makeStringAndClear()); + aTmpFont.QuickDrawText( &rOutDev, aTmpPos, aText, 0, aText.getLength(), {} ); + rOutDev.DrawStretchText( aTmpPos, rTextPortion.GetSize().Width(), aText ); + + if ( bStripOnly ) + { + // create EOL and EOP bools + const bool bEndOfLine(nPortion == pLine->GetEndPortion()); + const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines); + + const Color aOverlineColor(rOutDev.GetOverlineColor()); + const Color aTextLineColor(rOutDev.GetTextLineColor()); + + // StripPortions() data callback + GetEditEnginePtr()->DrawingTab( aTmpPos, + rTextPortion.GetSize().Width(), + OUString(rTextPortion.GetExtraValue()), + aTmpFont, n, rTextPortion.GetRightToLeftLevel(), + bEndOfLine, bEndOfParagraph, + aOverlineColor, aTextLineColor); + } + } + else if ( bStripOnly ) + { + // #i108052# When stripping, a callback for _empty_ paragraphs is also needed. + // This was optimized away (by not rendering the space-only tab portion), so do + // it manually here. + const bool bEndOfLine(nPortion == pLine->GetEndPortion()); + const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines); + + const Color aOverlineColor(rOutDev.GetOverlineColor()); + const Color aTextLineColor(rOutDev.GetTextLineColor()); + + GetEditEnginePtr()->DrawingText( + aTmpPos, OUString(), 0, 0, {}, + aTmpFont, n, 0, + nullptr, + nullptr, + bEndOfLine, bEndOfParagraph, + nullptr, + aOverlineColor, + aTextLineColor); + } + } + break; + case PortionKind::LINEBREAK: break; + } + if( bParsingFields ) + nPortion--; + else + nIndex = nIndex + rTextPortion.GetLen(); + + } + } + + if ( ( nLine != nLastLine ) && !aStatus.IsOutliner() ) + { + adjustYDirectionAware(aStartPos, nSBL); + } + + // no more visible actions? + if (getYOverflowDirectionAware(aStartPos, aClipRect)) + break; + } + + if ( !aStatus.IsOutliner() ) + { + const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); + tools::Long nUL = GetYValue( rULItem.GetLower() ); + adjustYDirectionAware(aStartPos, nUL); + } + + // #108052# Safer way for #i108052# and #i118881#: If for the current ParaPortion + // EOP is not written, do it now. This will be safer than before. It has shown + // that the reason for #i108052# was fixed/removed again, so this is a try to fix + // the number of paragraphs (and counting empty ones) now independent from the + // changes in EditEngine behaviour. + if(!bEndOfParagraphWritten && !bPaintBullet && bStripOnly) + { + const Color aOverlineColor(rOutDev.GetOverlineColor()); + const Color aTextLineColor(rOutDev.GetTextLineColor()); + + GetEditEnginePtr()->DrawingText( + aTmpPos, OUString(), 0, 0, {}, + aTmpFont, n, 0, + nullptr, + nullptr, + false, true, // support for EOL/EOP TEXT comments + nullptr, + aOverlineColor, + aTextLineColor); + } + } + else + { + adjustYDirectionAware(aStartPos, nParaHeight); + } + + if ( pPDFExtOutDevData ) + pPDFExtOutDevData->EndStructureElement(); + + // no more visible actions? + if (getYOverflowDirectionAware(aStartPos, aClipRect)) + break; + } +} + +void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice ) +{ + if ( !IsUpdateLayout() || IsInUndo() ) + return; + + assert( pView && "No View - No Paint!" ); + + // Intersection of paint area and output area. + tools::Rectangle aClipRect( pView->GetOutputArea() ); + aClipRect.Intersection( rRect ); + + OutputDevice& rTarget = pTargetDevice ? *pTargetDevice : *pView->GetWindow()->GetOutDev(); + + Point aStartPos; + if ( !IsEffectivelyVertical() ) + aStartPos = pView->GetOutputArea().TopLeft(); + else + { + if( IsTopToBottom() ) + aStartPos = pView->GetOutputArea().TopRight(); + else + aStartPos = pView->GetOutputArea().BottomLeft(); + } + adjustXDirectionAware(aStartPos, -(pView->GetVisDocLeft())); + adjustYDirectionAware(aStartPos, -(pView->GetVisDocTop())); + + // If Doc-width < Output Area,Width and not wrapped fields, + // the fields usually protrude if > line. + // (Not at the top, since there the Doc-width from formatting is already + // there) + if ( !IsEffectivelyVertical() && ( pView->GetOutputArea().GetWidth() > GetPaperSize().Width() ) ) + { + tools::Long nMaxX = pView->GetOutputArea().Left() + GetPaperSize().Width(); + if ( aClipRect.Left() > nMaxX ) + return; + if ( aClipRect.Right() > nMaxX ) + aClipRect.SetRight( nMaxX ); + } + + bool bClipRegion = rTarget.IsClipRegion(); + vcl::Region aOldRegion = rTarget.GetClipRegion(); + rTarget.IntersectClipRegion( aClipRect ); + + Paint(rTarget, aClipRect, aStartPos); + + if ( bClipRegion ) + rTarget.SetClipRegion( aOldRegion ); + else + rTarget.SetClipRegion(); + + pView->DrawSelectionXOR(pView->GetEditSelection(), nullptr, &rTarget); +} + +void ImpEditEngine::InsertContent( ContentNode* pNode, sal_Int32 nPos ) +{ + DBG_ASSERT( pNode, "NULL-Pointer in InsertContent! " ); + DBG_ASSERT( IsInUndo(), "InsertContent only for Undo()!" ); + GetParaPortions().Insert(nPos, std::make_unique<ParaPortion>( pNode )); + aEditDoc.Insert(nPos, pNode); + if ( IsCallParaInsertedOrDeleted() ) + GetEditEnginePtr()->ParagraphInserted( nPos ); +} + +EditPaM ImpEditEngine::SplitContent( sal_Int32 nNode, sal_Int32 nSepPos ) +{ + ContentNode* pNode = aEditDoc.GetObject( nNode ); + DBG_ASSERT( pNode, "Invalid Node in SplitContent" ); + DBG_ASSERT( IsInUndo(), "SplitContent only for Undo()!" ); + DBG_ASSERT( nSepPos <= pNode->Len(), "Index out of range: SplitContent" ); + EditPaM aPaM( pNode, nSepPos ); + return ImpInsertParaBreak( aPaM ); +} + +EditPaM ImpEditEngine::ConnectContents( sal_Int32 nLeftNode, bool bBackward ) +{ + ContentNode* pLeftNode = aEditDoc.GetObject( nLeftNode ); + ContentNode* pRightNode = aEditDoc.GetObject( nLeftNode+1 ); + DBG_ASSERT( pLeftNode, "Invalid left node in ConnectContents "); + DBG_ASSERT( pRightNode, "Invalid right node in ConnectContents "); + return ImpConnectParagraphs( pLeftNode, pRightNode, bBackward ); +} + +bool ImpEditEngine::SetUpdateLayout( bool bUp, EditView* pCurView, bool bForceUpdate ) +{ + const bool bPrevUpdateLayout = bUpdateLayout; + const bool bChanged = (bUpdateLayout != bUp); + + // When switching from true to false, all selections were visible, + // => paint over + // the other hand, were all invisible => paint + // If !bFormatted, e.g. after SetText, then if UpdateMode=true + // formatting is not needed immediately, probably because more text is coming. + // At latest it is formatted at a Paint/CalcTextWidth. + bUpdateLayout = bUp; + if ( bUpdateLayout && ( bChanged || bForceUpdate ) ) + FormatAndLayout( pCurView ); + return bPrevUpdateLayout; +} + +void ImpEditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow ) +{ + ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + DBG_ASSERT( pPPortion, "ShowParagraph: Paragraph does not exist! "); + if ( !(pPPortion && ( pPPortion->IsVisible() != bShow )) ) + return; + + pPPortion->SetVisible( bShow ); + + if ( !bShow ) + { + // Mark as deleted, so that no selection will end or begin at + // this paragraph... + aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pPPortion->GetNode(), nParagraph )); + UpdateSelections(); + // The region below will not be invalidated if UpdateMode = sal_False! + // If anyway, then save as sal_False before SetVisible ! + } + + if ( bShow && ( pPPortion->IsInvalid() || !pPPortion->nHeight ) ) + { + if ( !GetTextRanger() ) + { + if ( pPPortion->IsInvalid() ) + { + CreateLines( nParagraph, 0 ); // 0: No TextRanger + } + else + { + CalcHeight( pPPortion ); + } + nCurTextHeight += pPPortion->GetHeight(); + } + else + { + nCurTextHeight = 0x7fffffff; + } + } + + pPPortion->SetMustRepaint( true ); + if ( IsUpdateLayout() && !IsInUndo() && !GetTextRanger() ) + { + aInvalidRect = tools::Rectangle( Point( 0, GetParaPortions().GetYOffset( pPPortion ) ), + Point( GetPaperSize().Width(), nCurTextHeight ) ); + UpdateViews( GetActiveView() ); + } +} + +EditSelection ImpEditEngine::MoveParagraphs( Range aOldPositions, sal_Int32 nNewPos, EditView* pCurView ) +{ + DBG_ASSERT( GetParaPortions().Count() != 0, "No paragraphs found: MoveParagraphs" ); + if ( GetParaPortions().Count() == 0 ) + return EditSelection(); + aOldPositions.Justify(); + + EditSelection aSel( ImpMoveParagraphs( aOldPositions, nNewPos ) ); + + if ( nNewPos >= GetParaPortions().Count() ) + nNewPos = GetParaPortions().Count() - 1; + + // Where the paragraph was inserted it has to be properly redrawn: + // Where the paragraph was removed it has to be properly redrawn: + // ( and correspondingly in between as well...) + if ( pCurView && IsUpdateLayout() ) + { + // in this case one can redraw directly without invalidating the + // Portions + sal_Int32 nFirstPortion = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos ); + sal_Int32 nLastPortion = std::max( static_cast<sal_Int32>(aOldPositions.Max()), nNewPos ); + + ParaPortion* pUpperPortion = GetParaPortions().SafeGetObject( nFirstPortion ); + ParaPortion* pLowerPortion = GetParaPortions().SafeGetObject( nLastPortion ); + if (pUpperPortion && pLowerPortion) + { + aInvalidRect = tools::Rectangle(); // make empty + aInvalidRect.SetLeft( 0 ); + aInvalidRect.SetRight(GetColumnWidth(aPaperSize)); + aInvalidRect.SetTop( GetParaPortions().GetYOffset( pUpperPortion ) ); + aInvalidRect.SetBottom( GetParaPortions().GetYOffset( pLowerPortion ) + pLowerPortion->GetHeight() ); + + UpdateViews( pCurView ); + } + } + else + { + // redraw from the upper invalid position + sal_Int32 nFirstInvPara = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos ); + InvalidateFromParagraph( nFirstInvPara ); + } + return aSel; +} + +void ImpEditEngine::InvalidateFromParagraph( sal_Int32 nFirstInvPara ) +{ + // The following paragraphs are not invalidated, since ResetHeight() + // => size change => all the following are re-issued anyway. + ParaPortion* pTmpPortion; + if ( nFirstInvPara != 0 ) + { + pTmpPortion = GetParaPortions()[nFirstInvPara-1]; + pTmpPortion->MarkInvalid( pTmpPortion->GetNode()->Len(), 0 ); + } + else + { + pTmpPortion = GetParaPortions()[0]; + pTmpPortion->MarkSelectionInvalid( 0 ); + } + pTmpPortion->ResetHeight(); +} + +IMPL_LINK_NOARG(ImpEditEngine, StatusTimerHdl, Timer *, void) +{ + CallStatusHdl(); +} + +void ImpEditEngine::CallStatusHdl() +{ + if ( aStatusHdlLink.IsSet() && bool(aStatus.GetStatusWord()) ) + { + // The Status has to be reset before the Call, + // since other Flags might be set in the handler... + EditStatus aTmpStatus( aStatus ); + aStatus.Clear(); + aStatusHdlLink.Call( aTmpStatus ); + aStatusTimer.Stop(); // If called by hand... + } +} + +ContentNode* ImpEditEngine::GetPrevVisNode( ContentNode const * pCurNode ) +{ + const ParaPortion* pPortion = FindParaPortion( pCurNode ); + DBG_ASSERT( pPortion, "GetPrevVisibleNode: No matching portion!" ); + pPortion = GetPrevVisPortion( pPortion ); + if ( pPortion ) + return pPortion->GetNode(); + return nullptr; +} + +ContentNode* ImpEditEngine::GetNextVisNode( ContentNode const * pCurNode ) +{ + const ParaPortion* pPortion = FindParaPortion( pCurNode ); + DBG_ASSERT( pPortion, "GetNextVisibleNode: No matching portion!" ); + pPortion = GetNextVisPortion( pPortion ); + if ( pPortion ) + return pPortion->GetNode(); + return nullptr; +} + +const ParaPortion* ImpEditEngine::GetPrevVisPortion( const ParaPortion* pCurPortion ) const +{ + sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion ); + DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisPortion" ); + const ParaPortion* pPortion = nPara ? GetParaPortions()[--nPara] : nullptr; + while ( pPortion && !pPortion->IsVisible() ) + pPortion = nPara ? GetParaPortions()[--nPara] : nullptr; + + return pPortion; +} + +const ParaPortion* ImpEditEngine::GetNextVisPortion( const ParaPortion* pCurPortion ) const +{ + sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion ); + DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisNode" ); + const ParaPortion* pPortion = GetParaPortions().SafeGetObject( ++nPara ); + while ( pPortion && !pPortion->IsVisible() ) + pPortion = GetParaPortions().SafeGetObject( ++nPara ); + + return pPortion; +} + +tools::Long ImpEditEngine::CalcVertLineSpacing(Point& rStartPos) const +{ + tools::Long nTotalOccupiedHeight = 0; + sal_Int32 nTotalLineCount = 0; + const ParaPortionList& rParaPortions = GetParaPortions(); + sal_Int32 nParaCount = rParaPortions.Count(); + + for (sal_Int32 i = 0; i < nParaCount; ++i) + { + if (GetVerJustification(i) != SvxCellVerJustify::Block) + // All paragraphs must have the block justification set. + return 0; + + const ParaPortion* pPortion = rParaPortions[i]; + nTotalOccupiedHeight += pPortion->GetFirstLineOffset(); + + const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL); + sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) + ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; + + const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE); + tools::Long nUL = GetYValue( rULItem.GetLower() ); + + const EditLineList& rLines = pPortion->GetLines(); + sal_Int32 nLineCount = rLines.Count(); + nTotalLineCount += nLineCount; + for (sal_Int32 j = 0; j < nLineCount; ++j) + { + const EditLine& rLine = rLines[j]; + nTotalOccupiedHeight += rLine.GetHeight(); + if (j < nLineCount-1) + nTotalOccupiedHeight += nSBL; + nTotalOccupiedHeight += nUL; + } + } + + tools::Long nTotalSpace = getHeightDirectionAware(aPaperSize); + nTotalSpace -= nTotalOccupiedHeight; + if (nTotalSpace <= 0 || nTotalLineCount <= 1) + return 0; + + // Shift the text to the right for the asian layout mode. + if (IsEffectivelyVertical()) + adjustYDirectionAware(rStartPos, -nTotalSpace); + + return nTotalSpace / (nTotalLineCount-1); +} + +EditPaM ImpEditEngine::InsertParagraph( sal_Int32 nPara ) +{ + EditPaM aPaM; + if ( nPara != 0 ) + { + ContentNode* pNode = GetEditDoc().GetObject( nPara-1 ); + if ( !pNode ) + pNode = GetEditDoc().GetObject( GetEditDoc().Count() - 1 ); + assert(pNode && "Not a single paragraph in InsertParagraph ?"); + aPaM = EditPaM( pNode, pNode->Len() ); + } + else + { + ContentNode* pNode = GetEditDoc().GetObject( 0 ); + aPaM = EditPaM( pNode, 0 ); + } + + return ImpInsertParaBreak( aPaM ); +} + +std::optional<EditSelection> ImpEditEngine::SelectParagraph( sal_Int32 nPara ) +{ + std::optional<EditSelection> pSel; + ContentNode* pNode = GetEditDoc().GetObject( nPara ); + SAL_WARN_IF( !pNode, "editeng", "Paragraph does not exist: SelectParagraph" ); + if ( pNode ) + pSel.emplace( EditPaM( pNode, 0 ), EditPaM( pNode, pNode->Len() ) ); + + return pSel; +} + +void ImpEditEngine::FormatAndLayout( EditView* pCurView, bool bCalledFromUndo ) +{ + if ( bDowning ) + return ; + + if ( IsInUndo() ) + IdleFormatAndLayout( pCurView ); + else + { + if (bCalledFromUndo) + // in order to make bullet points that have had their styles changed, redraw themselves + for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) + GetParaPortions()[nPortion]->MarkInvalid( 0, 0 ); + FormatDoc(); + UpdateViews( pCurView ); + } + + EENotify aNotify(EE_NOTIFY_PROCESSNOTIFICATIONS); + GetNotifyHdl().Call(aNotify); +} + +void ImpEditEngine::SetFlatMode( bool bFlat ) +{ + if ( bFlat != aStatus.UseCharAttribs() ) + return; + + if ( !bFlat ) + aStatus.TurnOnFlags( EEControlBits::USECHARATTRIBS ); + else + aStatus.TurnOffFlags( EEControlBits::USECHARATTRIBS ); + + aEditDoc.CreateDefFont( !bFlat ); + + FormatFullDoc(); + UpdateViews(); + if ( pActiveView ) + pActiveView->ShowCursor(); +} + +void ImpEditEngine::SetCharStretching( sal_uInt16 nX, sal_uInt16 nY ) +{ + bool bChanged; + if ( !IsEffectivelyVertical() ) + { + bChanged = nStretchX!=nX || nStretchY!=nY; + nStretchX = nX; + nStretchY = nY; + } + else + { + bChanged = nStretchX!=nY || nStretchY!=nX; + nStretchX = nY; + nStretchY = nX; + } + + if (bChanged && aStatus.DoStretch()) + { + FormatFullDoc(); + // (potentially) need everything redrawn + aInvalidRect=tools::Rectangle(0,0,1000000,1000000); + UpdateViews( GetActiveView() ); + } +} + +const SvxNumberFormat* ImpEditEngine::GetNumberFormat( const ContentNode *pNode ) const +{ + const SvxNumberFormat *pRes = nullptr; + + if (pNode) + { + // get index of paragraph + sal_Int32 nPara = GetEditDoc().GetPos( pNode ); + DBG_ASSERT( nPara < EE_PARA_NOT_FOUND, "node not found in array" ); + if (nPara < EE_PARA_NOT_FOUND) + { + // the called function may be overridden by an OutlinerEditEng + // object to provide + // access to the SvxNumberFormat of the Outliner. + // The EditEngine implementation will just return 0. + pRes = pEditEngine->GetNumberFormat( nPara ); + } + } + + return pRes; +} + +sal_Int32 ImpEditEngine::GetSpaceBeforeAndMinLabelWidth( + const ContentNode *pNode, + sal_Int32 *pnSpaceBefore, sal_Int32 *pnMinLabelWidth ) const +{ + // nSpaceBefore matches the ODF attribute text:space-before + // nMinLabelWidth matches the ODF attribute text:min-label-width + + const SvxNumberFormat *pNumFmt = GetNumberFormat( pNode ); + + // if no number format was found we have no Outliner or the numbering level + // within the Outliner is -1 which means no number format should be applied. + // Thus the default values to be returned are 0. + sal_Int32 nSpaceBefore = 0; + sal_Int32 nMinLabelWidth = 0; + + if (pNumFmt) + { + nMinLabelWidth = -pNumFmt->GetFirstLineOffset(); + nSpaceBefore = pNumFmt->GetAbsLSpace() - nMinLabelWidth; + DBG_ASSERT( nMinLabelWidth >= 0, "ImpEditEngine::GetSpaceBeforeAndMinLabelWidth: min-label-width < 0 encountered" ); + } + if (pnSpaceBefore) + *pnSpaceBefore = nSpaceBefore; + if (pnMinLabelWidth) + *pnMinLabelWidth = nMinLabelWidth; + + return nSpaceBefore + nMinLabelWidth; +} + +const SvxLRSpaceItem& ImpEditEngine::GetLRSpaceItem( ContentNode* pNode ) +{ + return pNode->GetContentAttribs().GetItem( aStatus.IsOutliner() ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE ); +} + +// select a representative text language for the digit type according to the +// text numeral setting: +LanguageType ImpEditEngine::ImplCalcDigitLang(LanguageType eCurLang) const +{ + if (utl::ConfigManager::IsFuzzing()) + return LANGUAGE_ENGLISH_US; + + // #114278# Also setting up digit language from Svt options + // (cannot reliably inherit the outdev's setting) + if( !pCTLOptions ) + pCTLOptions.reset( new SvtCTLOptions ); + + LanguageType eLang = eCurLang; + const SvtCTLOptions::TextNumerals nCTLTextNumerals = pCTLOptions->GetCTLTextNumerals(); + + if ( SvtCTLOptions::NUMERALS_HINDI == nCTLTextNumerals ) + eLang = LANGUAGE_ARABIC_SAUDI_ARABIA; + else if ( SvtCTLOptions::NUMERALS_ARABIC == nCTLTextNumerals ) + eLang = LANGUAGE_ENGLISH; + else if ( SvtCTLOptions::NUMERALS_SYSTEM == nCTLTextNumerals ) + eLang = Application::GetSettings().GetLanguageTag().getLanguageType(); + + return eLang; +} + +OUString ImpEditEngine::convertDigits(std::u16string_view rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang) +{ + OUStringBuffer aBuf(rString); + for (sal_Int32 nIdx = nStt, nEnd = nStt + nLen; nIdx < nEnd; ++nIdx) + { + sal_Unicode cChar = aBuf[nIdx]; + if (cChar >= '0' && cChar <= '9') + aBuf[nIdx] = GetLocalizedChar(cChar, eDigitLang); + } + return aBuf.makeStringAndClear(); +} + +// Either sets the digit mode at the output device +void ImpEditEngine::ImplInitDigitMode(OutputDevice& rOutDev, LanguageType eCurLang) +{ + rOutDev.SetDigitLanguage(ImplCalcDigitLang(eCurLang)); +} + +void ImpEditEngine::ImplInitLayoutMode(OutputDevice& rOutDev, sal_Int32 nPara, sal_Int32 nIndex) +{ + bool bCTL = false; + bool bR2L = false; + if ( nIndex == -1 ) + { + bCTL = HasScriptType( nPara, i18n::ScriptType::COMPLEX ); + bR2L = IsRightToLeft( nPara ); + } + else + { + ContentNode* pNode = GetEditDoc().GetObject( nPara ); + short nScriptType = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) ); + bCTL = nScriptType == i18n::ScriptType::COMPLEX; + // this change was discussed in issue 37190 + bR2L = (GetRightToLeft( nPara, nIndex + 1) % 2) != 0; + // it also works for issue 55927 + } + + vcl::text::ComplexTextLayoutFlags nLayoutMode = rOutDev.GetLayoutMode(); + + // We always use the left position for DrawText() + nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiRtl; + + if ( !bCTL && !bR2L) + { + // No Bidi checking necessary + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiStrong; + } + else + { + // Bidi checking necessary + // Don't use BIDI_STRONG, VCL must do some checks. + nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiStrong; + + if ( bR2L ) + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl|vcl::text::ComplexTextLayoutFlags::TextOriginLeft; + } + + rOutDev.SetLayoutMode( nLayoutMode ); + + // #114278# Also setting up digit language from Svt options + // (cannot reliably inherit the outdev's setting) + LanguageType eLang = Application::GetSettings().GetLanguageTag().getLanguageType(); + ImplInitDigitMode(rOutDev, eLang); +} + +Reference < i18n::XBreakIterator > const & ImpEditEngine::ImplGetBreakIterator() const +{ + if ( !xBI.is() ) + { + Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + xBI = i18n::BreakIterator::create( xContext ); + } + return xBI; +} + +Reference < i18n::XExtendedInputSequenceChecker > const & ImpEditEngine::ImplGetInputSequenceChecker() const +{ + if ( !xISC.is() ) + { + Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + xISC = i18n::InputSequenceChecker::create( xContext ); + } + return xISC; +} + +Color ImpEditEngine::GetAutoColor() const +{ + Color aColor = GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor; + + if ( GetBackgroundColor() != COL_AUTO ) + { + if ( GetBackgroundColor().IsDark() && aColor.IsDark() ) + aColor = COL_WHITE; + else if ( GetBackgroundColor().IsBright() && aColor.IsBright() ) + aColor = COL_BLACK; + } + + return aColor; +} + +bool ImpEditEngine::ImplCalcAsianCompression(ContentNode* pNode, + TextPortion* pTextPortion, sal_Int32 nStartPos, + sal_Int32* pDXArray, sal_uInt16 n100thPercentFromMax, + bool bManipulateDXArray) +{ + DBG_ASSERT( GetAsianCompressionMode() != CharCompressType::NONE, "ImplCalcAsianCompression - Why?" ); + DBG_ASSERT( pTextPortion->GetLen(), "ImplCalcAsianCompression - Empty Portion?" ); + + // Percent is 1/100 Percent... + if ( n100thPercentFromMax == 10000 ) + pTextPortion->SetExtraInfos( nullptr ); + + bool bCompressed = false; + + if ( GetI18NScriptType( EditPaM( pNode, nStartPos+1 ) ) == i18n::ScriptType::ASIAN ) + { + tools::Long nNewPortionWidth = pTextPortion->GetSize().Width(); + sal_Int32 nPortionLen = pTextPortion->GetLen(); + for ( sal_Int32 n = 0; n < nPortionLen; n++ ) + { + AsianCompressionFlags nType = GetCharTypeForCompression( pNode->GetChar( n+nStartPos ) ); + + bool bCompressPunctuation = ( nType == AsianCompressionFlags::PunctuationLeft ) || ( nType == AsianCompressionFlags::PunctuationRight ); + bool bCompressKana = ( nType == AsianCompressionFlags::Kana ) && ( GetAsianCompressionMode() == CharCompressType::PunctuationAndKana ); + + // create Extra infos only if needed... + if ( bCompressPunctuation || bCompressKana ) + { + if ( !pTextPortion->GetExtraInfos() ) + { + ExtraPortionInfo* pExtraInfos = new ExtraPortionInfo; + pTextPortion->SetExtraInfos( pExtraInfos ); + pExtraInfos->nOrgWidth = pTextPortion->GetSize().Width(); + pExtraInfos->nAsianCompressionTypes = AsianCompressionFlags::Normal; + } + pTextPortion->GetExtraInfos()->nMaxCompression100thPercent = n100thPercentFromMax; + pTextPortion->GetExtraInfos()->nAsianCompressionTypes |= nType; + + tools::Long nOldCharWidth; + if ( (n+1) < nPortionLen ) + { + nOldCharWidth = pDXArray[n]; + } + else + { + if ( bManipulateDXArray ) + nOldCharWidth = nNewPortionWidth - pTextPortion->GetExtraInfos()->nPortionOffsetX; + else + nOldCharWidth = pTextPortion->GetExtraInfos()->nOrgWidth; + } + nOldCharWidth -= ( n ? pDXArray[n-1] : 0 ); + + tools::Long nCompress = 0; + + if ( bCompressPunctuation ) + { + nCompress = nOldCharWidth / 2; + } + else // Kana + { + nCompress = nOldCharWidth / 10; + } + + if ( n100thPercentFromMax != 10000 ) + { + nCompress *= n100thPercentFromMax; + nCompress /= 10000; + } + + if ( nCompress ) + { + bCompressed = true; + nNewPortionWidth -= nCompress; + pTextPortion->GetExtraInfos()->bCompressed = true; + + + // Special handling for rightpunctuation: For the 'compression' we must + // start the output before the normal char position... + if ( bManipulateDXArray && ( pTextPortion->GetLen() > 1 ) ) + { + if ( !pTextPortion->GetExtraInfos()->pOrgDXArray ) + pTextPortion->GetExtraInfos()->SaveOrgDXArray( pDXArray, pTextPortion->GetLen()-1 ); + + if ( nType == AsianCompressionFlags::PunctuationRight ) + { + // If it's the first char, I must handle it in Paint()... + if ( n ) + { + // -1: No entry for the last character + for ( sal_Int32 i = n-1; i < (nPortionLen-1); i++ ) + pDXArray[i] -= nCompress; + } + else + { + pTextPortion->GetExtraInfos()->bFirstCharIsRightPunktuation = true; + pTextPortion->GetExtraInfos()->nPortionOffsetX = -nCompress; + } + } + else + { + // -1: No entry for the last character + for ( sal_Int32 i = n; i < (nPortionLen-1); i++ ) + pDXArray[i] -= nCompress; + } + } + } + } + } + + if ( bCompressed && ( n100thPercentFromMax == 10000 ) ) + pTextPortion->GetExtraInfos()->nWidthFullCompression = nNewPortionWidth; + + pTextPortion->GetSize().setWidth( nNewPortionWidth ); + + if ( pTextPortion->GetExtraInfos() && ( n100thPercentFromMax != 10000 ) ) + { + // Maybe rounding errors in nNewPortionWidth, assure that width not bigger than expected + tools::Long nShrink = pTextPortion->GetExtraInfos()->nOrgWidth - pTextPortion->GetExtraInfos()->nWidthFullCompression; + nShrink *= n100thPercentFromMax; + nShrink /= 10000; + tools::Long nNewWidth = pTextPortion->GetExtraInfos()->nOrgWidth - nShrink; + if ( nNewWidth < pTextPortion->GetSize().Width() ) + pTextPortion->GetSize().setWidth( nNewWidth ); + } + } + return bCompressed; +} + + +void ImpEditEngine::ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* pParaPortion, tools::Long nRemainingWidth ) +{ + bool bFoundCompressedPortion = false; + tools::Long nCompressed = 0; + std::vector<TextPortion*> aCompressedPortions; + + sal_Int32 nPortion = pLine->GetEndPortion(); + TextPortion* pTP = &pParaPortion->GetTextPortions()[ nPortion ]; + while ( pTP && ( pTP->GetKind() == PortionKind::TEXT ) ) + { + if ( pTP->GetExtraInfos() && pTP->GetExtraInfos()->bCompressed ) + { + bFoundCompressedPortion = true; + nCompressed += pTP->GetExtraInfos()->nOrgWidth - pTP->GetSize().Width(); + aCompressedPortions.push_back(pTP); + } + pTP = ( nPortion > pLine->GetStartPortion() ) ? &pParaPortion->GetTextPortions()[ --nPortion ] : nullptr; + } + + if ( !bFoundCompressedPortion ) + return; + + tools::Long nCompressPercent = 0; + if ( nCompressed > nRemainingWidth ) + { + nCompressPercent = nCompressed - nRemainingWidth; + DBG_ASSERT( nCompressPercent < 200000, "ImplExpandCompressedPortions - Overflow!" ); + nCompressPercent *= 10000; + nCompressPercent /= nCompressed; + } + + for (TextPortion* pTP2 : aCompressedPortions) + { + pTP = pTP2; + pTP->GetExtraInfos()->bCompressed = false; + pTP->GetSize().setWidth( pTP->GetExtraInfos()->nOrgWidth ); + if ( nCompressPercent ) + { + sal_Int32 nTxtPortion = pParaPortion->GetTextPortions().GetPos( pTP ); + sal_Int32 nTxtPortionStart = pParaPortion->GetTextPortions().GetStartPos( nTxtPortion ); + DBG_ASSERT( nTxtPortionStart >= pLine->GetStart(), "Portion doesn't belong to the line!!!" ); + sal_Int32* pDXArray = pLine->GetCharPosArray().data() + (nTxtPortionStart - pLine->GetStart()); + if ( pTP->GetExtraInfos()->pOrgDXArray ) + memcpy( pDXArray, pTP->GetExtraInfos()->pOrgDXArray.get(), (pTP->GetLen()-1)*sizeof(sal_Int32) ); + ImplCalcAsianCompression( pParaPortion->GetNode(), pTP, nTxtPortionStart, pDXArray, static_cast<sal_uInt16>(nCompressPercent), true ); + } + } +} + +void ImpEditEngine::ImplUpdateOverflowingParaNum(tools::Long nPaperHeight) +{ + tools::Long nY = 0; + tools::Long nPH; + + for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) { + ParaPortion* pPara = GetParaPortions()[nPara]; + nPH = pPara->GetHeight(); + nY += nPH; + if ( nY > nPaperHeight /*nCurTextHeight*/ ) // found first paragraph overflowing + { + mnOverflowingPara = nPara; + SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing #Para#: " << nPara); + ImplUpdateOverflowingLineNum( nPaperHeight, nPara, nY-nPH); + return; + } + } +} + +void ImpEditEngine::ImplUpdateOverflowingLineNum(tools::Long nPaperHeight, + sal_uInt32 nOverflowingPara, + tools::Long nHeightBeforeOverflowingPara) +{ + tools::Long nY = nHeightBeforeOverflowingPara; + tools::Long nLH; + + ParaPortion *pPara = GetParaPortions()[nOverflowingPara]; + + // Like UpdateOverflowingParaNum but for each line in the first + // overflowing paragraph. + for ( sal_Int32 nLine = 0; nLine < pPara->GetLines().Count(); nLine++ ) { + // XXX: We must use a reference here because the copy constructor resets the height + EditLine &aLine = pPara->GetLines()[nLine]; + nLH = aLine.GetHeight(); + nY += nLH; + + // Debugging output + if (nLine == 0) { + SAL_INFO("editeng.chaining", "[CHAINING] First line has height " << nLH); + } + + if ( nY > nPaperHeight ) // found first line overflowing + { + mnOverflowingLine = nLine; + SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing -Line- to: " << nLine); + return; + } + } + + assert(false && "You should never get here"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit4.cxx b/editeng/source/editeng/impedit4.cxx new file mode 100644 index 000000000..499b69fbd --- /dev/null +++ b/editeng/source/editeng/impedit4.cxx @@ -0,0 +1,3103 @@ +/* -*- 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 <svl/srchitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/tstpitem.hxx> + +#include "eertfpar.hxx" +#include <editeng/editeng.hxx> +#include "impedit.hxx" +#include <editeng/editview.hxx> +#include "eehtml.hxx" +#include "editobj2.hxx" +#include <i18nlangtag/lang.h> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> + +#include <editxml.hxx> + +#include <editeng/autokernitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include "textconv.hxx" +#include <rtl/tencinfo.h> +#include <svtools/rtfout.hxx> +#include <tools/stream.hxx> +#include <edtspell.hxx> +#include <editeng/unolingu.hxx> +#include <com/sun/star/linguistic2/XThesaurus.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <unotools/transliterationwrapper.hxx> +#include <unotools/textsearch.hxx> +#include <comphelper/processfactory.hxx> +#include <vcl/help.hxx> +#include <vcl/metric.hxx> +#include <svtools/rtfkeywd.hxx> +#include <editeng/edtdlg.hxx> + +#include <cstddef> +#include <memory> +#include <unordered_map> +#include <vector> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; + + +EditPaM ImpEditEngine::Read(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, const EditSelection& rSel, SvKeyValueIterator* pHTTPHeaderAttrs) +{ + bool _bUpdate = SetUpdateLayout( false ); + EditPaM aPaM; + if ( eFormat == EETextFormat::Text ) + aPaM = ReadText( rInput, rSel ); + else if ( eFormat == EETextFormat::Rtf ) + aPaM = ReadRTF( rInput, rSel ); + else if ( eFormat == EETextFormat::Xml ) + aPaM = ReadXML( rInput, rSel ); + else if ( eFormat == EETextFormat::Html ) + aPaM = ReadHTML( rInput, rBaseURL, rSel, pHTTPHeaderAttrs ); + else + { + OSL_FAIL( "Read: Unknown Format" ); + } + + FormatFullDoc(); // perhaps a simple format is enough? + SetUpdateLayout( _bUpdate ); + + return aPaM; +} + +EditPaM ImpEditEngine::ReadText( SvStream& rInput, EditSelection aSel ) +{ + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( aSel ); + EditPaM aPaM = aSel.Max(); + + OUString aTmpStr; + bool bDone = rInput.ReadByteStringLine( aTmpStr, rInput.GetStreamCharSet() ); + while ( bDone ) + { + aPaM = ImpInsertText( EditSelection( aPaM, aPaM ), aTmpStr ); + aPaM = ImpInsertParaBreak( aPaM ); + bDone = rInput.ReadByteStringLine( aTmpStr, rInput.GetStreamCharSet() ); + } + return aPaM; +} + +EditPaM ImpEditEngine::ReadXML( SvStream& rInput, EditSelection aSel ) +{ + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( aSel ); + + ESelection aESel = CreateESel( aSel ); + + return ::SvxReadXML( *GetEditEnginePtr(), rInput, aESel ); +} + +EditPaM ImpEditEngine::ReadRTF( SvStream& rInput, EditSelection aSel ) +{ + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( aSel ); + + // The SvRTF parser expects the Which-mapping passed on in the pool, not + // dependent on a secondary. + SfxItemPool* pPool = &aEditDoc.GetItemPool(); + while (pPool->GetSecondaryPool() && pPool->GetName() != "EditEngineItemPool") + { + pPool = pPool->GetSecondaryPool(); + } + + DBG_ASSERT(pPool && pPool->GetName() == "EditEngineItemPool", + "ReadRTF: no EditEnginePool!"); + + EditRTFParserRef xPrsr = new EditRTFParser(rInput, aSel, *pPool, pEditEngine); + SvParserState eState = xPrsr->CallParser(); + if ( ( eState != SvParserState::Accepted ) && ( !rInput.GetError() ) ) + { + rInput.SetError( EE_READWRITE_WRONGFORMAT ); + return aSel.Min(); + } + return xPrsr->GetCurPaM(); +} + +EditPaM ImpEditEngine::ReadHTML( SvStream& rInput, const OUString& rBaseURL, EditSelection aSel, SvKeyValueIterator* pHTTPHeaderAttrs ) +{ + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( aSel ); + + EditHTMLParserRef xPrsr = new EditHTMLParser( rInput, rBaseURL, pHTTPHeaderAttrs ); + SvParserState eState = xPrsr->CallParser(pEditEngine, aSel.Max()); + if ( ( eState != SvParserState::Accepted ) && ( !rInput.GetError() ) ) + { + rInput.SetError( EE_READWRITE_WRONGFORMAT ); + return aSel.Min(); + } + return xPrsr->GetCurSelection().Max(); +} + +void ImpEditEngine::Write(SvStream& rOutput, EETextFormat eFormat, const EditSelection& rSel) +{ + if ( !rOutput.IsWritable() ) + rOutput.SetError( SVSTREAM_WRITE_ERROR ); + + if ( rOutput.GetError() ) + return; + + if ( eFormat == EETextFormat::Text ) + WriteText( rOutput, rSel ); + else if ( eFormat == EETextFormat::Rtf ) + WriteRTF( rOutput, rSel ); + else if ( eFormat == EETextFormat::Xml ) + WriteXML( rOutput, rSel ); + else if ( eFormat == EETextFormat::Html ) + ; + else + { + OSL_FAIL( "Write: Unknown Format" ); + } +} + +ErrCode ImpEditEngine::WriteText( SvStream& rOutput, EditSelection aSel ) +{ + sal_Int32 nStartNode, nEndNode; + bool bRange = aSel.HasRange(); + if ( bRange ) + { + aSel.Adjust( aEditDoc ); + nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() ); + nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() ); + } + else + { + nStartNode = 0; + nEndNode = aEditDoc.Count()-1; + } + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = aEditDoc.GetObject( nNode ); + DBG_ASSERT( pNode, "Node not found: Search&Replace" ); + + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = pNode->Len(); + if ( bRange ) + { + if ( nNode == nStartNode ) + nStartPos = aSel.Min().GetIndex(); + if ( nNode == nEndNode ) // can also be == nStart! + nEndPos = aSel.Max().GetIndex(); + } + OUString aTmpStr = EditDoc::GetParaAsString( pNode, nStartPos, nEndPos ); + rOutput.WriteByteStringLine( aTmpStr, rOutput.GetStreamCharSet() ); + } + + return rOutput.GetError(); +} + +bool ImpEditEngine::WriteItemListAsRTF( ItemList& rLst, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos, + std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList ) +{ + const SfxPoolItem* pAttrItem = rLst.First(); + while ( pAttrItem ) + { + WriteItemAsRTF( *pAttrItem, rOutput, nPara, nPos,rFontTable, rColorList ); + pAttrItem = rLst.Next(); + } + return rLst.Count() != 0; +} + +static void lcl_FindValidAttribs( ItemList& rLst, ContentNode* pNode, sal_Int32 nIndex, sal_uInt16 nScriptType ) +{ + std::size_t nAttr = 0; + EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + while ( pAttr && ( pAttr->GetStart() <= nIndex ) ) + { + // Start is checked in while ... + if ( pAttr->GetEnd() > nIndex ) + { + if ( IsScriptItemValid( pAttr->GetItem()->Which(), nScriptType ) ) + rLst.Insert( pAttr->GetItem() ); + } + nAttr++; + pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + } +} + +void ImpEditEngine::WriteXML(SvStream& rOutput, const EditSelection& rSel) +{ + ESelection aESel = CreateESel(rSel); + + SvxWriteXML( *GetEditEnginePtr(), rOutput, aESel ); +} + +ErrCode ImpEditEngine::WriteRTF( SvStream& rOutput, EditSelection aSel ) +{ + assert( IsUpdateLayout() && "WriteRTF for UpdateMode = sal_False!" ); + CheckIdleFormatter(); + if ( !IsFormatted() ) + FormatDoc(); + + sal_Int32 nStartNode, nEndNode; + aSel.Adjust( aEditDoc ); + + nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() ); + nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() ); + + // RTF header ... + rOutput.WriteChar( '{' ) ; + + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_RTF ); + + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ANSI ); + rtl_TextEncoding eDestEnc = RTL_TEXTENCODING_MS_1252; + + // Generate and write out Font table ... + std::vector<std::unique_ptr<SvxFontItem>> aFontTable; + // default font must be up front, so DEF font in RTF + aFontTable.emplace_back( new SvxFontItem( aEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO ) ) ); + aFontTable.emplace_back( new SvxFontItem( aEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO_CJK ) ) ); + aFontTable.emplace_back( new SvxFontItem( aEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO_CTL ) ) ); + for ( sal_uInt16 nScriptType = 0; nScriptType < 3; nScriptType++ ) + { + sal_uInt16 nWhich = EE_CHAR_FONTINFO; + if ( nScriptType == 1 ) + nWhich = EE_CHAR_FONTINFO_CJK; + else if ( nScriptType == 2 ) + nWhich = EE_CHAR_FONTINFO_CTL; + + for (const SfxPoolItem* pItem : aEditDoc.GetItemPool().GetItemSurrogates(nWhich)) + { + SvxFontItem const*const pFontItem = static_cast<const SvxFontItem*>(pItem); + bool bAlreadyExist = false; + size_t nTestMax = nScriptType ? aFontTable.size() : 1; + for ( size_t nTest = 0; !bAlreadyExist && ( nTest < nTestMax ); nTest++ ) + { + bAlreadyExist = *aFontTable[ nTest ] == *pFontItem; + } + + if ( !bAlreadyExist ) + aFontTable.emplace_back( new SvxFontItem( *pFontItem ) ); + } + } + + rOutput << endl; + rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FONTTBL ); + for ( std::vector<SvxFontItem*>::size_type j = 0; j < aFontTable.size(); j++ ) + { + SvxFontItem* pFontItem = aFontTable[ j ].get(); + rOutput.WriteChar( '{' ); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_F ); + rOutput.WriteUInt32AsString( j ); + switch ( pFontItem->GetFamily() ) + { + case FAMILY_DONTKNOW: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FNIL ); + break; + case FAMILY_DECORATIVE: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FDECOR ); + break; + case FAMILY_MODERN: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FMODERN ); + break; + case FAMILY_ROMAN: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FROMAN ); + break; + case FAMILY_SCRIPT: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FSCRIPT ); + break; + case FAMILY_SWISS: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FSWISS ); + break; + default: + break; + } + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FPRQ ); + sal_uInt16 nVal = 0; + switch( pFontItem->GetPitch() ) + { + case PITCH_FIXED: nVal = 1; break; + case PITCH_VARIABLE: nVal = 2; break; + default: + break; + } + rOutput.WriteUInt32AsString( nVal ); + + rtl_TextEncoding eChrSet = pFontItem->GetCharSet(); + // tdf#47679 OpenSymbol is not encoded in Symbol Encoding + // and anyway we always attempt to write as eDestEnc + // of RTL_TEXTENCODING_MS_1252 and pay no attention + // on export what encoding we claim to use for these + // fonts. + if (IsStarSymbol(pFontItem->GetFamilyName())) + eChrSet = RTL_TEXTENCODING_UTF8; + DBG_ASSERT( eChrSet != 9, "SystemCharSet?!" ); + if( RTL_TEXTENCODING_DONTKNOW == eChrSet ) + eChrSet = osl_getThreadTextEncoding(); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FCHARSET ); + rOutput.WriteUInt32AsString( rtl_getBestWindowsCharsetFromTextEncoding( eChrSet ) ); + + rOutput.WriteChar( ' ' ); + RTFOutFuncs::Out_String( rOutput, pFontItem->GetFamilyName(), eDestEnc ); + rOutput.WriteCharPtr( ";}" ); + } + rOutput.WriteChar( '}' ); + rOutput << endl; + + // Write out ColorList ... + SvxColorList aColorList; + // COL_AUTO should be the default color, always put it first + aColorList.emplace_back(COL_AUTO); + SvxColorItem const& rDefault(aEditDoc.GetItemPool().GetDefaultItem(EE_CHAR_COLOR)); + if (rDefault.GetValue() != COL_AUTO) // is the default always AUTO? + { + aColorList.push_back(rDefault.GetValue()); + } + for (const SfxPoolItem* pItem : aEditDoc.GetItemPool().GetItemSurrogates(EE_CHAR_COLOR)) + { + auto pColorItem(dynamic_cast<SvxColorItem const*>(pItem)); + if (pColorItem && pColorItem->GetValue() != COL_AUTO) // may be null! + { + aColorList.push_back(pColorItem->GetValue()); + } + } + + rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_COLORTBL ); + for ( SvxColorList::size_type j = 0; j < aColorList.size(); j++ ) + { + Color const color = aColorList[j]; + if (color != COL_AUTO) // auto is represented by "empty" element + { + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_RED ); + rOutput.WriteUInt32AsString( color.GetRed() ); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_GREEN ); + rOutput.WriteUInt32AsString( color.GetGreen() ); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_BLUE ); + rOutput.WriteUInt32AsString( color.GetBlue() ); + } + rOutput.WriteChar( ';' ); + } + rOutput.WriteChar( '}' ); + rOutput << endl; + + std::unordered_map<SfxStyleSheetBase*, sal_uInt32> aStyleSheetToIdMap; + // StyleSheets... + if ( GetStyleSheetPool() ) + { + std::shared_ptr<SfxStyleSheetIterator> aSSSIterator = std::make_shared<SfxStyleSheetIterator>(GetStyleSheetPool(), + SfxStyleFamily::All); + // fill aStyleSheetToIdMap + sal_uInt32 nId = 1; + for ( SfxStyleSheetBase* pStyle = aSSSIterator->First(); pStyle; + pStyle = aSSSIterator->Next() ) + { + aStyleSheetToIdMap[pStyle] = nId; + nId++; + } + + if ( aSSSIterator->Count() ) + { + + sal_uInt32 nStyle = 0; + rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_STYLESHEET ); + + for ( SfxStyleSheetBase* pStyle = aSSSIterator->First(); pStyle; + pStyle = aSSSIterator->Next() ) + { + + rOutput << endl; + rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_S ); + sal_uInt32 nNumber = nStyle + 1; + rOutput.WriteUInt32AsString( nNumber ); + + // Attribute, also from Parent! + for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ ) + { + if ( pStyle->GetItemSet().GetItemState( nParAttr ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = pStyle->GetItemSet().Get( nParAttr ); + WriteItemAsRTF( rItem, rOutput, 0, 0, aFontTable, aColorList ); + } + } + + // Parent ... (only if necessary) + if ( !pStyle->GetParent().isEmpty() && ( pStyle->GetParent() != pStyle->GetName() ) ) + { + SfxStyleSheet* pParent = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pStyle->GetParent(), pStyle->GetFamily() )); + DBG_ASSERT( pParent, "Parent not found!" ); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SBASEDON ); + nNumber = aStyleSheetToIdMap.find(pParent)->second; + rOutput.WriteUInt32AsString( nNumber ); + } + + // Next Style... (more) + // we assume that we have only SfxStyleSheet in the pool + SfxStyleSheet* pNext = static_cast<SfxStyleSheet*>(pStyle); + if ( !pStyle->GetFollow().isEmpty() && ( pStyle->GetFollow() != pStyle->GetName() ) ) + pNext = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pStyle->GetFollow(), pStyle->GetFamily() )); + + DBG_ASSERT( pNext, "Next not found!" ); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SNEXT ); + nNumber = aStyleSheetToIdMap.find(pNext)->second; + rOutput.WriteUInt32AsString( nNumber ); + + // Name of the template... + rOutput.WriteCharPtr( " " ); + RTFOutFuncs::Out_String( rOutput, pStyle->GetName(), eDestEnc ); + rOutput.WriteCharPtr( ";}" ); + nStyle++; + } + rOutput.WriteChar( '}' ); + rOutput << endl; + } + } + + // Write the pool defaults in advance ... + rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_IGNORE ).WriteCharPtr( "\\EditEnginePoolDefaults" ); + for ( sal_uInt16 nPoolDefItem = EE_PARA_START; nPoolDefItem <= EE_CHAR_END; nPoolDefItem++) + { + const SfxPoolItem& rItem = aEditDoc.GetItemPool().GetDefaultItem( nPoolDefItem ); + WriteItemAsRTF( rItem, rOutput, 0, 0, aFontTable, aColorList ); + } + rOutput.WriteChar( '}' ) << endl; + + // DefTab: + MapMode aTwpMode( MapUnit::MapTwip ); + sal_uInt16 nDefTabTwps = static_cast<sal_uInt16>(GetRefDevice()->LogicToLogic( + Point( aEditDoc.GetDefTab(), 0 ), + &GetRefMapMode(), &aTwpMode ).X()); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_DEFTAB ); + rOutput.WriteUInt32AsString( nDefTabTwps ); + rOutput << endl; + + // iterate over the paragraphs ... + rOutput.WriteChar( '{' ) << endl; + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = aEditDoc.GetObject( nNode ); + DBG_ASSERT( pNode, "Node not found: Search&Replace" ); + + // The paragraph attributes in advance ... + bool bAttr = false; + + // Template? + if ( pNode->GetStyleSheet() ) + { + // Number of template + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_S ); + sal_uInt32 nNumber = aStyleSheetToIdMap.find(pNode->GetStyleSheet())->second; + rOutput.WriteUInt32AsString( nNumber ); + + // All Attribute + // Attribute, also from Parent! + for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ ) + { + if ( pNode->GetStyleSheet()->GetItemSet().GetItemState( nParAttr ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = pNode->GetStyleSheet()->GetItemSet().Get( nParAttr ); + WriteItemAsRTF( rItem, rOutput, nNode, 0, aFontTable, aColorList ); + bAttr = true; + } + } + } + + for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ ) + { + // Now where stylesheet processing, only hard paragraph attributes! + if ( pNode->GetContentAttribs().GetItems().GetItemState( nParAttr ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItems().Get( nParAttr ); + WriteItemAsRTF( rItem, rOutput, nNode, 0, aFontTable, aColorList ); + bAttr = true; + } + } + if ( bAttr ) + rOutput.WriteChar( ' ' ); // Separator + + ItemList aAttribItems; + ParaPortion* pParaPortion = FindParaPortion( pNode ); + DBG_ASSERT( pParaPortion, "Portion not found: WriteRTF" ); + + sal_Int32 nIndex = 0; + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = pNode->Len(); + sal_Int32 nStartPortion = 0; + sal_Int32 nEndPortion = pParaPortion->GetTextPortions().Count() - 1; + bool bFinishPortion = false; + sal_Int32 nPortionStart; + + if ( nNode == nStartNode ) + { + nStartPos = aSel.Min().GetIndex(); + nStartPortion = pParaPortion->GetTextPortions().FindPortion( nStartPos, nPortionStart ); + if ( nStartPos != 0 ) + { + aAttribItems.Clear(); + lcl_FindValidAttribs( aAttribItems, pNode, nStartPos, GetI18NScriptType( EditPaM( pNode, 0 ) ) ); + if ( aAttribItems.Count() ) + { + // These attributes may not apply to the entire paragraph: + rOutput.WriteChar( '{' ); + WriteItemListAsRTF( aAttribItems, rOutput, nNode, nStartPos, aFontTable, aColorList ); + bFinishPortion = true; + } + aAttribItems.Clear(); + } + } + if ( nNode == nEndNode ) // can also be == nStart! + { + nEndPos = aSel.Max().GetIndex(); + nEndPortion = pParaPortion->GetTextPortions().FindPortion( nEndPos, nPortionStart ); + } + + const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature(nIndex); + // start at 0, so the index is right ... + for ( sal_Int32 n = 0; n <= nEndPortion; n++ ) + { + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n]; + if ( n < nStartPortion ) + { + nIndex = nIndex + rTextPortion.GetLen(); + continue; + } + + if ( pNextFeature && ( pNextFeature->GetStart() == nIndex ) && ( pNextFeature->GetItem()->Which() != EE_FEATURE_FIELD ) ) + { + WriteItemAsRTF( *pNextFeature->GetItem(), rOutput, nNode, nIndex, aFontTable, aColorList ); + pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1 ); + } + else + { + aAttribItems.Clear(); + sal_uInt16 nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) ); + SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N); + if ( !n || IsScriptChange( EditPaM( pNode, nIndex ) ) ) + { + SfxItemSet aAttribs = GetAttribs( nNode, nIndex+1, nIndex+1 ); + aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) ) ); + aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) ); + aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ) ) ); + aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ) ) ); + aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ) ) ); + } + // Insert hard attribs AFTER CJK attribs... + lcl_FindValidAttribs( aAttribItems, pNode, nIndex, nScriptTypeI18N ); + + rOutput.WriteChar( '{' ); + if ( WriteItemListAsRTF( aAttribItems, rOutput, nNode, nIndex, aFontTable, aColorList ) ) + rOutput.WriteChar( ' ' ); + + sal_Int32 nS = nIndex; + sal_Int32 nE = nIndex + rTextPortion.GetLen(); + if ( n == nStartPortion ) + nS = nStartPos; + if ( n == nEndPortion ) + nE = nEndPos; + + OUString aRTFStr = EditDoc::GetParaAsString( pNode, nS, nE); + RTFOutFuncs::Out_String( rOutput, aRTFStr, eDestEnc ); + rOutput.WriteChar( '}' ); + } + if ( bFinishPortion ) + { + rOutput.WriteChar( '}' ); + bFinishPortion = false; + } + + nIndex = nIndex + rTextPortion.GetLen(); + } + + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PAR ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PARD ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PLAIN ); + rOutput << endl; + } + // RTF-trailer ... + rOutput.WriteCharPtr( "}}" ); // 1xparentheses paragraphs, 1xparentheses RTF document + + aFontTable.clear(); + + return rOutput.GetError(); +} + + +void ImpEditEngine::WriteItemAsRTF( const SfxPoolItem& rItem, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos, + std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList ) +{ + sal_uInt16 nWhich = rItem.Which(); + switch ( nWhich ) + { + case EE_PARA_WRITINGDIR: + { + const SvxFrameDirectionItem& rWritingMode = static_cast<const SvxFrameDirectionItem&>(rItem); + if ( rWritingMode.GetValue() == SvxFrameDirection::Horizontal_RL_TB ) + rOutput.WriteCharPtr( "\\rtlpar" ); + else + rOutput.WriteCharPtr( "\\ltrpar" ); + } + break; + case EE_PARA_OUTLLEVEL: + { + sal_Int32 nLevel = static_cast<const SfxInt16Item&>(rItem).GetValue(); + if( nLevel >= 0 ) + { + rOutput.WriteCharPtr( "\\level" ); + rOutput.WriteInt32AsString( nLevel ); + } + } + break; + case EE_PARA_OUTLLRSPACE: + case EE_PARA_LRSPACE: + { + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FI ); + sal_Int32 nTxtFirst = static_cast<const SvxLRSpaceItem&>(rItem).GetTextFirstLineOffset(); + nTxtFirst = LogicToTwips( nTxtFirst ); + rOutput.WriteInt32AsString( nTxtFirst ); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_LI ); + sal_uInt32 nTxtLeft = static_cast< sal_uInt32 >(static_cast<const SvxLRSpaceItem&>(rItem).GetTextLeft()); + nTxtLeft = static_cast<sal_uInt32>(LogicToTwips( nTxtLeft )); + rOutput.WriteInt32AsString( nTxtLeft ); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_RI ); + sal_uInt32 nTxtRight = static_cast<const SvxLRSpaceItem&>(rItem).GetRight(); + nTxtRight = LogicToTwips( nTxtRight); + rOutput.WriteUInt32AsString( nTxtRight ); + } + break; + case EE_PARA_ULSPACE: + { + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SB ); + sal_uInt32 nUpper = static_cast<const SvxULSpaceItem&>(rItem).GetUpper(); + nUpper = static_cast<sal_uInt32>(LogicToTwips( nUpper )); + rOutput.WriteUInt32AsString( nUpper ); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SA ); + sal_uInt32 nLower = static_cast<const SvxULSpaceItem&>(rItem).GetLower(); + nLower = LogicToTwips( nLower ); + rOutput.WriteUInt32AsString( nLower ); + } + break; + case EE_PARA_SBL: + { + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SL ); + sal_Int32 nVal = static_cast<const SvxLineSpacingItem&>(rItem).GetLineHeight(); + char cMult = '0'; + if ( static_cast<const SvxLineSpacingItem&>(rItem).GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) + { + // From where do I get the value now? + // The SwRTF parser is based on a 240 Font! + nVal = static_cast<const SvxLineSpacingItem&>(rItem).GetPropLineSpace(); + nVal *= 240; + nVal /= 100; + cMult = '1'; + } + rOutput.WriteInt32AsString( nVal ); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SLMULT ).WriteChar( cMult ); + } + break; + case EE_PARA_JUST: + { + SvxAdjust eJustification = static_cast<const SvxAdjustItem&>(rItem).GetAdjust(); + switch ( eJustification ) + { + case SvxAdjust::Center: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_QC ); + break; + case SvxAdjust::Right: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_QR ); + break; + default: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_QL ); + break; + } + } + break; + case EE_PARA_TABS: + { + const SvxTabStopItem& rTabs = static_cast<const SvxTabStopItem&>(rItem); + for ( sal_uInt16 i = 0; i < rTabs.Count(); i++ ) + { + const SvxTabStop& rTab = rTabs[i]; + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_TX ); + rOutput.WriteInt32AsString( LogicToTwips( rTab.GetTabPos() ) ); + } + } + break; + case EE_CHAR_COLOR: + { + SvxColorList::const_iterator const iter = std::find( + rColorList.begin(), rColorList.end(), + static_cast<SvxColorItem const&>(rItem).GetValue()); + assert(iter != rColorList.end()); + sal_uInt32 const n = iter - rColorList.begin(); + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_CF ); + rOutput.WriteUInt32AsString( n ); + } + break; + case EE_CHAR_FONTINFO: + case EE_CHAR_FONTINFO_CJK: + case EE_CHAR_FONTINFO_CTL: + { + sal_uInt32 n = 0; + for (size_t i = 0; i < rFontTable.size(); ++i) + { + if (*rFontTable[i] == rItem) + { + n = i; + break; + } + } + + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_F ); + rOutput.WriteUInt32AsString( n ); + } + break; + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FS ); + sal_Int32 nHeight = static_cast<const SvxFontHeightItem&>(rItem).GetHeight(); + nHeight = LogicToTwips( nHeight ); + // Twips => HalfPoints + nHeight /= 10; + rOutput.WriteInt32AsString( nHeight ); + } + break; + case EE_CHAR_WEIGHT: + case EE_CHAR_WEIGHT_CJK: + case EE_CHAR_WEIGHT_CTL: + { + FontWeight e = static_cast<const SvxWeightItem&>(rItem).GetWeight(); + switch ( e ) + { + case WEIGHT_BOLD: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_B ); break; + default: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_B ).WriteChar( '0' ); break; + } + } + break; + case EE_CHAR_UNDERLINE: + { + // Must underlined if in WordLineMode, but the information is + // missing here + FontLineStyle e = static_cast<const SvxUnderlineItem&>(rItem).GetLineStyle(); + switch ( e ) + { + case LINESTYLE_NONE: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ULNONE ); break; + case LINESTYLE_SINGLE: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_UL ); break; + case LINESTYLE_DOUBLE: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ULDB ); break; + case LINESTYLE_DOTTED: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ULD ); break; + default: + break; + } + } + break; + case EE_CHAR_OVERLINE: + { + FontLineStyle e = static_cast<const SvxOverlineItem&>(rItem).GetLineStyle(); + switch ( e ) + { + case LINESTYLE_NONE: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OLNONE ); break; + case LINESTYLE_SINGLE: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OL ); break; + case LINESTYLE_DOUBLE: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OLDB ); break; + case LINESTYLE_DOTTED: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OLD ); break; + default: + break; + } + } + break; + case EE_CHAR_STRIKEOUT: + { + FontStrikeout e = static_cast<const SvxCrossedOutItem&>(rItem).GetStrikeout(); + switch ( e ) + { + case STRIKEOUT_SINGLE: + case STRIKEOUT_DOUBLE: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_STRIKE ); break; + case STRIKEOUT_NONE: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_STRIKE ).WriteChar( '0' ); break; + default: + break; + } + } + break; + case EE_CHAR_ITALIC: + case EE_CHAR_ITALIC_CJK: + case EE_CHAR_ITALIC_CTL: + { + FontItalic e = static_cast<const SvxPostureItem&>(rItem).GetPosture(); + switch ( e ) + { + case ITALIC_OBLIQUE: + case ITALIC_NORMAL: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_I ); break; + case ITALIC_NONE: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_I ).WriteChar( '0' ); break; + default: + break; + } + } + break; + case EE_CHAR_OUTLINE: + { + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OUTL ); + if ( !static_cast<const SvxContourItem&>(rItem).GetValue() ) + rOutput.WriteChar( '0' ); + } + break; + case EE_CHAR_RELIEF: + { + FontRelief nRelief = static_cast<const SvxCharReliefItem&>(rItem).GetValue(); + if ( nRelief == FontRelief::Embossed ) + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_EMBO ); + if ( nRelief == FontRelief::Engraved ) + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_IMPR ); + } + break; + case EE_CHAR_EMPHASISMARK: + { + FontEmphasisMark nMark = static_cast<const SvxEmphasisMarkItem&>(rItem).GetEmphasisMark(); + if ( nMark == FontEmphasisMark::NONE ) + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ACCNONE ); + else if ( nMark == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove) ) + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ACCCOMMA ); + else + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ACCDOT ); + } + break; + case EE_CHAR_SHADOW: + { + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SHAD ); + if ( !static_cast<const SvxShadowedItem&>(rItem).GetValue() ) + rOutput.WriteChar( '0' ); + } + break; + case EE_FEATURE_TAB: + { + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_TAB ); + } + break; + case EE_FEATURE_LINEBR: + { + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SL ); + } + break; + case EE_CHAR_KERNING: + { + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_EXPNDTW ); + rOutput.WriteInt32AsString( LogicToTwips( + static_cast<const SvxKerningItem&>(rItem).GetValue() ) ); + } + break; + case EE_CHAR_PAIRKERNING: + { + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_KERNING ); + rOutput.WriteUInt32AsString( static_cast<const SvxAutoKernItem&>(rItem).GetValue() ? 1 : 0 ); + } + break; + case EE_CHAR_ESCAPEMENT: + { + SvxFont aFont; + ContentNode* pNode = aEditDoc.GetObject( nPara ); + SeekCursor( pNode, nPos, aFont ); + MapMode aPntMode( MapUnit::MapPoint ); + tools::Long nFontHeight = GetRefDevice()->LogicToLogic( + aFont.GetFontSize(), &GetRefMapMode(), &aPntMode ).Height(); + nFontHeight *=2; // Half Points + sal_uInt16 const nProp = static_cast<const SvxEscapementItem&>(rItem).GetProportionalHeight(); + sal_uInt16 nProp100 = nProp*100; // For SWG-Token Prop in 100th percent. + short nEsc = static_cast<const SvxEscapementItem&>(rItem).GetEsc(); + const FontMetric& rFontMetric = GetRefDevice()->GetFontMetric(); + double fFontHeight = rFontMetric.GetAscent() + rFontMetric.GetDescent(); + double fAutoAscent = .8; + double fAutoDescent = .2; + if ( fFontHeight ) + { + fAutoAscent = rFontMetric.GetAscent() / fFontHeight; + fAutoDescent = rFontMetric.GetDescent() / fFontHeight; + } + if ( nEsc == DFLT_ESC_AUTO_SUPER ) + { + nEsc = fAutoAscent * (100 - nProp); + nProp100++; // A 1 afterwards means 'automatic'. + } + else if ( nEsc == DFLT_ESC_AUTO_SUB ) + { + nEsc = fAutoDescent * -(100 - nProp); + nProp100++; + } + // SWG: + if ( nEsc ) + { + rOutput.WriteCharPtr( "{\\*\\updnprop" ).WriteCharPtr( OString::number( + nProp100).getStr() ).WriteChar( '}' ); + } + tools::Long nUpDown = nFontHeight * std::abs( nEsc ) / 100; + if ( nEsc < 0 ) + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_DN ); + else if ( nEsc > 0 ) + rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_UP ); + rOutput.WriteOString( OString::number(nUpDown) ); + } + break; + } +} + +std::unique_ptr<EditTextObject> ImpEditEngine::GetEmptyTextObject() +{ + EditSelection aEmptySel; + aEmptySel.Min() = aEditDoc.GetStartPaM(); + aEmptySel.Max() = aEditDoc.GetStartPaM(); + + return CreateTextObject( aEmptySel ); +} + +std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject() +{ + EditSelection aCompleteSelection; + aCompleteSelection.Min() = aEditDoc.GetStartPaM(); + aCompleteSelection.Max() = aEditDoc.GetEndPaM(); + + return CreateTextObject( aCompleteSelection ); +} + +std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject(const EditSelection& rSel) +{ + return CreateTextObject(rSel, GetEditTextObjectPool(), aStatus.AllowBigObjects(), nBigTextObjectStart); +} + +std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject( EditSelection aSel, SfxItemPool* pPool, bool bAllowBigObjects, sal_Int32 nBigObjectStart ) +{ + sal_Int32 nStartNode, nEndNode; + sal_Int32 nTextPortions = 0; + + aSel.Adjust( aEditDoc ); + nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() ); + nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() ); + + bool bOnlyFullParagraphs = !( aSel.Min().GetIndex() || + ( aSel.Max().GetIndex() < aSel.Max().GetNode()->Len() ) ); + + // Templates are not saved! + // (Only the name and family, template itself must be in App!) + + const MapUnit eMapUnit = aEditDoc.GetItemPool().GetMetric(DEF_METRIC); + auto pTxtObj(std::make_unique<EditTextObjectImpl>(pPool, eMapUnit, GetVertical(), GetRotation(), + GetItemScriptType(aSel))); + + // iterate over the paragraphs ... + sal_Int32 nNode; + for ( nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = aEditDoc.GetObject( nNode ); + DBG_ASSERT( pNode, "Node not found: Search&Replace" ); + + if ( bOnlyFullParagraphs ) + { + const ParaPortion* pParaPortion = GetParaPortions()[nNode]; + nTextPortions += pParaPortion->GetTextPortions().Count(); + } + + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = pNode->Len(); + + bool bEmptyPara = nEndPos == 0; + + if ( ( nNode == nStartNode ) && !bOnlyFullParagraphs ) + nStartPos = aSel.Min().GetIndex(); + if ( ( nNode == nEndNode ) && !bOnlyFullParagraphs ) + nEndPos = aSel.Max().GetIndex(); + + + ContentInfo *pC = pTxtObj->CreateAndInsertContent(); + + // The paragraph attributes ... + pC->GetParaAttribs().Set( pNode->GetContentAttribs().GetItems() ); + + // The StyleSheet... + if ( pNode->GetStyleSheet() ) + { + pC->SetStyle(pNode->GetStyleSheet()->GetName()); + pC->SetFamily(pNode->GetStyleSheet()->GetFamily()); + } + + // The Text... + pC->SetText(pNode->Copy(nStartPos, nEndPos-nStartPos)); + auto& rCAttriblist = pC->GetCharAttribs(); + + // and the Attribute... + std::size_t nAttr = 0; + EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + rCAttriblist.reserve(rCAttriblist.size() + pNode->GetCharAttribs().GetAttribs().size()); + while ( pAttr ) + { + // In a blank paragraph keep the attributes! + if ( bEmptyPara || + ( ( pAttr->GetEnd() > nStartPos ) && ( pAttr->GetStart() < nEndPos ) ) ) + { + XEditAttribute aX = pTxtObj->CreateAttrib(*pAttr->GetItem(), pAttr->GetStart(), pAttr->GetEnd()); + // Possibly Correct ... + if ( ( nNode == nStartNode ) && ( nStartPos != 0 ) ) + { + aX.GetStart() = ( aX.GetStart() > nStartPos ) ? aX.GetStart()-nStartPos : 0; + aX.GetEnd() = aX.GetEnd() - nStartPos; + + } + if ( nNode == nEndNode ) + { + if ( aX.GetEnd() > (nEndPos-nStartPos) ) + aX.GetEnd() = nEndPos-nStartPos; + } + DBG_ASSERT( aX.GetEnd() <= (nEndPos-nStartPos), "CreateTextObject: Attribute too long!" ); + if ( !aX.GetLen() && !bEmptyPara ) + pTxtObj->DestroyAttrib(aX); + else + rCAttriblist.push_back(std::move(aX)); + } + nAttr++; + pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + } + + // If possible online spelling + if ( bAllowBigObjects && bOnlyFullParagraphs && pNode->GetWrongList() ) + pC->SetWrongList( pNode->GetWrongList()->Clone() ); + + } + + // Remember the portions info in case of large text objects: + // sleeper set up when Olli paragraphs not hacked! + if ( bAllowBigObjects && bOnlyFullParagraphs && IsFormatted() && IsUpdateLayout() && ( nTextPortions >= nBigObjectStart ) ) + { + XParaPortionList* pXList = new XParaPortionList( GetRefDevice(), GetColumnWidth(aPaperSize), nStretchX, nStretchY ); + pTxtObj->SetPortionInfo(std::unique_ptr<XParaPortionList>(pXList)); + for ( nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + const ParaPortion* pParaPortion = GetParaPortions()[nNode]; + XParaPortion* pX = new XParaPortion; + pXList->push_back(pX); + + pX->nHeight = pParaPortion->GetHeight(); + pX->nFirstLineOffset = pParaPortion->GetFirstLineOffset(); + + // The TextPortions + sal_uInt16 nCount = pParaPortion->GetTextPortions().Count(); + sal_uInt16 n; + for ( n = 0; n < nCount; n++ ) + { + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n]; + TextPortion* pNew = new TextPortion( rTextPortion ); + pX->aTextPortions.Append(pNew); + } + + // The lines + nCount = pParaPortion->GetLines().Count(); + for ( n = 0; n < nCount; n++ ) + { + const EditLine& rLine = pParaPortion->GetLines()[n]; + EditLine* pNew = rLine.Clone(); + pX->aLines.Append(pNew); + } +#ifdef DBG_UTIL + sal_uInt16 nTest; + int nTPLen = 0, nTxtLen = 0; + for ( nTest = pParaPortion->GetTextPortions().Count(); nTest; ) + nTPLen += pParaPortion->GetTextPortions()[--nTest].GetLen(); + for ( nTest = pParaPortion->GetLines().Count(); nTest; ) + nTxtLen += pParaPortion->GetLines()[--nTest].GetLen(); + DBG_ASSERT( ( nTPLen == pParaPortion->GetNode()->Len() ) && ( nTxtLen == pParaPortion->GetNode()->Len() ), "CreateBinTextObject: ParaPortion not completely formatted!" ); +#endif + } + } + return pTxtObj; +} + +void ImpEditEngine::SetText( const EditTextObject& rTextObject ) +{ + // Since setting a text object is not undo-able! + ResetUndoManager(); + bool _bUpdate = IsUpdateLayout(); + bool _bUndo = IsUndoEnabled(); + + SetText( OUString() ); + EditPaM aPaM = aEditDoc.GetStartPaM(); + + SetUpdateLayout( false ); + EnableUndo( false ); + + InsertText( rTextObject, EditSelection( aPaM, aPaM ) ); + SetVertical(rTextObject.GetVertical()); + SetRotation(rTextObject.GetRotation()); + + DBG_ASSERT( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "From where comes the Undo in SetText ?!" ); + SetUpdateLayout( _bUpdate ); + EnableUndo( _bUndo ); +} + +EditSelection ImpEditEngine::InsertText( const EditTextObject& rTextObject, EditSelection aSel ) +{ + aSel.Adjust( aEditDoc ); + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( aSel ); + EditSelection aNewSel = InsertTextObject( rTextObject, aSel.Max() ); + return aNewSel; +} + +EditSelection ImpEditEngine::InsertTextObject( const EditTextObject& rTextObject, EditPaM aPaM ) +{ + // Optimize: No getPos undFindParaportion, instead calculate index! + EditSelection aSel( aPaM, aPaM ); + DBG_ASSERT( !aSel.DbgIsBuggy( aEditDoc ), "InsertBibTextObject: Selection broken!(1)" ); + + bool bUsePortionInfo = false; + const EditTextObjectImpl& rTextObjectImpl = toImpl(rTextObject); + XParaPortionList* pPortionInfo = rTextObjectImpl.GetPortionInfo(); + + if ( pPortionInfo && ( static_cast<tools::Long>(pPortionInfo->GetPaperWidth()) == GetColumnWidth(aPaperSize) ) + && ( pPortionInfo->GetRefMapMode() == GetRefDevice()->GetMapMode() ) + && ( pPortionInfo->GetStretchX() == nStretchX ) + && ( pPortionInfo->GetStretchY() == nStretchY ) ) + { + if ( (pPortionInfo->GetRefDevPtr() == GetRefDevice()) || + (pPortionInfo->RefDevIsVirtual() && GetRefDevice()->IsVirtual()) ) + bUsePortionInfo = true; + } + + bool bConvertMetricOfItems = false; + MapUnit eSourceUnit = MapUnit(), eDestUnit = MapUnit(); + if (rTextObjectImpl.HasMetric()) + { + eSourceUnit = rTextObjectImpl.GetMetric(); + eDestUnit = aEditDoc.GetItemPool().GetMetric( DEF_METRIC ); + if ( eSourceUnit != eDestUnit ) + bConvertMetricOfItems = true; + } + + // Before, paragraph count was of type sal_uInt16 so if nContents exceeded + // 0xFFFF this wouldn't have worked anyway, given that nPara is used to + // number paragraphs and is fearlessly incremented. + sal_Int32 nContents = static_cast<sal_Int32>(rTextObjectImpl.GetContents().size()); + SAL_WARN_IF( nContents < 0, "editeng", "ImpEditEngine::InsertTextObject - contents overflow " << nContents); + sal_Int32 nPara = aEditDoc.GetPos( aPaM.GetNode() ); + + for (sal_Int32 n = 0; n < nContents; ++n, ++nPara) + { + const ContentInfo* pC = rTextObjectImpl.GetContents()[n].get(); + bool bNewContent = aPaM.GetNode()->Len() == 0; + const sal_Int32 nStartPos = aPaM.GetIndex(); + + aPaM = ImpFastInsertText( aPaM, pC->GetText() ); + + ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); + DBG_ASSERT( pPortion, "Blind Portion in FastInsertText" ); + pPortion->MarkInvalid( nStartPos, pC->GetText().getLength() ); + + // Character attributes ... + bool bAllreadyHasAttribs = aPaM.GetNode()->GetCharAttribs().Count() != 0; + size_t nNewAttribs = pC->GetCharAttribs().size(); + if ( nNewAttribs ) + { + bool bUpdateFields = false; + for (size_t nAttr = 0; nAttr < nNewAttribs; ++nAttr) + { + const XEditAttribute& rX = pC->GetCharAttribs()[nAttr]; + // Can happen when paragraphs > 16K, it is simply wrapped. + //TODO! Still true, still needed? + if ( rX.GetEnd() <= aPaM.GetNode()->Len() ) + { + if ( !bAllreadyHasAttribs || rX.IsFeature() ) + { + // Normal attributes then go faster ... + // Features shall not be inserted through + // EditDoc:: InsertAttrib, using FastInsertText they are + // already in the flow + DBG_ASSERT( rX.GetEnd() <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute too large!" ); + EditCharAttrib* pAttr; + if ( !bConvertMetricOfItems ) + pAttr = MakeCharAttrib( aEditDoc.GetItemPool(), *(rX.GetItem()), rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos ); + else + { + std::unique_ptr<SfxPoolItem> pNew(rX.GetItem()->Clone()); + ConvertItem( pNew, eSourceUnit, eDestUnit ); + pAttr = MakeCharAttrib( aEditDoc.GetItemPool(), *pNew, rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos ); + } + DBG_ASSERT( pAttr->GetEnd() <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute does not fit! (1)" ); + aPaM.GetNode()->GetCharAttribs().InsertAttrib( pAttr ); + if ( pAttr->Which() == EE_FEATURE_FIELD ) + bUpdateFields = true; + } + else + { + DBG_ASSERT( rX.GetEnd()+nStartPos <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute does not fit! (2)" ); + // Tabs and other Features can not be inserted through InsertAttrib: + aEditDoc.InsertAttrib( aPaM.GetNode(), rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos, *rX.GetItem() ); + } + } + } + if ( bUpdateFields ) + UpdateFields(); + + // Otherwise, quick format => no attributes! + pPortion->MarkSelectionInvalid( nStartPos ); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aPaM.GetNode()->GetCharAttribs()); +#endif + + bool bParaAttribs = false; + if ( bNewContent || ( ( n > 0 ) && ( n < (nContents-1) ) ) ) + { + // only style and ParaAttribs when new paragraph, or + // completely internal ... + bParaAttribs = pC->GetParaAttribs().Count() != 0; + if ( GetStyleSheetPool() && pC->GetStyle().getLength() ) + { + SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pC->GetStyle(), pC->GetFamily() )); + DBG_ASSERT( pStyle, "InsertBinTextObject - Style not found!" ); + SetStyleSheet( nPara, pStyle ); + } + if ( !bConvertMetricOfItems ) + SetParaAttribs( aEditDoc.GetPos( aPaM.GetNode() ), pC->GetParaAttribs() ); + else + { + SfxItemSet aAttribs( GetEmptyItemSet() ); + ConvertAndPutItems( aAttribs, pC->GetParaAttribs(), &eSourceUnit, &eDestUnit ); + SetParaAttribs( aEditDoc.GetPos( aPaM.GetNode() ), aAttribs ); + } + if ( bNewContent && bUsePortionInfo ) + { + const XParaPortion& rXP = (*pPortionInfo)[n]; + ParaPortion* pParaPortion = GetParaPortions()[ nPara ]; + DBG_ASSERT( pParaPortion, "InsertBinTextObject: ParaPortion?" ); + pParaPortion->nHeight = rXP.nHeight; + pParaPortion->nFirstLineOffset = rXP.nFirstLineOffset; + pParaPortion->bForceRepaint = true; + pParaPortion->SetValid(); // Do not format + + // The Text Portions + pParaPortion->GetTextPortions().Reset(); + sal_uInt16 nCount = rXP.aTextPortions.Count(); + for ( sal_uInt16 _n = 0; _n < nCount; _n++ ) + { + const TextPortion& rTextPortion = rXP.aTextPortions[_n]; + TextPortion* pNew = new TextPortion( rTextPortion ); + pParaPortion->GetTextPortions().Append(pNew); + } + + // The lines + pParaPortion->GetLines().Reset(); + nCount = rXP.aLines.Count(); + for ( sal_uInt16 m = 0; m < nCount; m++ ) + { + const EditLine& rLine = rXP.aLines[m]; + EditLine* pNew = rLine.Clone(); + pNew->SetInvalid(); // Paint again! + pParaPortion->GetLines().Append(pNew); + } +#ifdef DBG_UTIL + sal_uInt16 nTest; + int nTPLen = 0, nTxtLen = 0; + for ( nTest = pParaPortion->GetTextPortions().Count(); nTest; ) + nTPLen += pParaPortion->GetTextPortions()[--nTest].GetLen(); + for ( nTest = pParaPortion->GetLines().Count(); nTest; ) + nTxtLen += pParaPortion->GetLines()[--nTest].GetLen(); + DBG_ASSERT( ( nTPLen == pParaPortion->GetNode()->Len() ) && ( nTxtLen == pParaPortion->GetNode()->Len() ), "InsertTextObject: ParaPortion not completely formatted!" ); +#endif + } + } + if ( !bParaAttribs ) // DefFont is not calculated for FastInsertParagraph + { + aPaM.GetNode()->GetCharAttribs().GetDefFont() = aEditDoc.GetDefFont(); + if ( aStatus.UseCharAttribs() ) + aPaM.GetNode()->CreateDefFont(); + } + + if ( bNewContent && GetStatus().DoOnlineSpelling() && pC->GetWrongList() ) + { + aPaM.GetNode()->SetWrongList( pC->GetWrongList()->Clone() ); + } + + // Wrap when followed by other ... + if ( n < ( nContents-1) ) + { + if ( bNewContent ) + aPaM = ImpFastInsertParagraph( nPara+1 ); + else + aPaM = ImpInsertParaBreak( aPaM, false ); + } + } + + aSel.Max() = aPaM; + DBG_ASSERT( !aSel.DbgIsBuggy( aEditDoc ), "InsertBibTextObject: Selection broken!(1)" ); + return aSel; +} + +void ImpEditEngine::GetAllMisspellRanges( std::vector<editeng::MisspellRanges>& rRanges ) const +{ + std::vector<editeng::MisspellRanges> aRanges; + const EditDoc& rDoc = GetEditDoc(); + for (sal_Int32 i = 0, n = rDoc.Count(); i < n; ++i) + { + const ContentNode* pNode = rDoc.GetObject(i); + const WrongList* pWrongList = pNode->GetWrongList(); + if (!pWrongList) + continue; + + aRanges.emplace_back(i, std::vector(pWrongList->GetRanges())); + } + + aRanges.swap(rRanges); +} + +void ImpEditEngine::SetAllMisspellRanges( const std::vector<editeng::MisspellRanges>& rRanges ) +{ + EditDoc& rDoc = GetEditDoc(); + for (auto const& rParaRanges : rRanges) + { + ContentNode* pNode = rDoc.GetObject(rParaRanges.mnParagraph); + if (!pNode) + continue; + + pNode->CreateWrongList(); + WrongList* pWrongList = pNode->GetWrongList(); + pWrongList->SetRanges(std::vector(rParaRanges.maRanges)); + } +} + +editeng::LanguageSpan ImpEditEngine::GetLanguage( const EditPaM& rPaM, sal_Int32* pEndPos ) const +{ + short nScriptTypeI18N = GetI18NScriptType( rPaM, pEndPos ); // pEndPos will be valid now, pointing to ScriptChange or NodeLen + SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N); + sal_uInt16 nLangId = GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ); + const SvxLanguageItem* pLangItem = &static_cast<const SvxLanguageItem&>(rPaM.GetNode()->GetContentAttribs().GetItem( nLangId )); + const EditCharAttrib* pAttr = rPaM.GetNode()->GetCharAttribs().FindAttrib( nLangId, rPaM.GetIndex() ); + + editeng::LanguageSpan aLang; + + if ( pAttr ) + { + pLangItem = static_cast<const SvxLanguageItem*>(pAttr->GetItem()); + aLang.nStart = pAttr->GetStart(); + aLang.nEnd = pAttr->GetEnd(); + } + + if ( pEndPos && pAttr && ( pAttr->GetEnd() < *pEndPos ) ) + *pEndPos = pAttr->GetEnd(); + + aLang.nLang = pLangItem->GetLanguage(); + + return aLang; +} + +css::lang::Locale ImpEditEngine::GetLocale( const EditPaM& rPaM ) const +{ + return LanguageTag( GetLanguage( rPaM ).nLang ).getLocale(); +} + +Reference< XSpellChecker1 > const & ImpEditEngine::GetSpeller() +{ + if ( !xSpeller.is() ) + xSpeller = LinguMgr::GetSpellChecker(); + return xSpeller; +} + + +void ImpEditEngine::CreateSpellInfo( bool bMultipleDocs ) +{ + if (!pSpellInfo) + pSpellInfo.reset( new SpellInfo ); + else + *pSpellInfo = SpellInfo(); // reset to default values + + pSpellInfo->bMultipleDoc = bMultipleDocs; + // always spell draw objects completely, starting at the top. + // (spelling in only a selection or not starting with the top requires + // further changes elsewhere to work properly) + pSpellInfo->aSpellStart = EPaM(); + pSpellInfo->aSpellTo = EPaM( EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND ); +} + + +EESpellState ImpEditEngine::Spell(EditView* pEditView, weld::Widget* pDialogParent, bool bMultipleDoc) +{ + SAL_WARN_IF( !xSpeller.is(), "editeng", "No Spell checker set!" ); + + if ( !xSpeller.is() ) + return EESpellState::NoSpeller; + + aOnlineSpellTimer.Stop(); + + // In MultipleDoc always from the front / rear ... + if ( bMultipleDoc ) + { + pEditView->pImpEditView->SetEditSelection( aEditDoc.GetStartPaM() ); + } + + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + CreateSpellInfo( bMultipleDoc ); + + bool bIsStart = false; + if ( bMultipleDoc ) + bIsStart = true; // Accessible from the front or from behind ... + else if ( CreateEPaM( aEditDoc.GetStartPaM() ) == pSpellInfo->aSpellStart ) + bIsStart = true; + + { + EditSpellWrapper aWrp(pDialogParent, bIsStart, pEditView ); + aWrp.SpellDocument(); + } + + if ( !bMultipleDoc ) + { + pEditView->pImpEditView->DrawSelectionXOR(); + if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() ) + aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() ); + aCurSel.Min() = aCurSel.Max(); + pEditView->pImpEditView->SetEditSelection( aCurSel ); + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + } + EESpellState eState = pSpellInfo->eState; + pSpellInfo.reset(); + return eState; +} + + +bool ImpEditEngine::HasConvertibleTextPortion( LanguageType nSrcLang ) +{ + bool bHasConvTxt = false; + + sal_Int32 nParas = pEditEngine->GetParagraphCount(); + for (sal_Int32 k = 0; k < nParas; ++k) + { + std::vector<sal_Int32> aPortions; + pEditEngine->GetPortions( k, aPortions ); + for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos ) + { + sal_Int32 nEnd = aPortions[ nPos ]; + sal_Int32 nStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0; + + // if the paragraph is not empty we need to increase the index + // by one since the attribute of the character left to the + // specified position is evaluated. + if (nEnd > nStart) // empty para? + ++nStart; + LanguageType nLangFound = pEditEngine->GetLanguage( k, nStart ).nLang; +#ifdef DEBUG + lang::Locale aLocale( LanguageTag::convertToLocale( nLangFound ) ); +#endif + bHasConvTxt = (nSrcLang == nLangFound) || + (editeng::HangulHanjaConversion::IsChinese( nLangFound ) && + editeng::HangulHanjaConversion::IsChinese( nSrcLang )); + if (bHasConvTxt) + return bHasConvTxt; + } + } + + return bHasConvTxt; +} + +void ImpEditEngine::Convert( EditView* pEditView, weld::Widget* pDialogParent, + LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont, + sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc ) +{ + // modified version of ImpEditEngine::Spell + + // In MultipleDoc always from the front / rear ... + if ( bMultipleDoc ) + pEditView->pImpEditView->SetEditSelection( aEditDoc.GetStartPaM() ); + + + // initialize pConvInfo + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + aCurSel.Adjust( aEditDoc ); + pConvInfo.reset(new ConvInfo); + pConvInfo->bMultipleDoc = bMultipleDoc; + pConvInfo->aConvStart = CreateEPaM( aCurSel.Min() ); + + // if it is not just a selection and we are about to begin + // with the current conversion for the very first time + // we need to find the start of the current (initial) + // convertible unit in order for the text conversion to give + // the correct result for that. Since it is easier to obtain + // the start of the word we use that though. + if (!aCurSel.HasRange() && ImplGetBreakIterator().is()) + { + EditPaM aWordStartPaM( SelectWord( aCurSel, i18n::WordType::DICTIONARY_WORD ).Min() ); + + // since #118246 / #117803 still occurs if the cursor is placed + // between the two chinese characters to be converted (because both + // of them are words on their own!) using the word boundary here does + // not work. Thus since chinese conversion is not interactive we start + // at the begin of the paragraph to solve the problem, i.e. have the + // TextConversion service get those characters together in the same call. + pConvInfo->aConvStart.nIndex = editeng::HangulHanjaConversion::IsChinese( nSrcLang ) + ? 0 : aWordStartPaM.GetIndex(); + } + + pConvInfo->aConvContinue = pConvInfo->aConvStart; + + bool bIsStart = false; + if ( bMultipleDoc ) + bIsStart = true; // Accessible from the front or from behind ... + else if ( CreateEPaM( aEditDoc.GetStartPaM() ) == pConvInfo->aConvStart ) + bIsStart = true; + + TextConvWrapper aWrp( pDialogParent, + ::comphelper::getProcessComponentContext(), + LanguageTag::convertToLocale( nSrcLang ), + LanguageTag::convertToLocale( nDestLang ), + pDestFont, + nOptions, bIsInteractive, + bIsStart, pEditView ); + + + //!! optimization does not work since when update mode is false + //!! the object is 'lying' about it portions, paragraphs, + //!! EndPaM... later on. + //!! Should not be a great problem since text boxes or cells in + //!! Calc usually have only a rather short text. + // + // disallow formatting, updating the view, ... while + // non-interactively converting the document. (saves time) + //if (!bIsInteractive) + // SetUpdateMode( sal_False ); + + aWrp.Convert(); + + //if (!bIsInteractive) + //SetUpdateMode( sal_True, 0, sal_True ); + + if ( !bMultipleDoc ) + { + pEditView->pImpEditView->DrawSelectionXOR(); + if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() ) + aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() ); + aCurSel.Min() = aCurSel.Max(); + pEditView->pImpEditView->SetEditSelection( aCurSel ); + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + } + pConvInfo.reset(); +} + + +void ImpEditEngine::SetLanguageAndFont( + const ESelection &rESel, + LanguageType nLang, sal_uInt16 nLangWhichId, + const vcl::Font *pFont, sal_uInt16 nFontWhichId ) +{ + ESelection aOldSel = pActiveView->GetSelection(); + pActiveView->SetSelection( rESel ); + + // set new language attribute + SfxItemSet aNewSet( pActiveView->GetEmptyItemSet() ); + aNewSet.Put( SvxLanguageItem( nLang, nLangWhichId ) ); + + // new font to be set? + DBG_ASSERT( pFont, "target font missing?" ); + if (pFont) + { + // set new font attribute + SvxFontItem aFontItem = static_cast<const SvxFontItem&>( aNewSet.Get( nFontWhichId ) ); + aFontItem.SetFamilyName( pFont->GetFamilyName()); + aFontItem.SetFamily( pFont->GetFamilyType()); + aFontItem.SetStyleName( pFont->GetStyleName()); + aFontItem.SetPitch( pFont->GetPitch()); + aFontItem.SetCharSet( pFont->GetCharSet() ); + aNewSet.Put( aFontItem ); + } + + // apply new attributes + pActiveView->SetAttribs( aNewSet ); + + pActiveView->SetSelection( aOldSel ); +} + + +void ImpEditEngine::ImpConvert( OUString &rConvTxt, LanguageType &rConvTxtLang, + EditView* pEditView, LanguageType nSrcLang, const ESelection &rConvRange, + bool bAllowImplicitChangesForNotConvertibleText, + LanguageType nTargetLang, const vcl::Font *pTargetFont ) +{ + // modified version of ImpEditEngine::ImpSpell + + // looks for next convertible text portion to be passed on to the wrapper + + OUString aRes; + LanguageType nResLang = LANGUAGE_NONE; + + EditPaM aPos( CreateEditPaM( pConvInfo->aConvContinue ) ); + EditSelection aCurSel( aPos, aPos ); + + OUString aWord; + + while (aRes.isEmpty()) + { + // empty paragraph found that needs to have language and font set? + if (bAllowImplicitChangesForNotConvertibleText && + pEditEngine->GetText( pConvInfo->aConvContinue.nPara ).isEmpty()) + { + sal_Int32 nPara = pConvInfo->aConvContinue.nPara; + ESelection aESel( nPara, 0, nPara, 0 ); + // see comment for below same function call + SetLanguageAndFont( aESel, + nTargetLang, EE_CHAR_LANGUAGE_CJK, + pTargetFont, EE_CHAR_FONTINFO_CJK ); + } + + + if (pConvInfo->aConvContinue.nPara == pConvInfo->aConvTo.nPara && + pConvInfo->aConvContinue.nIndex >= pConvInfo->aConvTo.nIndex) + break; + + sal_Int32 nAttribStart = -1; + sal_Int32 nAttribEnd = -1; + sal_Int32 nCurPos = -1; + EPaM aCurStart = CreateEPaM( aCurSel.Min() ); + std::vector<sal_Int32> aPortions; + pEditEngine->GetPortions( aCurStart.nPara, aPortions ); + for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos ) + { + const sal_Int32 nEnd = aPortions[ nPos ]; + const sal_Int32 nStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0; + + // the language attribute is obtained from the left character + // (like usually all other attributes) + // thus we usually have to add 1 in order to get the language + // of the text right to the cursor position + const sal_Int32 nLangIdx = nEnd > nStart ? nStart + 1 : nStart; + LanguageType nLangFound = pEditEngine->GetLanguage( aCurStart.nPara, nLangIdx ).nLang; +#ifdef DEBUG + lang::Locale aLocale( LanguageTag::convertToLocale( nLangFound ) ); +#endif + bool bLangOk = (nLangFound == nSrcLang) || + (editeng::HangulHanjaConversion::IsChinese( nLangFound ) && + editeng::HangulHanjaConversion::IsChinese( nSrcLang )); + + if (nAttribEnd>=0) // start already found? + { + DBG_ASSERT(nEnd >= aCurStart.nIndex, "error while scanning attributes (a)" ); + DBG_ASSERT(nEnd >= nAttribEnd, "error while scanning attributes (b)" ); + if (/*nEnd >= aCurStart.nIndex &&*/ nLangFound == nResLang) + nAttribEnd = nEnd; + else // language attrib has changed + break; + } + if (nAttribStart<0 && // start not yet found? + nEnd > aCurStart.nIndex && bLangOk) + { + nAttribStart = nStart; + nAttribEnd = nEnd; + nResLang = nLangFound; + } + //! the list of portions may have changed compared to the previous + //! call to this function (because of possibly changed language + //! attribute!) + //! But since we don't want to start in the already processed part + //! we clip the start accordingly. + if (nAttribStart >= 0 && nAttribStart < aCurStart.nIndex) + { + nAttribStart = aCurStart.nIndex; + } + + // check script type to the right of the start of the current portion + EditPaM aPaM( CreateEditPaM( EPaM(aCurStart.nPara, nLangIdx) ) ); + bool bIsAsianScript = (i18n::ScriptType::ASIAN == GetI18NScriptType( aPaM )); + // not yet processed text part with for conversion + // not suitable language found that needs to be changed? + if (bAllowImplicitChangesForNotConvertibleText && + !bLangOk && !bIsAsianScript && nEnd > aCurStart.nIndex) + { + ESelection aESel( aCurStart.nPara, nStart, aCurStart.nPara, nEnd ); + // set language and font to target language and font of conversion + //! Now this especially includes all non convertible text e.g. + //! spaces, empty paragraphs and western text. + // This is in order for every *new* text entered at *any* position to + // have the correct language and font attributes set. + SetLanguageAndFont( aESel, + nTargetLang, EE_CHAR_LANGUAGE_CJK, + pTargetFont, EE_CHAR_FONTINFO_CJK ); + } + + nCurPos = nEnd; + } + + if (nAttribStart>=0 && nAttribEnd>=0) + { + aCurSel.Min().SetIndex( nAttribStart ); + aCurSel.Max().SetIndex( nAttribEnd ); + } + else if (nCurPos>=0) + { + // set selection to end of scanned text + // (used to set the position where to continue from later on) + aCurSel.Min().SetIndex( nCurPos ); + aCurSel.Max().SetIndex( nCurPos ); + } + + if ( !pConvInfo->bConvToEnd ) + { + EPaM aEPaM( CreateEPaM( aCurSel.Min() ) ); + if ( !( aEPaM < pConvInfo->aConvTo ) ) + break; + } + + // clip selected word to the converted area + // (main use when conversion starts/ends **within** a word) + EditPaM aPaM( CreateEditPaM( pConvInfo->aConvStart ) ); + if (pConvInfo->bConvToEnd && + aCurSel.Min().GetNode() == aPaM.GetNode() && + aCurSel.Min().GetIndex() < aPaM.GetIndex()) + aCurSel.Min().SetIndex( aPaM.GetIndex() ); + aPaM = CreateEditPaM( pConvInfo->aConvContinue ); + if (aCurSel.Min().GetNode() == aPaM.GetNode() && + aCurSel.Min().GetIndex() < aPaM.GetIndex()) + aCurSel.Min().SetIndex( aPaM.GetIndex() ); + aPaM = CreateEditPaM( pConvInfo->aConvTo ); + if ((!pConvInfo->bConvToEnd || rConvRange.HasRange())&& + aCurSel.Max().GetNode() == aPaM.GetNode() && + aCurSel.Max().GetIndex() > aPaM.GetIndex()) + aCurSel.Max().SetIndex( aPaM.GetIndex() ); + + aWord = GetSelected( aCurSel ); + + if ( !aWord.isEmpty() /* && bLangOk */) + aRes = aWord; + + // move to next word/paragraph if necessary + if ( aRes.isEmpty() ) + aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD ); + + pConvInfo->aConvContinue = CreateEPaM( aCurSel.Max() ); + } + + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->pImpEditView->SetEditSelection( aCurSel ); + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + + rConvTxt = aRes; + if ( !rConvTxt.isEmpty() ) + rConvTxtLang = nResLang; +} + + +Reference< XSpellAlternatives > ImpEditEngine::ImpSpell( EditView* pEditView ) +{ + DBG_ASSERT( xSpeller.is(), "No spell checker set!" ); + + ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 ); + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + aCurSel.Min() = aCurSel.Max(); + + Reference< XSpellAlternatives > xSpellAlt; + Sequence< PropertyValue > aEmptySeq; + while (!xSpellAlt.is()) + { + // Known (most likely) bug: If SpellToCurrent, the current has to be + // corrected at each replacement, otherwise it may not fit exactly in + // the end ... + if ( pSpellInfo->bSpellToEnd || pSpellInfo->bMultipleDoc ) + { + if ( aCurSel.Max().GetNode() == pLastNode ) + { + if ( aCurSel.Max().GetIndex() >= pLastNode->Len() ) + break; + } + } + else if ( !pSpellInfo->bSpellToEnd ) + { + EPaM aEPaM( CreateEPaM( aCurSel.Max() ) ); + if ( !( aEPaM < pSpellInfo->aSpellTo ) ) + break; + } + + aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD ); + OUString aWord = GetSelected( aCurSel ); + + // If afterwards a dot, this must be handed over! + // If an abbreviation ... + if ( !aWord.isEmpty() && ( aCurSel.Max().GetIndex() < aCurSel.Max().GetNode()->Len() ) ) + { + sal_Unicode cNext = aCurSel.Max().GetNode()->GetChar( aCurSel.Max().GetIndex() ); + if ( cNext == '.' ) + { + aCurSel.Max().SetIndex( aCurSel.Max().GetIndex()+1 ); + aWord += OUStringChar(cNext); + } + } + + if ( !aWord.isEmpty() ) + { + LanguageType eLang = GetLanguage( aCurSel.Max() ).nLang; + SvxSpellWrapper::CheckSpellLang( xSpeller, eLang ); + xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(eLang), aEmptySeq ); + } + + if ( !xSpellAlt.is() ) + aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD ); + else + pSpellInfo->eState = EESpellState::ErrorFound; + } + + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->pImpEditView->SetEditSelection( aCurSel ); + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + return xSpellAlt; +} + +Reference< XSpellAlternatives > ImpEditEngine::ImpFindNextError(EditSelection& rSelection) +{ + EditSelection aCurSel( rSelection.Min() ); + + Reference< XSpellAlternatives > xSpellAlt; + Sequence< PropertyValue > aEmptySeq; + while (!xSpellAlt.is()) + { + //check if the end of the selection has been reached + { + EPaM aEPaM( CreateEPaM( aCurSel.Max() ) ); + if ( !( aEPaM < CreateEPaM( rSelection.Max()) ) ) + break; + } + + aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD ); + OUString aWord = GetSelected( aCurSel ); + + // If afterwards a dot, this must be handed over! + // If an abbreviation ... + if ( !aWord.isEmpty() && ( aCurSel.Max().GetIndex() < aCurSel.Max().GetNode()->Len() ) ) + { + sal_Unicode cNext = aCurSel.Max().GetNode()->GetChar( aCurSel.Max().GetIndex() ); + if ( cNext == '.' ) + { + aCurSel.Max().SetIndex( aCurSel.Max().GetIndex()+1 ); + aWord += OUStringChar(cNext); + } + } + + if ( !aWord.isEmpty() ) + xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(GetLanguage( aCurSel.Max() ).nLang), aEmptySeq ); + + if ( !xSpellAlt.is() ) + aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD ); + else + { + pSpellInfo->eState = EESpellState::ErrorFound; + rSelection = aCurSel; + } + } + return xSpellAlt; +} + +bool ImpEditEngine::SpellSentence(EditView const & rEditView, + svx::SpellPortions& rToFill ) +{ + bool bRet = false; + EditSelection aCurSel( rEditView.pImpEditView->GetEditSelection() ); + if(!pSpellInfo) + CreateSpellInfo( true ); + pSpellInfo->aCurSentenceStart = aCurSel.Min(); + DBG_ASSERT( xSpeller.is(), "No spell checker set!" ); + pSpellInfo->aLastSpellPortions.clear(); + pSpellInfo->aLastSpellContentSelections.clear(); + rToFill.clear(); + //if no selection previously exists the range is extended to the end of the object + if (!aCurSel.HasRange()) + { + ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1); + aCurSel.Max() = EditPaM(pLastNode, pLastNode->Len()); + } + // check for next error in aCurSel and set aCurSel to that one if any was found + Reference< XSpellAlternatives > xAlt = ImpFindNextError(aCurSel); + if (xAlt.is()) + { + bRet = true; + //find the sentence boundaries + EditSelection aSentencePaM = SelectSentence(aCurSel); + //make sure that the sentence is never smaller than the error range! + if(aSentencePaM.Max().GetIndex() < aCurSel.Max().GetIndex()) + aSentencePaM.Max() = aCurSel.Max(); + //add the portion preceding the error + EditSelection aStartSelection(aSentencePaM.Min(), aCurSel.Min()); + if(aStartSelection.HasRange()) + AddPortionIterated(rEditView, aStartSelection, nullptr, rToFill); + //add the error portion + AddPortionIterated(rEditView, aCurSel, xAlt, rToFill); + //find the end of the sentence + //search for all errors in the rest of the sentence and add all the portions + do + { + EditSelection aNextSel(aCurSel.Max(), aSentencePaM.Max()); + xAlt = ImpFindNextError(aNextSel); + if(xAlt.is()) + { + //add the part between the previous and the current error + AddPortionIterated(rEditView, EditSelection(aCurSel.Max(), aNextSel.Min()), nullptr, rToFill); + //add the current error + AddPortionIterated(rEditView, aNextSel, xAlt, rToFill); + } + else + AddPortionIterated(rEditView, EditSelection(aCurSel.Max(), aSentencePaM.Max()), xAlt, rToFill); + aCurSel = aNextSel; + } + while( xAlt.is() ); + + //set the selection to the end of the current sentence + rEditView.pImpEditView->SetEditSelection(aSentencePaM.Max()); + } + return bRet; +} + +// Adds one portion to the SpellPortions +void ImpEditEngine::AddPortion( + const EditSelection& rSel, + const uno::Reference< XSpellAlternatives >& xAlt, + svx::SpellPortions& rToFill, + bool bIsField) +{ + if(!rSel.HasRange()) + return; + + svx::SpellPortion aPortion; + aPortion.sText = GetSelected( rSel ); + aPortion.eLanguage = GetLanguage( rSel.Min() ).nLang; + aPortion.xAlternatives = xAlt; + aPortion.bIsField = bIsField; + rToFill.push_back(aPortion); + + //save the spelled portions for later use + pSpellInfo->aLastSpellPortions.push_back(aPortion); + pSpellInfo->aLastSpellContentSelections.push_back(rSel); +} + +// Adds one or more portions of text to the SpellPortions depending on language changes +void ImpEditEngine::AddPortionIterated( + EditView const & rEditView, + const EditSelection& rSel, + const Reference< XSpellAlternatives >& xAlt, + svx::SpellPortions& rToFill) +{ + if (!rSel.HasRange()) + return; + + if(xAlt.is()) + { + AddPortion(rSel, xAlt, rToFill, false); + } + else + { + //iterate and search for language attribute changes + //save the start and end positions + bool bTest = rSel.Min().GetIndex() <= rSel.Max().GetIndex(); + EditPaM aStart(bTest ? rSel.Min() : rSel.Max()); + EditPaM aEnd(bTest ? rSel.Max() : rSel.Min()); + //iterate over the text to find changes in language + //set the mark equal to the point + EditPaM aCursor(aStart); + rEditView.pImpEditView->SetEditSelection( aCursor ); + LanguageType eStartLanguage = GetLanguage( aCursor ).nLang; + //search for a field attribute at the beginning - only the end position + //of this field is kept to end a portion at that position + const EditCharAttrib* pFieldAttr = aCursor.GetNode()->GetCharAttribs(). + FindFeature( aCursor.GetIndex() ); + bool bIsField = pFieldAttr && + pFieldAttr->GetStart() == aCursor.GetIndex() && + pFieldAttr->GetStart() != pFieldAttr->GetEnd() && + pFieldAttr->Which() == EE_FEATURE_FIELD; + sal_Int32 nEndField = bIsField ? pFieldAttr->GetEnd() : -1; + do + { + aCursor = CursorRight( aCursor); + //determine whether a field and has been reached + bool bIsEndField = nEndField == aCursor.GetIndex(); + //search for a new field attribute + const EditCharAttrib* _pFieldAttr = aCursor.GetNode()->GetCharAttribs(). + FindFeature( aCursor.GetIndex() ); + bIsField = _pFieldAttr && + _pFieldAttr->GetStart() == aCursor.GetIndex() && + _pFieldAttr->GetStart() != _pFieldAttr->GetEnd() && + _pFieldAttr->Which() == EE_FEATURE_FIELD; + //on every new field move the end position + if (bIsField) + nEndField = _pFieldAttr->GetEnd(); + + LanguageType eCurLanguage = GetLanguage( aCursor ).nLang; + if(eCurLanguage != eStartLanguage || bIsField || bIsEndField) + { + eStartLanguage = eCurLanguage; + //go one step back - the cursor currently selects the first character + //with a different language + //create a selection from start to the current Cursor + EditSelection aSelection(aStart, aCursor); + AddPortion(aSelection, xAlt, rToFill, bIsEndField); + aStart = aCursor; + } + } + while(aCursor.GetIndex() < aEnd.GetIndex()); + EditSelection aSelection(aStart, aCursor); + AddPortion(aSelection, xAlt, rToFill, bIsField); + } +} + +void ImpEditEngine::ApplyChangedSentence(EditView const & rEditView, + const svx::SpellPortions& rNewPortions, + bool bRecheck ) +{ + // Note: rNewPortions.size() == 0 is valid and happens when the whole + // sentence got removed in the dialog + + DBG_ASSERT(pSpellInfo, "pSpellInfo not initialized"); + if (!pSpellInfo || pSpellInfo->aLastSpellPortions.empty()) // no portions -> no text to be changed + return; + + // get current paragraph length to calculate later on how the sentence length changed, + // in order to place the cursor at the end of the sentence again + EditSelection aOldSel( rEditView.pImpEditView->GetEditSelection() ); + sal_Int32 nOldLen = aOldSel.Max().GetNode()->Len(); + + UndoActionStart( EDITUNDO_INSERT ); + if(pSpellInfo->aLastSpellPortions.size() == rNewPortions.size()) + { + DBG_ASSERT( !rNewPortions.empty(), "rNewPortions should not be empty here" ); + DBG_ASSERT( pSpellInfo->aLastSpellPortions.size() == pSpellInfo->aLastSpellContentSelections.size(), + "aLastSpellPortions and aLastSpellContentSelections size mismatch" ); + + //the simple case: the same number of elements on both sides + //each changed element has to be applied to the corresponding source element + svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.end(); + svx::SpellPortions::const_iterator aCurrentOldPortion = pSpellInfo->aLastSpellPortions.end(); + SpellContentSelections::const_iterator aCurrentOldPosition = pSpellInfo->aLastSpellContentSelections.end(); + bool bSetToEnd = false; + do + { + --aCurrentNewPortion; + --aCurrentOldPortion; + --aCurrentOldPosition; + //set the cursor to the end of the sentence - necessary to + //resume there at the next step + if(!bSetToEnd) + { + bSetToEnd = true; + rEditView.pImpEditView->SetEditSelection( aCurrentOldPosition->Max() ); + } + + SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( aCurrentNewPortion->eLanguage ); + sal_uInt16 nLangWhichId = EE_CHAR_LANGUAGE; + switch(nScriptType) + { + case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break; + case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break; + default: break; + } + if(aCurrentNewPortion->sText != aCurrentOldPortion->sText) + { + //change text and apply language + SfxItemSet aSet( aEditDoc.GetItemPool(), nLangWhichId, nLangWhichId ); + aSet.Put(SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId)); + SetAttribs( *aCurrentOldPosition, aSet ); + ImpInsertText( *aCurrentOldPosition, aCurrentNewPortion->sText ); + } + else if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage) + { + //apply language + SfxItemSet aSet( aEditDoc.GetItemPool(), nLangWhichId, nLangWhichId); + aSet.Put(SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId)); + SetAttribs( *aCurrentOldPosition, aSet ); + } + } + while(aCurrentNewPortion != rNewPortions.begin()); + } + else + { + DBG_ASSERT( !pSpellInfo->aLastSpellContentSelections.empty(), "aLastSpellContentSelections should not be empty here" ); + + //select the complete sentence + SpellContentSelections::const_iterator aCurrentEndPosition = pSpellInfo->aLastSpellContentSelections.end(); + --aCurrentEndPosition; + SpellContentSelections::const_iterator aCurrentStartPosition = pSpellInfo->aLastSpellContentSelections.begin(); + EditSelection aAllSentence(aCurrentStartPosition->Min(), aCurrentEndPosition->Max()); + + //delete the sentence completely + ImpDeleteSelection( aAllSentence ); + EditPaM aCurrentPaM = aAllSentence.Min(); + for(const auto& rCurrentNewPortion : rNewPortions) + { + //set the language attribute + LanguageType eCurLanguage = GetLanguage( aCurrentPaM ).nLang; + if(eCurLanguage != rCurrentNewPortion.eLanguage) + { + SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( rCurrentNewPortion.eLanguage ); + sal_uInt16 nLangWhichId = EE_CHAR_LANGUAGE; + switch(nScriptType) + { + case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break; + case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break; + default: break; + } + SfxItemSet aSet( aEditDoc.GetItemPool(), nLangWhichId, nLangWhichId); + aSet.Put(SvxLanguageItem(rCurrentNewPortion.eLanguage, nLangWhichId)); + SetAttribs( aCurrentPaM, aSet ); + } + //insert the new string and set the cursor to the end of the inserted string + aCurrentPaM = ImpInsertText( aCurrentPaM , rCurrentNewPortion.sText ); + } + } + UndoActionEnd(); + + EditPaM aNext; + if (bRecheck) + aNext = pSpellInfo->aCurSentenceStart; + else + { + // restore cursor position to the end of the modified sentence. + // (This will define the continuation position for spell/grammar checking) + // First: check if the sentence/para length changed + const sal_Int32 nDelta = rEditView.pImpEditView->GetEditSelection().Max().GetNode()->Len() - nOldLen; + const sal_Int32 nEndOfSentence = aOldSel.Max().GetIndex() + nDelta; + aNext = EditPaM( aOldSel.Max().GetNode(), nEndOfSentence ); + } + rEditView.pImpEditView->SetEditSelection( aNext ); + + if (IsUpdateLayout()) + FormatAndLayout(); + aEditDoc.SetModified(true); +} + +void ImpEditEngine::PutSpellingToSentenceStart( EditView const & rEditView ) +{ + if( pSpellInfo && !pSpellInfo->aLastSpellContentSelections.empty() ) + { + rEditView.pImpEditView->SetEditSelection( pSpellInfo->aLastSpellContentSelections.begin()->Min() ); + } +} + + +void ImpEditEngine::DoOnlineSpelling( ContentNode* pThisNodeOnly, bool bSpellAtCursorPos, bool bInterruptible ) +{ + /* + It will iterate over all the paragraphs, paragraphs with only + invalidated wrong list will be checked ... + + All the words are checked in the invalidated region. Is a word wrong, + but not in the wrong list, or vice versa, the range of the word will be + invalidated + (no Invalidate, but if only transitions wrong from right =>, simple Paint, + even out properly with VDev on transitions from wrong => right) + */ + + if ( !xSpeller.is() ) + return; + + EditPaM aCursorPos; + if( pActiveView && !bSpellAtCursorPos ) + { + aCursorPos = pActiveView->pImpEditView->GetEditSelection().Max(); + } + + bool bRestartTimer = false; + + ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count() - 1 ); + sal_Int32 nNodes = GetEditDoc().Count(); + sal_Int32 nInvalids = 0; + Sequence< PropertyValue > aEmptySeq; + for ( sal_Int32 n = 0; n < nNodes; n++ ) + { + ContentNode* pNode = GetEditDoc().GetObject( n ); + if ( pThisNodeOnly ) + pNode = pThisNodeOnly; + + pNode->EnsureWrongList(); + if (!pNode->GetWrongList()->IsValid()) + { + WrongList* pWrongList = pNode->GetWrongList(); + const size_t nInvStart = pWrongList->GetInvalidStart(); + const size_t nInvEnd = pWrongList->GetInvalidEnd(); + + sal_Int32 nPaintFrom = -1; + sal_Int32 nPaintTo = 0; + bool bSimpleRepaint = true; + + pWrongList->SetValid(); + + EditPaM aPaM( pNode, nInvStart ); + EditSelection aSel( aPaM, aPaM ); + while ( aSel.Max().GetNode() == pNode ) + { + if ( ( o3tl::make_unsigned(aSel.Min().GetIndex()) > nInvEnd ) + || ( ( aSel.Max().GetNode() == pLastNode ) && ( aSel.Max().GetIndex() >= pLastNode->Len() ) ) ) + break; // Document end or end of invalid region + + aSel = SelectWord( aSel, i18n::WordType::DICTIONARY_WORD ); + // If afterwards a dot, this must be handed over! + // If an abbreviation ... + bool bDottAdded = false; + if ( aSel.Max().GetIndex() < aSel.Max().GetNode()->Len() ) + { + sal_Unicode cNext = aSel.Max().GetNode()->GetChar( aSel.Max().GetIndex() ); + if ( cNext == '.' ) + { + aSel.Max().SetIndex( aSel.Max().GetIndex()+1 ); + bDottAdded = true; + } + } + OUString aWord = GetSelected(aSel); + + bool bChanged = false; + if (!aWord.isEmpty()) + { + const sal_Int32 nWStart = aSel.Min().GetIndex(); + const sal_Int32 nWEnd = aSel.Max().GetIndex(); + if ( !xSpeller->isValid( aWord, static_cast<sal_uInt16>(GetLanguage( EditPaM( aSel.Min().GetNode(), nWStart+1 ) ).nLang), aEmptySeq ) ) + { + // Check if already marked correctly... + const sal_Int32 nXEnd = bDottAdded ? nWEnd -1 : nWEnd; + if ( !pWrongList->HasWrong( nWStart, nXEnd ) ) + { + // Mark Word as wrong... + // But only when not at Cursor-Position... + bool bCursorPos = false; + if ( aCursorPos.GetNode() == pNode ) + { + if ( ( nWStart <= aCursorPos.GetIndex() ) && nWEnd >= aCursorPos.GetIndex() ) + bCursorPos = true; + } + if ( bCursorPos ) + { + // Then continue to mark as invalid ... + pWrongList->ResetInvalidRange(nWStart, nWEnd); + bRestartTimer = true; + } + else + { + // It may be that the Wrongs in the list are not + // spanning exactly over words because the + // WordDelimiters during expansion are not + // evaluated. + pWrongList->InsertWrong(nWStart, nXEnd); + bChanged = true; + } + } + } + else + { + // Check if not marked as wrong + if ( pWrongList->HasAnyWrong( nWStart, nWEnd ) ) + { + pWrongList->ClearWrongs( nWStart, nWEnd, pNode ); + bSimpleRepaint = false; + bChanged = true; + } + } + if ( bChanged ) + { + if ( nPaintFrom<0 ) + nPaintFrom = nWStart; + nPaintTo = nWEnd; + } + } + + EditPaM aLastEnd( aSel.Max() ); + aSel = WordRight( aSel.Max(), i18n::WordType::DICTIONARY_WORD ); + if ( bChanged && ( aSel.Min().GetNode() == pNode ) && + ( aSel.Min().GetIndex()-aLastEnd.GetIndex() > 1 ) ) + { + // If two words are separated by more than one blank, it + // can happen that when splitting a Wrongs the start of + // the second word is before the actually word + pWrongList->ClearWrongs( aLastEnd.GetIndex(), aSel.Min().GetIndex(), pNode ); + } + } + + // Invalidate? + if ( nPaintFrom>=0 ) + { + aStatus.GetStatusWord() |= EditStatusFlags::WRONGWORDCHANGED; + CallStatusHdl(); + + if (!aEditViews.empty()) + { + // For SimpleRepaint one was painted over a range without + // reaching VDEV, but then one would have to intersect, c + // clipping, ... over all views. Probably not worthwhile. + EditPaM aStartPaM( pNode, nPaintFrom ); + EditPaM aEndPaM( pNode, nPaintTo ); + tools::Rectangle aStartCursor( PaMtoEditCursor( aStartPaM ) ); + tools::Rectangle aEndCursor( PaMtoEditCursor( aEndPaM ) ); + DBG_ASSERT( aInvalidRect.IsEmpty(), "InvalidRect set!" ); + aInvalidRect.SetLeft( 0 ); + aInvalidRect.SetRight( GetPaperSize().Width() ); + aInvalidRect.SetTop( aStartCursor.Top() ); + aInvalidRect.SetBottom( aEndCursor.Bottom() ); + if ( pActiveView && pActiveView->HasSelection() ) + { + // Then no output through VDev. + UpdateViews(); + } + else if ( bSimpleRepaint ) + { + for (EditView* pView : aEditViews) + { + tools::Rectangle aClipRect( aInvalidRect ); + aClipRect.Intersection( pView->GetVisArea() ); + if ( !aClipRect.IsEmpty() ) + { + // convert to window coordinates... + aClipRect.SetPos( pView->pImpEditView->GetWindowPos( aClipRect.TopLeft() ) ); + pView->pImpEditView->InvalidateAtWindow(aClipRect); + } + } + } + else + { + UpdateViews( pActiveView ); + } + aInvalidRect = tools::Rectangle(); + } + } + // After two corrected nodes give up the control... + nInvalids++; + if ( bInterruptible && ( nInvalids >= 2 ) ) + { + bRestartTimer = true; + break; + } + } + + if ( pThisNodeOnly ) + break; + } + if ( bRestartTimer ) + aOnlineSpellTimer.Start(); +} + + +EESpellState ImpEditEngine::HasSpellErrors() +{ + DBG_ASSERT( xSpeller.is(), "No spell checker set!" ); + + ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count() - 1 ); + EditSelection aCurSel( aEditDoc.GetStartPaM() ); + + OUString aWord; + Reference< XSpellAlternatives > xSpellAlt; + Sequence< PropertyValue > aEmptySeq; + while ( !xSpellAlt.is() ) + { + if ( ( aCurSel.Max().GetNode() == pLastNode ) && + ( aCurSel.Max().GetIndex() >= pLastNode->Len() ) ) + { + return EESpellState::Ok; + } + + aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD ); + aWord = GetSelected( aCurSel ); + if ( !aWord.isEmpty() ) + { + LanguageType eLang = GetLanguage( aCurSel.Max() ).nLang; + SvxSpellWrapper::CheckSpellLang( xSpeller, eLang ); + xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(eLang), aEmptySeq ); + } + aCurSel = WordRight( aCurSel.Max(), css::i18n::WordType::DICTIONARY_WORD ); + } + + return EESpellState::ErrorFound; +} + +void ImpEditEngine::ClearSpellErrors() +{ + aEditDoc.ClearSpellErrors(); +} + +EESpellState ImpEditEngine::StartThesaurus(EditView* pEditView, weld::Widget* pDialogParent) +{ + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + if ( !aCurSel.HasRange() ) + aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD ); + OUString aWord( GetSelected( aCurSel ) ); + + Reference< XThesaurus > xThes( LinguMgr::GetThesaurus() ); + if (!xThes.is()) + return EESpellState::ErrorFound; + + EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractThesaurusDialog> xDlg(pFact->CreateThesaurusDialog(pDialogParent, xThes, + aWord, GetLanguage( aCurSel.Max() ).nLang )); + if (xDlg->Execute() == RET_OK) + { + // Replace Word... + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->pImpEditView->SetEditSelection( aCurSel ); + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->InsertText(xDlg->GetWord()); + pEditView->ShowCursor(true, false); + } + + return EESpellState::Ok; +} + +sal_Int32 ImpEditEngine::StartSearchAndReplace( EditView* pEditView, const SvxSearchItem& rSearchItem ) +{ + sal_Int32 nFound = 0; + + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + + // FIND_ALL is not possible without multiple selection. + if ( ( rSearchItem.GetCommand() == SvxSearchCmd::FIND ) || + ( rSearchItem.GetCommand() == SvxSearchCmd::FIND_ALL ) ) + { + if ( Search( rSearchItem, pEditView ) ) + nFound++; + } + else if ( rSearchItem.GetCommand() == SvxSearchCmd::REPLACE ) + { + // The word is selected if the user not altered the selection + // in between: + if ( aCurSel.HasRange() ) + { + pEditView->InsertText( rSearchItem.GetReplaceString() ); + nFound = 1; + } + else + if( Search( rSearchItem, pEditView ) ) + nFound = 1; + } + else if ( rSearchItem.GetCommand() == SvxSearchCmd::REPLACE_ALL ) + { + // The Writer replaces all front beginning to end ... + SvxSearchItem aTmpItem( rSearchItem ); + aTmpItem.SetBackward( false ); + + pEditView->pImpEditView->DrawSelectionXOR(); + + aCurSel.Adjust( aEditDoc ); + EditPaM aStartPaM = aTmpItem.GetSelection() ? aCurSel.Min() : aEditDoc.GetStartPaM(); + EditSelection aFoundSel( aCurSel.Max() ); + bool bFound = ImpSearch( aTmpItem, aCurSel, aStartPaM, aFoundSel ); + if ( bFound ) + UndoActionStart( EDITUNDO_REPLACEALL ); + while ( bFound ) + { + nFound++; + aStartPaM = ImpInsertText( aFoundSel, rSearchItem.GetReplaceString() ); + bFound = ImpSearch( aTmpItem, aCurSel, aStartPaM, aFoundSel ); + } + if ( nFound ) + { + EditPaM aNewPaM( aFoundSel.Max() ); + if ( aNewPaM.GetIndex() > aNewPaM.GetNode()->Len() ) + aNewPaM.SetIndex( aNewPaM.GetNode()->Len() ); + pEditView->pImpEditView->SetEditSelection( aNewPaM ); + FormatAndLayout( pEditView ); + UndoActionEnd(); + } + else + { + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + } + } + return nFound; +} + +bool ImpEditEngine::Search( const SvxSearchItem& rSearchItem, EditView* pEditView ) +{ + EditSelection aSel( pEditView->pImpEditView->GetEditSelection() ); + aSel.Adjust( aEditDoc ); + EditPaM aStartPaM( aSel.Max() ); + if ( rSearchItem.GetSelection() && !rSearchItem.GetBackward() ) + aStartPaM = aSel.Min(); + + EditSelection aFoundSel; + bool bFound = ImpSearch( rSearchItem, aSel, aStartPaM, aFoundSel ); + if ( bFound && ( aFoundSel == aSel ) ) // For backwards-search + { + aStartPaM = aSel.Min(); + bFound = ImpSearch( rSearchItem, aSel, aStartPaM, aFoundSel ); + } + + pEditView->pImpEditView->DrawSelectionXOR(); + if ( bFound ) + { + // First, set the minimum, so the whole word is in the visible range. + pEditView->pImpEditView->SetEditSelection( aFoundSel.Min() ); + pEditView->ShowCursor( true, false ); + pEditView->pImpEditView->SetEditSelection( aFoundSel ); + } + else + pEditView->pImpEditView->SetEditSelection( aSel.Max() ); + + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + return bFound; +} + +bool ImpEditEngine::ImpSearch( const SvxSearchItem& rSearchItem, + const EditSelection& rSearchSelection, const EditPaM& rStartPos, EditSelection& rFoundSel ) +{ + i18nutil::SearchOptions2 aSearchOptions( rSearchItem.GetSearchOptions() ); + aSearchOptions.Locale = GetLocale( rStartPos ); + + bool bBack = rSearchItem.GetBackward(); + bool bSearchInSelection = rSearchItem.GetSelection(); + sal_Int32 nStartNode = aEditDoc.GetPos( rStartPos.GetNode() ); + sal_Int32 nEndNode; + if ( bSearchInSelection ) + { + nEndNode = aEditDoc.GetPos( bBack ? rSearchSelection.Min().GetNode() : rSearchSelection.Max().GetNode() ); + } + else + { + nEndNode = bBack ? 0 : aEditDoc.Count()-1; + } + + utl::TextSearch aSearcher( aSearchOptions ); + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; + bBack ? ( nNode >= nEndNode ) : ( nNode <= nEndNode) ; + bBack ? nNode-- : nNode++ ) + { + // For backwards-search if nEndNode = 0: + if ( nNode < 0 ) + return false; + + ContentNode* pNode = aEditDoc.GetObject( nNode ); + + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = pNode->GetExpandedLen(); + if ( nNode == nStartNode ) + { + if ( bBack ) + nEndPos = rStartPos.GetIndex(); + else + nStartPos = rStartPos.GetIndex(); + } + if ( ( nNode == nEndNode ) && bSearchInSelection ) + { + if ( bBack ) + nStartPos = rSearchSelection.Min().GetIndex(); + else + nEndPos = rSearchSelection.Max().GetIndex(); + } + + // Searching ... + OUString aParaStr( pNode->GetExpandedText() ); + bool bFound = false; + if ( bBack ) + { + sal_Int32 nTemp; + nTemp = nStartPos; + nStartPos = nEndPos; + nEndPos = nTemp; + + bFound = aSearcher.SearchBackward( aParaStr, &nStartPos, &nEndPos); + } + else + { + bFound = aSearcher.SearchForward( aParaStr, &nStartPos, &nEndPos); + } + if ( bFound ) + { + pNode->UnExpandPositions( nStartPos, nEndPos ); + + rFoundSel.Min().SetNode( pNode ); + rFoundSel.Min().SetIndex( nStartPos ); + rFoundSel.Max().SetNode( pNode ); + rFoundSel.Max().SetIndex( nEndPos ); + return true; + } + } + return false; +} + +bool ImpEditEngine::HasText( const SvxSearchItem& rSearchItem ) +{ + SvxSearchItem aTmpItem( rSearchItem ); + aTmpItem.SetBackward( false ); + aTmpItem.SetSelection( false ); + + EditPaM aStartPaM( aEditDoc.GetStartPaM() ); + EditSelection aDummySel( aStartPaM ); + EditSelection aFoundSel; + return ImpSearch( aTmpItem, aDummySel, aStartPaM, aFoundSel ); +} + +void ImpEditEngine::SetAutoCompleteText(const OUString& rStr, bool bClearTipWindow) +{ + aAutoCompleteText = rStr; + if ( bClearTipWindow && pActiveView ) + Help::ShowQuickHelp( pActiveView->GetWindow(), tools::Rectangle(), OUString() ); +} + +namespace +{ + struct eeTransliterationChgData + { + sal_Int32 nStart; + sal_Int32 nLen; + EditSelection aSelection; + OUString aNewText; + uno::Sequence< sal_Int32 > aOffsets; + }; +} + +EditSelection ImpEditEngine::TransliterateText( const EditSelection& rSelection, TransliterationFlags nTransliterationMode ) +{ + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + if (!_xBI.is()) + return rSelection; + + EditSelection aSel( rSelection ); + aSel.Adjust( aEditDoc ); + + if ( !aSel.HasRange() ) + { + /* Cursor is inside of a word */ + if (nTransliterationMode == TransliterationFlags::SENTENCE_CASE) + aSel = SelectSentence( aSel ); + else + aSel = SelectWord( aSel ); + } + + // tdf#107176: if there's still no range, just return aSel + if ( !aSel.HasRange() ) + return aSel; + + EditSelection aNewSel( aSel ); + + const sal_Int32 nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() ); + const sal_Int32 nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() ); + + bool bChanges = false; + bool bLenChanged = false; + std::unique_ptr<EditUndoTransliteration> pUndo; + + utl::TransliterationWrapper aTransliterationWrapper( ::comphelper::getProcessComponentContext(), nTransliterationMode ); + bool bConsiderLanguage = aTransliterationWrapper.needLanguageForTheMode(); + + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = aEditDoc.GetObject( nNode ); + const OUString& aNodeStr = pNode->GetString(); + const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; + const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : aNodeStr.getLength(); // can also be == nStart! + + sal_Int32 nCurrentStart = nStartPos; + sal_Int32 nCurrentEnd = nEndPos; + LanguageType nLanguage = LANGUAGE_SYSTEM; + + // since we don't use Hiragana/Katakana or half-width/full-width transliterations here + // it is fine to use ANYWORD_IGNOREWHITESPACES. (ANY_WORD btw is broken and will + // occasionally miss words in consecutive sentences). Also with ANYWORD_IGNOREWHITESPACES + // text like 'just-in-time' will be converted to 'Just-In-Time' which seems to be the + // proper thing to do. + const sal_Int16 nWordType = i18n::WordType::ANYWORD_IGNOREWHITESPACES; + + //! In order to have less trouble with changing text size, e.g. because + //! of ligatures or German small sz being resolved, we need to process + //! the text replacements from end to start. + //! This way the offsets for the yet to be changed words will be + //! left unchanged by the already replaced text. + //! For this we temporarily save the changes to be done in this vector + std::vector< eeTransliterationChgData > aChanges; + eeTransliterationChgData aChgData; + + if (nTransliterationMode == TransliterationFlags::TITLE_CASE) + { + // for 'capitalize every word' we need to iterate over each word + + i18n::Boundary aSttBndry; + i18n::Boundary aEndBndry; + aSttBndry = _xBI->getWordBoundary( + aNodeStr, nStartPos, + GetLocale( EditPaM( pNode, nStartPos + 1 ) ), + nWordType, true /*prefer forward direction*/); + aEndBndry = _xBI->getWordBoundary( + aNodeStr, nEndPos, + GetLocale( EditPaM( pNode, nEndPos + 1 ) ), + nWordType, false /*prefer backward direction*/); + + // prevent backtracking to the previous word if selection is at word boundary + if (aSttBndry.endPos <= nStartPos) + { + aSttBndry = _xBI->nextWord( + aNodeStr, aSttBndry.endPos, + GetLocale( EditPaM( pNode, aSttBndry.endPos + 1 ) ), + nWordType); + } + // prevent advancing to the next word if selection is at word boundary + if (aEndBndry.startPos >= nEndPos) + { + aEndBndry = _xBI->previousWord( + aNodeStr, aEndBndry.startPos, + GetLocale( EditPaM( pNode, aEndBndry.startPos + 1 ) ), + nWordType); + } + + /* Nothing to do if user selection lies entirely outside of word start and end boundary computed above. + * Skip this node, because otherwise the below logic for constraining to the selection will fail */ + if (aSttBndry.startPos >= aSel.Max().GetIndex() || aEndBndry.endPos <= aSel.Min().GetIndex()) { + continue; + } + + // prevent going outside of the user's selection, which may + // start or end in the middle of a word + if (nNode == nStartNode) { + aSttBndry.startPos = std::max(aSttBndry.startPos, aSel.Min().GetIndex()); + aSttBndry.endPos = std::min(aSttBndry.endPos, aSel.Max().GetIndex()); + aEndBndry.startPos = std::max(aEndBndry.startPos, aSttBndry.startPos); + aEndBndry.endPos = std::min(aEndBndry.endPos, aSel.Max().GetIndex()); + } + + i18n::Boundary aCurWordBndry( aSttBndry ); + while (aCurWordBndry.endPos && aCurWordBndry.startPos <= aEndBndry.startPos) + { + nCurrentStart = aCurWordBndry.startPos; + nCurrentEnd = aCurWordBndry.endPos; + sal_Int32 nLen = nCurrentEnd - nCurrentStart; + DBG_ASSERT( nLen > 0, "invalid word length of 0" ); + + Sequence< sal_Int32 > aOffsets; + OUString aNewText( aTransliterationWrapper.transliterate(aNodeStr, + GetLanguage( EditPaM( pNode, nCurrentStart + 1 ) ).nLang, + nCurrentStart, nLen, &aOffsets )); + + if (aNodeStr != aNewText) + { + aChgData.nStart = nCurrentStart; + aChgData.nLen = nLen; + aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) ); + aChgData.aNewText = aNewText; + aChgData.aOffsets = aOffsets; + aChanges.push_back( aChgData ); + } +#if OSL_DEBUG_LEVEL > 1 + OUString aSelTxt ( GetSelected( aChgData.aSelection ) ); + (void) aSelTxt; +#endif + + aCurWordBndry = _xBI->nextWord(aNodeStr, nCurrentStart, + GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ), + nWordType); + } + DBG_ASSERT( nCurrentEnd >= aEndBndry.endPos, "failed to reach end of transliteration" ); + } + else if (nTransliterationMode == TransliterationFlags::SENTENCE_CASE) + { + // for 'sentence case' we need to iterate sentence by sentence + + sal_Int32 nLastStart = _xBI->beginOfSentence( + aNodeStr, nEndPos, + GetLocale( EditPaM( pNode, nEndPos + 1 ) ) ); + sal_Int32 nLastEnd = _xBI->endOfSentence( + aNodeStr, nLastStart, + GetLocale( EditPaM( pNode, nLastStart + 1 ) ) ); + + // extend nCurrentStart, nCurrentEnd to the current sentence boundaries + nCurrentStart = _xBI->beginOfSentence( + aNodeStr, nStartPos, + GetLocale( EditPaM( pNode, nStartPos + 1 ) ) ); + nCurrentEnd = _xBI->endOfSentence( + aNodeStr, nCurrentStart, + GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) ); + + // prevent backtracking to the previous sentence if selection starts at end of a sentence + if (nCurrentEnd <= nStartPos) + { + // now nCurrentStart is probably located on a non-letter word. (unless we + // are in Asian text with no spaces...) + // Thus to get the real sentence start we should locate the next real word, + // that is one found by DICTIONARY_WORD + i18n::Boundary aBndry = _xBI->nextWord( aNodeStr, nCurrentEnd, + GetLocale( EditPaM( pNode, nCurrentEnd + 1 ) ), + i18n::WordType::DICTIONARY_WORD); + + // now get new current sentence boundaries + nCurrentStart = _xBI->beginOfSentence( + aNodeStr, aBndry.startPos, + GetLocale( EditPaM( pNode, aBndry.startPos + 1 ) ) ); + nCurrentEnd = _xBI->endOfSentence( + aNodeStr, nCurrentStart, + GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) ); + } + // prevent advancing to the next sentence if selection ends at start of a sentence + if (nLastStart >= nEndPos) + { + // now nCurrentStart is probably located on a non-letter word. (unless we + // are in Asian text with no spaces...) + // Thus to get the real sentence start we should locate the previous real word, + // that is one found by DICTIONARY_WORD + i18n::Boundary aBndry = _xBI->previousWord( aNodeStr, nLastStart, + GetLocale( EditPaM( pNode, nLastStart + 1 ) ), + i18n::WordType::DICTIONARY_WORD); + nLastEnd = _xBI->endOfSentence( + aNodeStr, aBndry.startPos, + GetLocale( EditPaM( pNode, aBndry.startPos + 1 ) ) ); + if (nCurrentEnd > nLastEnd) + nCurrentEnd = nLastEnd; + } + + // prevent making any change outside of the user's selection + nCurrentStart = std::max(aSel.Min().GetIndex(), nCurrentStart); + nCurrentEnd = std::min(aSel.Max().GetIndex(), nCurrentEnd); + nLastStart = std::max(aSel.Min().GetIndex(), nLastStart); + nLastEnd = std::min(aSel.Max().GetIndex(), nLastEnd); + + while (nCurrentStart < nLastEnd) + { + const sal_Int32 nLen = nCurrentEnd - nCurrentStart; + DBG_ASSERT( nLen > 0, "invalid word length of 0" ); + + Sequence< sal_Int32 > aOffsets; + OUString aNewText( aTransliterationWrapper.transliterate( aNodeStr, + GetLanguage( EditPaM( pNode, nCurrentStart + 1 ) ).nLang, + nCurrentStart, nLen, &aOffsets )); + + if (aNodeStr != aNewText) + { + aChgData.nStart = nCurrentStart; + aChgData.nLen = nLen; + aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) ); + aChgData.aNewText = aNewText; + aChgData.aOffsets = aOffsets; + aChanges.push_back( aChgData ); + } + + i18n::Boundary aFirstWordBndry = _xBI->nextWord( + aNodeStr, nCurrentEnd, + GetLocale( EditPaM( pNode, nCurrentEnd + 1 ) ), + nWordType); + nCurrentStart = aFirstWordBndry.startPos; + nCurrentEnd = _xBI->endOfSentence( + aNodeStr, nCurrentStart, + GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) ); + } + DBG_ASSERT( nCurrentEnd >= nLastEnd, "failed to reach end of transliteration" ); + } + else + { + do + { + if ( bConsiderLanguage ) + { + nLanguage = GetLanguage( EditPaM( pNode, nCurrentStart+1 ), &nCurrentEnd ).nLang; + if ( nCurrentEnd > nEndPos ) + nCurrentEnd = nEndPos; + } + + const sal_Int32 nLen = nCurrentEnd - nCurrentStart; + + Sequence< sal_Int32 > aOffsets; + OUString aNewText( aTransliterationWrapper.transliterate( aNodeStr, nLanguage, nCurrentStart, nLen, &aOffsets ) ); + + if (aNodeStr != aNewText) + { + aChgData.nStart = nCurrentStart; + aChgData.nLen = nLen; + aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) ); + aChgData.aNewText = aNewText; + aChgData.aOffsets = aOffsets; + aChanges.push_back( aChgData ); + } + + nCurrentStart = nCurrentEnd; + } while( nCurrentEnd < nEndPos ); + } + + if (!aChanges.empty()) + { + // Create a single UndoAction on Demand for all the changes ... + if ( !pUndo && IsUndoEnabled() && !IsInUndo() ) + { + // adjust selection to include all changes + for (const eeTransliterationChgData & aChange : aChanges) + { + const EditSelection &rSel = aChange.aSelection; + if (aSel.Min().GetNode() == rSel.Min().GetNode() && + aSel.Min().GetIndex() > rSel.Min().GetIndex()) + aSel.Min().SetIndex( rSel.Min().GetIndex() ); + if (aSel.Max().GetNode() == rSel.Max().GetNode() && + aSel.Max().GetIndex() < rSel.Max().GetIndex()) + aSel.Max().SetIndex( rSel.Max().GetIndex() ); + } + aNewSel = aSel; + + ESelection aESel( CreateESel( aSel ) ); + pUndo.reset(new EditUndoTransliteration(pEditEngine, aESel, nTransliterationMode)); + + const bool bSingleNode = aSel.Min().GetNode()== aSel.Max().GetNode(); + const bool bHasAttribs = aSel.Min().GetNode()->GetCharAttribs().HasAttrib( aSel.Min().GetIndex(), aSel.Max().GetIndex() ); + if (bSingleNode && !bHasAttribs) + pUndo->SetText( aSel.Min().GetNode()->Copy( aSel.Min().GetIndex(), aSel.Max().GetIndex()-aSel.Min().GetIndex() ) ); + else + pUndo->SetText( CreateTextObject( aSel, nullptr ) ); + } + + // now apply the changes from end to start to leave the offsets of the + // yet unchanged text parts remain the same. + for (size_t i = 0; i < aChanges.size(); ++i) + { + eeTransliterationChgData& rData = aChanges[ aChanges.size() - 1 - i ]; + + bChanges = true; + if (rData.nLen != rData.aNewText.getLength()) + bLenChanged = true; + + // Change text without losing the attributes + const sal_Int32 nDiffs = + ReplaceTextOnly( rData.aSelection.Min().GetNode(), + rData.nStart, rData.aNewText, rData.aOffsets ); + + // adjust selection in end node to possibly changed size + if (aSel.Max().GetNode() == rData.aSelection.Max().GetNode()) + aNewSel.Max().SetIndex( aNewSel.Max().GetIndex() + nDiffs ); + + sal_Int32 nSelNode = aEditDoc.GetPos( rData.aSelection.Min().GetNode() ); + ParaPortion* pParaPortion = GetParaPortions()[nSelNode]; + pParaPortion->MarkSelectionInvalid( rData.nStart ); + } + } + } + + if ( pUndo ) + { + ESelection aESel( CreateESel( aNewSel ) ); + pUndo->SetNewSelection( aESel ); + InsertUndo( std::move(pUndo) ); + } + + if ( bChanges ) + { + TextModified(); + SetModifyFlag( true ); + if ( bLenChanged ) + UpdateSelections(); + if (IsUpdateLayout()) + FormatAndLayout(); + } + + return aNewSel; +} + + +short ImpEditEngine::ReplaceTextOnly( + ContentNode* pNode, + sal_Int32 nCurrentStart, + std::u16string_view rNewText, + const uno::Sequence< sal_Int32 >& rOffsets ) +{ + // Change text without losing the attributes + sal_Int32 nCharsAfterTransliteration = rOffsets.getLength(); + const sal_Int32* pOffsets = rOffsets.getConstArray(); + short nDiffs = 0; + for ( sal_Int32 n = 0; n < nCharsAfterTransliteration; n++ ) + { + sal_Int32 nCurrentPos = nCurrentStart+n; + sal_Int32 nDiff = (nCurrentPos-nDiffs) - pOffsets[n]; + + if ( !nDiff ) + { + DBG_ASSERT( nCurrentPos < pNode->Len(), "TransliterateText - String smaller than expected!" ); + pNode->SetChar( nCurrentPos, rNewText[n] ); + } + else if ( nDiff < 0 ) + { + // Replace first char, delete the rest... + DBG_ASSERT( nCurrentPos < pNode->Len(), "TransliterateText - String smaller than expected!" ); + pNode->SetChar( nCurrentPos, rNewText[n] ); + + DBG_ASSERT( (nCurrentPos+1) < pNode->Len(), "TransliterateText - String smaller than expected!" ); + GetEditDoc().RemoveChars( EditPaM( pNode, nCurrentPos+1 ), -nDiff); + } + else + { + DBG_ASSERT( nDiff == 1, "TransliterateText - Diff other than expected! But should work..." ); + GetEditDoc().InsertText( EditPaM( pNode, nCurrentPos ), OUString(rNewText[n]) ); + + } + nDiffs = sal::static_int_cast< short >(nDiffs + nDiff); + } + + return nDiffs; +} + + +void ImpEditEngine::SetAsianCompressionMode( CharCompressType n ) +{ + if ( n != nAsianCompressionMode ) + { + nAsianCompressionMode = n; + if ( ImplHasText() ) + { + FormatFullDoc(); + UpdateViews(); + } + } +} + +void ImpEditEngine::SetKernAsianPunctuation( bool b ) +{ + if ( b != bKernAsianPunctuation ) + { + bKernAsianPunctuation = b; + if ( ImplHasText() ) + { + FormatFullDoc(); + UpdateViews(); + } + } +} + +void ImpEditEngine::SetAddExtLeading( bool bExtLeading ) +{ + if ( IsAddExtLeading() != bExtLeading ) + { + bAddExtLeading = bExtLeading; + if ( ImplHasText() ) + { + FormatFullDoc(); + UpdateViews(); + } + } +}; + + +bool ImpEditEngine::ImplHasText() const +{ + return ( ( GetEditDoc().Count() > 1 ) || GetEditDoc().GetObject(0)->Len() ); +} + +sal_Int32 ImpEditEngine::LogicToTwips(sal_Int32 n) +{ + Size aSz(n, 0); + MapMode aTwipsMode( MapUnit::MapTwip ); + aSz = pRefDev->LogicToLogic( aSz, nullptr, &aTwipsMode ); + return aSz.Width(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit5.cxx b/editeng/source/editeng/impedit5.cxx new file mode 100644 index 000000000..79825dc4d --- /dev/null +++ b/editeng/source/editeng/impedit5.cxx @@ -0,0 +1,845 @@ +/* -*- 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 "impedit.hxx" +#include <editeng/editeng.hxx> +#include <svl/hint.hxx> +#include <sfx2/app.hxx> +#include <utility> + +void ImpEditEngine::SetStyleSheetPool( SfxStyleSheetPool* pSPool ) +{ + if ( pStylePool != pSPool ) + { + pStylePool = pSPool; + } +} + +const SfxStyleSheet* ImpEditEngine::GetStyleSheet( sal_Int32 nPara ) const +{ + const ContentNode* pNode = aEditDoc.GetObject( nPara ); + return pNode ? pNode->GetContentAttribs().GetStyleSheet() : nullptr; +} + +SfxStyleSheet* ImpEditEngine::GetStyleSheet( sal_Int32 nPara ) +{ + ContentNode* pNode = aEditDoc.GetObject( nPara ); + return pNode ? pNode->GetContentAttribs().GetStyleSheet() : nullptr; +} + +void ImpEditEngine::SetStyleSheet( EditSelection aSel, SfxStyleSheet* pStyle ) +{ + aSel.Adjust( aEditDoc ); + + sal_Int32 nStartPara = aEditDoc.GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndPara = aEditDoc.GetPos( aSel.Max().GetNode() ); + + bool _bUpdate = SetUpdateLayout( false ); + + for ( sal_Int32 n = nStartPara; n <= nEndPara; n++ ) + SetStyleSheet( n, pStyle ); + + SetUpdateLayout( _bUpdate ); +} + +void ImpEditEngine::SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle ) +{ + DBG_ASSERT( GetStyleSheetPool() || !pStyle, "SetStyleSheet: No StyleSheetPool registered!" ); + ContentNode* pNode = aEditDoc.GetObject( nPara ); + SfxStyleSheet* pCurStyle = pNode->GetStyleSheet(); + if ( pStyle != pCurStyle ) + { + if ( IsUndoEnabled() && !IsInUndo() && aStatus.DoUndoAttribs() ) + { + OUString aPrevStyleName; + if ( pCurStyle ) + aPrevStyleName = pCurStyle->GetName(); + + OUString aNewStyleName; + if ( pStyle ) + aNewStyleName = pStyle->GetName(); + + InsertUndo( + std::make_unique<EditUndoSetStyleSheet>(pEditEngine, aEditDoc.GetPos( pNode ), + aPrevStyleName, pCurStyle ? pCurStyle->GetFamily() : SfxStyleFamily::Para, + aNewStyleName, pStyle ? pStyle->GetFamily() : SfxStyleFamily::Para, + pNode->GetContentAttribs().GetItems() ) ); + } + if ( pCurStyle ) + EndListening( *pCurStyle ); + pNode->SetStyleSheet( pStyle, aStatus.UseCharAttribs() ); + if ( pStyle ) + StartListening(*pStyle, DuplicateHandling::Prevent); + ParaAttribsChanged( pNode ); + } + if (IsUpdateLayout()) + FormatAndLayout(); +} + +void ImpEditEngine::UpdateParagraphsWithStyleSheet( SfxStyleSheet* pStyle ) +{ + SvxFont aFontFromStyle; + CreateFont( aFontFromStyle, pStyle->GetItemSet() ); + + bool bUsed = false; + for ( sal_Int32 nNode = 0; nNode < aEditDoc.Count(); nNode++ ) + { + ContentNode* pNode = aEditDoc.GetObject( nNode ); + if ( pNode->GetStyleSheet() == pStyle ) + { + bUsed = true; + if ( aStatus.UseCharAttribs() ) + pNode->SetStyleSheet( pStyle, aFontFromStyle ); + else + pNode->SetStyleSheet( pStyle, false ); + + ParaAttribsChanged( pNode ); + } + } + if ( bUsed ) + { + GetEditEnginePtr()->StyleSheetChanged( pStyle ); + if (IsUpdateLayout()) + FormatAndLayout(); + } +} + +void ImpEditEngine::RemoveStyleFromParagraphs( SfxStyleSheet const * pStyle ) +{ + for ( sal_Int32 nNode = 0; nNode < aEditDoc.Count(); nNode++ ) + { + ContentNode* pNode = aEditDoc.GetObject(nNode); + if ( pNode->GetStyleSheet() == pStyle ) + { + pNode->SetStyleSheet( nullptr ); + ParaAttribsChanged( pNode ); + } + } + if (IsUpdateLayout()) + FormatAndLayout(); +} + +void ImpEditEngine::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + // So that not a lot of unnecessary formatting is done when destructing: + if ( !bDowning ) + { + + const SfxStyleSheetHint* pStyleSheetHint = dynamic_cast<const SfxStyleSheetHint*>(&rHint); + if ( pStyleSheetHint ) + { + DBG_ASSERT( dynamic_cast< const SfxStyleSheet* >(pStyleSheetHint->GetStyleSheet()) != nullptr, "No SfxStyleSheet!" ); + SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>( pStyleSheetHint->GetStyleSheet() ); + SfxHintId nId = pStyleSheetHint->GetId(); + if ( ( nId == SfxHintId::StyleSheetInDestruction ) || + ( nId == SfxHintId::StyleSheetErased ) ) + { + RemoveStyleFromParagraphs( pStyle ); + } + else if ( nId == SfxHintId::StyleSheetModified ) + { + UpdateParagraphsWithStyleSheet( pStyle ); + } + } + else if ( auto pStyle = dynamic_cast< SfxStyleSheet* >(&rBC) ) + { + SfxHintId nId = rHint.GetId(); + if ( nId == SfxHintId::Dying ) + { + RemoveStyleFromParagraphs( pStyle ); + } + else if ( nId == SfxHintId::DataChanged ) + { + UpdateParagraphsWithStyleSheet( pStyle ); + } + } + } + if(dynamic_cast<const SfxApplication*>(&rBC) != nullptr && rHint.GetId() == SfxHintId::Dying) + Dispose(); +} + +std::unique_ptr<EditUndoSetAttribs> ImpEditEngine::CreateAttribUndo( EditSelection aSel, const SfxItemSet& rSet ) +{ + DBG_ASSERT( !aSel.DbgIsBuggy( aEditDoc ), "CreateAttribUndo: Incorrect selection "); + aSel.Adjust( aEditDoc ); + + ESelection aESel( CreateESel( aSel ) ); + + sal_Int32 nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() ); + + DBG_ASSERT( nStartNode <= nEndNode, "CreateAttribUndo: Start > End ?!" ); + + std::unique_ptr<EditUndoSetAttribs> pUndo; + if ( rSet.GetPool() != &aEditDoc.GetItemPool() ) + { + SfxItemSet aTmpSet( GetEmptyItemSet() ); + aTmpSet.Put( rSet ); + pUndo.reset( new EditUndoSetAttribs(pEditEngine, aESel, std::move(aTmpSet)) ); + } + else + { + pUndo.reset( new EditUndoSetAttribs(pEditEngine, aESel, rSet) ); + } + + SfxItemPool* pPool = pUndo->GetNewAttribs().GetPool(); + + for ( sal_Int32 nPara = nStartNode; nPara <= nEndNode; nPara++ ) + { + ContentNode* pNode = aEditDoc.GetObject( nPara ); + DBG_ASSERT( aEditDoc.GetObject( nPara ), "Node not found: CreateAttribUndo" ); + ContentAttribsInfo* pInf = new ContentAttribsInfo( pNode->GetContentAttribs().GetItems() ); + pUndo->AppendContentInfo(pInf); + + for ( sal_Int32 nAttr = 0; nAttr < pNode->GetCharAttribs().Count(); nAttr++ ) + { + const EditCharAttrib& rAttr = *pNode->GetCharAttribs().GetAttribs()[nAttr]; + if (rAttr.GetLen()) + { + EditCharAttrib* pNew = MakeCharAttrib(*pPool, *rAttr.GetItem(), rAttr.GetStart(), rAttr.GetEnd()); + pInf->AppendCharAttrib(pNew); + } + } + } + return pUndo; +} + +ViewShellId ImpEditEngine::CreateViewShellId() +{ + ViewShellId nRet(-1); + + const EditView* pEditView = pEditEngine ? pEditEngine->GetActiveView() : nullptr; + const OutlinerViewShell* pViewShell = pEditView ? pEditView->GetImpEditView()->GetViewShell() : nullptr; + if (pViewShell) + nRet = pViewShell->GetViewShellId(); + + return nRet; +} + +void ImpEditEngine::UndoActionStart( sal_uInt16 nId, const ESelection& aSel ) +{ + if ( IsUndoEnabled() && !IsInUndo() ) + { + GetUndoManager().EnterListAction( GetEditEnginePtr()->GetUndoComment( nId ), OUString(), nId, CreateViewShellId() ); + DBG_ASSERT( !pUndoMarkSelection, "UndoAction SelectionMarker?" ); + pUndoMarkSelection.reset(new ESelection( aSel )); + } +} + +void ImpEditEngine::UndoActionStart( sal_uInt16 nId ) +{ + if ( IsUndoEnabled() && !IsInUndo() ) + { + GetUndoManager().EnterListAction( GetEditEnginePtr()->GetUndoComment( nId ), OUString(), nId, CreateViewShellId() ); + DBG_ASSERT( !pUndoMarkSelection, "UndoAction SelectionMarker?" ); + } +} + +void ImpEditEngine::UndoActionEnd() +{ + if ( IsUndoEnabled() && !IsInUndo() ) + { + GetUndoManager().LeaveListAction(); + pUndoMarkSelection.reset(); + } +} + +void ImpEditEngine::InsertUndo( std::unique_ptr<EditUndo> pUndo, bool bTryMerge ) +{ + DBG_ASSERT( !IsInUndo(), "InsertUndo in Undo mode!" ); + if ( pUndoMarkSelection ) + { + GetUndoManager().AddUndoAction( std::make_unique<EditUndoMarkSelection>(pEditEngine, *pUndoMarkSelection) ); + pUndoMarkSelection.reset(); + } + GetUndoManager().AddUndoAction( std::move(pUndo), bTryMerge ); + + mbLastTryMerge = bTryMerge; +} + +void ImpEditEngine::ResetUndoManager() +{ + if ( HasUndoManager() ) + GetUndoManager().Clear(); +} + +void ImpEditEngine::EnableUndo( bool bEnable ) +{ + // When switching the mode Delete list: + if ( bEnable != IsUndoEnabled() ) + ResetUndoManager(); + + bUndoEnabled = bEnable; +} + +void ImpEditEngine::Undo( EditView* pView ) +{ + if ( HasUndoManager() && GetUndoManager().GetUndoActionCount() ) + { + SetActiveView( pView ); + GetUndoManager().Undo(); + } +} + +void ImpEditEngine::Redo( EditView* pView ) +{ + if ( HasUndoManager() && GetUndoManager().GetRedoActionCount() ) + { + SetActiveView( pView ); + GetUndoManager().Redo(); + } +} + +SfxItemSet ImpEditEngine::GetAttribs( EditSelection aSel, EditEngineAttribs nOnlyHardAttrib ) +{ + + aSel.Adjust( aEditDoc ); + + SfxItemSet aCurSet( GetEmptyItemSet() ); + + sal_Int32 nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() ); + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = aEditDoc.GetObject( nNode ); + DBG_ASSERT( aEditDoc.GetObject( nNode ), "Node not found: GetAttrib" ); + + const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; + const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // Can also be == nStart! + + // Problem: Templates... + // => Other way: + // 1) Hard character attributes, as usual... + // 2) Examine Style and paragraph attributes only when OFF... + + // First the very hard formatting... + EditDoc::FindAttribs( pNode, nStartPos, nEndPos, aCurSet ); + + if( nOnlyHardAttrib != EditEngineAttribs::OnlyHard ) + { + // and then paragraph formatting and template... + for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++) + { + if ( aCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT ) + { + if ( nOnlyHardAttrib == EditEngineAttribs::All ) + { + const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItem( nWhich ); + aCurSet.Put( rItem ); + } + else if ( pNode->GetContentAttribs().GetItems().GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItems().Get( nWhich ); + aCurSet.Put( rItem ); + } + } + else if ( aCurSet.GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem* pItem = nullptr; + if ( nOnlyHardAttrib == EditEngineAttribs::All ) + { + pItem = &pNode->GetContentAttribs().GetItem( nWhich ); + } + else if ( pNode->GetContentAttribs().GetItems().GetItemState( nWhich ) == SfxItemState::SET ) + { + pItem = &pNode->GetContentAttribs().GetItems().Get( nWhich ); + } + // pItem can only be NULL when nOnlyHardAttrib... + if ( !pItem || ( *pItem != aCurSet.Get( nWhich ) ) ) + { + // Problem: When Paragraph style with for example font, + // but the Font is hard and completely different, + // wrong in selection if invalidated.... + // => better not invalidate, instead CHANGE! + // It would be better to fill each paragraph with + // an itemset and compare this in large. + if ( nWhich <= EE_PARA_END ) + aCurSet.InvalidateItem( nWhich ); + } + } + } + } + } + + // fill empty slots with defaults ... + if ( nOnlyHardAttrib == EditEngineAttribs::All ) + { + for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + if ( aCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT ) + { + aCurSet.Put( aEditDoc.GetItemPool().GetDefaultItem( nWhich ) ); + } + } + } + return aCurSet; +} + + +SfxItemSet ImpEditEngine::GetAttribs( sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, GetAttribsFlags nFlags ) const +{ + // Optimized function with fewer Puts(), which cause unnecessary cloning from default items. + // If this works, change GetAttribs( EditSelection ) to use this for each paragraph and merge the results! + + + ContentNode* pNode = const_cast<ContentNode*>(aEditDoc.GetObject(nPara)); + DBG_ASSERT( pNode, "GetAttribs - unknown paragraph!" ); + DBG_ASSERT( nStart <= nEnd, "getAttribs: Start > End not supported!" ); + + SfxItemSet aAttribs(GetEmptyItemSet()); + + if ( pNode ) + { + if ( nEnd > pNode->Len() ) + nEnd = pNode->Len(); + + if ( nStart > nEnd ) + nStart = nEnd; + + // StyleSheet / Parattribs... + + if ( pNode->GetStyleSheet() && ( nFlags & GetAttribsFlags::STYLESHEET ) ) + aAttribs.Set(pNode->GetStyleSheet()->GetItemSet()); + + if ( nFlags & GetAttribsFlags::PARAATTRIBS ) + aAttribs.Put( pNode->GetContentAttribs().GetItems() ); + + // CharAttribs... + + if ( nFlags & GetAttribsFlags::CHARATTRIBS ) + { + // Make testing easier... + const SfxItemPool& rPool = GetEditDoc().GetItemPool(); + pNode->GetCharAttribs().OptimizeRanges(const_cast<SfxItemPool&>(rPool)); + + const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); + for (const auto & nAttr : rAttrs) + { + const EditCharAttrib& rAttr = *nAttr; + + if ( nStart == nEnd ) + { + sal_Int32 nCursorPos = nStart; + if ( ( rAttr.GetStart() <= nCursorPos ) && ( rAttr.GetEnd() >= nCursorPos ) ) + { + // To be used the attribute has to start BEFORE the position, or it must be a + // new empty attr AT the position, or we are on position 0. + if ( ( rAttr.GetStart() < nCursorPos ) || rAttr.IsEmpty() || !nCursorPos ) + { + // maybe this attrib ends here and a new attrib with 0 Len may follow and be valid here, + // but that s no problem, the empty item will come later and win. + aAttribs.Put( *rAttr.GetItem() ); + } + } + } + else + { + // Check every attribute covering the area, partial or full. + if ( ( rAttr.GetStart() < nEnd ) && ( rAttr.GetEnd() > nStart ) ) + { + if ( ( rAttr.GetStart() <= nStart ) && ( rAttr.GetEnd() >= nEnd ) ) + { + // full coverage + aAttribs.Put( *rAttr.GetItem() ); + } + else + { + // OptimizeRanges() assures that not the same attr can follow for full coverage + // only partial, check with current, when using para/style, otherwise invalid. + if ( !( nFlags & (GetAttribsFlags::PARAATTRIBS|GetAttribsFlags::STYLESHEET) ) || + ( *rAttr.GetItem() != aAttribs.Get( rAttr.Which() ) ) ) + { + aAttribs.InvalidateItem( rAttr.Which() ); + } + } + } + } + + if ( rAttr.GetStart() > nEnd ) + { + break; + } + } + } + } + + return aAttribs; +} + + +void ImpEditEngine::SetAttribs( EditSelection aSel, const SfxItemSet& rSet, SetAttribsMode nSpecial, bool bSetSelection ) +{ + aSel.Adjust( aEditDoc ); + + // When no selection => use the Attribute on the word. + // ( the RTF-parser should actually never call the Method without a Range ) + if ( nSpecial == SetAttribsMode::WholeWord && !aSel.HasRange() ) + aSel = SelectWord( aSel, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, false ); + + sal_Int32 nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() ); + + if ( IsUndoEnabled() && !IsInUndo() && aStatus.DoUndoAttribs() ) + { + std::unique_ptr<EditUndoSetAttribs> pUndo = CreateAttribUndo( aSel, rSet ); + pUndo->SetSpecial( nSpecial ); + pUndo->SetUpdateSelection(bSetSelection); + InsertUndo( std::move(pUndo) ); + } + + bool bCheckLanguage = false; + if ( GetStatus().DoOnlineSpelling() ) + { + bCheckLanguage = ( rSet.GetItemState( EE_CHAR_LANGUAGE ) == SfxItemState::SET ) || + ( rSet.GetItemState( EE_CHAR_LANGUAGE_CJK ) == SfxItemState::SET ) || + ( rSet.GetItemState( EE_CHAR_LANGUAGE_CTL ) == SfxItemState::SET ); + } + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + bool bParaAttribFound = false; + bool bCharAttribFound = false; + + DBG_ASSERT( aEditDoc.GetObject( nNode ), "Node not found: SetAttribs" ); + DBG_ASSERT( GetParaPortions().SafeGetObject( nNode ), "Portion not found: SetAttribs" ); + + ContentNode* pNode = aEditDoc.GetObject( nNode ); + ParaPortion* pPortion = GetParaPortions()[nNode]; + + const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; + const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart! + + // Iterate over the Items... + for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++) + { + if ( rSet.GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = rSet.Get( nWhich ); + if ( nWhich <= EE_PARA_END ) + { + pNode->GetContentAttribs().GetItems().Put( rItem ); + bParaAttribFound = true; + } + else + { + aEditDoc.InsertAttrib( pNode, nStartPos, nEndPos, rItem ); + bCharAttribFound = true; + if ( nSpecial == SetAttribsMode::Edge ) + { + CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); + for (std::unique_ptr<EditCharAttrib> & rAttrib : rAttribs) + { + EditCharAttrib& rAttr = *rAttrib; + if (rAttr.GetStart() > nEndPos) + break; + + if (rAttr.GetEnd() == nEndPos && rAttr.Which() == nWhich) + { + rAttr.SetEdge(true); + break; + } + } + } + } + } + } + + if ( bParaAttribFound ) + { + ParaAttribsChanged( pPortion->GetNode() ); + } + else if ( bCharAttribFound ) + { + bFormatted = false; + if ( !pNode->Len() || ( nStartPos != nEndPos ) ) + { + pPortion->MarkSelectionInvalid( nStartPos ); + if ( bCheckLanguage ) + pNode->GetWrongList()->SetInvalidRange(nStartPos, nEndPos); + } + } + } +} + +void ImpEditEngine::RemoveCharAttribs( EditSelection aSel, EERemoveParaAttribsMode eMode, sal_uInt16 nWhich ) +{ + aSel.Adjust( aEditDoc ); + + sal_Int32 nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() ); + bool bRemoveParaAttribs = eMode == EERemoveParaAttribsMode::RemoveAll; + const SfxItemSet* _pEmptyItemSet = bRemoveParaAttribs ? &GetEmptyItemSet() : nullptr; + + if ( IsUndoEnabled() && !IsInUndo() && aStatus.DoUndoAttribs() ) + { + // Possibly a special Undo, or itemset* + std::unique_ptr<EditUndoSetAttribs> pUndo = CreateAttribUndo( aSel, GetEmptyItemSet() ); + pUndo->SetRemoveAttribs( true ); + pUndo->SetRemoveParaAttribs( bRemoveParaAttribs ); + pUndo->SetRemoveWhich( nWhich ); + InsertUndo( std::move(pUndo) ); + } + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = aEditDoc.GetObject( nNode ); + ParaPortion* pPortion = GetParaPortions()[nNode]; + + DBG_ASSERT( aEditDoc.GetObject( nNode ), "Node not found: SetAttribs" ); + DBG_ASSERT( GetParaPortions().SafeGetObject( nNode ), "Portion not found: SetAttribs" ); + + const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; + const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart! + + // Optimize: If whole paragraph, then RemoveCharAttribs (nPara)? + bool bChanged = aEditDoc.RemoveAttribs( pNode, nStartPos, nEndPos, nWhich ); + if ( bRemoveParaAttribs ) + { + SetParaAttribs( nNode, *_pEmptyItemSet ); // Invalidated + } + else if (eMode == EERemoveParaAttribsMode::RemoveCharItems) + { + // For 'Format-Standard' also the character attributes should + // disappear, which were set as paragraph attributes by the + // DrawingEngine. These could not have been set by the user anyway. + + // #106871# Not when nWhich + // Would have been better to offer a separate method for format/standard... + if ( !nWhich ) + { + SfxItemSet aAttribs( GetParaAttribs( nNode ) ); + for ( sal_uInt16 nW = EE_CHAR_START; nW <= EE_CHAR_END; nW++ ) + aAttribs.ClearItem( nW ); + SetParaAttribs( nNode, aAttribs ); + } + } + + if ( bChanged && !bRemoveParaAttribs ) + { + bFormatted = false; + pPortion->MarkSelectionInvalid( nStartPos ); + } + } +} + +void ImpEditEngine::RemoveCharAttribs( sal_Int32 nPara, sal_uInt16 nWhich, bool bRemoveFeatures ) +{ + ContentNode* pNode = aEditDoc.GetObject( nPara ); + ParaPortion* pPortion = GetParaPortions().SafeGetObject( nPara ); + + DBG_ASSERT( pNode, "Node not found: RemoveCharAttribs" ); + DBG_ASSERT( pPortion, "Portion not found: RemoveCharAttribs" ); + + if ( !pNode || !pPortion ) + return; + + size_t nAttr = 0; + CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); + EditCharAttrib* pAttr = GetAttrib(rAttrs, nAttr); + while ( pAttr ) + { + if ( ( !pAttr->IsFeature() || bRemoveFeatures ) && + ( !nWhich || ( pAttr->GetItem()->Which() == nWhich ) ) ) + { + pNode->GetCharAttribs().Remove(nAttr); + nAttr--; + } + nAttr++; + pAttr = GetAttrib(rAttrs, nAttr); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(pNode->GetCharAttribs()); +#endif + + pPortion->MarkSelectionInvalid( 0 ); +} + +void ImpEditEngine::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + ContentNode* pNode = aEditDoc.GetObject( nPara ); + + if ( !pNode ) + return; + + if ( pNode->GetContentAttribs().GetItems() == rSet ) + return; + + if ( IsUndoEnabled() && !IsInUndo() && aStatus.DoUndoAttribs() ) + { + if ( rSet.GetPool() != &aEditDoc.GetItemPool() ) + { + SfxItemSet aTmpSet( GetEmptyItemSet() ); + aTmpSet.Put( rSet ); + InsertUndo(std::make_unique<EditUndoSetParaAttribs>(pEditEngine, nPara, pNode->GetContentAttribs().GetItems(), aTmpSet)); + } + else + { + InsertUndo(std::make_unique<EditUndoSetParaAttribs>(pEditEngine, nPara, pNode->GetContentAttribs().GetItems(), rSet)); + } + } + + bool bCheckLanguage = ( rSet.GetItemState( EE_CHAR_LANGUAGE ) == SfxItemState::SET ) || + ( rSet.GetItemState( EE_CHAR_LANGUAGE_CJK ) == SfxItemState::SET ) || + ( rSet.GetItemState( EE_CHAR_LANGUAGE_CTL ) == SfxItemState::SET ); + + pNode->GetContentAttribs().GetItems().Set( rSet ); + + if ( bCheckLanguage && pNode->GetWrongList() ) + pNode->GetWrongList()->ResetInvalidRange(0, pNode->Len()); + + if ( aStatus.UseCharAttribs() ) + pNode->CreateDefFont(); + + ParaAttribsChanged( pNode ); +} + +const SfxItemSet& ImpEditEngine::GetParaAttribs( sal_Int32 nPara ) const +{ + const ContentNode* pNode = aEditDoc.GetObject( nPara ); + assert(pNode && "Node not found: GetParaAttribs"); + return pNode->GetContentAttribs().GetItems(); +} + +bool ImpEditEngine::HasParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + const ContentNode* pNode = aEditDoc.GetObject( nPara ); + assert(pNode && "Node not found: HasParaAttrib"); + return pNode->GetContentAttribs().HasItem( nWhich ); +} + +const SfxPoolItem& ImpEditEngine::GetParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + const ContentNode* pNode = aEditDoc.GetObject(nPara); + assert(pNode && "Node not found: GetParaAttrib"); + return pNode->GetContentAttribs().GetItem(nWhich); +} + +void ImpEditEngine::GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const +{ + rLst.clear(); + const ContentNode* pNode = aEditDoc.GetObject( nPara ); + if ( !pNode ) + return; + + rLst.reserve(pNode->GetCharAttribs().Count()); + const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); + for (const auto & i : rAttrs) + { + const EditCharAttrib& rAttr = *i; + EECharAttrib aEEAttr(rAttr.GetStart(), rAttr.GetEnd(), rAttr.GetItem()); + rLst.push_back(aEEAttr); + } +} + +void ImpEditEngine::ParaAttribsToCharAttribs( ContentNode* pNode ) +{ + pNode->GetCharAttribs().DeleteEmptyAttribs( GetEditDoc().GetItemPool() ); + sal_Int32 nEndPos = pNode->Len(); + for ( sal_uInt16 nWhich = EE_CHAR_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + if ( pNode->GetContentAttribs().HasItem( nWhich ) ) + { + const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItem( nWhich ); + // Fill the gap: + sal_Int32 nLastEnd = 0; + const EditCharAttrib* pAttr = pNode->GetCharAttribs().FindNextAttrib( nWhich, nLastEnd ); + while ( pAttr ) + { + nLastEnd = pAttr->GetEnd(); + if ( pAttr->GetStart() > nLastEnd ) + aEditDoc.InsertAttrib( pNode, nLastEnd, pAttr->GetStart(), rItem ); + // #112831# Last Attr might go from 0xffff to 0x0000 + pAttr = nLastEnd ? pNode->GetCharAttribs().FindNextAttrib( nWhich, nLastEnd ) : nullptr; + } + + // And the Rest: + if ( nLastEnd < nEndPos ) + aEditDoc.InsertAttrib( pNode, nLastEnd, nEndPos, rItem ); + } + } + bFormatted = false; + // Portion does not need to be invalidated here, happens elsewhere. +} + +IdleFormattter::IdleFormattter() + : Idle("editeng::ImpEditEngine aIdleFormatter") +{ + pView = nullptr; + nRestarts = 0; +} + +IdleFormattter::~IdleFormattter() +{ + pView = nullptr; +} + +void IdleFormattter::DoIdleFormat( EditView* pV ) +{ + pView = pV; + + if ( IsActive() ) + nRestarts++; + + if ( nRestarts > 4 ) + ForceTimeout(); + else + Start(); +} + +void IdleFormattter::ForceTimeout() +{ + if ( IsActive() ) + { + Stop(); + Invoke(); + } +} + +ImplIMEInfos::ImplIMEInfos( const EditPaM& rPos, OUString _aOldTextAfterStartPos ) + : aOldTextAfterStartPos(std::move( _aOldTextAfterStartPos )), + aPos(rPos), + nLen(0), + bWasCursorOverwrite(false) + { + } + +ImplIMEInfos::~ImplIMEInfos() +{ +} + +void ImplIMEInfos::CopyAttribs( const ExtTextInputAttr* pA, sal_uInt16 nL ) +{ + nLen = nL; + pAttribs.reset( new ExtTextInputAttr[ nL ] ); + memcpy( pAttribs.get(), pA, nL*sizeof(ExtTextInputAttr) ); +} + +void ImplIMEInfos::DestroyAttribs() +{ + pAttribs.reset(); + nLen = 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/misspellrange.cxx b/editeng/source/editeng/misspellrange.cxx new file mode 100644 index 000000000..562a9905c --- /dev/null +++ b/editeng/source/editeng/misspellrange.cxx @@ -0,0 +1,21 @@ +/* -*- 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/. + */ + +#include <editeng/misspellrange.hxx> + +namespace editeng { + +MisspellRange::MisspellRange(size_t nStart, size_t nEnd) : mnStart(nStart), mnEnd(nEnd) {} + +MisspellRanges::MisspellRanges(sal_Int32 nParagraph, std::vector<MisspellRange>&& rRanges) : + mnParagraph(nParagraph), maRanges(std::move(rRanges)) {} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/section.cxx b/editeng/source/editeng/section.cxx new file mode 100644 index 000000000..f65b0158a --- /dev/null +++ b/editeng/source/editeng/section.cxx @@ -0,0 +1,19 @@ +/* -*- 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/. + */ + +#include <editeng/section.hxx> + +namespace editeng { + +Section::Section(sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd) : + mnParagraph(nPara), mnStart(nStart), mnEnd(nEnd){} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/textconv.cxx b/editeng/source/editeng/textconv.cxx new file mode 100644 index 000000000..a4d5386ea --- /dev/null +++ b/editeng/source/editeng/textconv.cxx @@ -0,0 +1,543 @@ +/* -*- 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 "impedit.hxx" +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> +#include <editeng/langitem.hxx> +#include <editeng/fontitem.hxx> +#include "textconv.hxx" +#include <osl/diagnose.h> +#include <vcl/weld.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::linguistic2; + +TextConvWrapper::TextConvWrapper( weld::Widget* pWindow, + const Reference< XComponentContext >& rxContext, + const lang::Locale& rSourceLocale, + const lang::Locale& rTargetLocale, + const vcl::Font* pTargetFont, + sal_Int32 nOptions, + bool bIsInteractive, + bool bIsStart, + EditView* pView ) : + HangulHanjaConversion( pWindow, rxContext, rSourceLocale, rTargetLocale, pTargetFont, nOptions, bIsInteractive ) + , m_nConvTextLang(LANGUAGE_NONE) + , m_nUnitOffset(0) + , m_nLastPos(0) + , m_aConvSel(pView->GetSelection()) + , m_pEditView(pView) + , m_pWin(pWindow) + , m_bStartChk(false) + , m_bStartDone(bIsStart) + , m_bEndDone(false) + , m_bAllowChange(false) +{ + DBG_ASSERT( pWindow, "TextConvWrapper: window missing" ); + + m_aConvSel.Adjust(); // make Start <= End +} + + +TextConvWrapper::~TextConvWrapper() +{ +} + + +bool TextConvWrapper::ConvNext_impl() +{ + // modified version of SvxSpellWrapper::SpellNext + + if( m_bStartChk ) + m_bStartDone = true; + else + m_bEndDone = true; + + if ( m_bStartDone && m_bEndDone ) + { + if ( ConvMore_impl() ) // examine another document? + { + m_bStartDone = true; + m_bEndDone = false; + ConvStart_impl( SvxSpellArea::Body ); + return true; + } + return false; + + } + + if ( m_bStartDone && m_bEndDone ) + { + if ( ConvMore_impl() ) // examine another document? + { + m_bStartDone = true; + m_bEndDone = false; + ConvStart_impl( SvxSpellArea::Body ); + return true; + } + } + else if (!m_aConvSel.HasRange()) + { + m_bStartChk = !m_bStartDone; + ConvStart_impl( m_bStartChk ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd ); + return true; + } + + return false; +} + +void TextConvWrapper::FindConvText_impl() +{ + // modified version of SvxSpellWrapper::FindSpellError + weld::WaitObject aWait(m_pWin); + while ( true ) + { + if (ConvContinue_impl() || !ConvNext_impl()) + break; + } +} + +bool TextConvWrapper::ConvMore_impl() +{ + // modified version of SvxSpellWrapper::SpellMore + + bool bMore = false; + EditEngine* pEE = m_pEditView->GetEditEngine(); + ImpEditEngine* pImpEE = m_pEditView->GetImpEditEngine(); + ConvInfo* pConvInfo = pImpEE->GetConvInfo(); + if ( pConvInfo->bMultipleDoc ) + { + bMore = pEE->ConvertNextDocument(); + if ( bMore ) + { + // The text has been entered in this engine ... + m_pEditView->GetImpEditView()->SetEditSelection( + pEE->GetEditDoc().GetStartPaM() ); + } + } + return bMore; +} + + +void TextConvWrapper::ConvStart_impl( SvxSpellArea eArea ) +{ + // modified version of EditSpellWrapper::SpellStart + + EditEngine* pEE = m_pEditView->GetEditEngine(); + ImpEditEngine* pImpEE = m_pEditView->GetImpEditEngine(); + ConvInfo* pConvInfo = pImpEE->GetConvInfo(); + + if ( eArea == SvxSpellArea::BodyStart ) + { + // Is called when Spell-forward has reached the end, and to start over + if ( m_bEndDone ) + { + pConvInfo->bConvToEnd = false; + pConvInfo->aConvTo = pConvInfo->aConvStart; + pConvInfo->aConvContinue = EPaM( 0, 0 ); + m_pEditView->GetImpEditView()->SetEditSelection( + pEE->GetEditDoc().GetStartPaM() ); + } + else + { + pConvInfo->bConvToEnd = true; + pConvInfo->aConvTo = pImpEE->CreateEPaM( + pEE->GetEditDoc().GetStartPaM() ); + } + } + else if ( eArea == SvxSpellArea::BodyEnd ) + { + // Is called when Spell-forward starts + pConvInfo->bConvToEnd = true; + if (m_aConvSel.HasRange()) + { + // user selection: convert to end of selection + pConvInfo->aConvTo.nPara = m_aConvSel.nEndPara; + pConvInfo->aConvTo.nIndex = m_aConvSel.nEndPos; + pConvInfo->bConvToEnd = false; + } + else + { + // nothing selected: convert to end of document + pConvInfo->aConvTo = pImpEE->CreateEPaM( + pEE->GetEditDoc().GetEndPaM() ); + } + } + else if ( eArea == SvxSpellArea::Body ) + { + // called by ConvNext_impl... + pConvInfo->aConvContinue = pConvInfo->aConvStart; + pConvInfo->aConvTo = pImpEE->CreateEPaM( + pEE->GetEditDoc().GetEndPaM() ); + } + else + { + OSL_FAIL( "ConvStart_impl: Unknown Area!" ); + } +} + + +bool TextConvWrapper::ConvContinue_impl() +{ + // modified version of EditSpellWrapper::SpellContinue + + // get next convertible text portion and its language + m_aConvText.clear(); + m_nConvTextLang = LANGUAGE_NONE; + m_pEditView->GetImpEditEngine()->ImpConvert( m_aConvText, m_nConvTextLang, + m_pEditView, GetSourceLanguage(), m_aConvSel, + m_bAllowChange, GetTargetLanguage(), GetTargetFont() ); + return !m_aConvText.isEmpty(); +} + + +void TextConvWrapper::SetLanguageAndFont( const ESelection &rESel, + LanguageType nLang, sal_uInt16 nLangWhichId, + const vcl::Font *pFont, sal_uInt16 nFontWhichId ) +{ + ESelection aOldSel = m_pEditView->GetSelection(); + m_pEditView->SetSelection( rESel ); + + // set new language attribute + SfxItemSet aNewSet( m_pEditView->GetEmptyItemSet() ); + aNewSet.Put( SvxLanguageItem( nLang, nLangWhichId ) ); + + // new font to be set? + DBG_ASSERT( pFont, "target font missing?" ); + if (pFont) + { + // set new font attribute + SvxFontItem aFontItem = static_cast<const SvxFontItem&>( aNewSet.Get( nFontWhichId ) ); + aFontItem.SetFamilyName( pFont->GetFamilyName()); + aFontItem.SetFamily( pFont->GetFamilyType()); + aFontItem.SetStyleName( pFont->GetStyleName()); + aFontItem.SetPitch( pFont->GetPitch()); + aFontItem.SetCharSet(pFont->GetCharSet()); + aNewSet.Put( aFontItem ); + } + + // apply new attributes + m_pEditView->SetAttribs( aNewSet ); + + m_pEditView->SetSelection( aOldSel ); +} + + +void TextConvWrapper::SelectNewUnit_impl( + const sal_Int32 nUnitStart, + const sal_Int32 nUnitEnd ) +{ + const bool bOK = 0 <= nUnitStart && 0 <= nUnitEnd && nUnitStart <= nUnitEnd; + DBG_ASSERT( bOK, "invalid arguments" ); + if (!bOK) + return; + + ESelection aSelection = m_pEditView->GetSelection(); + DBG_ASSERT( aSelection.nStartPara == aSelection.nEndPara, + "paragraph mismatch in selection" ); + aSelection.nStartPos = (m_nLastPos + m_nUnitOffset + nUnitStart); + aSelection.nEndPos = (m_nLastPos + m_nUnitOffset + nUnitEnd); + m_pEditView->SetSelection( aSelection ); +} + + +void TextConvWrapper::GetNextPortion( + OUString& /* [out] */ rNextPortion, + LanguageType& /* [out] */ rLangOfPortion, + bool /* [in] */ _bAllowImplicitChangesForNotConvertibleText ) +{ + m_bAllowChange = _bAllowImplicitChangesForNotConvertibleText; + + FindConvText_impl(); + rNextPortion = m_aConvText; + rLangOfPortion = m_nConvTextLang; + m_nUnitOffset = 0; + + ESelection aSelection = m_pEditView->GetSelection(); + DBG_ASSERT( aSelection.nStartPara == aSelection.nEndPara, + "paragraph mismatch in selection" ); + DBG_ASSERT( aSelection.nStartPos <= aSelection.nEndPos, + "start pos > end pos" ); + m_nLastPos = aSelection.nStartPos; +} + + +void TextConvWrapper::HandleNewUnit( + const sal_Int32 nUnitStart, + const sal_Int32 nUnitEnd ) +{ + SelectNewUnit_impl( nUnitStart, nUnitEnd ); +} + +#ifdef DBG_UTIL +namespace +{ + bool IsSimilarChinese( LanguageType nLang1, LanguageType nLang2 ) + { + using namespace editeng; + return (HangulHanjaConversion::IsTraditional(nLang1) && HangulHanjaConversion::IsTraditional(nLang2)) || + (HangulHanjaConversion::IsSimplified(nLang1) && HangulHanjaConversion::IsSimplified(nLang2)); + } +} +#endif + +void TextConvWrapper::ReplaceUnit( + const sal_Int32 nUnitStart, const sal_Int32 nUnitEnd, + const OUString& rOrigText, + const OUString& rReplaceWith, + const css::uno::Sequence< sal_Int32 > &rOffsets, + ReplacementAction eAction, + LanguageType *pNewUnitLanguage ) +{ + const bool bOK = 0 <= nUnitStart && 0 <= nUnitEnd && nUnitStart <= nUnitEnd; + DBG_ASSERT( bOK, "invalid arguments" ); + if (!bOK) + return; + + // select current unit + SelectNewUnit_impl( nUnitStart, nUnitEnd ); + + OUString aOrigTxt( m_pEditView->GetSelected() ); + OUString aNewTxt( rReplaceWith ); + switch (eAction) + { + case eExchange : + break; + case eReplacementBracketed : + aNewTxt = aOrigTxt + "(" + rReplaceWith + ")"; + break; + case eOriginalBracketed : + aNewTxt = rReplaceWith + "(" + aOrigTxt + ")"; + break; + case eReplacementAbove : + case eOriginalAbove : + case eReplacementBelow : + case eOriginalBelow : + OSL_FAIL( "Rubies not supported" ); + break; + default: + OSL_FAIL( "unexpected case" ); + } + m_nUnitOffset = m_nUnitOffset + nUnitStart + aNewTxt.getLength(); + + // remember current original language for later use + ImpEditEngine *pImpEditEng = m_pEditView->GetImpEditEngine(); + ESelection aOldSel = m_pEditView->GetSelection(); + //EditSelection aOldEditSel = pEditView->GetImpEditView()->GetEditSelection(); + +#ifdef DBG_UTIL + LanguageType nOldLang = pImpEditEng->GetLanguage( pImpEditEng->CreateSel( aOldSel ).Min() ).nLang; +#endif + + pImpEditEng->UndoActionStart( EDITUNDO_INSERT ); + + // according to FT we should currently not bother about keeping + // attributes in Hangul/Hanja conversion and leave that untouched. + // Thus we do this only for Chinese translation... + bool bIsChineseConversion = IsChinese( GetSourceLanguage() ); + if (bIsChineseConversion) + ChangeText( aNewTxt, rOrigText, &rOffsets, &aOldSel ); + else + ChangeText( aNewTxt, rOrigText, nullptr, nullptr ); + + // change language and font if necessary + if (bIsChineseConversion) + { + DBG_ASSERT( GetTargetLanguage() == LANGUAGE_CHINESE_SIMPLIFIED || GetTargetLanguage() == LANGUAGE_CHINESE_TRADITIONAL, + "TextConvWrapper::ReplaceUnit : unexpected target language" ); + + ESelection aNewSel( aOldSel ); + aNewSel.nStartPos = aNewSel.nStartPos - aNewTxt.getLength(); + + if (pNewUnitLanguage) + { +#ifdef DBG_UTIL + DBG_ASSERT(!IsSimilarChinese( *pNewUnitLanguage, nOldLang ), + "similar language should not be changed!"); +#endif + SetLanguageAndFont( aNewSel, *pNewUnitLanguage, EE_CHAR_LANGUAGE_CJK, + GetTargetFont(), EE_CHAR_FONTINFO_CJK ); + } + } + + pImpEditEng->UndoActionEnd(); + + // adjust ConvContinue / ConvTo if necessary + ImpEditEngine* pImpEE = m_pEditView->GetImpEditEngine(); + ConvInfo* pConvInfo = pImpEE->GetConvInfo(); + sal_Int32 nDelta = aNewTxt.getLength() - aOrigTxt.getLength(); + if (nDelta != 0) + { + // Note: replacement is always done in the current paragraph + // which is the one ConvContinue points to + pConvInfo->aConvContinue.nIndex = pConvInfo->aConvContinue.nIndex + nDelta; + + // if that is the same as the one where the conversions ends + // the end needs to be updated also + if (pConvInfo->aConvTo.nPara == pConvInfo->aConvContinue.nPara) + pConvInfo->aConvTo.nIndex = pConvInfo->aConvTo.nIndex + nDelta; + } +} + + +void TextConvWrapper::ChangeText( const OUString &rNewText, + const OUString& rOrigText, + const uno::Sequence< sal_Int32 > *pOffsets, + ESelection *pESelection ) +{ + //!! code is a modified copy of SwHHCWrapper::ChangeText from sw !! + + DBG_ASSERT( !rNewText.isEmpty(), "unexpected empty string" ); + if (rNewText.isEmpty()) + return; + + if (pOffsets && pESelection) // try to keep as much attributation as possible ? + { + pESelection->Adjust(); + + // remember cursor start position for later setting of the cursor + const sal_Int32 nStartIndex = pESelection->nStartPos; + + const sal_Int32 nIndices = pOffsets->getLength(); + const sal_Int32 *pIndices = pOffsets->getConstArray(); + const sal_Int32 nConvTextLen = rNewText.getLength(); + sal_Int32 nPos = 0; + sal_Int32 nChgPos = -1; + sal_Int32 nConvChgPos = -1; + + // offset to calculate the position in the text taking into + // account that text may have been replaced with new text of + // different length. Negative values allowed! + sal_Int32 nCorrectionOffset = 0; + + DBG_ASSERT(nIndices == 0 || nIndices == nConvTextLen, + "mismatch between string length and sequence length!" ); + + // find all substrings that need to be replaced (and only those) + while (true) + { + // get index in original text that matches nPos in new text + sal_Int32 nIndex; + if (nPos < nConvTextLen) + nIndex = nPos < nIndices ? pIndices[nPos] : nPos; + else + { + nPos = nConvTextLen; + nIndex = rOrigText.getLength(); + } + + // end of string also terminates non-matching char sequence + if (nPos == nConvTextLen || rOrigText[nIndex] == rNewText[nPos]) + { + // substring that needs to be replaced found? + if (nChgPos>=0 && nConvChgPos>=0) + { + const sal_Int32 nChgLen = nIndex - nChgPos; + const sal_Int32 nConvChgLen = nPos - nConvChgPos; + OUString aInNew( rNewText.copy( nConvChgPos, nConvChgLen ) ); + + // set selection to sub string to be replaced in original text + ESelection aSel( *pESelection ); + sal_Int32 nChgInNodeStartIndex = nStartIndex + nCorrectionOffset + nChgPos; + aSel.nStartPos = nChgInNodeStartIndex; + aSel.nEndPos = nChgInNodeStartIndex + nChgLen; + m_pEditView->SetSelection( aSel ); + + // replace selected sub string with the corresponding + // sub string from the new text while keeping as + // much from the attributes as possible + ChangeText_impl( aInNew, true ); + + nCorrectionOffset += nConvChgLen - nChgLen; + + nChgPos = -1; + nConvChgPos = -1; + } + } + else + { + // begin of non-matching char sequence found ? + if (nChgPos<0 && nConvChgPos<0) + { + nChgPos = nIndex; + nConvChgPos = nPos; + } + } + if (nPos >= nConvTextLen) + break; + ++nPos; + } + + // set cursor to the end of the inserted text + // (as it would happen after ChangeText_impl (Delete and Insert) + // of the whole text in the 'else' branch below) + pESelection->nStartPos = pESelection->nEndPos = nStartIndex + nConvTextLen; + } + else + { + ChangeText_impl( rNewText, false ); + } +} + + +void TextConvWrapper::ChangeText_impl( const OUString &rNewText, bool bKeepAttributes ) +{ + if (bKeepAttributes) + { + // save attributes to be restored + SfxItemSet aSet( m_pEditView->GetAttribs() ); + + // replace old text and select new text + m_pEditView->InsertText( rNewText, true ); + + // since 'SetAttribs' below function like merging with the attributes + // from the itemset with any existing ones we have to get rid of all + // all attributes now. (Those attributes that may take effect left + // to the position where the new text gets inserted after the old text + // was deleted) + m_pEditView->RemoveAttribs(EERemoveParaAttribsMode::RemoveNone, 0); + // apply saved attributes to new inserted text + m_pEditView->SetAttribs( aSet ); + } + else + { + m_pEditView->InsertText( rNewText ); + } +} + + +void TextConvWrapper::Convert() +{ + m_bStartChk = false; + ConvStart_impl( SvxSpellArea::BodyEnd ); + ConvertDocument(); +} + + +bool TextConvWrapper::HasRubySupport() const +{ + return false; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/textconv.hxx b/editeng/source/editeng/textconv.hxx new file mode 100644 index 000000000..6afb20acf --- /dev/null +++ b/editeng/source/editeng/textconv.hxx @@ -0,0 +1,107 @@ +/* -*- 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 <editeng/editdata.hxx> +#include <editeng/svxenum.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Sequence.hxx> +#include <editeng/hangulhanja.hxx> + +class EditView; + +class TextConvWrapper final : public editeng::HangulHanjaConversion +{ + OUString m_aConvText; // convertible text part found last time + LanguageType m_nConvTextLang; // language of aConvText + sal_uInt16 m_nUnitOffset; // offset of current unit in the current text portion (word) + sal_uInt16 m_nLastPos; // starting position of the last found text portion (word) + + ESelection m_aConvSel; // selection to be converted if + // 'HasRange' is true, other conversion + // starts from the cursor position + + EditView * m_pEditView; + weld::Widget* m_pWin; + + bool m_bStartChk; + bool m_bStartDone; + bool m_bEndDone; + bool m_bAllowChange; // storage for _bAllowImplicitChangesForNotConvertibleText + // parameters value of function GetNextPortion. + // used to transport the value to where it is needed. + + + // from SvxSpellWrapper copied and modified + bool ConvNext_impl(); // former SpellNext + void FindConvText_impl(); // former FindSpellError + bool ConvMore_impl(); // former SpellMore + + // from EditSpellWrapper copied and modified + void ConvStart_impl( SvxSpellArea eSpell ); // former SpellStart + bool ConvContinue_impl(); // former SpellContinue + + void SelectNewUnit_impl( const sal_Int32 nUnitStart, + const sal_Int32 nUnitEnd ); + + void ChangeText( const OUString &rNewText, + const OUString& rOrigText, + const css::uno::Sequence< sal_Int32 > *pOffsets, + ESelection *pESelection ); + void ChangeText_impl( const OUString &rNewText, bool bKeepAttributes ); + + TextConvWrapper (const TextConvWrapper &) = delete; + TextConvWrapper & operator= (const TextConvWrapper &) = delete; + + virtual void GetNextPortion( OUString& /* [out] */ rNextPortion, + LanguageType& /* [out] */ rLangOfPortion, + bool /* [in] */ _bAllowImplicitChangesForNotConvertibleText ) override; + virtual void HandleNewUnit( const sal_Int32 nUnitStart, + const sal_Int32 nUnitEnd ) override; + virtual void ReplaceUnit( + const sal_Int32 nUnitStart, const sal_Int32 nUnitEnd, + const OUString& rOrigText, + const OUString& rReplaceWith, + const css::uno::Sequence< sal_Int32 > &rOffsets, + ReplacementAction eAction, + LanguageType *pNewUnitLanguage ) override; + + virtual bool HasRubySupport() const override; + + void SetLanguageAndFont( const ESelection &rESel, + LanguageType nLang, sal_uInt16 nLangWhichId, + const vcl::Font *pFont, sal_uInt16 nFontWhichId ); + + +public: + TextConvWrapper(weld::Widget* pWindow, + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::lang::Locale& rSourceLocale, + const css::lang::Locale& rTargetLocale, + const vcl::Font* pTargetFont, + sal_Int32 nOptions, + bool bIsInteractive, + bool bIsStart, EditView* pView ); + + virtual ~TextConvWrapper() override; + + void Convert(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/CustomPropertyField.cxx b/editeng/source/items/CustomPropertyField.cxx new file mode 100644 index 000000000..939a6af0c --- /dev/null +++ b/editeng/source/items/CustomPropertyField.cxx @@ -0,0 +1,72 @@ +/* -*- 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/. + * + */ + +#include <editeng/CustomPropertyField.hxx> +#include <utility> +#include <vcl/metaact.hxx> +#include <com/sun/star/beans/XPropertyContainer.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> + +using namespace css; + +namespace editeng +{ + +CustomPropertyField::CustomPropertyField(OUString aName, OUString aCurrentPresentation) + : msName(std::move(aName)) + , msCurrentPresentation(std::move(aCurrentPresentation)) +{} + +CustomPropertyField::~CustomPropertyField() +{} + +std::unique_ptr<SvxFieldData> CustomPropertyField::Clone() const +{ + return std::make_unique<CustomPropertyField>(msName, msCurrentPresentation); +} + +bool CustomPropertyField::operator==(const SvxFieldData& rOther) const +{ + if (typeid(rOther) != typeid(*this)) + return false; + + const CustomPropertyField& rOtherField = static_cast<const CustomPropertyField&>(rOther); + return (msName == rOtherField.msName && + msCurrentPresentation == rOtherField.msCurrentPresentation); +} + +MetaAction* CustomPropertyField::createBeginComment() const +{ + return new MetaCommentAction("FIELD_SEQ_BEGIN"); +} + +OUString CustomPropertyField::GetFormatted(uno::Reference<document::XDocumentProperties> const & xDocumentProperties) +{ + if (msName.isEmpty()) + return OUString(); + if (!xDocumentProperties.is()) + return OUString(); + uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties(); + if (!xPropertyContainer.is()) + return OUString(); + uno::Reference<beans::XPropertySet> xPropertySet(xPropertyContainer, uno::UNO_QUERY); + if (!xPropertySet.is()) + return OUString(); + uno::Any aAny = xPropertySet->getPropertyValue(msName); + if (!aAny.has<OUString>()) + return OUString(); + msCurrentPresentation = aAny.get<OUString>(); + return msCurrentPresentation; +} + +} // end editeng namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/borderline.cxx b/editeng/source/items/borderline.cxx new file mode 100644 index 000000000..42d61b5da --- /dev/null +++ b/editeng/source/items/borderline.cxx @@ -0,0 +1,699 @@ +/* -*- 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 <algorithm> + +#include <basegfx/color/bcolor.hxx> +#include <basegfx/color/bcolortools.hxx> + +#include <editeng/borderline.hxx> +#include <editeng/itemtype.hxx> +#include <editeng/editrids.hrc> +#include <editeng/eerdll.hxx> +#include <tools/bigint.hxx> + +using namespace ::com::sun::star::table::BorderLineStyle; + +// class SvxBorderLine -------------------------------------------------- + +namespace { + + Color lcl_compute3DColor( Color aMain, int nLight, int nMedium, int nDark ) + { + basegfx::BColor color = aMain.getBColor( ); + basegfx::BColor hsl = basegfx::utils::rgb2hsl( color ); + + int nCoef = 0; + if ( hsl.getZ( ) >= 0.5 ) + nCoef = nLight; + else if ( 0.5 > hsl.getZ() && hsl.getZ() >= 0.25 ) + nCoef = nMedium; + else + nCoef = nDark; + + double L = std::min(hsl.getZ() * 255.0 + nCoef, 255.0); + hsl.setZ( L / 255.0 ); + color = basegfx::utils::hsl2rgb( hsl ); + + return Color( color ); + } +} // Anonymous namespace + +namespace editeng { + +Color SvxBorderLine::darkColor( Color aMain ) +{ + return aMain; +} + +Color SvxBorderLine::lightColor( Color aMain ) +{ + + // Divide Luminance by 2 + basegfx::BColor color = aMain.getBColor( ); + basegfx::BColor hsl = basegfx::utils::rgb2hsl( color ); + hsl.setZ( hsl.getZ() * 0.5 ); + color = basegfx::utils::hsl2rgb( hsl ); + + return Color( color ); +} + + +Color SvxBorderLine::threeDLightColor( Color aMain ) +{ + // These values have been defined in an empirical way + return lcl_compute3DColor( aMain, 3, 40, 83 ); +} + +Color SvxBorderLine::threeDDarkColor( Color aMain ) +{ + // These values have been defined in an empirical way + return lcl_compute3DColor( aMain, -85, -43, -1 ); +} + +Color SvxBorderLine::threeDMediumColor( Color aMain ) +{ + // These values have been defined in an empirical way + return lcl_compute3DColor( aMain, -42, -0, 42 ); +} + +SvxBorderLine::SvxBorderLine( const Color *pCol, tools::Long nWidth, + SvxBorderLineStyle nStyle, + Color (*pColorOutFn)( Color ), Color (*pColorInFn)( Color ) ) + : m_nWidth(nWidth) + , m_nMult(1) + , m_nDiv(1) + , m_pColorOutFn(pColorOutFn) + , m_pColorInFn(pColorInFn) + , m_pColorGapFn(nullptr) + , m_aWidthImpl(SvxBorderLine::getWidthImpl(nStyle)) + , m_nStyle(nStyle) + , m_bMirrorWidths(false) + , m_bUseLeftTop(false) +{ + if ( pCol ) + aColor = *pCol; +} + +SvxBorderLineStyle +ConvertBorderStyleFromWord(int const nWordLineStyle) +{ + switch (nWordLineStyle) + { + // First the single lines + case 1: + case 2: // thick line + case 5: // hairline + // and the unsupported special cases which we map to a single line + case 20: + return SvxBorderLineStyle::SOLID; + case 6: + return SvxBorderLineStyle::DOTTED; + case 7: + return SvxBorderLineStyle::DASHED; + case 22: + return SvxBorderLineStyle::FINE_DASHED; + case 8: + return SvxBorderLineStyle::DASH_DOT; + case 9: + return SvxBorderLineStyle::DASH_DOT_DOT; + // then the shading beams which we represent by a double line + case 23: + return SvxBorderLineStyle::DOUBLE; + // then the double lines, for which we have good matches + case 3: + case 10: // Don't have triple so use double + case 21: // Don't have double wave: use double instead + return SvxBorderLineStyle::DOUBLE; + case 11: + return SvxBorderLineStyle::THINTHICK_SMALLGAP; + case 12: + case 13: // Don't have thin thick thin, so use thick thin + return SvxBorderLineStyle::THICKTHIN_SMALLGAP; + case 14: + return SvxBorderLineStyle::THINTHICK_MEDIUMGAP; + case 15: + case 16: // Don't have thin thick thin, so use thick thin + return SvxBorderLineStyle::THICKTHIN_MEDIUMGAP; + case 17: + return SvxBorderLineStyle::THINTHICK_LARGEGAP; + case 18: + case 19: // Don't have thin thick thin, so use thick thin + return SvxBorderLineStyle::THICKTHIN_LARGEGAP; + case 24: + return SvxBorderLineStyle::EMBOSSED; + case 25: + return SvxBorderLineStyle::ENGRAVED; + case 26: + return SvxBorderLineStyle::OUTSET; + case 27: + return SvxBorderLineStyle::INSET; + default: + return SvxBorderLineStyle::NONE; + } +} + +const double THINTHICK_SMALLGAP_line2 = 15.0; +const double THINTHICK_SMALLGAP_gap = 15.0; +const double THINTHICK_LARGEGAP_line1 = 30.0; +const double THINTHICK_LARGEGAP_line2 = 15.0; +const double THICKTHIN_SMALLGAP_line1 = 15.0; +const double THICKTHIN_SMALLGAP_gap = 15.0; +const double THICKTHIN_LARGEGAP_line1 = 15.0; +const double THICKTHIN_LARGEGAP_line2 = 30.0; +const double OUTSET_line1 = 15.0; +const double INSET_line2 = 15.0; + +double +ConvertBorderWidthFromWord(SvxBorderLineStyle const eStyle, double const i_fWidth, + int const nWordLineStyle) +{ + // fdo#68779: at least for RTF, 0.75pt is the default if width is missing + double const fWidth((i_fWidth == 0.0) ? 15.0 : i_fWidth); + switch (eStyle) + { + // Single lines + case SvxBorderLineStyle::SOLID: + switch (nWordLineStyle) + { + case 2: + return (fWidth * 2.0); // thick + case 5: // fdo#55526: map 0 hairline width to > 0 + return std::max(fWidth, 1.0); + default: + return fWidth; + } + break; + + case SvxBorderLineStyle::DOTTED: + case SvxBorderLineStyle::DASHED: + case SvxBorderLineStyle::DASH_DOT: + case SvxBorderLineStyle::DASH_DOT_DOT: + return fWidth; + + // Display a minimum effective border width of 1pt + case SvxBorderLineStyle::FINE_DASHED: + return (fWidth > 0 && fWidth < 20) ? 20 : fWidth; + + // Double lines + case SvxBorderLineStyle::DOUBLE: + return fWidth * 3.0; + + case SvxBorderLineStyle::THINTHICK_MEDIUMGAP: + case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP: + case SvxBorderLineStyle::EMBOSSED: + case SvxBorderLineStyle::ENGRAVED: + return fWidth * 2.0; + + case SvxBorderLineStyle::THINTHICK_SMALLGAP: + return fWidth + THINTHICK_SMALLGAP_line2 + THINTHICK_SMALLGAP_gap; + + case SvxBorderLineStyle::THINTHICK_LARGEGAP: + return fWidth + THINTHICK_LARGEGAP_line1 + THINTHICK_LARGEGAP_line2; + + case SvxBorderLineStyle::THICKTHIN_SMALLGAP: + return fWidth + THICKTHIN_SMALLGAP_line1 + THICKTHIN_SMALLGAP_gap; + + case SvxBorderLineStyle::THICKTHIN_LARGEGAP: + return fWidth + THICKTHIN_LARGEGAP_line1 + THICKTHIN_LARGEGAP_line2; + + case SvxBorderLineStyle::OUTSET: + return (fWidth * 2.0) + OUTSET_line1; + + case SvxBorderLineStyle::INSET: + return (fWidth * 2.0) + INSET_line2; + + default: + assert(false); // should only be called for known border style + } + return 0; +} + +double +ConvertBorderWidthToWord(SvxBorderLineStyle const eStyle, double const fWidth) +{ + if ( !fWidth ) + return 0; + + switch (eStyle) + { + // Single lines + case SvxBorderLineStyle::SOLID: + case SvxBorderLineStyle::DOTTED: + case SvxBorderLineStyle::DASHED: + case SvxBorderLineStyle::FINE_DASHED: + case SvxBorderLineStyle::DASH_DOT: + case SvxBorderLineStyle::DASH_DOT_DOT: + return fWidth; + + // Double lines + case SvxBorderLineStyle::DOUBLE: + case SvxBorderLineStyle::DOUBLE_THIN: + return std::max(1.0, fWidth / 3.0); + + case SvxBorderLineStyle::THINTHICK_MEDIUMGAP: + case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP: + case SvxBorderLineStyle::EMBOSSED: + case SvxBorderLineStyle::ENGRAVED: + return std::max(1.0, fWidth / 2.0); + + case SvxBorderLineStyle::THINTHICK_SMALLGAP: + return std::max(1.0, fWidth - THINTHICK_SMALLGAP_line2 - THINTHICK_SMALLGAP_gap); + + case SvxBorderLineStyle::THINTHICK_LARGEGAP: + return std::max(1.0, fWidth - THINTHICK_LARGEGAP_line1 - THINTHICK_LARGEGAP_line2); + + case SvxBorderLineStyle::THICKTHIN_SMALLGAP: + return std::max(1.0, fWidth - THICKTHIN_SMALLGAP_line1 - THICKTHIN_SMALLGAP_gap); + + case SvxBorderLineStyle::THICKTHIN_LARGEGAP: + return std::max(1.0, fWidth - THICKTHIN_LARGEGAP_line1 - THICKTHIN_LARGEGAP_line2); + + case SvxBorderLineStyle::OUTSET: + return std::max(1.0, (fWidth - OUTSET_line1) / 2.0); + + case SvxBorderLineStyle::INSET: + return std::max(1.0, (fWidth - INSET_line2) / 2.0); + + case SvxBorderLineStyle::NONE: + return 0; + + default: + assert(false); // should only be called for known border style + return 0; + } +} + +/** Get the BorderWithImpl object corresponding to the given #nStyle, all the + units handled by the resulting object are Twips and the + BorderWidthImpl::GetLine1() corresponds to the Outer Line. + */ +BorderWidthImpl SvxBorderLine::getWidthImpl( SvxBorderLineStyle nStyle ) +{ + BorderWidthImpl aImpl; + + switch ( nStyle ) + { + // No line: no width + case SvxBorderLineStyle::NONE: + aImpl = BorderWidthImpl( BorderWidthImplFlags::FIXED, 0.0 ); + break; + + // Single lines + case SvxBorderLineStyle::SOLID: + case SvxBorderLineStyle::DOTTED: + case SvxBorderLineStyle::DASHED: + case SvxBorderLineStyle::FINE_DASHED: + case SvxBorderLineStyle::DASH_DOT: + case SvxBorderLineStyle::DASH_DOT_DOT: + aImpl = BorderWidthImpl( BorderWidthImplFlags::CHANGE_LINE1, 1.0 ); + break; + + // Double lines + + case SvxBorderLineStyle::DOUBLE: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + // fdo#46112 fdo#38542 fdo#43249: + // non-constant widths must sum to 1 + 1.0/3.0, 1.0/3.0, 1.0/3.0 ); + break; + + case SvxBorderLineStyle::DOUBLE_THIN: + aImpl = BorderWidthImpl(BorderWidthImplFlags::CHANGE_DIST, 10.0, 10.0, 1.0); + break; + + case SvxBorderLineStyle::THINTHICK_SMALLGAP: + aImpl = BorderWidthImpl( BorderWidthImplFlags::CHANGE_LINE1, 1.0, + THINTHICK_SMALLGAP_line2, THINTHICK_SMALLGAP_gap ); + break; + + case SvxBorderLineStyle::THINTHICK_MEDIUMGAP: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + 0.5, 0.25, 0.25 ); + break; + + case SvxBorderLineStyle::THINTHICK_LARGEGAP: + aImpl = BorderWidthImpl( BorderWidthImplFlags::CHANGE_DIST, + THINTHICK_LARGEGAP_line1, THINTHICK_LARGEGAP_line2, 1.0 ); + break; + + case SvxBorderLineStyle::THICKTHIN_SMALLGAP: + aImpl = BorderWidthImpl( BorderWidthImplFlags::CHANGE_LINE2, THICKTHIN_SMALLGAP_line1, + 1.0, THICKTHIN_SMALLGAP_gap ); + break; + + case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + 0.25, 0.5, 0.25 ); + break; + + case SvxBorderLineStyle::THICKTHIN_LARGEGAP: + aImpl = BorderWidthImpl( BorderWidthImplFlags::CHANGE_DIST, THICKTHIN_LARGEGAP_line1, + THICKTHIN_LARGEGAP_line2, 1.0 ); + break; + + // Engraved / Embossed + /* + * Word compat: the lines widths are exactly following this rule, should be: + * 0.75pt up to 3pt and then 3pt + */ + + case SvxBorderLineStyle::EMBOSSED: + case SvxBorderLineStyle::ENGRAVED: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + 0.25, 0.25, 0.5 ); + break; + + // Inset / Outset + /* + * Word compat: the gap width should be measured relatively to the biggest width for the + * row or column. + */ + case SvxBorderLineStyle::OUTSET: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + OUTSET_line1, 0.5, 0.5 ); + break; + + case SvxBorderLineStyle::INSET: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_DIST, + 0.5, INSET_line2, 0.5 ); + break; + } + + return aImpl; +} + +void SvxBorderLine::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + m_nMult = nMult; + m_nDiv = nDiv; +} + +void SvxBorderLine::GuessLinesWidths( SvxBorderLineStyle nStyle, sal_uInt16 nOut, sal_uInt16 nIn, sal_uInt16 nDist ) +{ + if (SvxBorderLineStyle::NONE == nStyle) + { + nStyle = SvxBorderLineStyle::SOLID; + if ( nOut > 0 && nIn > 0 ) + nStyle = SvxBorderLineStyle::DOUBLE; + } + + if ( nStyle == SvxBorderLineStyle::DOUBLE ) + { + static const SvxBorderLineStyle aDoubleStyles[] = + { + SvxBorderLineStyle::DOUBLE, + SvxBorderLineStyle::DOUBLE_THIN, + SvxBorderLineStyle::THINTHICK_SMALLGAP, + SvxBorderLineStyle::THINTHICK_MEDIUMGAP, + SvxBorderLineStyle::THINTHICK_LARGEGAP, + SvxBorderLineStyle::THICKTHIN_SMALLGAP, + SvxBorderLineStyle::THICKTHIN_MEDIUMGAP, + SvxBorderLineStyle::THICKTHIN_LARGEGAP + }; + + static size_t const len = SAL_N_ELEMENTS(aDoubleStyles); + tools::Long nWidth = 0; + SvxBorderLineStyle nTestStyle(SvxBorderLineStyle::NONE); + for (size_t i = 0; i < len && nWidth == 0; ++i) + { + nTestStyle = aDoubleStyles[i]; + BorderWidthImpl aWidthImpl = getWidthImpl( nTestStyle ); + nWidth = aWidthImpl.GuessWidth( nOut, nIn, nDist ); + } + + // If anything matched, then set it + if ( nWidth > 0 ) + { + nStyle = nTestStyle; + SetBorderLineStyle(nStyle); + m_nWidth = nWidth; + } + else + { + // fdo#38542: not a known double, default to something custom... + SetBorderLineStyle(nStyle); + m_nWidth = nOut + nIn + nDist; + if (m_nWidth) + { + m_aWidthImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + static_cast<double>(nOut ) / static_cast<double>(m_nWidth), + static_cast<double>(nIn ) / static_cast<double>(m_nWidth), + static_cast<double>(nDist) / static_cast<double>(m_nWidth)); + } + } + } + else + { + SetBorderLineStyle(nStyle); + if (nOut == 0 && nIn > 0) + { + // If only inner width is given swap inner and outer widths for + // single line styles, otherwise GuessWidth() marks this as invalid + // and returns a 0 width. + switch (nStyle) + { + case SvxBorderLineStyle::SOLID: + case SvxBorderLineStyle::DOTTED: + case SvxBorderLineStyle::DASHED: + case SvxBorderLineStyle::FINE_DASHED: + case SvxBorderLineStyle::DASH_DOT: + case SvxBorderLineStyle::DASH_DOT_DOT: + std::swap( nOut, nIn); + break; + default: + ; // nothing + } + } + m_nWidth = m_aWidthImpl.GuessWidth( nOut, nIn, nDist ); + } +} + +sal_uInt16 SvxBorderLine::GetOutWidth() const +{ + sal_uInt16 nOut = static_cast<sal_uInt16>(BigInt::Scale( m_aWidthImpl.GetLine1( m_nWidth ), m_nMult, m_nDiv )); + if ( m_bMirrorWidths ) + nOut = static_cast<sal_uInt16>(BigInt::Scale( m_aWidthImpl.GetLine2( m_nWidth ), m_nMult, m_nDiv )); + return nOut; +} + +sal_uInt16 SvxBorderLine::GetInWidth() const +{ + sal_uInt16 nIn = static_cast<sal_uInt16>(BigInt::Scale( m_aWidthImpl.GetLine2( m_nWidth ), m_nMult, m_nDiv )); + if ( m_bMirrorWidths ) + nIn = static_cast<sal_uInt16>(BigInt::Scale( m_aWidthImpl.GetLine1( m_nWidth ), m_nMult, m_nDiv )); + return nIn; +} + +sal_uInt16 SvxBorderLine::GetDistance() const +{ + return static_cast<sal_uInt16>(BigInt::Scale( m_aWidthImpl.GetGap( m_nWidth ), m_nMult, m_nDiv )); +} + + +bool SvxBorderLine::operator==( const SvxBorderLine& rCmp ) const +{ + return ( ( aColor == rCmp.aColor ) && + ( m_nWidth == rCmp.m_nWidth ) && + ( m_bMirrorWidths == rCmp.m_bMirrorWidths ) && + ( m_aWidthImpl == rCmp.m_aWidthImpl ) && + ( m_nStyle == rCmp.GetBorderLineStyle()) && + ( m_bUseLeftTop == rCmp.m_bUseLeftTop ) && + ( m_pColorOutFn == rCmp.m_pColorOutFn ) && + ( m_pColorInFn == rCmp.m_pColorInFn ) && + ( m_pColorGapFn == rCmp.m_pColorGapFn ) ); +} + +void SvxBorderLine::SetBorderLineStyle( SvxBorderLineStyle nNew ) +{ + m_nStyle = nNew; + m_aWidthImpl = getWidthImpl( m_nStyle ); + + switch ( nNew ) + { + case SvxBorderLineStyle::EMBOSSED: + m_pColorOutFn = threeDLightColor; + m_pColorInFn = threeDDarkColor; + m_pColorGapFn = threeDMediumColor; + m_bUseLeftTop = true; + break; + case SvxBorderLineStyle::ENGRAVED: + m_pColorOutFn = threeDDarkColor; + m_pColorInFn = threeDLightColor; + m_pColorGapFn = threeDMediumColor; + m_bUseLeftTop = true; + break; + case SvxBorderLineStyle::OUTSET: + m_pColorOutFn = lightColor; + m_pColorInFn = darkColor; + m_bUseLeftTop = true; + m_pColorGapFn = nullptr; + break; + case SvxBorderLineStyle::INSET: + m_pColorOutFn = darkColor; + m_pColorInFn = lightColor; + m_bUseLeftTop = true; + m_pColorGapFn = nullptr; + break; + default: + m_pColorOutFn = darkColor; + m_pColorInFn = darkColor; + m_bUseLeftTop = false; + m_pColorGapFn = nullptr; + break; + } +} + +Color SvxBorderLine::GetColorOut( bool bLeftOrTop ) const +{ + Color aResult = aColor; + + if ( m_aWidthImpl.IsDouble() && m_pColorOutFn != nullptr ) + { + if ( !bLeftOrTop && m_bUseLeftTop ) + aResult = (*m_pColorInFn)( aColor ); + else + aResult = (*m_pColorOutFn)( aColor ); + } + + return aResult; +} + +Color SvxBorderLine::GetColorIn( bool bLeftOrTop ) const +{ + Color aResult = aColor; + + if ( m_aWidthImpl.IsDouble() && m_pColorInFn != nullptr ) + { + if ( !bLeftOrTop && m_bUseLeftTop ) + aResult = (*m_pColorOutFn)( aColor ); + else + aResult = (*m_pColorInFn)( aColor ); + } + + return aResult; +} + +Color SvxBorderLine::GetColorGap( ) const +{ + Color aResult = aColor; + + if ( m_aWidthImpl.IsDouble() && m_pColorGapFn != nullptr ) + { + aResult = (*m_pColorGapFn)( aColor ); + } + + return aResult; +} + +void SvxBorderLine::SetWidth( tools::Long nWidth ) +{ + m_nWidth = nWidth; +} + +OUString SvxBorderLine::GetValueString(MapUnit eSrcUnit, + MapUnit eDestUnit, + const IntlWrapper* pIntl, + bool bMetricStr) const +{ + static TranslateId aStyleIds[] = + { + RID_SOLID, + RID_DOTTED, + RID_DASHED, + RID_DOUBLE, + RID_THINTHICK_SMALLGAP, + RID_THINTHICK_MEDIUMGAP, + RID_THINTHICK_LARGEGAP, + RID_THICKTHIN_SMALLGAP, + RID_THICKTHIN_MEDIUMGAP, + RID_THICKTHIN_LARGEGAP, + RID_EMBOSSED, + RID_ENGRAVED, + RID_OUTSET, + RID_INSET, + RID_FINE_DASHED, + RID_DOUBLE_THIN, + RID_DASH_DOT, + RID_DASH_DOT_DOT + }; + OUString aStr = "(" + ::GetColorString( aColor ) + cpDelim; + + if ( static_cast<int>(m_nStyle) < int(SAL_N_ELEMENTS(aStyleIds)) ) + { + TranslateId pResId = aStyleIds[static_cast<int>(m_nStyle)]; + aStr += EditResId(pResId); + } + else + { + OUString sMetric = EditResId(GetMetricId( eDestUnit )); + aStr += GetMetricText( static_cast<tools::Long>(GetInWidth()), eSrcUnit, eDestUnit, pIntl ); + if ( bMetricStr ) + aStr += sMetric; + aStr += cpDelim + + GetMetricText( static_cast<tools::Long>(GetOutWidth()), eSrcUnit, eDestUnit, pIntl ); + if ( bMetricStr ) + aStr += sMetric; + aStr += cpDelim + + GetMetricText( static_cast<tools::Long>(GetDistance()), eSrcUnit, eDestUnit, pIntl ); + if ( bMetricStr ) + aStr += sMetric; + } + aStr += ")"; + return aStr; +} + +bool SvxBorderLine::HasPriority( const SvxBorderLine& rOtherLine ) const +{ + const sal_uInt16 nThisSize = GetScaledWidth(); + const sal_uInt16 nOtherSize = rOtherLine.GetScaledWidth(); + + if ( nThisSize > nOtherSize ) + { + return true; + } + else if ( nThisSize < nOtherSize ) + { + return false; + } + else if ( rOtherLine.GetInWidth() && !GetInWidth() ) + { + return true; + } + + return false; +} + +bool operator!=( const SvxBorderLine& rLeft, const SvxBorderLine& rRight ) +{ + return !(rLeft == rRight); +} + +} // namespace editeng + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/bulitem.cxx b/editeng/source/items/bulitem.cxx new file mode 100644 index 000000000..769179748 --- /dev/null +++ b/editeng/source/items/bulitem.cxx @@ -0,0 +1,159 @@ +/* -*- 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/outdev.hxx> + +#include <editeng/bulletitem.hxx> + +SvxBulletItem::SvxBulletItem( sal_uInt16 _nWhich ) + : SfxPoolItem(_nWhich) + , aFont(OutputDevice::GetDefaultFont( DefaultFontType::FIXED, LANGUAGE_SYSTEM, GetDefaultFontFlags::NONE )) + , nStart(1) + , nStyle(SvxBulletStyle::N123) + , nWidth(1200) // 1.2cm + , nScale(75) + , cSymbol(' ') +{ + aFont.SetAlignment(ALIGN_BOTTOM); + aFont.SetTransparent( true ); +} + + +SvxBulletItem::SvxBulletItem( const SvxBulletItem& rItem ) + : SfxPoolItem(rItem) + , aFont(rItem.aFont) + , pGraphicObject(rItem.pGraphicObject ? new GraphicObject( *rItem.pGraphicObject ) : nullptr) + , aPrevText(rItem.aPrevText) + , aFollowText(rItem.aFollowText) + , nStart(rItem.nStart) + , nStyle(rItem.nStyle) + , nWidth(rItem.nWidth) + , nScale(rItem.nScale) + , cSymbol(rItem.cSymbol) +{ +} + + +SvxBulletItem::~SvxBulletItem() +{ +} + +SvxBulletItem* SvxBulletItem::Clone( SfxItemPool * /*pPool*/ ) const +{ + return new SvxBulletItem( *this ); +} + +void SvxBulletItem::CopyValidProperties( const SvxBulletItem& rCopyFrom ) +{ + vcl::Font _aFont = GetFont(); + vcl::Font aNewFont = rCopyFrom.GetFont(); + _aFont.SetFamilyName( aNewFont.GetFamilyName() ); + _aFont.SetFamily( aNewFont.GetFamilyType() ); + _aFont.SetStyleName( aNewFont.GetStyleName() ); + _aFont.SetColor( aNewFont.GetColor() ); + SetSymbol( rCopyFrom.cSymbol ); + SetGraphicObject( rCopyFrom.GetGraphicObject() ); + SetScale( rCopyFrom.nScale ); + SetStart( rCopyFrom.nStart ); + SetStyle( rCopyFrom.nStyle ); + aPrevText = rCopyFrom.aPrevText; + aFollowText = rCopyFrom.aFollowText; + SetFont( _aFont ); +} + + +bool SvxBulletItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + const SvxBulletItem& rBullet = static_cast<const SvxBulletItem&>(rItem); + // Compare with ValidMask, otherwise no put possible in an AttrSet if the + // item differs only in terms of the ValidMask from an existing one. + if( nStyle != rBullet.nStyle || + nScale != rBullet.nScale || + nWidth != rBullet.nWidth || + nStart != rBullet.nStart || + cSymbol != rBullet.cSymbol || + aPrevText != rBullet.aPrevText || + aFollowText != rBullet.aFollowText ) + return false; + + if( ( nStyle != SvxBulletStyle::BMP ) && ( aFont != rBullet.aFont ) ) + return false; + + if( nStyle == SvxBulletStyle::BMP ) + { + if( ( pGraphicObject && !rBullet.pGraphicObject ) || ( !pGraphicObject && rBullet.pGraphicObject ) ) + return false; + + if( ( pGraphicObject && rBullet.pGraphicObject ) && + ( ( *pGraphicObject != *rBullet.pGraphicObject ) || + ( pGraphicObject->GetPrefSize() != rBullet.pGraphicObject->GetPrefSize() ) ) ) + { + return false; + } + } + + return true; +} + + +OUString SvxBulletItem::GetFullText() const +{ + return aPrevText + OUStringChar(cSymbol) + aFollowText; +} + + +bool SvxBulletItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + rText = GetFullText(); + return true; +} + + +const GraphicObject& SvxBulletItem::GetGraphicObject() const +{ + if( pGraphicObject ) + return *pGraphicObject; + else + { + static const GraphicObject aDefaultObject; + return aDefaultObject; + } +} + + +void SvxBulletItem::SetGraphicObject( const GraphicObject& rGraphicObject ) +{ + if( ( GraphicType::NONE == rGraphicObject.GetType() ) || ( GraphicType::Default == rGraphicObject.GetType() ) ) + { + pGraphicObject.reset(); + } + else + { + pGraphicObject.reset( new GraphicObject( rGraphicObject ) ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/charhiddenitem.cxx b/editeng/source/items/charhiddenitem.cxx new file mode 100644 index 000000000..ec2a0af3c --- /dev/null +++ b/editeng/source/items/charhiddenitem.cxx @@ -0,0 +1,53 @@ +/* -*- 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 <editeng/charhiddenitem.hxx> +#include <editeng/editrids.hrc> +#include <editeng/eerdll.hxx> +#include <unotools/resmgr.hxx> + + +SvxCharHiddenItem::SvxCharHiddenItem( const bool bHidden, const sal_uInt16 nId ) : + SfxBoolItem( nId, bHidden ) +{ +} + +SvxCharHiddenItem* SvxCharHiddenItem::Clone( SfxItemPool * ) const +{ + return new SvxCharHiddenItem( *this ); +} + +bool SvxCharHiddenItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, + const IntlWrapper & /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_CHARHIDDEN_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_CHARHIDDEN_TRUE; + rText = EditResId(pId); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/flditem.cxx b/editeng/source/items/flditem.cxx new file mode 100644 index 000000000..1f6a9d9cc --- /dev/null +++ b/editeng/source/items/flditem.cxx @@ -0,0 +1,935 @@ +/* -*- 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 <osl/file.hxx> +#include <utility> +#include <vcl/metaact.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <tools/urlobj.hxx> + +#include <editeng/flditem.hxx> +#include <editeng/CustomPropertyField.hxx> +#include <editeng/measfld.hxx> +#include <editeng/unonames.hxx> + +#include <tools/debug.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/FilenameDisplayFormat.hpp> +#include <com/sun/star/util/DateTime.hpp> + +using namespace com::sun::star; + +SvxFieldData* SvxFieldData::Create(const uno::Reference<text::XTextContent>& xTextContent) +{ + uno::Reference<beans::XPropertySet> xPropSet(xTextContent, uno::UNO_QUERY); + if (!xPropSet.is()) + return nullptr; + + // we do not support these fields from Writer, so make sure we do not throw + // here - see fdo#63436 how to possibly extend Writer to make use of this + uno::Any aAny; + try { + aAny = xPropSet->getPropertyValue(UNO_TC_PROP_TEXTFIELD_TYPE); + if ( !aAny.has<sal_Int32>() ) + return nullptr; + + sal_Int32 nFieldType = aAny.get<sal_Int32>(); + + switch (nFieldType) + { + case text::textfield::Type::TIME: + case text::textfield::Type::EXTENDED_TIME: + case text::textfield::Type::DATE: + { + bool bIsDate = false; + xPropSet->getPropertyValue(UNO_TC_PROP_IS_DATE) >>= bIsDate; + + if (bIsDate) + { + util::DateTime aDateTime = xPropSet->getPropertyValue(UNO_TC_PROP_DATE_TIME).get<util::DateTime>(); + Date aDate(aDateTime.Day, aDateTime.Month, aDateTime.Year); + bool bIsFixed = false; + xPropSet->getPropertyValue(UNO_TC_PROP_IS_FIXED) >>= bIsFixed; + + SvxDateField* pData = new SvxDateField(aDate, bIsFixed ? SvxDateType::Fix : SvxDateType::Var); + sal_Int32 nNumFmt = -1; + xPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; + if (static_cast<SvxDateFormat>(nNumFmt) >= SvxDateFormat::AppDefault && + static_cast<SvxDateFormat>(nNumFmt) <= SvxDateFormat::F) + pData->SetFormat(static_cast<SvxDateFormat>(nNumFmt)); + + return pData; + } + + if (nFieldType != text::textfield::Type::TIME) + { + util::DateTime aDateTime = xPropSet->getPropertyValue(UNO_TC_PROP_DATE_TIME).get<util::DateTime>(); + tools::Time aTime(aDateTime); + + bool bIsFixed = false; + xPropSet->getPropertyValue(UNO_TC_PROP_IS_FIXED) >>= bIsFixed; + + SvxExtTimeField* pData = new SvxExtTimeField(aTime, bIsFixed ? SvxTimeType::Fix : SvxTimeType::Var); + + sal_Int32 nNumFmt = -1; + xPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; + if (static_cast<SvxTimeFormat>(nNumFmt) >= SvxTimeFormat::AppDefault && + static_cast<SvxTimeFormat>(nNumFmt) <= SvxTimeFormat::HH12_MM_SS_00_AMPM) + pData->SetFormat(static_cast<SvxTimeFormat>(nNumFmt)); + + return pData; + } + + return new SvxTimeField(); + } + case text::textfield::Type::URL: + { + OUString aRep, aTarget, aURL; + sal_Int16 nFmt = -1; + xPropSet->getPropertyValue(UNO_TC_PROP_URL_REPRESENTATION) >>= aRep; + xPropSet->getPropertyValue(UNO_TC_PROP_URL_TARGET) >>= aTarget; + xPropSet->getPropertyValue(UNO_TC_PROP_URL) >>= aURL; + xPropSet->getPropertyValue(UNO_TC_PROP_URL_FORMAT) >>= nFmt; + SvxURLField* pData = new SvxURLField(aURL, aRep, aRep.isEmpty() ? SvxURLFormat::Url : SvxURLFormat::Repr); + pData->SetTargetFrame(aTarget); + if (static_cast<SvxURLFormat>(nFmt) >= SvxURLFormat::AppDefault && + static_cast<SvxURLFormat>(nFmt) <= SvxURLFormat::Repr) + pData->SetFormat(static_cast<SvxURLFormat>(nFmt)); + + return pData; + } + case text::textfield::Type::PAGE: + return new SvxPageField(); + case text::textfield::Type::PAGES: + return new SvxPagesField(); + case text::textfield::Type::PAGE_NAME: + return new SvxPageTitleField(); + case text::textfield::Type::DOCINFO_TITLE: + return new SvxFileField(); + case text::textfield::Type::TABLE: + { + sal_Int32 nTab = 0; + xPropSet->getPropertyValue(UNO_TC_PROP_TABLE_POSITION) >>= nTab; + return new SvxTableField(nTab); + } + case text::textfield::Type::EXTENDED_FILE: + { + OUString aPresentation; + bool bIsFixed = false; + sal_Int16 nFmt = text::FilenameDisplayFormat::FULL; + xPropSet->getPropertyValue(UNO_TC_PROP_IS_FIXED) >>= bIsFixed; + xPropSet->getPropertyValue(UNO_TC_PROP_CURRENT_PRESENTATION) >>= aPresentation; + xPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nFmt; + + SvxFileFormat eFmt = SvxFileFormat::NameAndExt; + switch (nFmt) + { + case text::FilenameDisplayFormat::FULL: eFmt = SvxFileFormat::PathFull; break; + case text::FilenameDisplayFormat::PATH: eFmt = SvxFileFormat::PathOnly; break; + case text::FilenameDisplayFormat::NAME: eFmt = SvxFileFormat::NameOnly; break; + default:; + } + + // pass fixed attribute to constructor + return new SvxExtFileField( + aPresentation, bIsFixed ? SvxFileType::Fix : SvxFileType::Var, eFmt); + } + case text::textfield::Type::AUTHOR: + { + bool bIsFixed = false; + bool bFullName = false; + sal_Int16 nFmt = -1; + OUString aPresentation, aContent, aFirstName, aLastName; + xPropSet->getPropertyValue(UNO_TC_PROP_IS_FIXED) >>= bIsFixed; + xPropSet->getPropertyValue(UNO_TC_PROP_AUTHOR_FULLNAME) >>= bFullName; + xPropSet->getPropertyValue(UNO_TC_PROP_CURRENT_PRESENTATION) >>= aPresentation; + xPropSet->getPropertyValue(UNO_TC_PROP_AUTHOR_CONTENT) >>= aContent; + xPropSet->getPropertyValue(UNO_TC_PROP_AUTHOR_FORMAT) >>= nFmt; + + // do we have CurrentPresentation given? Mimic behaviour of + // writer, which means: prefer CurrentPresentation over Content + // if both are given. + if (!aPresentation.isEmpty()) + aContent = aPresentation; + + sal_Int32 nPos = aContent.lastIndexOf(' ', 0); + if (nPos > 0) + { + aFirstName = aContent.copy(0, nPos); + aLastName = aContent.copy(nPos + 1); + } + else + { + aLastName = aContent; + } + + // #92009# pass fixed attribute to constructor + SvxAuthorField* pData = new SvxAuthorField( + aFirstName, aLastName, OUString(), bIsFixed ? SvxAuthorType::Fix : SvxAuthorType::Var); + + if (!bIsFixed) + { + if (!bFullName) + { + pData->SetFormat(SvxAuthorFormat::ShortName); + } + else if (static_cast<SvxAuthorFormat>(nFmt) >= SvxAuthorFormat::FullName && + static_cast<SvxAuthorFormat>(nFmt) <= SvxAuthorFormat::ShortName) + { + pData->SetFormat(static_cast<SvxAuthorFormat>(nFmt)); + } + } + + return pData; + } + case text::textfield::Type::MEASURE: + { + SdrMeasureFieldKind eKind = SdrMeasureFieldKind::Value; + sal_Int16 nTmp = -1; + xPropSet->getPropertyValue(UNO_TC_PROP_MEASURE_KIND) >>= nTmp; + if (nTmp == static_cast<sal_Int16>(SdrMeasureFieldKind::Unit) || + nTmp == static_cast<sal_Int16>(SdrMeasureFieldKind::Rotate90Blanks)) + eKind = static_cast<SdrMeasureFieldKind>(nTmp); + + return new SdrMeasureField(eKind); + } + case text::textfield::Type::PRESENTATION_HEADER: + return new SvxHeaderField(); + case text::textfield::Type::PRESENTATION_FOOTER: + return new SvxFooterField(); + case text::textfield::Type::PRESENTATION_DATE_TIME: + return new SvxDateTimeField(); + case text::textfield::Type::DOCINFO_CUSTOM: + { + OUString sName; + xPropSet->getPropertyValue(UNO_TC_PROP_NAME) >>= sName; + + OUString sCurrentPresentation; + xPropSet->getPropertyValue(UNO_TC_PROP_CURRENT_PRESENTATION) >>= sCurrentPresentation; + + return new editeng::CustomPropertyField(sName, sCurrentPresentation); + } + default: + ; + }; + } catch ( const beans::UnknownPropertyException& ) + { + return nullptr; + } + + return nullptr; +} + + +SvxFieldData::SvxFieldData() +{ +} + + +SvxFieldData::~SvxFieldData() +{ +} + + +std::unique_ptr<SvxFieldData> SvxFieldData::Clone() const +{ + return std::make_unique<SvxFieldData>(); +} + + +bool SvxFieldData::operator==( const SvxFieldData& rFld ) const +{ + DBG_ASSERT( typeid(*this) == typeid(rFld), "==: Different Types" ); + (void)rFld; + return true; // Basic class is always the same. +} + + +MetaAction* SvxFieldData::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN" ); +} + +MetaAction* SvxFieldData::createEndComment() +{ + return new MetaCommentAction( "FIELD_SEQ_END" ); +} + + +SvxFieldItem::SvxFieldItem( std::unique_ptr<SvxFieldData> pField, const sal_uInt16 nId ) : + SfxPoolItem( nId ) + , mpField( std::move(pField) ) +{ +} + +SvxFieldItem::SvxFieldItem( const SvxFieldData& rField, const sal_uInt16 nId ) : + SfxPoolItem( nId ) + , mpField( rField.Clone() ) +{ +} + + +SvxFieldItem::SvxFieldItem( const SvxFieldItem& rItem ) : + SfxPoolItem ( rItem ) + , mpField( rItem.mpField ? rItem.mpField->Clone() : nullptr ) +{ +} + +SvxFieldItem::~SvxFieldItem() +{ +} + +SvxFieldItem* SvxFieldItem::Clone( SfxItemPool* ) const +{ + return new SvxFieldItem(*this); +} + +bool SvxFieldItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + + const SvxFieldData* pOtherFld = static_cast<const SvxFieldItem&>(rItem).GetField(); + if( mpField.get() == pOtherFld ) + return true; + if( mpField == nullptr || pOtherFld == nullptr ) + return false; + return ( typeid(*mpField) == typeid(*pOtherFld) ) + && ( *mpField == *pOtherFld ); +} + + +// The following are the derivatives of SvxFieldData ... + + +SvxDateField::SvxDateField() +{ + nFixDate = Date( Date::SYSTEM ).GetDate(); + eType = SvxDateType::Var; + eFormat = SvxDateFormat::StdSmall; +} + + +SvxDateField::SvxDateField( const Date& rDate, SvxDateType eT, SvxDateFormat eF ) +{ + nFixDate = rDate.GetDate(); + eType = eT; + eFormat = eF; +} + + +std::unique_ptr<SvxFieldData> SvxDateField::Clone() const +{ + return std::make_unique<SvxDateField>( *this ); +} + + +bool SvxDateField::operator==( const SvxFieldData& rOther ) const +{ + if ( typeid(rOther) != typeid(*this) ) + return false; + + const SvxDateField& rOtherFld = static_cast<const SvxDateField&>(rOther); + return ( ( nFixDate == rOtherFld.nFixDate ) && + ( eType == rOtherFld.eType ) && + ( eFormat == rOtherFld.eFormat ) ); +} + + + +OUString SvxDateField::GetFormatted( SvNumberFormatter& rFormatter, LanguageType eLang ) const +{ + Date aDate( Date::EMPTY ); + if ( eType == SvxDateType::Fix ) + aDate.SetDate( nFixDate ); + else + aDate = Date( Date::SYSTEM ); // current date + + return GetFormatted( aDate, eFormat, rFormatter, eLang ); +} + +OUString SvxDateField::GetFormatted( Date const & aDate, SvxDateFormat eFormat, SvNumberFormatter& rFormatter, LanguageType eLang ) +{ + if ( eFormat == SvxDateFormat::System ) + { + OSL_FAIL( "SvxDateFormat::System not implemented!" ); + eFormat = SvxDateFormat::StdSmall; + } + else if ( eFormat == SvxDateFormat::AppDefault ) + { + OSL_FAIL( "SvxDateFormat::AppDefault: take them from where? "); + eFormat = SvxDateFormat::StdSmall; + } + + sal_uInt32 nFormatKey; + + switch( eFormat ) + { + case SvxDateFormat::StdSmall: + // short + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYSTEM_SHORT, eLang ); + break; + case SvxDateFormat::StdBig: + // long + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYSTEM_LONG, eLang ); + break; + case SvxDateFormat::A: + // 13.02.96 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_DDMMYY, eLang ); + break; + case SvxDateFormat::B: + // 13.02.1996 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_DDMMYYYY, eLang ); + break; + case SvxDateFormat::C: + // 13. Feb 1996 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_DMMMYYYY, eLang ); + break; + case SvxDateFormat::D: + // 13. February 1996 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_DMMMMYYYY, eLang ); + break; + case SvxDateFormat::E: + // The, 13. February 1996 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_NNDMMMMYYYY, eLang ); + break; + case SvxDateFormat::F: + // Tuesday, 13. February 1996 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_NNNNDMMMMYYYY, eLang ); + break; + default: + nFormatKey = rFormatter.GetStandardFormat( SvNumFormatType::DATE, eLang ); + } + + double fDiffDate = aDate - rFormatter.GetNullDate(); + OUString aStr; + const Color* pColor = nullptr; + rFormatter.GetOutputString( fDiffDate, nFormatKey, aStr, &pColor ); + return aStr; +} + +MetaAction* SvxDateField::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN" ); +} + +SvxURLField::SvxURLField() +{ + eFormat = SvxURLFormat::Url; +} + + +SvxURLField::SvxURLField( OUString _aURL, OUString aRepres, SvxURLFormat eFmt ) + : aURL(std::move( _aURL )), aRepresentation(std::move( aRepres )) +{ + eFormat = eFmt; +} + + +std::unique_ptr<SvxFieldData> SvxURLField::Clone() const +{ + return std::make_unique<SvxURLField>( *this ); +} + + +bool SvxURLField::operator==( const SvxFieldData& rOther ) const +{ + if ( typeid(rOther) != typeid(*this) ) + return false; + + const SvxURLField& rOtherFld = static_cast<const SvxURLField&>(rOther); + return ( ( eFormat == rOtherFld.eFormat ) && + ( aURL == rOtherFld.aURL ) && + ( aRepresentation == rOtherFld.aRepresentation ) && + ( aTargetFrame == rOtherFld.aTargetFrame ) ); +} + + +MetaAction* SvxURLField::createBeginComment() const +{ + // #i46618# Adding target URL to metafile comment + return new MetaCommentAction( "FIELD_SEQ_BEGIN", + 0, + reinterpret_cast<const sal_uInt8*>(aURL.getStr()), + 2*aURL.getLength() ); +} + +// +// SvxPageTitleField methods +// + +SvxPageTitleField::SvxPageTitleField() {} + +std::unique_ptr<SvxFieldData> SvxPageTitleField::Clone() const +{ + return std::make_unique<SvxPageTitleField>(); +} + +bool SvxPageTitleField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxPageTitleField *>(&rCmp) != nullptr ); +} + +MetaAction* SvxPageTitleField::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN;PageTitleField" ); +} + +// +// SvxPagesField +// +// The fields that were removed from Calc: + + +SvxPageField::SvxPageField() {} + +std::unique_ptr<SvxFieldData> SvxPageField::Clone() const +{ + return std::make_unique<SvxPageField>(); // empty +} + +bool SvxPageField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxPageField *>(&rCmp) != nullptr ); +} + +MetaAction* SvxPageField::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN;PageField" ); +} + + +SvxPagesField::SvxPagesField() {} + +std::unique_ptr<SvxFieldData> SvxPagesField::Clone() const +{ + return std::make_unique<SvxPagesField>(); // empty +} + +bool SvxPagesField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxPagesField *>(&rCmp) != nullptr); +} + +SvxTimeField::SvxTimeField() {} + +std::unique_ptr<SvxFieldData> SvxTimeField::Clone() const +{ + return std::make_unique<SvxTimeField>(); // empty +} + +bool SvxTimeField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxTimeField *>(&rCmp) != nullptr); +} + +MetaAction* SvxTimeField::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN" ); +} + +SvxFileField::SvxFileField() {} + +std::unique_ptr<SvxFieldData> SvxFileField::Clone() const +{ + return std::make_unique<SvxFileField>(); // empty +} + +bool SvxFileField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxFileField *>(&rCmp) != nullptr ); +} + +SvxTableField::SvxTableField() : mnTab(0) {} + +SvxTableField::SvxTableField(int nTab) : mnTab(nTab) {} + +void SvxTableField::SetTab(int nTab) +{ + mnTab = nTab; +} + + +std::unique_ptr<SvxFieldData> SvxTableField::Clone() const +{ + return std::make_unique<SvxTableField>(mnTab); +} + +bool SvxTableField::operator==( const SvxFieldData& rCmp ) const +{ + if (dynamic_cast<const SvxTableField *>(&rCmp) == nullptr) + return false; + + return mnTab == static_cast<const SvxTableField&>(rCmp).mnTab; +} + +// SvxExtTimeField + + +SvxExtTimeField::SvxExtTimeField() + : m_nFixTime( tools::Time(tools::Time::SYSTEM).GetTime() ) +{ + eType = SvxTimeType::Var; + eFormat = SvxTimeFormat::Standard; +} + + +SvxExtTimeField::SvxExtTimeField( const tools::Time& rTime, SvxTimeType eT, SvxTimeFormat eF ) + : m_nFixTime( rTime.GetTime() ) +{ + eType = eT; + eFormat = eF; +} + + +std::unique_ptr<SvxFieldData> SvxExtTimeField::Clone() const +{ + return std::make_unique<SvxExtTimeField>( *this ); +} + + +bool SvxExtTimeField::operator==( const SvxFieldData& rOther ) const +{ + if ( typeid(rOther) != typeid(*this) ) + return false; + + const SvxExtTimeField& rOtherFld = static_cast<const SvxExtTimeField&>(rOther); + return ((m_nFixTime == rOtherFld.m_nFixTime) && + ( eType == rOtherFld.eType ) && + ( eFormat == rOtherFld.eFormat ) ); +} + + +OUString SvxExtTimeField::GetFormatted( SvNumberFormatter& rFormatter, LanguageType eLang ) const +{ + tools::Time aTime( tools::Time::EMPTY ); + if ( eType == SvxTimeType::Fix ) + aTime.SetTime(m_nFixTime); + else + aTime = tools::Time( tools::Time::SYSTEM ); // current time + return GetFormatted( aTime, eFormat, rFormatter, eLang ); +} + +OUString SvxExtTimeField::GetFormatted( tools::Time const & aTime, SvxTimeFormat eFormat, SvNumberFormatter& rFormatter, LanguageType eLang ) +{ + switch( eFormat ) + { + case SvxTimeFormat::System : + OSL_FAIL( "SvxTimeFormat::System: not implemented" ); + eFormat = SvxTimeFormat::Standard; + break; + case SvxTimeFormat::AppDefault : + OSL_FAIL( "SvxTimeFormat::AppDefault: not implemented" ); + eFormat = SvxTimeFormat::Standard; + break; + default: ;//prevent warning + } + + sal_uInt32 nFormatKey; + + switch( eFormat ) + { + case SvxTimeFormat::HH12_MM: + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HHMMAMPM, eLang ); + break; + case SvxTimeFormat::HH12_MM_SS_00: + { + // no builtin format available, try to insert or reuse + OUString aFormatCode( "HH:MM:SS.00 AM/PM" ); + sal_Int32 nCheckPos; + SvNumFormatType nType; + rFormatter.PutandConvertEntry( aFormatCode, nCheckPos, nType, + nFormatKey, LANGUAGE_ENGLISH_US, eLang, true); + DBG_ASSERT( nCheckPos == 0, "SvxTimeFormat::HH12_MM_SS_00: could not insert format code" ); + if ( nCheckPos ) + { + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HH_MMSS00, eLang ); + } + break; + } + case SvxTimeFormat::HH24_MM: + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HHMM, eLang ); + break; + case SvxTimeFormat::HH24_MM_SS_00: + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HH_MMSS00, eLang ); + break; + case SvxTimeFormat::HH12_MM_SS: + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HHMMSSAMPM, eLang ); + break; + case SvxTimeFormat::HH24_MM_SS: + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HHMMSS, eLang ); + break; + case SvxTimeFormat::Standard: + default: + nFormatKey = rFormatter.GetStandardFormat( SvNumFormatType::TIME, eLang ); + } + + double fFracTime = aTime.GetTimeInDays(); + OUString aStr; + const Color* pColor = nullptr; + rFormatter.GetOutputString( fFracTime, nFormatKey, aStr, &pColor ); + return aStr; +} + +MetaAction* SvxExtTimeField::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN" ); +} + + +// SvxExtFileField + + +SvxExtFileField::SvxExtFileField() +{ + eType = SvxFileType::Var; + eFormat = SvxFileFormat::PathFull; +} + + +SvxExtFileField::SvxExtFileField( const OUString& rStr, SvxFileType eT, SvxFileFormat eF ) +{ + aFile = rStr; + eType = eT; + eFormat = eF; +} + + +std::unique_ptr<SvxFieldData> SvxExtFileField::Clone() const +{ + return std::make_unique<SvxExtFileField>( *this ); +} + + +bool SvxExtFileField::operator==( const SvxFieldData& rOther ) const +{ + if ( typeid(rOther) != typeid(*this) ) + return false; + + const SvxExtFileField& rOtherFld = static_cast<const SvxExtFileField&>(rOther); + return ( ( aFile == rOtherFld.aFile ) && + ( eType == rOtherFld.eType ) && + ( eFormat == rOtherFld.eFormat ) ); +} + + +OUString SvxExtFileField::GetFormatted() const +{ + OUString aString; + + INetURLObject aURLObj( aFile ); + + if( INetProtocol::NotValid == aURLObj.GetProtocol() ) + { + // invalid? try to interpret string as system file name + OUString aURLStr; + + osl::FileBase::getFileURLFromSystemPath( aFile, aURLStr ); + + aURLObj.SetURL( aURLStr ); + } + + // #92009# Be somewhat liberate when trying to + // get formatted content out of the FileField + if( INetProtocol::NotValid == aURLObj.GetProtocol() ) + { + // still not valid? Then output as is + aString = aFile; + } + else if( INetProtocol::File == aURLObj.GetProtocol() ) + { + switch( eFormat ) + { + case SvxFileFormat::PathFull: + aString = aURLObj.getFSysPath(FSysStyle::Detect); + break; + + case SvxFileFormat::PathOnly: + aURLObj.removeSegment(INetURLObject::LAST_SEGMENT, false); + // #101742# Leave trailing slash at the pathname + aURLObj.setFinalSlash(); + aString = aURLObj.getFSysPath(FSysStyle::Detect); + break; + + case SvxFileFormat::NameOnly: + aString = aURLObj.getBase(INetURLObject::LAST_SEGMENT,true,INetURLObject::DecodeMechanism::Unambiguous); + break; + + case SvxFileFormat::NameAndExt: + aString = aURLObj.getName(INetURLObject::LAST_SEGMENT,true,INetURLObject::DecodeMechanism::Unambiguous); + break; + } + } + else + { + switch( eFormat ) + { + case SvxFileFormat::PathFull: + aString = aURLObj.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); + break; + + case SvxFileFormat::PathOnly: + aURLObj.removeSegment(INetURLObject::LAST_SEGMENT, false); + // #101742# Leave trailing slash at the pathname + aURLObj.setFinalSlash(); + aString = aURLObj.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); + break; + + case SvxFileFormat::NameOnly: + aString = aURLObj.getBase(); + break; + + case SvxFileFormat::NameAndExt: + aString = aURLObj.getName(); + break; + } + } + + return aString; +} + + +// SvxAuthorField + + +SvxAuthorField::SvxAuthorField( const OUString& rFirstName, + const OUString& rLastName, + const OUString& rShortName, + SvxAuthorType eT, SvxAuthorFormat eF ) +{ + aName = rLastName; + aFirstName = rFirstName; + aShortName = rShortName; + eType = eT; + eFormat = eF; +} + + +std::unique_ptr<SvxFieldData> SvxAuthorField::Clone() const +{ + return std::make_unique<SvxAuthorField>( *this ); +} + + +bool SvxAuthorField::operator==( const SvxFieldData& rOther ) const +{ + if ( typeid(rOther) != typeid(*this) ) + return false; + + const SvxAuthorField& rOtherFld = static_cast<const SvxAuthorField&>(rOther); + return ( ( aName == rOtherFld.aName ) && + ( aFirstName == rOtherFld.aFirstName ) && + ( aShortName == rOtherFld.aShortName ) && + ( eType == rOtherFld.eType ) && + ( eFormat == rOtherFld.eFormat ) ); +} + + +OUString SvxAuthorField::GetFormatted() const +{ + OUString aString; + + switch( eFormat ) + { + case SvxAuthorFormat::FullName: + aString = aFirstName + " " + aName; + break; + case SvxAuthorFormat::LastName: + aString = aName; + break; + + case SvxAuthorFormat::FirstName: + aString = aFirstName; + break; + + case SvxAuthorFormat::ShortName: + aString = aShortName; + break; + } + + return aString; +} + +SvxHeaderField::SvxHeaderField() {} + +std::unique_ptr<SvxFieldData> SvxHeaderField::Clone() const +{ + return std::make_unique<SvxHeaderField>(); // empty +} + +bool SvxHeaderField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxHeaderField *>(&rCmp) != nullptr ); +} + +SvxFooterField::SvxFooterField() {} + +std::unique_ptr<SvxFieldData> SvxFooterField::Clone() const +{ + return std::make_unique<SvxFooterField>(); // empty +} + +bool SvxFooterField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxFooterField *>(&rCmp) != nullptr ); +} + +std::unique_ptr<SvxFieldData> SvxDateTimeField::Clone() const +{ + return std::make_unique<SvxDateTimeField>(); // empty +} + +bool SvxDateTimeField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxDateTimeField *>(&rCmp) != nullptr ); +} + +SvxDateTimeField::SvxDateTimeField() {} + +OUString SvxDateTimeField::GetFormatted( + Date const & rDate, tools::Time const & rTime, + SvxDateFormat eDateFormat, SvxTimeFormat eTimeFormat, + SvNumberFormatter& rFormatter, LanguageType eLanguage ) +{ + OUString aRet; + + if(eDateFormat != SvxDateFormat::AppDefault) + { + aRet = SvxDateField::GetFormatted( rDate, eDateFormat, rFormatter, eLanguage ); + } + + if(eTimeFormat != SvxTimeFormat::AppDefault) + { + OUStringBuffer aBuf(aRet); + + if (!aRet.isEmpty()) + aBuf.append(' '); + + aBuf.append( + SvxExtTimeField::GetFormatted(rTime, eTimeFormat, rFormatter, eLanguage)); + + aRet = aBuf.makeStringAndClear(); + } + + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/frmitems.cxx b/editeng/source/items/frmitems.cxx new file mode 100644 index 000000000..35e1be7b0 --- /dev/null +++ b/editeng/source/items/frmitems.cxx @@ -0,0 +1,3518 @@ +/* -*- 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 <com/sun/star/uno/Any.hxx> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/script/Converter.hpp> +#include <com/sun/star/table/ShadowLocation.hpp> +#include <com/sun/star/table/ShadowFormat.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/table/BorderLineStyle.hpp> +#include <com/sun/star/style/BreakType.hpp> +#include <com/sun/star/style/GraphicLocation.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/frame/status/UpperLowerMarginScale.hpp> +#include <com/sun/star/frame/status/LeftRightMarginScale.hpp> +#include <com/sun/star/drawing/ShadingPattern.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> + +#include <osl/diagnose.h> +#include <i18nutil/unicode.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <utility> +#include <vcl/GraphicObject.hxx> +#include <tools/urlobj.hxx> +#include <tools/bigint.hxx> +#include <svl/memberid.h> +#include <rtl/math.hxx> +#include <rtl/ustring.hxx> +#include <tools/mapunit.hxx> +#include <tools/UnitConversion.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <editeng/editrids.hrc> +#include <editeng/pbinitem.hxx> +#include <editeng/sizeitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/prntitem.hxx> +#include <editeng/opaqitem.hxx> +#include <editeng/protitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/lineitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/itemtype.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/memberids.h> +#include <libxml/xmlwriter.h> +#include <o3tl/enumrange.hxx> +#include <o3tl/safeint.hxx> +#include <vcl/GraphicLoader.hxx> +#include <unotools/securityoptions.hxx> + +#include <boost/property_tree/ptree.hpp> + +using namespace ::editeng; +using namespace ::com::sun::star; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::table::BorderLineStyle; + + +SfxPoolItem* SvxPaperBinItem::CreateDefault() { return new SvxPaperBinItem(0);} +SfxPoolItem* SvxSizeItem::CreateDefault() { return new SvxSizeItem(0);} +SfxPoolItem* SvxLRSpaceItem::CreateDefault() { return new SvxLRSpaceItem(0);} +SfxPoolItem* SvxULSpaceItem::CreateDefault() { return new SvxULSpaceItem(0);} +SfxPoolItem* SvxProtectItem::CreateDefault() { return new SvxProtectItem(0);} +SfxPoolItem* SvxBrushItem::CreateDefault() { return new SvxBrushItem(0);} +SfxPoolItem* SvxShadowItem::CreateDefault() { return new SvxShadowItem(0);} +SfxPoolItem* SvxBoxItem::CreateDefault() { return new SvxBoxItem(0);} +SfxPoolItem* SvxBoxInfoItem::CreateDefault() { return new SvxBoxInfoItem(0);} +SfxPoolItem* SvxFormatBreakItem::CreateDefault() { return new SvxFormatBreakItem(SvxBreak::NONE, 0);} +SfxPoolItem* SvxFormatKeepItem::CreateDefault() { return new SvxFormatKeepItem(false, 0);} +SfxPoolItem* SvxLineItem::CreateDefault() { return new SvxLineItem(0);} + +SvxPaperBinItem* SvxPaperBinItem::Clone( SfxItemPool* ) const +{ + return new SvxPaperBinItem( *this ); +} + +bool SvxPaperBinItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + rText = OUString::number( GetValue() ); + return true; + + case SfxItemPresentation::Complete: + { + sal_uInt8 nValue = GetValue(); + + if ( PAPERBIN_PRINTER_SETTINGS == nValue ) + rText = EditResId(RID_SVXSTR_PAPERBIN_SETTINGS); + else + { + rText = EditResId(RID_SVXSTR_PAPERBIN) + " " + OUString::number( nValue ); + } + return true; + } + //no break necessary + default: ; //prevent warning + } + + return false; +} + + +SvxSizeItem::SvxSizeItem( const sal_uInt16 nId, const Size& rSize ) : + + SfxPoolItem( nId ), + + m_aSize( rSize ) +{ +} + + +bool SvxSizeItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + awt::Size aTmp(m_aSize.Width(), m_aSize.Height()); + if( bConvert ) + { + aTmp.Height = convertTwipToMm100(aTmp.Height); + aTmp.Width = convertTwipToMm100(aTmp.Width); + } + + switch( nMemberId ) + { + case MID_SIZE_SIZE: rVal <<= aTmp; break; + case MID_SIZE_WIDTH: rVal <<= aTmp.Width; break; + case MID_SIZE_HEIGHT: rVal <<= aTmp.Height; break; + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + return true; +} + + +bool SvxSizeItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + switch( nMemberId ) + { + case MID_SIZE_SIZE: + { + awt::Size aTmp; + if( rVal >>= aTmp ) + { + if(bConvert) + { + aTmp.Height = o3tl::toTwips(aTmp.Height, o3tl::Length::mm100); + aTmp.Width = o3tl::toTwips(aTmp.Width, o3tl::Length::mm100); + } + m_aSize = Size( aTmp.Width, aTmp.Height ); + } + else + { + return false; + } + } + break; + case MID_SIZE_WIDTH: + { + sal_Int32 nVal = 0; + if(!(rVal >>= nVal )) + return false; + + m_aSize.setWidth( bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal ); + } + break; + case MID_SIZE_HEIGHT: + { + sal_Int32 nVal = 0; + if(!(rVal >>= nVal)) + return true; + + m_aSize.setHeight( bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal ); + } + break; + default: OSL_FAIL("Wrong MemberId!"); + return false; + } + return true; +} + + +SvxSizeItem::SvxSizeItem( const sal_uInt16 nId ) : + + SfxPoolItem( nId ) +{ +} + + +bool SvxSizeItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return ( m_aSize == static_cast<const SvxSizeItem&>( rAttr ).GetSize() ); +} + +SvxSizeItem* SvxSizeItem::Clone( SfxItemPool* ) const +{ + return new SvxSizeItem( *this ); +} + +bool SvxSizeItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + OUString cpDelimTmp(cpDelim); + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + rText = GetMetricText( m_aSize.Width(), eCoreUnit, ePresUnit, &rIntl ) + + cpDelimTmp + + GetMetricText( m_aSize.Height(), eCoreUnit, ePresUnit, &rIntl ); + return true; + + case SfxItemPresentation::Complete: + rText = EditResId(RID_SVXITEMS_SIZE_WIDTH) + + GetMetricText( m_aSize.Width(), eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_SIZE_HEIGHT) + + GetMetricText( m_aSize.Height(), eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + return true; + // no break necessary + default: ; // prevent warning + + } + return false; +} + + +void SvxSizeItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + m_aSize.setWidth( BigInt::Scale( m_aSize.Width(), nMult, nDiv ) ); + m_aSize.setHeight( BigInt::Scale( m_aSize.Height(), nMult, nDiv ) ); +} + + +bool SvxSizeItem::HasMetrics() const +{ + return true; +} + + +SvxLRSpaceItem::SvxLRSpaceItem( const sal_uInt16 nId ) : + + SfxPoolItem( nId ), + + nTxtLeft ( 0 ), + nLeftMargin ( 0 ), + nRightMargin ( 0 ), + m_nGutterMargin(0), + m_nRightGutterMargin(0), + nPropFirstLineOffset( 100 ), + nPropLeftMargin( 100 ), + nPropRightMargin( 100 ), + nFirstLineOffset ( 0 ), + bAutoFirst ( false ), + bExplicitZeroMarginValRight(false), + bExplicitZeroMarginValLeft(false) +{ +} + + +SvxLRSpaceItem::SvxLRSpaceItem( const tools::Long nLeft, const tools::Long nRight, + const tools::Long nTLeft, const short nOfset, + const sal_uInt16 nId ) +: SfxPoolItem( nId ), + + nTxtLeft ( nTLeft ), + nLeftMargin ( nLeft ), + nRightMargin ( nRight ), + m_nGutterMargin(0), + m_nRightGutterMargin(0), + nPropFirstLineOffset( 100 ), + nPropLeftMargin( 100 ), + nPropRightMargin( 100 ), + nFirstLineOffset ( nOfset ), + bAutoFirst ( false ), + bExplicitZeroMarginValRight(false), + bExplicitZeroMarginValLeft(false) +{ +} + + +bool SvxLRSpaceItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bRet = true; + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + // now all signed + case 0: + { + css::frame::status::LeftRightMarginScale aLRSpace; + aLRSpace.Left = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nLeftMargin) : nLeftMargin); + aLRSpace.TextLeft = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nTxtLeft) : nTxtLeft); + aLRSpace.Right = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nRightMargin) : nRightMargin); + aLRSpace.ScaleLeft = static_cast<sal_Int16>(nPropLeftMargin); + aLRSpace.ScaleRight = static_cast<sal_Int16>(nPropRightMargin); + aLRSpace.FirstLine = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nFirstLineOffset) : nFirstLineOffset); + aLRSpace.ScaleFirstLine = static_cast<sal_Int16>(nPropFirstLineOffset); + aLRSpace.AutoFirstLine = IsAutoFirst(); + rVal <<= aLRSpace; + break; + } + case MID_L_MARGIN: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nLeftMargin) : nLeftMargin); + break; + + case MID_TXT_LMARGIN : + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nTxtLeft) : nTxtLeft); + break; + case MID_R_MARGIN: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nRightMargin) : nRightMargin); + break; + case MID_L_REL_MARGIN: + rVal <<= static_cast<sal_Int16>(nPropLeftMargin); + break; + case MID_R_REL_MARGIN: + rVal <<= static_cast<sal_Int16>(nPropRightMargin); + break; + + case MID_FIRST_LINE_INDENT: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nFirstLineOffset) : nFirstLineOffset); + break; + + case MID_FIRST_LINE_REL_INDENT: + rVal <<= static_cast<sal_Int16>(nPropFirstLineOffset); + break; + + case MID_FIRST_AUTO: + rVal <<= IsAutoFirst(); + break; + + case MID_GUTTER_MARGIN: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(m_nGutterMargin) + : m_nGutterMargin); + break; + + default: + bRet = false; + // SfxDispatchController_Impl::StateChanged calls this with hardcoded 0 triggering this; there used to be a MID_LR_MARGIN 0 but what type would it have? + OSL_FAIL("unknown MemberId"); + } + return bRet; +} + + +bool SvxLRSpaceItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0 != (nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + sal_Int32 nVal = 0; + if( nMemberId != 0 && nMemberId != MID_FIRST_AUTO && + nMemberId != MID_L_REL_MARGIN && nMemberId != MID_R_REL_MARGIN) + if(!(rVal >>= nVal)) + return false; + + switch( nMemberId ) + { + case 0: + { + css::frame::status::LeftRightMarginScale aLRSpace; + if(!(rVal >>= aLRSpace)) + return false; + + SetLeft( bConvert ? o3tl::toTwips(aLRSpace.Left, o3tl::Length::mm100) : aLRSpace.Left ); + SetTextLeft( bConvert ? o3tl::toTwips(aLRSpace.TextLeft, o3tl::Length::mm100) : aLRSpace.TextLeft ); + SetRight(bConvert ? o3tl::toTwips(aLRSpace.Right, o3tl::Length::mm100) : aLRSpace.Right); + nPropLeftMargin = aLRSpace.ScaleLeft; + nPropRightMargin = aLRSpace.ScaleRight; + SetTextFirstLineOffset(bConvert ? o3tl::toTwips(aLRSpace.FirstLine, o3tl::Length::mm100) : aLRSpace.FirstLine); + SetPropTextFirstLineOffset ( aLRSpace.ScaleFirstLine ); + SetAutoFirst( aLRSpace.AutoFirstLine ); + break; + } + case MID_L_MARGIN: + SetLeft( bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal ); + break; + + case MID_TXT_LMARGIN : + SetTextLeft( bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal ); + break; + + case MID_R_MARGIN: + SetRight(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + case MID_L_REL_MARGIN: + case MID_R_REL_MARGIN: + { + sal_Int32 nRel = 0; + if((rVal >>= nRel) && nRel >= 0 && nRel < SAL_MAX_UINT16) + { + if(MID_L_REL_MARGIN== nMemberId) + nPropLeftMargin = static_cast<sal_uInt16>(nRel); + else + nPropRightMargin = static_cast<sal_uInt16>(nRel); + } + else + return false; + } + break; + case MID_FIRST_LINE_INDENT : + SetTextFirstLineOffset(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + + case MID_FIRST_LINE_REL_INDENT: + SetPropTextFirstLineOffset ( nVal ); + break; + + case MID_FIRST_AUTO: + SetAutoFirst( Any2Bool(rVal) ); + break; + + case MID_GUTTER_MARGIN: + SetGutterMargin(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + + default: + OSL_FAIL("unknown MemberId"); + return false; + } + return true; +} + + +/// Adapt nLeftMargin and nTxtLeft. +void SvxLRSpaceItem::AdjustLeft() +{ + if ( 0 > nFirstLineOffset ) + nLeftMargin = nTxtLeft + nFirstLineOffset; + else + nLeftMargin = nTxtLeft; +} + + +bool SvxLRSpaceItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxLRSpaceItem& rOther = static_cast<const SvxLRSpaceItem&>(rAttr); + + return ( + nFirstLineOffset == rOther.GetTextFirstLineOffset() && + nTxtLeft == rOther.GetTextLeft() && + m_nGutterMargin == rOther.GetGutterMargin() && + m_nRightGutterMargin == rOther.GetRightGutterMargin() && + nLeftMargin == rOther.GetLeft() && + nRightMargin == rOther.GetRight() && + nPropFirstLineOffset == rOther.GetPropTextFirstLineOffset() && + nPropLeftMargin == rOther.GetPropLeft() && + nPropRightMargin == rOther.GetPropRight() && + bAutoFirst == rOther.IsAutoFirst() && + bExplicitZeroMarginValRight == rOther.IsExplicitZeroMarginValRight() && + bExplicitZeroMarginValLeft == rOther.IsExplicitZeroMarginValLeft() ); +} + +SvxLRSpaceItem* SvxLRSpaceItem::Clone( SfxItemPool* ) const +{ + return new SvxLRSpaceItem( *this ); +} + +bool SvxLRSpaceItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + if ( 100 != nPropLeftMargin ) + { + rText = unicode::formatPercent(nPropLeftMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + rText = GetMetricText( nLeftMargin, + eCoreUnit, ePresUnit, &rIntl ); + rText += cpDelim; + if ( 100 != nPropFirstLineOffset ) + { + rText += unicode::formatPercent(nPropFirstLineOffset, + Application::GetSettings().GetUILanguageTag()); + } + else + rText += GetMetricText( static_cast<tools::Long>(nFirstLineOffset), + eCoreUnit, ePresUnit, &rIntl ); + rText += cpDelim; + if ( 100 != nRightMargin ) + { + rText += unicode::formatPercent(nRightMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + rText += GetMetricText( nRightMargin, + eCoreUnit, ePresUnit, &rIntl ); + return true; + } + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_LRSPACE_LEFT); + if ( 100 != nPropLeftMargin ) + rText += unicode::formatPercent(nPropLeftMargin, + Application::GetSettings().GetUILanguageTag()); + else + { + rText += GetMetricText( nLeftMargin, eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + rText += cpDelim; + if ( 100 != nPropFirstLineOffset || nFirstLineOffset ) + { + rText += EditResId(RID_SVXITEMS_LRSPACE_FLINE); + if ( 100 != nPropFirstLineOffset ) + rText += unicode::formatPercent(nPropFirstLineOffset, + Application::GetSettings().GetUILanguageTag()); + else + { + rText += GetMetricText( static_cast<tools::Long>(nFirstLineOffset), + eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + rText += cpDelim; + } + rText += EditResId(RID_SVXITEMS_LRSPACE_RIGHT); + if ( 100 != nPropRightMargin ) + rText += unicode::formatPercent(nPropRightMargin, + Application::GetSettings().GetUILanguageTag()); + else + { + rText += GetMetricText( nRightMargin, + eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + return true; + } + default: ; // prevent warning + } + return false; +} + + +void SvxLRSpaceItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + nFirstLineOffset = static_cast<short>(BigInt::Scale( nFirstLineOffset, nMult, nDiv )); + nTxtLeft = BigInt::Scale( nTxtLeft, nMult, nDiv ); + nLeftMargin = BigInt::Scale( nLeftMargin, nMult, nDiv ); + nRightMargin = BigInt::Scale( nRightMargin, nMult, nDiv ); +} + + +bool SvxLRSpaceItem::HasMetrics() const +{ + return true; +} + + +void SvxLRSpaceItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxLRSpaceItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nFirstLineOffset"), BAD_CAST(OString::number(nFirstLineOffset).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nTxtLeft"), BAD_CAST(OString::number(nTxtLeft).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLeftMargin"), BAD_CAST(OString::number(nLeftMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nRightMargin"), BAD_CAST(OString::number(nRightMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nGutterMargin"), + BAD_CAST(OString::number(m_nGutterMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nRightGutterMargin"), + BAD_CAST(OString::number(m_nRightGutterMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nPropFirstLineOffset"), BAD_CAST(OString::number(nPropFirstLineOffset).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nPropLeftMargin"), BAD_CAST(OString::number(nPropLeftMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nPropRightMargin"), BAD_CAST(OString::number(nPropRightMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bAutoFirst"), BAD_CAST(OString::number(int(bAutoFirst)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bExplicitZeroMarginValRight"), BAD_CAST(OString::number(int(bExplicitZeroMarginValRight)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bExplicitZeroMarginValLeft"), BAD_CAST(OString::number(int(bExplicitZeroMarginValLeft)).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + + +boost::property_tree::ptree SvxLRSpaceItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON(); + + boost::property_tree::ptree aState; + + MapUnit eTargetUnit = MapUnit::MapInch; + + OUString sLeft = GetMetricText(GetLeft(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + OUString sRight = GetMetricText(GetRight(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + OUString sFirstline = GetMetricText(GetTextFirstLineOffset(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + aState.put("left", sLeft); + aState.put("right", sRight); + aState.put("firstline", sFirstline); + aState.put("unit", "inch"); + + aTree.push_back(std::make_pair("state", aState)); + + return aTree; +} + + +SvxULSpaceItem::SvxULSpaceItem( const sal_uInt16 nId ) + : SfxPoolItem(nId) + , nUpper(0) + , nLower(0) + , bContext(false) + , nPropUpper(100) + , nPropLower(100) +{ +} + + +SvxULSpaceItem::SvxULSpaceItem( const sal_uInt16 nUp, const sal_uInt16 nLow, + const sal_uInt16 nId ) + : SfxPoolItem(nId) + , nUpper(nUp) + , nLower(nLow) + , bContext(false) + , nPropUpper(100) + , nPropLower(100) +{ +} + + +bool SvxULSpaceItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + // now all signed + case 0: + { + css::frame::status::UpperLowerMarginScale aUpperLowerMarginScale; + aUpperLowerMarginScale.Upper = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nUpper) : nUpper); + aUpperLowerMarginScale.Lower = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nLower) : nPropUpper); + aUpperLowerMarginScale.ScaleUpper = static_cast<sal_Int16>(nPropUpper); + aUpperLowerMarginScale.ScaleLower = static_cast<sal_Int16>(nPropLower); + rVal <<= aUpperLowerMarginScale; + break; + } + case MID_UP_MARGIN: rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nUpper) : nUpper); break; + case MID_LO_MARGIN: rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nLower) : nLower); break; + case MID_CTX_MARGIN: rVal <<= bContext; break; + case MID_UP_REL_MARGIN: rVal <<= static_cast<sal_Int16>(nPropUpper); break; + case MID_LO_REL_MARGIN: rVal <<= static_cast<sal_Int16>(nPropLower); break; + } + return true; +} + + +bool SvxULSpaceItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + sal_Int32 nVal = 0; + bool bVal = false; + switch( nMemberId ) + { + case 0: + { + css::frame::status::UpperLowerMarginScale aUpperLowerMarginScale; + if ( !(rVal >>= aUpperLowerMarginScale )) + return false; + { + SetUpper(bConvert ? o3tl::toTwips(aUpperLowerMarginScale.Upper, o3tl::Length::mm100) : aUpperLowerMarginScale.Upper); + SetLower(bConvert ? o3tl::toTwips(aUpperLowerMarginScale.Lower, o3tl::Length::mm100) : aUpperLowerMarginScale.Lower); + if( aUpperLowerMarginScale.ScaleUpper > 1 ) + nPropUpper = aUpperLowerMarginScale.ScaleUpper; + if( aUpperLowerMarginScale.ScaleLower > 1 ) + nPropUpper = aUpperLowerMarginScale.ScaleLower; + } + } + break; + case MID_UP_MARGIN : + if(!(rVal >>= nVal)) + return false; + SetUpper(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + case MID_LO_MARGIN : + if(!(rVal >>= nVal) || nVal < 0) + return false; + SetLower(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + case MID_CTX_MARGIN : + if (!(rVal >>= bVal)) + return false; + SetContextValue(bVal); + break; + case MID_UP_REL_MARGIN: + case MID_LO_REL_MARGIN: + { + sal_Int32 nRel = 0; + if((rVal >>= nRel) && nRel > 1 ) + { + if(MID_UP_REL_MARGIN == nMemberId) + nPropUpper = static_cast<sal_uInt16>(nRel); + else + nPropLower = static_cast<sal_uInt16>(nRel); + } + else + return false; + } + break; + + default: + OSL_FAIL("unknown MemberId"); + return false; + } + return true; +} + + +bool SvxULSpaceItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxULSpaceItem& rSpaceItem = static_cast<const SvxULSpaceItem&>( rAttr ); + return ( nUpper == rSpaceItem.nUpper && + nLower == rSpaceItem.nLower && + bContext == rSpaceItem.bContext && + nPropUpper == rSpaceItem.nPropUpper && + nPropLower == rSpaceItem.nPropLower ); +} + +SvxULSpaceItem* SvxULSpaceItem::Clone( SfxItemPool* ) const +{ + return new SvxULSpaceItem( *this ); +} + +bool SvxULSpaceItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, + const IntlWrapper& rIntl +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + if ( 100 != nPropUpper ) + { + rText = unicode::formatPercent(nPropUpper, + Application::GetSettings().GetUILanguageTag()); + } + else + rText = GetMetricText( static_cast<tools::Long>(nUpper), eCoreUnit, ePresUnit, &rIntl ); + rText += cpDelim; + if ( 100 != nPropLower ) + { + rText += unicode::formatPercent(nPropLower, + Application::GetSettings().GetUILanguageTag()); + } + else + rText += GetMetricText( static_cast<tools::Long>(nLower), eCoreUnit, ePresUnit, &rIntl ); + return true; + } + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_ULSPACE_UPPER); + if ( 100 != nPropUpper ) + { + rText += unicode::formatPercent(nPropUpper, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText += GetMetricText( static_cast<tools::Long>(nUpper), eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + rText += cpDelim + EditResId(RID_SVXITEMS_ULSPACE_LOWER); + if ( 100 != nPropLower ) + { + rText += unicode::formatPercent(nPropLower, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText += GetMetricText( static_cast<tools::Long>(nLower), eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + return true; + } + default: ; // prevent warning + } + return false; +} + + +void SvxULSpaceItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + nUpper = static_cast<sal_uInt16>(BigInt::Scale( nUpper, nMult, nDiv )); + nLower = static_cast<sal_uInt16>(BigInt::Scale( nLower, nMult, nDiv )); +} + + +bool SvxULSpaceItem::HasMetrics() const +{ + return true; +} + + +void SvxULSpaceItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxULSpaceItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nUpper"), BAD_CAST(OString::number(nUpper).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLower"), BAD_CAST(OString::number(nLower).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bContext"), BAD_CAST(OString::boolean(bContext).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nPropUpper"), BAD_CAST(OString::number(nPropUpper).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nPropLower"), BAD_CAST(OString::number(nPropLower).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +boost::property_tree::ptree SvxULSpaceItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON(); + + boost::property_tree::ptree aState; + + MapUnit eTargetUnit = MapUnit::MapInch; + + OUString sUpper = GetMetricText(GetUpper(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + OUString sLower = GetMetricText(GetLower(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + aState.put("upper", sUpper); + aState.put("lower", sLower); + aState.put("unit", "inch"); + + aTree.push_back(std::make_pair("state", aState)); + + return aTree; +} + +SvxPrintItem* SvxPrintItem::Clone( SfxItemPool* ) const +{ + return new SvxPrintItem( *this ); +} + +bool SvxPrintItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + TranslateId pId = RID_SVXITEMS_PRINT_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_PRINT_TRUE; + rText = EditResId(pId); + return true; +} + +SvxOpaqueItem* SvxOpaqueItem::Clone( SfxItemPool* ) const +{ + return new SvxOpaqueItem( *this ); +} + +bool SvxOpaqueItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + TranslateId pId = RID_SVXITEMS_OPAQUE_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_OPAQUE_TRUE; + rText = EditResId(pId); + return true; +} + + +bool SvxProtectItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxProtectItem& rItem = static_cast<const SvxProtectItem&>(rAttr); + return ( bCntnt == rItem.bCntnt && + bSize == rItem.bSize && + bPos == rItem.bPos ); +} + + +bool SvxProtectItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + bool bValue; + switch(nMemberId) + { + case MID_PROTECT_CONTENT : bValue = bCntnt; break; + case MID_PROTECT_SIZE : bValue = bSize; break; + case MID_PROTECT_POSITION: bValue = bPos; break; + default: + OSL_FAIL("Wrong MemberId"); + return false; + } + + rVal <<= bValue; + return true; +} + + +bool SvxProtectItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bVal( Any2Bool(rVal) ); + switch(nMemberId) + { + case MID_PROTECT_CONTENT : bCntnt = bVal; break; + case MID_PROTECT_SIZE : bSize = bVal; break; + case MID_PROTECT_POSITION: bPos = bVal; break; + default: + OSL_FAIL("Wrong MemberId"); + return false; + } + return true; +} + +SvxProtectItem* SvxProtectItem::Clone( SfxItemPool* ) const +{ + return new SvxProtectItem( *this ); +} + +bool SvxProtectItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + TranslateId pId = RID_SVXITEMS_PROT_CONTENT_FALSE; + + if ( bCntnt ) + pId = RID_SVXITEMS_PROT_CONTENT_TRUE; + rText = EditResId(pId) + cpDelim; + pId = RID_SVXITEMS_PROT_SIZE_FALSE; + + if ( bSize ) + pId = RID_SVXITEMS_PROT_SIZE_TRUE; + rText += EditResId(pId) + cpDelim; + pId = RID_SVXITEMS_PROT_POS_FALSE; + + if ( bPos ) + pId = RID_SVXITEMS_PROT_POS_TRUE; + rText += EditResId(pId); + return true; +} + + +void SvxProtectItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxProtectItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("content"), BAD_CAST(OString::boolean(bCntnt).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("size"), BAD_CAST(OString::boolean(bSize).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("position"), BAD_CAST(OString::boolean(bPos).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + + +SvxShadowItem::SvxShadowItem( const sal_uInt16 nId, + const Color *pColor, const sal_uInt16 nW, + const SvxShadowLocation eLoc ) : + SfxEnumItemInterface( nId ), + aShadowColor(COL_GRAY), + nWidth ( nW ), + eLocation ( eLoc ) +{ + if ( pColor ) + aShadowColor = *pColor; +} + + +bool SvxShadowItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + table::ShadowFormat aShadow; + table::ShadowLocation eSet = table::ShadowLocation_NONE; + switch( eLocation ) + { + case SvxShadowLocation::TopLeft : eSet = table::ShadowLocation_TOP_LEFT ; break; + case SvxShadowLocation::TopRight : eSet = table::ShadowLocation_TOP_RIGHT ; break; + case SvxShadowLocation::BottomLeft : eSet = table::ShadowLocation_BOTTOM_LEFT ; break; + case SvxShadowLocation::BottomRight: eSet = table::ShadowLocation_BOTTOM_RIGHT; break; + default: ; // prevent warning + } + aShadow.Location = eSet; + aShadow.ShadowWidth = bConvert ? convertTwipToMm100(nWidth) : nWidth; + aShadow.IsTransparent = aShadowColor.IsTransparent(); + aShadow.Color = sal_Int32(aShadowColor); + + sal_Int8 nTransparence = rtl::math::round((float(255 - aShadowColor.GetAlpha()) * 100) / 255); + + switch ( nMemberId ) + { + case MID_LOCATION: rVal <<= aShadow.Location; break; + case MID_WIDTH: rVal <<= aShadow.ShadowWidth; break; + case MID_TRANSPARENT: rVal <<= aShadow.IsTransparent; break; + case MID_BG_COLOR: rVal <<= aShadow.Color; break; + case 0: rVal <<= aShadow; break; + case MID_SHADOW_TRANSPARENCE: rVal <<= nTransparence; break; + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + return true; +} + +bool SvxShadowItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + table::ShadowFormat aShadow; + uno::Any aAny; + bool bRet = QueryValue( aAny, bConvert ? CONVERT_TWIPS : 0 ) && ( aAny >>= aShadow ); + switch ( nMemberId ) + { + case MID_LOCATION: + { + bRet = (rVal >>= aShadow.Location); + if ( !bRet ) + { + sal_Int16 nVal = 0; + bRet = (rVal >>= nVal); + aShadow.Location = static_cast<table::ShadowLocation>(nVal); + } + + break; + } + + case MID_WIDTH: rVal >>= aShadow.ShadowWidth; break; + case MID_TRANSPARENT: rVal >>= aShadow.IsTransparent; break; + case MID_BG_COLOR: rVal >>= aShadow.Color; break; + case 0: rVal >>= aShadow; break; + case MID_SHADOW_TRANSPARENCE: + { + sal_Int32 nTransparence = 0; + if ((rVal >>= nTransparence) && !o3tl::checked_multiply<sal_Int32>(nTransparence, 255, nTransparence)) + { + Color aColor(ColorTransparency, aShadow.Color); + aColor.SetAlpha(255 - rtl::math::round(float(nTransparence) / 100)); + aShadow.Color = sal_Int32(aColor); + } + break; + } + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + if ( bRet ) + { + switch( aShadow.Location ) + { + case table::ShadowLocation_NONE : eLocation = SvxShadowLocation::NONE; break; + case table::ShadowLocation_TOP_LEFT : eLocation = SvxShadowLocation::TopLeft; break; + case table::ShadowLocation_TOP_RIGHT : eLocation = SvxShadowLocation::TopRight; break; + case table::ShadowLocation_BOTTOM_LEFT : eLocation = SvxShadowLocation::BottomLeft ; break; + case table::ShadowLocation_BOTTOM_RIGHT: eLocation = SvxShadowLocation::BottomRight; break; + default: ; // prevent warning + } + + nWidth = bConvert ? o3tl::toTwips(aShadow.ShadowWidth, o3tl::Length::mm100) : aShadow.ShadowWidth; + Color aSet(ColorTransparency, aShadow.Color); + aShadowColor = aSet; + } + + return bRet; +} + + +bool SvxShadowItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxShadowItem& rItem = static_cast<const SvxShadowItem&>(rAttr); + return ( ( aShadowColor == rItem.aShadowColor ) && + ( nWidth == rItem.GetWidth() ) && + ( eLocation == rItem.GetLocation() ) ); +} + +SvxShadowItem* SvxShadowItem::Clone( SfxItemPool* ) const +{ + return new SvxShadowItem( *this ); +} + +sal_uInt16 SvxShadowItem::CalcShadowSpace( SvxShadowItemSide nShadow ) const +{ + sal_uInt16 nSpace = 0; + + switch ( nShadow ) + { + case SvxShadowItemSide::TOP: + if ( eLocation == SvxShadowLocation::TopLeft || + eLocation == SvxShadowLocation::TopRight ) + nSpace = nWidth; + break; + + case SvxShadowItemSide::BOTTOM: + if ( eLocation == SvxShadowLocation::BottomLeft || + eLocation == SvxShadowLocation::BottomRight ) + nSpace = nWidth; + break; + + case SvxShadowItemSide::LEFT: + if ( eLocation == SvxShadowLocation::TopLeft || + eLocation == SvxShadowLocation::BottomLeft ) + nSpace = nWidth; + break; + + case SvxShadowItemSide::RIGHT: + if ( eLocation == SvxShadowLocation::TopRight || + eLocation == SvxShadowLocation::BottomRight ) + nSpace = nWidth; + break; + + default: + OSL_FAIL( "wrong shadow" ); + } + return nSpace; +} + +static TranslateId RID_SVXITEMS_SHADOW[] = +{ + RID_SVXITEMS_SHADOW_NONE, + RID_SVXITEMS_SHADOW_TOPLEFT, + RID_SVXITEMS_SHADOW_TOPRIGHT, + RID_SVXITEMS_SHADOW_BOTTOMLEFT, + RID_SVXITEMS_SHADOW_BOTTOMRIGHT +}; + +bool SvxShadowItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + rText = ::GetColorString( aShadowColor ) + cpDelim; + TranslateId pId = RID_SVXITEMS_TRANSPARENT_FALSE; + + if ( aShadowColor.IsTransparent() ) + pId = RID_SVXITEMS_TRANSPARENT_TRUE; + rText += EditResId(pId) + + cpDelim + + GetMetricText( static_cast<tools::Long>(nWidth), eCoreUnit, ePresUnit, &rIntl ) + + cpDelim + + EditResId(RID_SVXITEMS_SHADOW[static_cast<int>(eLocation)]); + return true; + } + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_SHADOW_COMPLETE) + + ::GetColorString( aShadowColor ) + + cpDelim; + + TranslateId pId = RID_SVXITEMS_TRANSPARENT_FALSE; + if ( aShadowColor.IsTransparent() ) + pId = RID_SVXITEMS_TRANSPARENT_TRUE; + rText += EditResId(pId) + + cpDelim + + GetMetricText( static_cast<tools::Long>(nWidth), eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)) + + cpDelim + + EditResId(RID_SVXITEMS_SHADOW[static_cast<int>(eLocation)]); + return true; + } + default: ; // prevent warning + } + return false; +} + + +void SvxShadowItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + nWidth = static_cast<sal_uInt16>(BigInt::Scale( nWidth, nMult, nDiv )); +} + + +bool SvxShadowItem::HasMetrics() const +{ + return true; +} + + +sal_uInt16 SvxShadowItem::GetValueCount() const +{ + return sal_uInt16(SvxShadowLocation::End); // SvxShadowLocation::BottomRight + 1 +} + +sal_uInt16 SvxShadowItem::GetEnumValue() const +{ + return static_cast<sal_uInt16>(GetLocation()); +} + + +void SvxShadowItem::SetEnumValue( sal_uInt16 nVal ) +{ + SetLocation( static_cast<SvxShadowLocation>(nVal) ); +} + +void SvxShadowItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxShadowItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("aShadowColor"), BAD_CAST(aShadowColor.AsRGBHexString().toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nWidth"), BAD_CAST(OString::number(nWidth).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eLocation"), BAD_CAST(OString::number(static_cast<int>(eLocation)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(EditResId(RID_SVXITEMS_SHADOW[static_cast<int>(eLocation)]).toUtf8().getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxBoxItem ------------------------------------------------------ + +SvxBoxItem::SvxBoxItem( const SvxBoxItem& rCpy ) : + + SfxPoolItem ( rCpy ), + pTop ( rCpy.pTop ? new SvxBorderLine( *rCpy.pTop ) : nullptr ), + pBottom ( rCpy.pBottom ? new SvxBorderLine( *rCpy.pBottom ) : nullptr ), + pLeft ( rCpy.pLeft ? new SvxBorderLine( *rCpy.pLeft ) : nullptr ), + pRight ( rCpy.pRight ? new SvxBorderLine( *rCpy.pRight ) : nullptr ), + nTopDist ( rCpy.nTopDist ), + nBottomDist ( rCpy.nBottomDist ), + nLeftDist ( rCpy.nLeftDist ), + nRightDist ( rCpy.nRightDist ), + bRemoveAdjCellBorder ( rCpy.bRemoveAdjCellBorder ) +{ +} + + +SvxBoxItem::SvxBoxItem( const sal_uInt16 nId ) : + SfxPoolItem( nId ), + nTopDist ( 0 ), + nBottomDist ( 0 ), + nLeftDist ( 0 ), + nRightDist ( 0 ), + bRemoveAdjCellBorder ( false ) +{ +} + + +SvxBoxItem::~SvxBoxItem() +{ +} + +void SvxBoxItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxBoxItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("top-dist"), + BAD_CAST(OString::number(nTopDist).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bottom-dist"), + BAD_CAST(OString::number(nBottomDist).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("left-dist"), + BAD_CAST(OString::number(nLeftDist).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("right-dist"), + BAD_CAST(OString::number(nRightDist).getStr())); + SfxPoolItem::dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +boost::property_tree::ptree SvxBoxItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree; + + boost::property_tree::ptree aState; + aState.put("top", GetTop() && !GetTop()->isEmpty()); + aState.put("bottom", GetBottom() && !GetBottom()->isEmpty()); + aState.put("left", GetLeft() && !GetLeft()->isEmpty()); + aState.put("right", GetRight() && !GetRight()->isEmpty()); + + aTree.push_back(std::make_pair("state", aState)); + aTree.put("commandName", ".uno:BorderOuter"); + + return aTree; +} + + +static bool CmpBrdLn( const std::unique_ptr<SvxBorderLine> & pBrd1, const SvxBorderLine* pBrd2 ) +{ + if( pBrd1.get() == pBrd2 ) + return true; + if( pBrd1 == nullptr || pBrd2 == nullptr) + return false; + return *pBrd1 == *pBrd2; +} + + +bool SvxBoxItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxBoxItem& rBoxItem = static_cast<const SvxBoxItem&>(rAttr); + return ( + ( nTopDist == rBoxItem.nTopDist ) && + ( nBottomDist == rBoxItem.nBottomDist ) && + ( nLeftDist == rBoxItem.nLeftDist ) && + ( nRightDist == rBoxItem.nRightDist ) && + ( bRemoveAdjCellBorder == rBoxItem.bRemoveAdjCellBorder ) && + CmpBrdLn( pTop, rBoxItem.GetTop() ) && + CmpBrdLn( pBottom, rBoxItem.GetBottom() ) && + CmpBrdLn( pLeft, rBoxItem.GetLeft() ) && + CmpBrdLn( pRight, rBoxItem.GetRight() ) ); +} + + +table::BorderLine2 SvxBoxItem::SvxLineToLine(const SvxBorderLine* pLine, bool bConvert) +{ + table::BorderLine2 aLine; + if(pLine) + { + aLine.Color = sal_Int32(pLine->GetColor()); + aLine.InnerLineWidth = sal_uInt16( bConvert ? convertTwipToMm100(pLine->GetInWidth() ): pLine->GetInWidth() ); + aLine.OuterLineWidth = sal_uInt16( bConvert ? convertTwipToMm100(pLine->GetOutWidth()): pLine->GetOutWidth() ); + aLine.LineDistance = sal_uInt16( bConvert ? convertTwipToMm100(pLine->GetDistance()): pLine->GetDistance() ); + aLine.LineStyle = sal_Int16(pLine->GetBorderLineStyle()); + aLine.LineWidth = sal_uInt32( bConvert ? convertTwipToMm100( pLine->GetWidth( ) ) : pLine->GetWidth( ) ); + } + else + { + aLine.Color = aLine.InnerLineWidth = aLine.OuterLineWidth = aLine.LineDistance = 0; + aLine.LineStyle = table::BorderLineStyle::NONE; // 0 is SOLID! + } + return aLine; +} + +bool SvxBoxItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + table::BorderLine2 aRetLine; + sal_Int16 nDist = 0; + bool bDistMember = false; + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case 0: + { + // 4 Borders and 5 distances + uno::Sequence< uno::Any > aSeq{ + uno::Any(SvxBoxItem::SvxLineToLine(GetLeft(), bConvert)), + uno::Any(SvxBoxItem::SvxLineToLine(GetRight(), bConvert)), + uno::Any(SvxBoxItem::SvxLineToLine(GetBottom(), bConvert)), + uno::Any(SvxBoxItem::SvxLineToLine(GetTop(), bConvert)), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100( GetSmallestDistance()) : GetSmallestDistance())), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100( nTopDist ) : nTopDist )), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100( nBottomDist ) : nBottomDist )), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100( nLeftDist ) : nLeftDist )), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100( nRightDist ) : nRightDist )) + }; + rVal <<= aSeq; + return true; + } + case MID_LEFT_BORDER: + case LEFT_BORDER: + aRetLine = SvxBoxItem::SvxLineToLine(GetLeft(), bConvert); + break; + case MID_RIGHT_BORDER: + case RIGHT_BORDER: + aRetLine = SvxBoxItem::SvxLineToLine(GetRight(), bConvert); + break; + case MID_BOTTOM_BORDER: + case BOTTOM_BORDER: + aRetLine = SvxBoxItem::SvxLineToLine(GetBottom(), bConvert); + break; + case MID_TOP_BORDER: + case TOP_BORDER: + aRetLine = SvxBoxItem::SvxLineToLine(GetTop(), bConvert); + break; + case BORDER_DISTANCE: + nDist = GetSmallestDistance(); + bDistMember = true; + break; + case TOP_BORDER_DISTANCE: + nDist = nTopDist; + bDistMember = true; + break; + case BOTTOM_BORDER_DISTANCE: + nDist = nBottomDist; + bDistMember = true; + break; + case LEFT_BORDER_DISTANCE: + nDist = nLeftDist; + bDistMember = true; + break; + case RIGHT_BORDER_DISTANCE: + nDist = nRightDist; + bDistMember = true; + break; + case LINE_STYLE: + case LINE_WIDTH: + // it doesn't make sense to return a value for these since it's + // probably ambiguous + return true; + } + + if( bDistMember ) + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nDist) : nDist); + else + rVal <<= aRetLine; + + return true; +} + +namespace +{ + +bool +lcl_lineToSvxLine(const table::BorderLine& rLine, SvxBorderLine& rSvxLine, bool bConvert, bool bGuessWidth) +{ + rSvxLine.SetColor( Color(ColorTransparency, rLine.Color)); + if ( bGuessWidth ) + { + rSvxLine.GuessLinesWidths( rSvxLine.GetBorderLineStyle(), + bConvert ? o3tl::toTwips(rLine.OuterLineWidth, o3tl::Length::mm100) : rLine.OuterLineWidth, + bConvert ? o3tl::toTwips(rLine.InnerLineWidth, o3tl::Length::mm100) : rLine.InnerLineWidth, + bConvert ? o3tl::toTwips(rLine.LineDistance, o3tl::Length::mm100) : rLine.LineDistance ); + } + + bool bRet = !rSvxLine.isEmpty(); + return bRet; +} + +} + + +bool SvxBoxItem::LineToSvxLine(const css::table::BorderLine& rLine, SvxBorderLine& rSvxLine, bool bConvert) +{ + return lcl_lineToSvxLine(rLine, rSvxLine, bConvert, true); +} + +bool +SvxBoxItem::LineToSvxLine(const css::table::BorderLine2& rLine, SvxBorderLine& rSvxLine, bool bConvert) +{ + SvxBorderLineStyle const nStyle = + (rLine.LineStyle < 0 || BORDER_LINE_STYLE_MAX < rLine.LineStyle) + ? SvxBorderLineStyle::SOLID // default + : static_cast<SvxBorderLineStyle>(rLine.LineStyle); + + rSvxLine.SetBorderLineStyle( nStyle ); + + bool bGuessWidth = true; + if ( rLine.LineWidth ) + { + rSvxLine.SetWidth( bConvert? o3tl::toTwips(rLine.LineWidth, o3tl::Length::mm100) : rLine.LineWidth ); + // fdo#46112: double does not necessarily mean symmetric + // for backwards compatibility + bGuessWidth = (SvxBorderLineStyle::DOUBLE == nStyle || SvxBorderLineStyle::DOUBLE_THIN == nStyle) && + (rLine.InnerLineWidth > 0) && (rLine.OuterLineWidth > 0); + } + + return lcl_lineToSvxLine(rLine, rSvxLine, bConvert, bGuessWidth); +} + + +namespace +{ + +bool +lcl_extractBorderLine(const uno::Any& rAny, table::BorderLine2& rLine) +{ + if (rAny >>= rLine) + return true; + + table::BorderLine aBorderLine; + if (rAny >>= aBorderLine) + { + rLine.Color = aBorderLine.Color; + rLine.InnerLineWidth = aBorderLine.InnerLineWidth; + rLine.OuterLineWidth = aBorderLine.OuterLineWidth; + rLine.LineDistance = aBorderLine.LineDistance; + rLine.LineStyle = table::BorderLineStyle::SOLID; + return true; + } + + return false; +} + +template<typename Item, typename Line> +bool +lcl_setLine(const uno::Any& rAny, Item& rItem, Line nLine, const bool bConvert) +{ + bool bDone = false; + table::BorderLine2 aBorderLine; + if (lcl_extractBorderLine(rAny, aBorderLine)) + { + SvxBorderLine aLine; + bool bSet = SvxBoxItem::LineToSvxLine(aBorderLine, aLine, bConvert); + rItem.SetLine( bSet ? &aLine : nullptr, nLine); + bDone = true; + } + return bDone; +} + +} + +bool SvxBoxItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + SvxBoxItemLine nLine = SvxBoxItemLine::TOP; + bool bDistMember = false; + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case 0: + { + uno::Sequence< uno::Any > aSeq; + if (( rVal >>= aSeq ) && ( aSeq.getLength() == 9 )) + { + // 4 Borders and 5 distances + const SvxBoxItemLine aBorders[] = { SvxBoxItemLine::LEFT, SvxBoxItemLine::RIGHT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::TOP }; + for (size_t n(0); n != std::size(aBorders); ++n) + { + if (!lcl_setLine(aSeq[n], *this, aBorders[n], bConvert)) + return false; + } + + // WTH are the borders and the distances saved in different order? + SvxBoxItemLine const nLines[4] = { SvxBoxItemLine::TOP, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::LEFT, SvxBoxItemLine::RIGHT }; + for ( sal_Int32 n = 4; n < 9; n++ ) + { + sal_Int32 nDist = 0; + if ( aSeq[n] >>= nDist ) + { + if( bConvert ) + nDist = o3tl::toTwips(nDist, o3tl::Length::mm100); + if ( n == 4 ) + SetAllDistances(nDist); + else + SetDistance( nDist, nLines[n-5] ); + } + else + return false; + } + + return true; + } + else + return false; + } + case LEFT_BORDER_DISTANCE: + bDistMember = true; + [[fallthrough]]; + case LEFT_BORDER: + case MID_LEFT_BORDER: + nLine = SvxBoxItemLine::LEFT; + break; + case RIGHT_BORDER_DISTANCE: + bDistMember = true; + [[fallthrough]]; + case RIGHT_BORDER: + case MID_RIGHT_BORDER: + nLine = SvxBoxItemLine::RIGHT; + break; + case BOTTOM_BORDER_DISTANCE: + bDistMember = true; + [[fallthrough]]; + case BOTTOM_BORDER: + case MID_BOTTOM_BORDER: + nLine = SvxBoxItemLine::BOTTOM; + break; + case TOP_BORDER_DISTANCE: + bDistMember = true; + [[fallthrough]]; + case TOP_BORDER: + case MID_TOP_BORDER: + nLine = SvxBoxItemLine::TOP; + break; + case LINE_STYLE: + { + drawing::LineStyle eDrawingStyle; + rVal >>= eDrawingStyle; + SvxBorderLineStyle eBorderStyle = SvxBorderLineStyle::NONE; + switch ( eDrawingStyle ) + { + default: + case drawing::LineStyle_NONE: + break; + case drawing::LineStyle_SOLID: + eBorderStyle = SvxBorderLineStyle::SOLID; + break; + case drawing::LineStyle_DASH: + eBorderStyle = SvxBorderLineStyle::DASHED; + break; + } + + // Set the line style on all borders + for( SvxBoxItemLine n : o3tl::enumrange<SvxBoxItemLine>() ) + { + editeng::SvxBorderLine* pLine = const_cast< editeng::SvxBorderLine* >( GetLine( n ) ); + if( pLine ) + pLine->SetBorderLineStyle( eBorderStyle ); + } + return true; + } + break; + case LINE_WIDTH: + { + // Set the line width on all borders + tools::Long nWidth(0); + rVal >>= nWidth; + if( bConvert ) + nWidth = o3tl::toTwips(nWidth, o3tl::Length::mm100); + + // Set the line Width on all borders + for( SvxBoxItemLine n : o3tl::enumrange<SvxBoxItemLine>() ) + { + editeng::SvxBorderLine* pLine = const_cast< editeng::SvxBorderLine* >( GetLine( n ) ); + if( pLine ) + pLine->SetWidth( nWidth ); + } + } + return true; + } + + if( bDistMember || nMemberId == BORDER_DISTANCE ) + { + sal_Int32 nDist = 0; + if(!(rVal >>= nDist)) + return false; + + { + if( bConvert ) + nDist = o3tl::toTwips(nDist, o3tl::Length::mm100); + if( nMemberId == BORDER_DISTANCE ) + SetAllDistances(nDist); + else + SetDistance( nDist, nLine ); + } + } + else + { + SvxBorderLine aLine; + if( !rVal.hasValue() ) + return false; + + table::BorderLine2 aBorderLine; + if( lcl_extractBorderLine(rVal, aBorderLine) ) + { + // usual struct + } + else if (rVal.getValueTypeClass() == uno::TypeClass_SEQUENCE ) + { + // serialization for basic macro recording + uno::Reference < script::XTypeConverter > xConverter + ( script::Converter::create(::comphelper::getProcessComponentContext()) ); + uno::Sequence < uno::Any > aSeq; + uno::Any aNew; + try { aNew = xConverter->convertTo( rVal, cppu::UnoType<uno::Sequence < uno::Any >>::get() ); } + catch (const uno::Exception&) {} + + aNew >>= aSeq; + if (aSeq.getLength() >= 4 && aSeq.getLength() <= 6) + { + sal_Int32 nVal = 0; + if ( aSeq[0] >>= nVal ) + aBorderLine.Color = nVal; + if ( aSeq[1] >>= nVal ) + aBorderLine.InnerLineWidth = static_cast<sal_Int16>(nVal); + if ( aSeq[2] >>= nVal ) + aBorderLine.OuterLineWidth = static_cast<sal_Int16>(nVal); + if ( aSeq[3] >>= nVal ) + aBorderLine.LineDistance = static_cast<sal_Int16>(nVal); + if (aSeq.getLength() >= 5) // fdo#40874 added fields + { + if (aSeq[4] >>= nVal) + { + aBorderLine.LineStyle = nVal; + } + if (aSeq.getLength() >= 6) + { + if (aSeq[5] >>= nVal) + { + aBorderLine.LineWidth = nVal; + } + } + } + } + else + return false; + } + else + return false; + + bool bSet = SvxBoxItem::LineToSvxLine(aBorderLine, aLine, bConvert); + SetLine(bSet ? &aLine : nullptr, nLine); + } + + return true; +} + +SvxBoxItem* SvxBoxItem::Clone( SfxItemPool* ) const +{ + return new SvxBoxItem( *this ); +} + +bool SvxBoxItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + OUString cpDelimTmp(cpDelim); + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + rText.clear(); + + if ( pTop ) + { + rText = pTop->GetValueString( eCoreUnit, ePresUnit, &rIntl ) + cpDelimTmp; + } + if( !(pTop && pBottom && pLeft && pRight && + *pTop == *pBottom && *pTop == *pLeft && *pTop == *pRight) ) + { + if ( pBottom ) + { + rText += pBottom->GetValueString( eCoreUnit, ePresUnit, &rIntl ) + cpDelimTmp; + } + if ( pLeft ) + { + rText += pLeft->GetValueString( eCoreUnit, ePresUnit, &rIntl ) + cpDelimTmp; + } + if ( pRight ) + { + rText += pRight->GetValueString( eCoreUnit, ePresUnit, &rIntl ) + cpDelimTmp; + } + } + rText += GetMetricText( static_cast<tools::Long>(nTopDist), eCoreUnit, ePresUnit, &rIntl ); + if( nTopDist != nBottomDist || nTopDist != nLeftDist || + nTopDist != nRightDist ) + { + rText += cpDelimTmp + + GetMetricText( static_cast<tools::Long>(nBottomDist), eCoreUnit, + ePresUnit, &rIntl ) + + cpDelimTmp + + GetMetricText( static_cast<tools::Long>(nLeftDist), eCoreUnit, ePresUnit, &rIntl ) + + cpDelimTmp + + GetMetricText( static_cast<tools::Long>(nRightDist), eCoreUnit, + ePresUnit, &rIntl ); + } + return true; + } + case SfxItemPresentation::Complete: + { + if( !(pTop || pBottom || pLeft || pRight) ) + { + rText = EditResId(RID_SVXITEMS_BORDER_NONE) + cpDelimTmp; + } + else + { + rText = EditResId(RID_SVXITEMS_BORDER_COMPLETE); + if( pTop && pBottom && pLeft && pRight && + *pTop == *pBottom && *pTop == *pLeft && *pTop == *pRight ) + { + rText += pTop->GetValueString( eCoreUnit, ePresUnit, &rIntl, true ) + cpDelimTmp; + } + else + { + if ( pTop ) + { + rText += EditResId(RID_SVXITEMS_BORDER_TOP) + + pTop->GetValueString( eCoreUnit, ePresUnit, &rIntl, true ) + + cpDelimTmp; + } + if ( pBottom ) + { + rText += EditResId(RID_SVXITEMS_BORDER_BOTTOM) + + pBottom->GetValueString( eCoreUnit, ePresUnit, &rIntl, true ) + + cpDelimTmp; + } + if ( pLeft ) + { + rText += EditResId(RID_SVXITEMS_BORDER_LEFT) + + pLeft->GetValueString( eCoreUnit, ePresUnit, &rIntl, true ) + + cpDelimTmp; + } + if ( pRight ) + { + rText += EditResId(RID_SVXITEMS_BORDER_RIGHT) + + pRight->GetValueString( eCoreUnit, ePresUnit, &rIntl, true ) + + cpDelimTmp; + } + } + } + + rText += EditResId(RID_SVXITEMS_BORDER_DISTANCE); + if( nTopDist == nBottomDist && nTopDist == nLeftDist && + nTopDist == nRightDist ) + { + rText += GetMetricText( static_cast<tools::Long>(nTopDist), eCoreUnit, + ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + else + { + rText += EditResId(RID_SVXITEMS_BORDER_TOP) + + GetMetricText( static_cast<tools::Long>(nTopDist), eCoreUnit, + ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_BORDER_BOTTOM) + + GetMetricText( static_cast<tools::Long>(nBottomDist), eCoreUnit, + ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_BORDER_LEFT) + + GetMetricText( static_cast<tools::Long>(nLeftDist), eCoreUnit, + ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_BORDER_RIGHT) + + GetMetricText( static_cast<tools::Long>(nRightDist), eCoreUnit, + ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + return true; + } + default: ; // prevent warning + } + return false; +} + + +void SvxBoxItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + if ( pTop ) pTop->ScaleMetrics( nMult, nDiv ); + if ( pBottom ) pBottom->ScaleMetrics( nMult, nDiv ); + if ( pLeft ) pLeft->ScaleMetrics( nMult, nDiv ); + if ( pRight ) pRight->ScaleMetrics( nMult, nDiv ); + nTopDist = static_cast<sal_Int16>(BigInt::Scale( nTopDist, nMult, nDiv )); + nBottomDist = static_cast<sal_Int16>(BigInt::Scale( nBottomDist, nMult, nDiv )); + nLeftDist = static_cast<sal_Int16>(BigInt::Scale( nLeftDist, nMult, nDiv )); + nRightDist = static_cast<sal_Int16>(BigInt::Scale( nRightDist, nMult, nDiv )); +} + + +bool SvxBoxItem::HasMetrics() const +{ + return true; +} + + +const SvxBorderLine *SvxBoxItem::GetLine( SvxBoxItemLine nLine ) const +{ + const SvxBorderLine *pRet = nullptr; + + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + pRet = pTop.get(); + break; + case SvxBoxItemLine::BOTTOM: + pRet = pBottom.get(); + break; + case SvxBoxItemLine::LEFT: + pRet = pLeft.get(); + break; + case SvxBoxItemLine::RIGHT: + pRet = pRight.get(); + break; + default: + OSL_FAIL( "wrong line" ); + break; + } + + return pRet; +} + + +void SvxBoxItem::SetLine( const SvxBorderLine* pNew, SvxBoxItemLine nLine ) +{ + std::unique_ptr<SvxBorderLine> pTmp( pNew ? new SvxBorderLine( *pNew ) : nullptr ); + + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + pTop = std::move( pTmp ); + break; + case SvxBoxItemLine::BOTTOM: + pBottom = std::move( pTmp ); + break; + case SvxBoxItemLine::LEFT: + pLeft = std::move( pTmp ); + break; + case SvxBoxItemLine::RIGHT: + pRight = std::move( pTmp ); + break; + default: + OSL_FAIL( "wrong line" ); + } +} + + +sal_uInt16 SvxBoxItem::GetSmallestDistance() const +{ + // The smallest distance that is not 0 will be returned. + sal_uInt16 nDist = nTopDist; + if( nBottomDist && (!nDist || nBottomDist < nDist) ) + nDist = nBottomDist; + if( nLeftDist && (!nDist || nLeftDist < nDist) ) + nDist = nLeftDist; + if( nRightDist && (!nDist || nRightDist < nDist) ) + nDist = nRightDist; + + return nDist; +} + + +sal_Int16 SvxBoxItem::GetDistance( SvxBoxItemLine nLine, bool bAllowNegative ) const +{ + sal_Int16 nDist = 0; + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + nDist = nTopDist; + break; + case SvxBoxItemLine::BOTTOM: + nDist = nBottomDist; + break; + case SvxBoxItemLine::LEFT: + nDist = nLeftDist; + break; + case SvxBoxItemLine::RIGHT: + nDist = nRightDist; + break; + default: + OSL_FAIL( "wrong line" ); + } + + if (!bAllowNegative && nDist < 0) + { + nDist = 0; + } + return nDist; +} + + +void SvxBoxItem::SetDistance( sal_Int16 nNew, SvxBoxItemLine nLine ) +{ + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + nTopDist = nNew; + break; + case SvxBoxItemLine::BOTTOM: + nBottomDist = nNew; + break; + case SvxBoxItemLine::LEFT: + nLeftDist = nNew; + break; + case SvxBoxItemLine::RIGHT: + nRightDist = nNew; + break; + default: + OSL_FAIL( "wrong line" ); + } +} + +sal_uInt16 SvxBoxItem::CalcLineWidth( SvxBoxItemLine nLine ) const +{ + SvxBorderLine* pTmp = nullptr; + sal_uInt16 nWidth = 0; + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + pTmp = pTop.get(); + break; + case SvxBoxItemLine::BOTTOM: + pTmp = pBottom.get(); + break; + case SvxBoxItemLine::LEFT: + pTmp = pLeft.get(); + break; + case SvxBoxItemLine::RIGHT: + pTmp = pRight.get(); + break; + default: + OSL_FAIL( "wrong line" ); + } + + if( pTmp ) + nWidth = pTmp->GetScaledWidth(); + + return nWidth; +} + +sal_Int16 SvxBoxItem::CalcLineSpace( SvxBoxItemLine nLine, bool bEvenIfNoLine, bool bAllowNegative ) const +{ + SvxBorderLine* pTmp = nullptr; + sal_Int16 nDist = 0; + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + pTmp = pTop.get(); + nDist = nTopDist; + break; + case SvxBoxItemLine::BOTTOM: + pTmp = pBottom.get(); + nDist = nBottomDist; + break; + case SvxBoxItemLine::LEFT: + pTmp = pLeft.get(); + nDist = nLeftDist; + break; + case SvxBoxItemLine::RIGHT: + pTmp = pRight.get(); + nDist = nRightDist; + break; + default: + OSL_FAIL( "wrong line" ); + } + + if( pTmp ) + { + nDist = nDist + pTmp->GetScaledWidth(); + } + else if( !bEvenIfNoLine ) + nDist = 0; + + if (!bAllowNegative && nDist < 0) + { + nDist = 0; + } + + return nDist; +} + +bool SvxBoxItem::HasBorder( bool bTreatPaddingAsBorder ) const +{ + return CalcLineSpace( SvxBoxItemLine::BOTTOM, bTreatPaddingAsBorder ) + || CalcLineSpace( SvxBoxItemLine::RIGHT, bTreatPaddingAsBorder ) + || CalcLineSpace( SvxBoxItemLine::TOP, bTreatPaddingAsBorder ) + || CalcLineSpace( SvxBoxItemLine::LEFT, bTreatPaddingAsBorder ); +} + +// class SvxBoxInfoItem -------------------------------------------------- + +SvxBoxInfoItem::SvxBoxInfoItem( const sal_uInt16 nId ) : + SfxPoolItem( nId ), + mbEnableHor( false ), + mbEnableVer( false ), + nDefDist( 0 ) +{ + bDist = bMinDist = false; + ResetFlags(); +} + + +SvxBoxInfoItem::SvxBoxInfoItem( const SvxBoxInfoItem& rCpy ) : + SfxPoolItem( rCpy ), + pHori( rCpy.pHori ? new SvxBorderLine( *rCpy.pHori ) : nullptr ), + pVert( rCpy.pVert ? new SvxBorderLine( *rCpy.pVert ) : nullptr ), + mbEnableHor( rCpy.mbEnableHor ), + mbEnableVer( rCpy.mbEnableVer ), + bDist( rCpy.bDist ), + bMinDist ( rCpy.bMinDist ), + nValidFlags( rCpy.nValidFlags ), + nDefDist( rCpy.nDefDist ) +{ +} + +SvxBoxInfoItem::~SvxBoxInfoItem() +{ +} + + +boost::property_tree::ptree SvxBoxInfoItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree; + + boost::property_tree::ptree aState; + aState.put("vertical", GetVert() && !GetVert()->isEmpty()); + aState.put("horizontal", GetHori() && !GetHori()->isEmpty()); + + aTree.push_back(std::make_pair("state", aState)); + aTree.put("commandName", ".uno:BorderInner"); + + return aTree; +} + + +bool SvxBoxInfoItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxBoxInfoItem& rBoxInfo = static_cast<const SvxBoxInfoItem&>(rAttr); + + return ( mbEnableHor == rBoxInfo.mbEnableHor + && mbEnableVer == rBoxInfo.mbEnableVer + && bDist == rBoxInfo.IsDist() + && bMinDist == rBoxInfo.IsMinDist() + && nValidFlags == rBoxInfo.nValidFlags + && nDefDist == rBoxInfo.GetDefDist() + && CmpBrdLn( pHori, rBoxInfo.GetHori() ) + && CmpBrdLn( pVert, rBoxInfo.GetVert() ) + ); +} + + +void SvxBoxInfoItem::SetLine( const SvxBorderLine* pNew, SvxBoxInfoItemLine nLine ) +{ + std::unique_ptr<SvxBorderLine> pTmp( pNew ? new SvxBorderLine( *pNew ) : nullptr ); + + if ( SvxBoxInfoItemLine::HORI == nLine ) + { + pHori = std::move(pTmp); + } + else if ( SvxBoxInfoItemLine::VERT == nLine ) + { + pVert = std::move(pTmp); + } + else + { + OSL_FAIL( "wrong line" ); + } +} + +SvxBoxInfoItem* SvxBoxInfoItem::Clone( SfxItemPool* ) const +{ + return new SvxBoxInfoItem( *this ); +} + +bool SvxBoxInfoItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + rText.clear(); + return false; +} + + +void SvxBoxInfoItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + if ( pHori ) pHori->ScaleMetrics( nMult, nDiv ); + if ( pVert ) pVert->ScaleMetrics( nMult, nDiv ); + nDefDist = static_cast<sal_uInt16>(BigInt::Scale( nDefDist, nMult, nDiv )); +} + + +bool SvxBoxInfoItem::HasMetrics() const +{ + return true; +} + + +void SvxBoxInfoItem::ResetFlags() +{ + nValidFlags = static_cast<SvxBoxInfoItemValidFlags>(0x7F); // all valid except Disable +} + +bool SvxBoxInfoItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + table::BorderLine2 aRetLine; + sal_Int16 nVal=0; + bool bIntMember = false; + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case 0: + { + // 2 BorderLines, flags, valid flags and distance + if ( IsTable() ) + nVal |= 0x01; + if ( IsDist() ) + nVal |= 0x02; + if ( IsMinDist() ) + nVal |= 0x04; + css::uno::Sequence< css::uno::Any > aSeq{ + uno::Any(SvxBoxItem::SvxLineToLine( pHori.get(), bConvert)), + uno::Any(SvxBoxItem::SvxLineToLine( pVert.get(), bConvert)), + uno::Any(nVal), + uno::Any(static_cast<sal_Int16>(nValidFlags)), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100(GetDefDist()) : GetDefDist())) + }; + rVal <<= aSeq; + return true; + } + + case MID_HORIZONTAL: + aRetLine = SvxBoxItem::SvxLineToLine( pHori.get(), bConvert); + break; + case MID_VERTICAL: + aRetLine = SvxBoxItem::SvxLineToLine( pVert.get(), bConvert); + break; + case MID_FLAGS: + bIntMember = true; + if ( IsTable() ) + nVal |= 0x01; + if ( IsDist() ) + nVal |= 0x02; + if ( IsMinDist() ) + nVal |= 0x04; + rVal <<= nVal; + break; + case MID_VALIDFLAGS: + bIntMember = true; + rVal <<= static_cast<sal_Int16>(nValidFlags); + break; + case MID_DISTANCE: + bIntMember = true; + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(GetDefDist()) : GetDefDist()); + break; + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + if( !bIntMember ) + rVal <<= aRetLine; + + return true; +} + + +bool SvxBoxInfoItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + bool bRet; + switch(nMemberId) + { + case 0: + { + css::uno::Sequence< css::uno::Any > aSeq; + if (( rVal >>= aSeq ) && ( aSeq.getLength() == 5 )) + { + // 2 BorderLines, flags, valid flags and distance + if (!lcl_setLine(aSeq[0], *this, SvxBoxInfoItemLine::HORI, bConvert)) + return false; + if (!lcl_setLine(aSeq[1], *this, SvxBoxInfoItemLine::VERT, bConvert)) + return false; + + sal_Int16 nFlags( 0 ); + sal_Int32 nVal( 0 ); + if ( aSeq[2] >>= nFlags ) + { + SetTable ( ( nFlags & 0x01 ) != 0 ); + SetDist ( ( nFlags & 0x02 ) != 0 ); + SetMinDist( ( nFlags & 0x04 ) != 0 ); + } + else + return false; + if ( aSeq[3] >>= nFlags ) + nValidFlags = static_cast<SvxBoxInfoItemValidFlags>(nFlags); + else + return false; + if (( aSeq[4] >>= nVal ) && ( nVal >= 0 )) + { + if( bConvert ) + nVal = o3tl::toTwips(nVal, o3tl::Length::mm100); + SetDefDist( nVal ); + } + } + return true; + } + + case MID_HORIZONTAL: + case MID_VERTICAL: + { + if( !rVal.hasValue() ) + return false; + + table::BorderLine2 aBorderLine; + if( lcl_extractBorderLine(rVal, aBorderLine) ) + { + // usual struct + } + else if (rVal.getValueTypeClass() == uno::TypeClass_SEQUENCE ) + { + // serialization for basic macro recording + uno::Reference < script::XTypeConverter > xConverter( script::Converter::create(::comphelper::getProcessComponentContext()) ); + uno::Any aNew; + uno::Sequence < uno::Any > aSeq; + try { aNew = xConverter->convertTo( rVal, cppu::UnoType<uno::Sequence < uno::Any >>::get() ); } + catch (const uno::Exception&) {} + + if ((aNew >>= aSeq) && + aSeq.getLength() >= 4 && aSeq.getLength() <= 6) + { + sal_Int32 nVal = 0; + if ( aSeq[0] >>= nVal ) + aBorderLine.Color = nVal; + if ( aSeq[1] >>= nVal ) + aBorderLine.InnerLineWidth = static_cast<sal_Int16>(nVal); + if ( aSeq[2] >>= nVal ) + aBorderLine.OuterLineWidth = static_cast<sal_Int16>(nVal); + if ( aSeq[3] >>= nVal ) + aBorderLine.LineDistance = static_cast<sal_Int16>(nVal); + if (aSeq.getLength() >= 5) // fdo#40874 added fields + { + if (aSeq[4] >>= nVal) + { + aBorderLine.LineStyle = nVal; + } + if (aSeq.getLength() >= 6) + { + if (aSeq[5] >>= nVal) + { + aBorderLine.LineWidth = nVal; + } + } + } + } + else + return false; + } + else if (rVal.getValueType() == cppu::UnoType<css::uno::Sequence < sal_Int16 >>::get() ) + { + // serialization for basic macro recording + css::uno::Sequence < sal_Int16 > aSeq; + rVal >>= aSeq; + if (aSeq.getLength() >= 4 && aSeq.getLength() <= 6) + { + aBorderLine.Color = aSeq[0]; + aBorderLine.InnerLineWidth = aSeq[1]; + aBorderLine.OuterLineWidth = aSeq[2]; + aBorderLine.LineDistance = aSeq[3]; + if (aSeq.getLength() >= 5) // fdo#40874 added fields + { + aBorderLine.LineStyle = aSeq[4]; + if (aSeq.getLength() >= 6) + { + aBorderLine.LineWidth = aSeq[5]; + } + } + } + else + return false; + } + else + return false; + + SvxBorderLine aLine; + bool bSet = SvxBoxItem::LineToSvxLine(aBorderLine, aLine, bConvert); + if ( bSet ) + SetLine( &aLine, nMemberId == MID_HORIZONTAL ? SvxBoxInfoItemLine::HORI : SvxBoxInfoItemLine::VERT ); + break; + } + case MID_FLAGS: + { + sal_Int16 nFlags = sal_Int16(); + bRet = (rVal >>= nFlags); + if ( bRet ) + { + SetTable ( ( nFlags & 0x01 ) != 0 ); + SetDist ( ( nFlags & 0x02 ) != 0 ); + SetMinDist( ( nFlags & 0x04 ) != 0 ); + } + + break; + } + case MID_VALIDFLAGS: + { + sal_Int16 nFlags = sal_Int16(); + bRet = (rVal >>= nFlags); + if ( bRet ) + nValidFlags = static_cast<SvxBoxInfoItemValidFlags>(nFlags); + break; + } + case MID_DISTANCE: + { + sal_Int32 nVal = 0; + bRet = (rVal >>= nVal); + if ( bRet && nVal>=0 ) + { + if( bConvert ) + nVal = o3tl::toTwips(nVal, o3tl::Length::mm100); + SetDefDist( static_cast<sal_uInt16>(nVal) ); + } + break; + } + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + return true; +} + + +namespace editeng +{ + +void BorderDistanceFromWord(bool bFromEdge, sal_Int32& nMargin, sal_Int32& nBorderDistance, + sal_Int32 nBorderWidth) +{ + // See https://wiki.openoffice.org/wiki/Writer/MSInteroperability/PageBorder + + sal_Int32 nNewMargin = nMargin; + sal_Int32 nNewBorderDistance = nBorderDistance; + + if (bFromEdge) + { + nNewMargin = nBorderDistance; + nNewBorderDistance = nMargin - nBorderDistance - nBorderWidth; + } + else + { + nNewMargin -= nBorderDistance + nBorderWidth; + } + + // Ensure correct distance from page edge to text in cases not supported by us: + // when border is outside entire page area (!bFromEdge && BorderDistance > Margin), + // and when border is inside page body area (bFromEdge && BorderDistance > Margin) + if (nNewMargin < 0) + { + nNewMargin = 0; + nNewBorderDistance = std::max<sal_Int32>(nMargin - nBorderWidth, 0); + } + else if (nNewBorderDistance < 0) + { + nNewMargin = nMargin; + } + + nMargin = nNewMargin; + nBorderDistance = nNewBorderDistance; +} + +// Heuristics to decide if we need to use "from edge" offset of borders +// +// There are two cases when we can safely use "from text" or "from edge" offset without distorting +// border position (modulo rounding errors): +// 1. When distance of all borders from text is no greater than 31 pt, we use "from text" +// 2. Otherwise, if distance of all borders from edge is no greater than 31 pt, we use "from edge" +// In all other cases, the position of borders would be distorted on export, because Word doesn't +// support the offset of >31 pts (https://msdn.microsoft.com/en-us/library/ff533820), and we need +// to decide which type of offset would provide less wrong result (i.e., the result would look +// closer to original). Here, we just check sum of distances from text to borders, and if it is +// less than sum of distances from borders to edges. The alternative would be to compare total areas +// between text-and-borders and between borders-and-edges (taking into account different lengths of +// borders, and visual impact of that). +void BorderDistancesToWord(const SvxBoxItem& rBox, const WordPageMargins& rMargins, + WordBorderDistances& rDistances) +{ + // Use signed sal_Int32 that can hold sal_uInt16, to prevent overflow at subtraction below + const sal_Int32 nT = rBox.GetDistance(SvxBoxItemLine::TOP, /*bAllowNegative=*/true); + const sal_Int32 nL = rBox.GetDistance(SvxBoxItemLine::LEFT, /*bAllowNegative=*/true); + const sal_Int32 nB = rBox.GetDistance(SvxBoxItemLine::BOTTOM, /*bAllowNegative=*/true); + const sal_Int32 nR = rBox.GetDistance(SvxBoxItemLine::RIGHT, /*bAllowNegative=*/true); + + // Only take into account existing borders + const SvxBorderLine* pLnT = rBox.GetLine(SvxBoxItemLine::TOP); + const SvxBorderLine* pLnL = rBox.GetLine(SvxBoxItemLine::LEFT); + const SvxBorderLine* pLnB = rBox.GetLine(SvxBoxItemLine::BOTTOM); + const SvxBorderLine* pLnR = rBox.GetLine(SvxBoxItemLine::RIGHT); + + // We need to take border widths into account + const tools::Long nWidthT = pLnT ? pLnT->GetScaledWidth() : 0; + const tools::Long nWidthL = pLnL ? pLnL->GetScaledWidth() : 0; + const tools::Long nWidthB = pLnB ? pLnB->GetScaledWidth() : 0; + const tools::Long nWidthR = pLnR ? pLnR->GetScaledWidth() : 0; + + // Resulting distances from text to borders + const sal_Int32 nT2BT = pLnT ? nT : 0; + const sal_Int32 nT2BL = pLnL ? nL : 0; + const sal_Int32 nT2BB = pLnB ? nB : 0; + const sal_Int32 nT2BR = pLnR ? nR : 0; + + // Resulting distances from edge to borders + const sal_Int32 nE2BT = pLnT ? std::max<sal_Int32>(rMargins.nTop - nT - nWidthT, 0) : 0; + const sal_Int32 nE2BL = pLnL ? std::max<sal_Int32>(rMargins.nLeft - nL - nWidthL, 0) : 0; + const sal_Int32 nE2BB = pLnB ? std::max<sal_Int32>(rMargins.nBottom - nB - nWidthB, 0) : 0; + const sal_Int32 nE2BR = pLnR ? std::max<sal_Int32>(rMargins.nRight - nR - nWidthR, 0) : 0; + + const sal_Int32 n32pt = 32 * 20; + // 1. If all borders are in range of 31 pts from text + if (nT2BT >= 0 && nT2BT < n32pt && nT2BL >= 0 && nT2BL < n32pt && nT2BB >= 0 && nT2BB < n32pt && nT2BR >= 0 && nT2BR < n32pt) + { + rDistances.bFromEdge = false; + } + else + { + // 2. If all borders are in range of 31 pts from edge + if (nE2BT < n32pt && nE2BL < n32pt && nE2BB < n32pt && nE2BR < n32pt) + { + rDistances.bFromEdge = true; + } + else + { + // Let's try to guess which would be the best approximation + rDistances.bFromEdge = + (nT2BT + nT2BL + nT2BB + nT2BR) > (nE2BT + nE2BL + nE2BB + nE2BR); + } + } + + if (rDistances.bFromEdge) + { + rDistances.nTop = sal::static_int_cast<sal_uInt16>(nE2BT); + rDistances.nLeft = sal::static_int_cast<sal_uInt16>(nE2BL); + rDistances.nBottom = sal::static_int_cast<sal_uInt16>(nE2BB); + rDistances.nRight = sal::static_int_cast<sal_uInt16>(nE2BR); + } + else + { + rDistances.nTop = sal::static_int_cast<sal_uInt16>(nT2BT); + rDistances.nLeft = sal::static_int_cast<sal_uInt16>(nT2BL); + rDistances.nBottom = sal::static_int_cast<sal_uInt16>(nT2BB); + rDistances.nRight = sal::static_int_cast<sal_uInt16>(nT2BR); + } +} + +} + +// class SvxFormatBreakItem ------------------------------------------------- + +bool SvxFormatBreakItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return GetValue() == static_cast<const SvxFormatBreakItem&>( rAttr ).GetValue(); +} + + +bool SvxFormatBreakItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + rText = GetValueTextByPos( GetEnumValue() ); + return true; +} + +OUString SvxFormatBreakItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_BREAK[] = + { + RID_SVXITEMS_BREAK_NONE, + RID_SVXITEMS_BREAK_COLUMN_BEFORE, + RID_SVXITEMS_BREAK_COLUMN_AFTER, + RID_SVXITEMS_BREAK_COLUMN_BOTH, + RID_SVXITEMS_BREAK_PAGE_BEFORE, + RID_SVXITEMS_BREAK_PAGE_AFTER, + RID_SVXITEMS_BREAK_PAGE_BOTH + }; + static_assert(std::size(RID_SVXITEMS_BREAK) == size_t(SvxBreak::End), "unexpected size"); + assert(nPos < sal_uInt16(SvxBreak::End) && "enum overflow!"); + return EditResId(RID_SVXITEMS_BREAK[nPos]); +} + +bool SvxFormatBreakItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + style::BreakType eBreak = style::BreakType_NONE; + switch ( GetBreak() ) + { + case SvxBreak::ColumnBefore: eBreak = style::BreakType_COLUMN_BEFORE; break; + case SvxBreak::ColumnAfter: eBreak = style::BreakType_COLUMN_AFTER ; break; + case SvxBreak::ColumnBoth: eBreak = style::BreakType_COLUMN_BOTH ; break; + case SvxBreak::PageBefore: eBreak = style::BreakType_PAGE_BEFORE ; break; + case SvxBreak::PageAfter: eBreak = style::BreakType_PAGE_AFTER ; break; + case SvxBreak::PageBoth: eBreak = style::BreakType_PAGE_BOTH ; break; + default: ; // prevent warning + } + rVal <<= eBreak; + return true; +} + +bool SvxFormatBreakItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + style::BreakType nBreak; + + if(!(rVal >>= nBreak)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + + nBreak = static_cast<style::BreakType>(nValue); + } + + SvxBreak eBreak = SvxBreak::NONE; + switch( nBreak ) + { + case style::BreakType_COLUMN_BEFORE: eBreak = SvxBreak::ColumnBefore; break; + case style::BreakType_COLUMN_AFTER: eBreak = SvxBreak::ColumnAfter; break; + case style::BreakType_COLUMN_BOTH: eBreak = SvxBreak::ColumnBoth; break; + case style::BreakType_PAGE_BEFORE: eBreak = SvxBreak::PageBefore; break; + case style::BreakType_PAGE_AFTER: eBreak = SvxBreak::PageAfter; break; + case style::BreakType_PAGE_BOTH: eBreak = SvxBreak::PageBoth; break; + default: ; // prevent warning + } + SetValue(eBreak); + + return true; +} + +SvxFormatBreakItem* SvxFormatBreakItem::Clone( SfxItemPool* ) const +{ + return new SvxFormatBreakItem( *this ); +} + +sal_uInt16 SvxFormatBreakItem::GetValueCount() const +{ + return sal_uInt16(SvxBreak::End); // SvxBreak::PageBoth + 1 +} + +SvxFormatKeepItem* SvxFormatKeepItem::Clone( SfxItemPool* ) const +{ + return new SvxFormatKeepItem( *this ); +} + +bool SvxFormatKeepItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& + ) const +{ + TranslateId pId = RID_SVXITEMS_FMTKEEP_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_FMTKEEP_TRUE; + rText = EditResId(pId); + return true; +} + +void SvxFormatKeepItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxFormatKeepItem")); + + SfxBoolItem::dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +SvxLineItem::SvxLineItem( const sal_uInt16 nId ) : + SfxPoolItem ( nId ) +{ +} + + +SvxLineItem::SvxLineItem( const SvxLineItem& rCpy ) : + SfxPoolItem ( rCpy ), + pLine(rCpy.pLine ? new SvxBorderLine( *rCpy.pLine ) : nullptr) +{ +} + + +SvxLineItem::~SvxLineItem() +{ +} + + +bool SvxLineItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return CmpBrdLn( pLine, static_cast<const SvxLineItem&>(rAttr).GetLine() ); +} + +SvxLineItem* SvxLineItem::Clone( SfxItemPool* ) const +{ + return new SvxLineItem( *this ); +} + +bool SvxLineItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemId ) const +{ + bool bConvert = 0!=(nMemId&CONVERT_TWIPS); + nMemId &= ~CONVERT_TWIPS; + if ( nMemId == 0 ) + { + rVal <<= SvxBoxItem::SvxLineToLine(pLine.get(), bConvert); + return true; + } + else if ( pLine ) + { + switch ( nMemId ) + { + case MID_FG_COLOR: rVal <<= pLine->GetColor(); break; + case MID_OUTER_WIDTH: rVal <<= sal_Int32(pLine->GetOutWidth()); break; + case MID_INNER_WIDTH: rVal <<= sal_Int32(pLine->GetInWidth( )); break; + case MID_DISTANCE: rVal <<= sal_Int32(pLine->GetDistance()); break; + default: + OSL_FAIL( "Wrong MemberId" ); + return false; + } + } + + return true; +} + + +bool SvxLineItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemId ) +{ + bool bConvert = 0!=(nMemId&CONVERT_TWIPS); + nMemId &= ~CONVERT_TWIPS; + sal_Int32 nVal = 0; + if ( nMemId == 0 ) + { + table::BorderLine2 aLine; + if ( lcl_extractBorderLine(rVal, aLine) ) + { + if ( !pLine ) + pLine.reset( new SvxBorderLine ); + if( !SvxBoxItem::LineToSvxLine(aLine, *pLine, bConvert) ) + pLine.reset(); + return true; + } + return false; + } + else if ( rVal >>= nVal ) + { + if ( !pLine ) + pLine.reset( new SvxBorderLine ); + + switch ( nMemId ) + { + case MID_FG_COLOR: pLine->SetColor( Color(ColorTransparency, nVal) ); break; + case MID_LINE_STYLE: + pLine->SetBorderLineStyle(static_cast<SvxBorderLineStyle>(nVal)); + break; + default: + OSL_FAIL( "Wrong MemberId" ); + return false; + } + + return true; + } + + return false; +} + + +bool SvxLineItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + rText.clear(); + + if ( pLine ) + rText = pLine->GetValueString( eCoreUnit, ePresUnit, &rIntl, + (SfxItemPresentation::Complete == ePres) ); + return true; +} + + +void SvxLineItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + if ( pLine ) pLine->ScaleMetrics( nMult, nDiv ); +} + + +bool SvxLineItem::HasMetrics() const +{ + return true; +} + + +void SvxLineItem::SetLine( const SvxBorderLine* pNew ) +{ + pLine.reset( pNew ? new SvxBorderLine( *pNew ) : nullptr ); +} + +SvxBrushItem::SvxBrushItem(sal_uInt16 _nWhich) + : SfxPoolItem(_nWhich) + , aColor(COL_TRANSPARENT) + , aFilterColor(COL_TRANSPARENT) + , nShadingValue(ShadingPattern::CLEAR) + , nGraphicTransparency(0) + , eGraphicPos(GPOS_NONE) + , bLoadAgain(true) +{ +} + +SvxBrushItem::SvxBrushItem(const Color& rColor, sal_uInt16 _nWhich) + : SfxPoolItem(_nWhich) + , aColor(rColor) + , aFilterColor(COL_TRANSPARENT) + , nShadingValue(ShadingPattern::CLEAR) + , nGraphicTransparency(0) + , eGraphicPos(GPOS_NONE) + , bLoadAgain(true) +{ +} + +SvxBrushItem::SvxBrushItem(const Graphic& rGraphic, SvxGraphicPosition ePos, sal_uInt16 _nWhich) + : SfxPoolItem(_nWhich) + , aColor(COL_TRANSPARENT) + , aFilterColor(COL_TRANSPARENT) + , nShadingValue(ShadingPattern::CLEAR) + , xGraphicObject(new GraphicObject(rGraphic)) + , nGraphicTransparency(0) + , eGraphicPos((GPOS_NONE != ePos) ? ePos : GPOS_MM) + , bLoadAgain(true) +{ + DBG_ASSERT( GPOS_NONE != ePos, "SvxBrushItem-Ctor with GPOS_NONE == ePos" ); +} + +SvxBrushItem::SvxBrushItem(const GraphicObject& rGraphicObj, SvxGraphicPosition ePos, sal_uInt16 _nWhich) + : SfxPoolItem(_nWhich) + , aColor(COL_TRANSPARENT) + , aFilterColor(COL_TRANSPARENT) + , nShadingValue(ShadingPattern::CLEAR) + , xGraphicObject(new GraphicObject(rGraphicObj)) + , nGraphicTransparency(0) + , eGraphicPos((GPOS_NONE != ePos) ? ePos : GPOS_MM) + , bLoadAgain(true) +{ + DBG_ASSERT( GPOS_NONE != ePos, "SvxBrushItem-Ctor with GPOS_NONE == ePos" ); +} + +SvxBrushItem::SvxBrushItem(OUString aLink, OUString aFilter, + SvxGraphicPosition ePos, sal_uInt16 _nWhich) + : SfxPoolItem(_nWhich) + , aColor(COL_TRANSPARENT) + , aFilterColor(COL_TRANSPARENT) + , nShadingValue(ShadingPattern::CLEAR) + , nGraphicTransparency(0) + , maStrLink(std::move(aLink)) + , maStrFilter(std::move(aFilter)) + , eGraphicPos((GPOS_NONE != ePos) ? ePos : GPOS_MM) + , bLoadAgain(true) +{ + DBG_ASSERT( GPOS_NONE != ePos, "SvxBrushItem-Ctor with GPOS_NONE == ePos" ); +} + +SvxBrushItem::SvxBrushItem(const SvxBrushItem& rItem) + : SfxPoolItem(rItem) + , aColor(rItem.aColor) + , aFilterColor(rItem.aFilterColor) + , nShadingValue(rItem.nShadingValue) + , xGraphicObject(rItem.xGraphicObject ? new GraphicObject(*rItem.xGraphicObject) : nullptr) + , nGraphicTransparency(rItem.nGraphicTransparency) + , maStrLink(rItem.maStrLink) + , maStrFilter(rItem.maStrFilter) + , eGraphicPos(rItem.eGraphicPos) + , bLoadAgain(rItem.bLoadAgain) +{ +} + +SvxBrushItem::SvxBrushItem(SvxBrushItem&& rItem) + : SfxPoolItem(std::move(rItem)) + , aColor(std::move(rItem.aColor)) + , aFilterColor(std::move(rItem.aFilterColor)) + , nShadingValue(std::move(rItem.nShadingValue)) + , xGraphicObject(std::move(rItem.xGraphicObject)) + , nGraphicTransparency(std::move(rItem.nGraphicTransparency)) + , maStrLink(std::move(rItem.maStrLink)) + , maStrFilter(std::move(rItem.maStrFilter)) + , eGraphicPos(std::move(rItem.eGraphicPos)) + , bLoadAgain(std::move(rItem.bLoadAgain)) +{ +} + +SvxBrushItem::~SvxBrushItem() +{ +} + +bool SvxBrushItem::isUsed() const +{ + if (GPOS_NONE != GetGraphicPos()) + { + // graphic used + return true; + } + else if (!GetColor().IsFullyTransparent()) + { + // color used + return true; + } + + return false; +} + + +static sal_Int8 lcl_PercentToTransparency(tools::Long nPercent) +{ + // 0xff must not be returned! + return sal_Int8(nPercent ? (50 + 0xfe * nPercent) / 100 : 0); +} + + +sal_Int8 SvxBrushItem::TransparencyToPercent(sal_Int32 nTrans) +{ + return static_cast<sal_Int8>((nTrans * 100 + 127) / 254); +} + + +bool SvxBrushItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId) + { + case MID_BACK_COLOR: + rVal <<= aColor; + break; + case MID_BACK_COLOR_R_G_B: + rVal <<= aColor.GetRGBColor(); + break; + case MID_BACK_COLOR_TRANSPARENCY: + rVal <<= SvxBrushItem::TransparencyToPercent(255 - aColor.GetAlpha()); + break; + case MID_GRAPHIC_POSITION: + rVal <<= static_cast<style::GraphicLocation>(static_cast<sal_Int16>(eGraphicPos)); + break; + + case MID_GRAPHIC_TRANSPARENT: + rVal <<= ( aColor.GetAlpha() == 0 ); + break; + + case MID_GRAPHIC_URL: + case MID_GRAPHIC: + { + uno::Reference<graphic::XGraphic> xGraphic; + if (!maStrLink.isEmpty()) + { + Graphic aGraphic(vcl::graphic::loadFromURL(maStrLink)); + xGraphic = aGraphic.GetXGraphic(); + } + else if (xGraphicObject) + { + xGraphic = xGraphicObject->GetGraphic().GetXGraphic(); + } + rVal <<= xGraphic; + } + break; + + case MID_GRAPHIC_FILTER: + { + rVal <<= maStrFilter; + } + break; + + case MID_GRAPHIC_TRANSPARENCY: + rVal <<= nGraphicTransparency; + break; + + case MID_SHADING_VALUE: + { + rVal <<= nShadingValue; + } + break; + } + + return true; +} + + +bool SvxBrushItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId) + { + case MID_BACK_COLOR: + case MID_BACK_COLOR_R_G_B: + { + Color aNewCol; + if ( !( rVal >>= aNewCol ) ) + return false; + if(MID_BACK_COLOR_R_G_B == nMemberId) + { + aNewCol.SetAlpha(aColor.GetAlpha()); + } + aColor = aNewCol; + } + break; + case MID_BACK_COLOR_TRANSPARENCY: + { + sal_Int32 nTrans = 0; + if ( !( rVal >>= nTrans ) || nTrans < 0 || nTrans > 100 ) + return false; + aColor.SetAlpha(255 - lcl_PercentToTransparency(nTrans)); + } + break; + + case MID_GRAPHIC_POSITION: + { + style::GraphicLocation eLocation; + if ( !( rVal>>=eLocation ) ) + { + sal_Int32 nValue = 0; + if ( !( rVal >>= nValue ) ) + return false; + eLocation = static_cast<style::GraphicLocation>(nValue); + } + SetGraphicPos( static_cast<SvxGraphicPosition>(static_cast<sal_uInt16>(eLocation)) ); + } + break; + + case MID_GRAPHIC_TRANSPARENT: + aColor.SetAlpha( Any2Bool( rVal ) ? 0 : 255 ); + break; + + case MID_GRAPHIC_URL: + case MID_GRAPHIC: + { + Graphic aGraphic; + + if (rVal.getValueType() == ::cppu::UnoType<OUString>::get()) + { + OUString aURL = rVal.get<OUString>(); + aGraphic = vcl::graphic::loadFromURL(aURL); + } + else if (rVal.getValueType() == cppu::UnoType<graphic::XGraphic>::get()) + { + auto xGraphic = rVal.get<uno::Reference<graphic::XGraphic>>(); + aGraphic = Graphic(xGraphic); + } + + if (!aGraphic.IsNone()) + { + maStrLink.clear(); + + std::unique_ptr<GraphicObject> xOldGrfObj(std::move(xGraphicObject)); + xGraphicObject.reset(new GraphicObject(aGraphic)); + ApplyGraphicTransparency_Impl(); + xOldGrfObj.reset(); + + if (!aGraphic.IsNone() && eGraphicPos == GPOS_NONE) + { + eGraphicPos = GPOS_MM; + } + else if (aGraphic.IsNone()) + { + eGraphicPos = GPOS_NONE; + } + } + } + break; + + case MID_GRAPHIC_FILTER: + { + if( rVal.getValueType() == ::cppu::UnoType<OUString>::get() ) + { + OUString sLink; + rVal >>= sLink; + SetGraphicFilter( sLink ); + } + } + break; + case MID_GRAPHIC_TRANSPARENCY : + { + sal_Int32 nTmp = 0; + rVal >>= nTmp; + if(nTmp >= 0 && nTmp <= 100) + { + nGraphicTransparency = sal_Int8(nTmp); + if (xGraphicObject) + ApplyGraphicTransparency_Impl(); + } + } + break; + + case MID_SHADING_VALUE: + { + sal_Int32 nVal = 0; + if (!(rVal >>= nVal)) + return false; + + nShadingValue = nVal; + } + break; + } + + return true; +} + + +bool SvxBrushItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& + ) const +{ + if ( GPOS_NONE == eGraphicPos ) + { + rText = ::GetColorString( aColor ) + cpDelim; + TranslateId pId = RID_SVXITEMS_TRANSPARENT_FALSE; + + if ( aColor.IsTransparent() ) + pId = RID_SVXITEMS_TRANSPARENT_TRUE; + rText += EditResId(pId); + } + else + { + rText = EditResId(RID_SVXITEMS_GRAPHIC); + } + + return true; +} + +bool SvxBrushItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxBrushItem& rCmp = static_cast<const SvxBrushItem&>(rAttr); + bool bEqual = ( aColor == rCmp.aColor && aFilterColor == rCmp.aFilterColor && + eGraphicPos == rCmp.eGraphicPos && nGraphicTransparency == rCmp.nGraphicTransparency); + + if ( bEqual ) + { + if ( GPOS_NONE != eGraphicPos ) + { + bEqual = maStrLink == rCmp.maStrLink; + + if ( bEqual ) + { + bEqual = maStrFilter == rCmp.maStrFilter; + } + + if ( bEqual ) + { + if (!rCmp.xGraphicObject) + bEqual = !xGraphicObject; + else + bEqual = xGraphicObject && + (*xGraphicObject == *rCmp.xGraphicObject); + } + } + + if (bEqual) + { + bEqual = nShadingValue == rCmp.nShadingValue; + } + } + + return bEqual; +} + +SvxBrushItem* SvxBrushItem::Clone( SfxItemPool* ) const +{ + return new SvxBrushItem( *this ); +} + +const GraphicObject* SvxBrushItem::GetGraphicObject(OUString const & referer) const +{ + if (bLoadAgain && !maStrLink.isEmpty() && !xGraphicObject) + // when graphics already loaded, use as a cache + { + if (SvtSecurityOptions::isUntrustedReferer(referer)) { + return nullptr; + } + + // tdf#94088 prepare graphic and state + Graphic aGraphic; + bool bGraphicLoaded = false; + + // try to create stream directly from given URL + std::unique_ptr<SvStream> xStream(utl::UcbStreamHelper::CreateStream(maStrLink, StreamMode::STD_READ)); + // tdf#94088 if we have a stream, try to load it directly as graphic + if (xStream && !xStream->GetError()) + { + if (ERRCODE_NONE == GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, maStrLink, *xStream, + GRFILTER_FORMAT_DONTKNOW, nullptr, GraphicFilterImportFlags::DontSetLogsizeForJpeg)) + { + bGraphicLoaded = true; + } + } + + // tdf#94088 if no succeeded, try if the string (which is not empty) contains + // a 'data:' scheme url and try to load that (embedded graphics) + if(!bGraphicLoaded) + { + INetURLObject aGraphicURL( maStrLink ); + + if( INetProtocol::Data == aGraphicURL.GetProtocol() ) + { + std::unique_ptr<SvMemoryStream> const xMemStream(aGraphicURL.getData()); + if (xMemStream) + { + if (ERRCODE_NONE == GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, u"", *xMemStream)) + { + bGraphicLoaded = true; + + // tdf#94088 delete the no longer needed data scheme URL which + // is potentially pretty // large, containing a base64 encoded copy of the graphic + const_cast< SvxBrushItem* >(this)->maStrLink.clear(); + } + } + } + } + + // tdf#94088 when we got a graphic, set it + if(bGraphicLoaded && GraphicType::NONE != aGraphic.GetType()) + { + xGraphicObject.reset(new GraphicObject); + xGraphicObject->SetGraphic(aGraphic); + const_cast < SvxBrushItem*> (this)->ApplyGraphicTransparency_Impl(); + } + else + { + bLoadAgain = false; + } + } + + return xGraphicObject.get(); +} + +void SvxBrushItem::setGraphicTransparency(sal_Int8 nNew) +{ + if (nNew != nGraphicTransparency) + { + nGraphicTransparency = nNew; + ApplyGraphicTransparency_Impl(); + } +} + +const Graphic* SvxBrushItem::GetGraphic(OUString const & referer) const +{ + const GraphicObject* pGrafObj = GetGraphicObject(referer); + return( pGrafObj ? &( pGrafObj->GetGraphic() ) : nullptr ); +} + +void SvxBrushItem::SetGraphicPos( SvxGraphicPosition eNew ) +{ + eGraphicPos = eNew; + + if ( GPOS_NONE == eGraphicPos ) + { + xGraphicObject.reset(); + maStrLink.clear(); + maStrFilter.clear(); + } + else + { + if (!xGraphicObject && maStrLink.isEmpty()) + { + xGraphicObject.reset(new GraphicObject); // Creating a dummy + } + } +} + +void SvxBrushItem::SetGraphic( const Graphic& rNew ) +{ + if ( maStrLink.isEmpty() ) + { + if (xGraphicObject) + xGraphicObject->SetGraphic(rNew); + else + xGraphicObject.reset(new GraphicObject(rNew)); + + ApplyGraphicTransparency_Impl(); + + if ( GPOS_NONE == eGraphicPos ) + eGraphicPos = GPOS_MM; // None would be brush, then Default: middle + } + else + { + OSL_FAIL( "SetGraphic() on linked graphic! :-/" ); + } +} + +void SvxBrushItem::SetGraphicObject( const GraphicObject& rNewObj ) +{ + if ( maStrLink.isEmpty() ) + { + if (xGraphicObject) + *xGraphicObject = rNewObj; + else + xGraphicObject.reset(new GraphicObject(rNewObj)); + + ApplyGraphicTransparency_Impl(); + + if ( GPOS_NONE == eGraphicPos ) + eGraphicPos = GPOS_MM; // None would be brush, then Default: middle + } + else + { + OSL_FAIL( "SetGraphic() on linked graphic! :-/" ); + } +} + +void SvxBrushItem::SetGraphicLink( const OUString& rNew ) +{ + if ( rNew.isEmpty() ) + maStrLink.clear(); + else + { + maStrLink = rNew; + xGraphicObject.reset(); + } +} + +void SvxBrushItem::SetGraphicFilter( const OUString& rNew ) +{ + maStrFilter = rNew; +} + +void SvxBrushItem::ApplyGraphicTransparency_Impl() +{ + DBG_ASSERT(xGraphicObject, "no GraphicObject available" ); + if (xGraphicObject) + { + GraphicAttr aAttr(xGraphicObject->GetAttr()); + aAttr.SetAlpha(255 - lcl_PercentToTransparency( + nGraphicTransparency)); + xGraphicObject->SetAttr(aAttr); + } +} + +void SvxBrushItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxBrushItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("color"), BAD_CAST(aColor.AsRGBHexString().toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("filtercolor"), BAD_CAST(aFilterColor.AsRGBHexString().toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("shadingValue"), BAD_CAST(OString::number(nShadingValue).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("link"), BAD_CAST(maStrLink.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("filter"), BAD_CAST(maStrFilter.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("graphicPos"), BAD_CAST(OString::number(eGraphicPos).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("loadAgain"), BAD_CAST(OString::boolean(bLoadAgain).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + + +SvxFrameDirectionItem::SvxFrameDirectionItem( SvxFrameDirection nValue , + sal_uInt16 _nWhich ) + : SfxEnumItem<SvxFrameDirection>( _nWhich, nValue ) +{ +} + + +SvxFrameDirectionItem::~SvxFrameDirectionItem() +{ +} + +SvxFrameDirectionItem* SvxFrameDirectionItem::Clone( SfxItemPool * ) const +{ + return new SvxFrameDirectionItem( *this ); +} + +TranslateId getFrmDirResId(size_t nIndex) +{ + TranslateId const RID_SVXITEMS_FRMDIR[] = + { + RID_SVXITEMS_FRMDIR_HORI_LEFT_TOP, + RID_SVXITEMS_FRMDIR_HORI_RIGHT_TOP, + RID_SVXITEMS_FRMDIR_VERT_TOP_RIGHT, + RID_SVXITEMS_FRMDIR_VERT_TOP_LEFT, + RID_SVXITEMS_FRMDIR_ENVIRONMENT, + RID_SVXITEMS_FRMDIR_VERT_BOT_LEFT + }; + return RID_SVXITEMS_FRMDIR[nIndex]; +} + +bool SvxFrameDirectionItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper&) const +{ + rText = EditResId(getFrmDirResId(GetEnumValue())); + return true; +} + +bool SvxFrameDirectionItem::PutValue( const css::uno::Any& rVal, + sal_uInt8 ) +{ + sal_Int16 nVal = sal_Int16(); + bool bRet = ( rVal >>= nVal ); + if( bRet ) + { + // translate WritingDirection2 constants into SvxFrameDirection + switch( nVal ) + { + case text::WritingMode2::LR_TB: + SetValue( SvxFrameDirection::Horizontal_LR_TB ); + break; + case text::WritingMode2::RL_TB: + SetValue( SvxFrameDirection::Horizontal_RL_TB ); + break; + case text::WritingMode2::TB_RL: + SetValue( SvxFrameDirection::Vertical_RL_TB ); + break; + case text::WritingMode2::TB_LR: + SetValue( SvxFrameDirection::Vertical_LR_TB ); + break; + case text::WritingMode2::BT_LR: + SetValue( SvxFrameDirection::Vertical_LR_BT ); + break; + case text::WritingMode2::PAGE: + SetValue( SvxFrameDirection::Environment ); + break; + default: + bRet = false; + break; + } + } + + return bRet; +} + + +bool SvxFrameDirectionItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 ) const +{ + // translate SvxFrameDirection into WritingDirection2 + sal_Int16 nVal; + bool bRet = true; + switch( GetValue() ) + { + case SvxFrameDirection::Horizontal_LR_TB: + nVal = text::WritingMode2::LR_TB; + break; + case SvxFrameDirection::Horizontal_RL_TB: + nVal = text::WritingMode2::RL_TB; + break; + case SvxFrameDirection::Vertical_RL_TB: + nVal = text::WritingMode2::TB_RL; + break; + case SvxFrameDirection::Vertical_LR_TB: + nVal = text::WritingMode2::TB_LR; + break; + case SvxFrameDirection::Vertical_LR_BT: + nVal = text::WritingMode2::BT_LR; + break; + case SvxFrameDirection::Environment: + nVal = text::WritingMode2::PAGE; + break; + default: + OSL_FAIL("Unknown SvxFrameDirection value!"); + bRet = false; + break; + } + + // return value + error state + if( bRet ) + { + rVal <<= nVal; + } + return bRet; +} + +void SvxFrameDirectionItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxFrameDirectionItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nWhich"), + BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("m_nValue"), + BAD_CAST(OString::number(static_cast<sal_Int16>(GetValue())).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/itemtype.cxx b/editeng/source/items/itemtype.cxx new file mode 100644 index 000000000..cbb83c83b --- /dev/null +++ b/editeng/source/items/itemtype.cxx @@ -0,0 +1,232 @@ +/* -*- 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 <osl/diagnose.h> +#include <vcl/outdev.hxx> +#include <editeng/editrids.hrc> +#include <unotools/intlwrapper.hxx> +#include <unotools/localedatawrapper.hxx> +#include <editeng/itemtype.hxx> +#include <editeng/eerdll.hxx> +#include <rtl/ustrbuf.hxx> + + +OUString GetMetricText( tools::Long nVal, MapUnit eSrcUnit, MapUnit eDestUnit, const IntlWrapper* pIntl ) +{ + bool bNeg = false; + bool bShowAtLeastOneDecimalDigit = true; + sal_Int32 nRet = 0; + + if ( nVal < 0 ) + { + bNeg = true; + nVal *= -1; + } + + switch ( eDestUnit ) + { + case MapUnit::Map100thMM: + case MapUnit::Map10thMM: + case MapUnit::MapMM: + case MapUnit::MapCM: + { + nRet = OutputDevice::LogicToLogic( nVal, eSrcUnit, MapUnit::Map100thMM ); + + switch ( eDestUnit ) + { + case MapUnit::Map100thMM: nRet *= 1000; break; + case MapUnit::Map10thMM: nRet *= 100; break; + case MapUnit::MapMM: nRet *= 10; break; + default: ;//prevent warning + } + break; + } + + case MapUnit::Map1000thInch: + case MapUnit::Map100thInch: + case MapUnit::Map10thInch: + case MapUnit::MapInch: + { + nRet = OutputDevice::LogicToLogic( nVal, eSrcUnit, MapUnit::Map1000thInch ); + + switch ( eDestUnit ) + { + case MapUnit::Map1000thInch: nRet *= 1000; break; + case MapUnit::Map100thInch: nRet *= 100; break; + case MapUnit::Map10thInch: nRet *= 10; break; + default: ;//prevent warning + } + break; + } + + case MapUnit::MapPoint: + // fractions of a point are used, e.g., for font size + nRet = OutputDevice::LogicToLogic(nVal, eSrcUnit, MapUnit::MapTwip) * 50; + bShowAtLeastOneDecimalDigit = false; + break; + + case MapUnit::MapTwip: + case MapUnit::MapPixel: + return OUString::number( OutputDevice::LogicToLogic( + nVal, eSrcUnit, eDestUnit )); + + default: + OSL_FAIL( "not supported mapunit" ); + return OUString(); + } + + if ( MapUnit::MapCM == eDestUnit || MapUnit::MapInch == eDestUnit ) + { + sal_Int32 nMod = nRet % 10; + + if ( nMod > 4 ) + nRet += 10 - nMod; + else if ( nMod > 0 ) + nRet -= nMod; + } + + OUStringBuffer sRet; + + if ( bNeg ) + sRet.append('-'); + + tools::Long nDiff = 1000; + for( int nDigits = 4; nDigits; --nDigits, nDiff /= 10 ) + { + if ( nRet < nDiff ) + sRet.append('0'); + else + sRet.append(nRet / nDiff); + nRet %= nDiff; + if( 4 == nDigits && (bShowAtLeastOneDecimalDigit || nRet) ) + { + if(pIntl) + sRet.append(pIntl->getLocaleData()->getNumDecimalSep()); + else + sRet.append(','); + if( !nRet ) + { + sRet.append('0'); + break; + } + } + else if( !nRet ) + break; + } + return sRet.makeStringAndClear(); +} + +OUString GetColorString( const Color& rCol ) +{ + if (rCol == COL_AUTO) + return EditResId(RID_SVXSTR_AUTOMATIC); + + static const Color aColAry[] = { + COL_BLACK, COL_BLUE, COL_GREEN, COL_CYAN, + COL_RED, COL_MAGENTA, COL_BROWN, COL_GRAY, + COL_LIGHTGRAY, COL_LIGHTBLUE, COL_LIGHTGREEN, COL_LIGHTCYAN, + COL_LIGHTRED, COL_LIGHTMAGENTA, COL_YELLOW, COL_WHITE }; + + sal_uInt16 nColor = 0; + while ( nColor < SAL_N_ELEMENTS(aColAry) && + aColAry[nColor] != rCol.GetRGBColor() ) + { + nColor += 1; + } + + static TranslateId RID_SVXITEMS_COLORS[] = + { + RID_SVXITEMS_COLOR_BLACK, + RID_SVXITEMS_COLOR_BLUE, + RID_SVXITEMS_COLOR_GREEN, + RID_SVXITEMS_COLOR_CYAN, + RID_SVXITEMS_COLOR_RED, + RID_SVXITEMS_COLOR_MAGENTA, + RID_SVXITEMS_COLOR_BROWN, + RID_SVXITEMS_COLOR_GRAY, + RID_SVXITEMS_COLOR_LIGHTGRAY, + RID_SVXITEMS_COLOR_LIGHTBLUE, + RID_SVXITEMS_COLOR_LIGHTGREEN, + RID_SVXITEMS_COLOR_LIGHTCYAN, + RID_SVXITEMS_COLOR_LIGHTRED, + RID_SVXITEMS_COLOR_LIGHTMAGENTA, + RID_SVXITEMS_COLOR_YELLOW, + RID_SVXITEMS_COLOR_WHITE + }; + + static_assert(SAL_N_ELEMENTS(aColAry) == SAL_N_ELEMENTS(RID_SVXITEMS_COLORS), "must match"); + + OUString sStr; + if ( nColor < SAL_N_ELEMENTS(aColAry) ) + sStr = EditResId(RID_SVXITEMS_COLORS[nColor]); + + if ( sStr.isEmpty() ) + { + sStr += "RGB(" + + OUString::number( rCol.GetRed() ) + cpDelim + + OUString::number( rCol.GetGreen() ) + cpDelim + + OUString::number( rCol.GetBlue() ) + ")"; + } + return sStr; +} + +TranslateId GetMetricId( MapUnit eUnit ) +{ + TranslateId pId = RID_SVXITEMS_METRIC_MM; + + switch ( eUnit ) + { + case MapUnit::Map100thMM: + case MapUnit::Map10thMM: + case MapUnit::MapMM: + pId = RID_SVXITEMS_METRIC_MM; + break; + + case MapUnit::MapCM: + pId = RID_SVXITEMS_METRIC_CM; + break; + + case MapUnit::Map1000thInch: + case MapUnit::Map100thInch: + case MapUnit::Map10thInch: + case MapUnit::MapInch: + pId = RID_SVXITEMS_METRIC_INCH; + break; + + case MapUnit::MapPoint: + pId = RID_SVXITEMS_METRIC_POINT; + break; + + case MapUnit::MapTwip: + pId = RID_SVXITEMS_METRIC_TWIP; + break; + + case MapUnit::MapPixel: + pId = RID_SVXITEMS_METRIC_PIXEL; + break; + + default: + OSL_FAIL( "not supported mapunit" ); + } + return pId; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/justifyitem.cxx b/editeng/source/items/justifyitem.cxx new file mode 100644 index 000000000..7fe699cb2 --- /dev/null +++ b/editeng/source/items/justifyitem.cxx @@ -0,0 +1,368 @@ +/* -*- 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 <editeng/justifyitem.hxx> +#include <editeng/memberids.h> +#include <editeng/eerdll.hxx> + +#include <com/sun/star/table/CellHoriJustify.hpp> +#include <com/sun/star/style/ParagraphAdjust.hpp> +#include <com/sun/star/table/CellJustifyMethod.hpp> +#include <com/sun/star/table/CellVertJustify2.hpp> +#include <com/sun/star/style/VerticalAlignment.hpp> + +#include <strings.hrc> + +SfxPoolItem* SvxHorJustifyItem::CreateDefault() { return new SvxHorJustifyItem(SvxCellHorJustify::Standard, 0) ;} +SfxPoolItem* SvxVerJustifyItem::CreateDefault() { return new SvxVerJustifyItem(SvxCellVerJustify::Standard, 0) ;} + +using namespace ::com::sun::star; + + +SvxHorJustifyItem::SvxHorJustifyItem( const sal_uInt16 nId ) : + SfxEnumItem( nId, SvxCellHorJustify::Standard ) +{ +} + +SvxHorJustifyItem::SvxHorJustifyItem( const SvxCellHorJustify eJustify, + const sal_uInt16 nId ) : + SfxEnumItem( nId, eJustify ) +{ +} + + +bool SvxHorJustifyItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper&) const +{ + rText = GetValueText(GetValue()); + return true; +} + + +bool SvxHorJustifyItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_HORJUST_HORJUST: + { + table::CellHoriJustify eUno = table::CellHoriJustify_STANDARD; + switch ( GetValue() ) + { + case SvxCellHorJustify::Standard: eUno = table::CellHoriJustify_STANDARD; break; + case SvxCellHorJustify::Left: eUno = table::CellHoriJustify_LEFT; break; + case SvxCellHorJustify::Center: eUno = table::CellHoriJustify_CENTER; break; + case SvxCellHorJustify::Right: eUno = table::CellHoriJustify_RIGHT; break; + case SvxCellHorJustify::Block: eUno = table::CellHoriJustify_BLOCK; break; + case SvxCellHorJustify::Repeat: eUno = table::CellHoriJustify_REPEAT; break; + } + rVal <<= eUno; + } + break; + case MID_HORJUST_ADJUST: + { + // ParagraphAdjust values, as in SvxAdjustItem + // (same value for ParaAdjust and ParaLastLineAdjust) + + style::ParagraphAdjust nAdjust = style::ParagraphAdjust_LEFT; + switch ( GetValue() ) + { + // ParagraphAdjust_LEFT is used for STANDARD and REPEAT + case SvxCellHorJustify::Standard: + case SvxCellHorJustify::Repeat: + case SvxCellHorJustify::Left: nAdjust = style::ParagraphAdjust_LEFT; break; + case SvxCellHorJustify::Center: nAdjust = style::ParagraphAdjust_CENTER; break; + case SvxCellHorJustify::Right: nAdjust = style::ParagraphAdjust_RIGHT; break; + case SvxCellHorJustify::Block: nAdjust = style::ParagraphAdjust_BLOCK; break; + } + rVal <<= static_cast<sal_Int16>(nAdjust); // as sal_Int16 + } + break; + } + return true; +} + +bool SvxHorJustifyItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_HORJUST_HORJUST: + { + table::CellHoriJustify eUno; + if(!(rVal >>= eUno)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + eUno = static_cast<table::CellHoriJustify>(nValue); + } + SvxCellHorJustify eSvx = SvxCellHorJustify::Standard; + switch (eUno) + { + case table::CellHoriJustify_STANDARD: eSvx = SvxCellHorJustify::Standard; break; + case table::CellHoriJustify_LEFT: eSvx = SvxCellHorJustify::Left; break; + case table::CellHoriJustify_CENTER: eSvx = SvxCellHorJustify::Center; break; + case table::CellHoriJustify_RIGHT: eSvx = SvxCellHorJustify::Right; break; + case table::CellHoriJustify_BLOCK: eSvx = SvxCellHorJustify::Block; break; + case table::CellHoriJustify_REPEAT: eSvx = SvxCellHorJustify::Repeat; break; + default: ; //prevent warning + } + SetValue( eSvx ); + } + break; + case MID_HORJUST_ADJUST: + { + // property contains ParagraphAdjust values as sal_Int16 + sal_Int16 nVal = sal_Int16(); + if(!(rVal >>= nVal)) + return false; + + SvxCellHorJustify eSvx = SvxCellHorJustify::Standard; + switch (static_cast<style::ParagraphAdjust>(nVal)) + { + // STRETCH is treated as BLOCK + case style::ParagraphAdjust_LEFT: eSvx = SvxCellHorJustify::Left; break; + case style::ParagraphAdjust_RIGHT: eSvx = SvxCellHorJustify::Right; break; + case style::ParagraphAdjust_STRETCH: + case style::ParagraphAdjust_BLOCK: eSvx = SvxCellHorJustify::Block; break; + case style::ParagraphAdjust_CENTER: eSvx = SvxCellHorJustify::Center; break; + default: break; + } + SetValue( eSvx ); + } + } + return true; +} + +OUString SvxHorJustifyItem::GetValueText(SvxCellHorJustify nVal) +{ + assert(nVal <= SvxCellHorJustify::Repeat && "enum overflow!"); + return EditResId(RID_SVXITEMS_HORJUST[static_cast<size_t>(nVal)]); +} + +SvxHorJustifyItem* SvxHorJustifyItem::Clone( SfxItemPool* ) const +{ + return new SvxHorJustifyItem( *this ); +} + +sal_uInt16 SvxHorJustifyItem::GetValueCount() const +{ + return sal_uInt16(SvxCellHorJustify::Repeat) + 1; // Last Enum value + 1 +} + + +SvxVerJustifyItem::SvxVerJustifyItem( const sal_uInt16 nId ) : + SfxEnumItem( nId, SvxCellVerJustify::Standard ) +{ +} + +SvxVerJustifyItem::SvxVerJustifyItem( const SvxCellVerJustify eJustify, + const sal_uInt16 nId ) : + SfxEnumItem( nId, eJustify ) +{ +} + + +bool SvxVerJustifyItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, + const IntlWrapper& ) const +{ + rText = GetValueText( GetValue() ); + return true; +} + + +bool SvxVerJustifyItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_HORJUST_ADJUST: + { + style::VerticalAlignment eUno = style::VerticalAlignment_TOP; + switch ( GetValue() ) + { + case SvxCellVerJustify::Top: eUno = style::VerticalAlignment_TOP; break; + case SvxCellVerJustify::Center: eUno = style::VerticalAlignment_MIDDLE; break; + case SvxCellVerJustify::Bottom: eUno = style::VerticalAlignment_BOTTOM; break; + default: ; //prevent warning + } + rVal <<= eUno; + break; + } + default: + { + sal_Int32 nUno = table::CellVertJustify2::STANDARD; + switch ( GetValue() ) + { + case SvxCellVerJustify::Standard: nUno = table::CellVertJustify2::STANDARD; break; + case SvxCellVerJustify::Top: nUno = table::CellVertJustify2::TOP; break; + case SvxCellVerJustify::Center: nUno = table::CellVertJustify2::CENTER; break; + case SvxCellVerJustify::Bottom: nUno = table::CellVertJustify2::BOTTOM; break; + case SvxCellVerJustify::Block: nUno = table::CellVertJustify2::BLOCK; break; + default: ; //prevent warning + } + rVal <<= nUno; + break; + } + } + return true; +} + +bool SvxVerJustifyItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_HORJUST_ADJUST: + { + // property contains ParagraphAdjust values as sal_Int16 + style::VerticalAlignment nVal = style::VerticalAlignment_TOP; + if(!(rVal >>= nVal)) + return false; + + SvxCellVerJustify eSvx = SvxCellVerJustify::Standard; + switch (nVal) + { + case style::VerticalAlignment_TOP: eSvx = SvxCellVerJustify::Top; break; + case style::VerticalAlignment_MIDDLE: eSvx = SvxCellVerJustify::Center; break; + case style::VerticalAlignment_BOTTOM: eSvx = SvxCellVerJustify::Bottom; break; + default:; + } + SetValue( eSvx ); + break; + } + default: + { + sal_Int32 eUno = table::CellVertJustify2::STANDARD; + rVal >>= eUno; + + SvxCellVerJustify eSvx = SvxCellVerJustify::Standard; + switch (eUno) + { + case table::CellVertJustify2::STANDARD: eSvx = SvxCellVerJustify::Standard; break; + case table::CellVertJustify2::TOP: eSvx = SvxCellVerJustify::Top; break; + case table::CellVertJustify2::CENTER: eSvx = SvxCellVerJustify::Center; break; + case table::CellVertJustify2::BOTTOM: eSvx = SvxCellVerJustify::Bottom; break; + case table::CellVertJustify2::BLOCK: eSvx = SvxCellVerJustify::Block; break; + default: ; //prevent warning + } + SetValue( eSvx ); + break; + } + } + + return true; +} + +OUString SvxVerJustifyItem::GetValueText( SvxCellVerJustify nVal ) +{ + assert(nVal <= SvxCellVerJustify::Block && "enum overflow!"); + return EditResId(RID_SVXITEMS_VERJUST[static_cast<size_t>(nVal)]); +} + +SvxVerJustifyItem* SvxVerJustifyItem::Clone( SfxItemPool* ) const +{ + return new SvxVerJustifyItem( *this ); +} + +sal_uInt16 SvxVerJustifyItem::GetValueCount() const +{ + return static_cast<sal_uInt16>(SvxCellVerJustify::Bottom) + 1; // Last Enum value + 1 +} + +SvxJustifyMethodItem::SvxJustifyMethodItem( const SvxCellJustifyMethod eJustify, + const sal_uInt16 nId ) : + SfxEnumItem( nId, eJustify ) +{ +} + +bool SvxJustifyMethodItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, + const IntlWrapper& ) const +{ + rText = GetValueText( GetValue() ); + return true; +} + + +bool SvxJustifyMethodItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + sal_Int32 nUno = table::CellJustifyMethod::AUTO; + switch (GetValue()) + { + case SvxCellJustifyMethod::Auto: nUno = table::CellJustifyMethod::AUTO; break; + case SvxCellJustifyMethod::Distribute: nUno = table::CellJustifyMethod::DISTRIBUTE; break; + default:; + } + rVal <<= nUno; + return true; +} + +bool SvxJustifyMethodItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + sal_Int32 nVal = table::CellJustifyMethod::AUTO; + if (!(rVal >>= nVal)) + return false; + + SvxCellJustifyMethod eSvx = SvxCellJustifyMethod::Auto; + switch (nVal) + { + case table::CellJustifyMethod::AUTO: + eSvx = SvxCellJustifyMethod::Auto; + break; + case table::CellJustifyMethod::DISTRIBUTE: + eSvx = SvxCellJustifyMethod::Distribute; + break; + default:; + } + SetValue(eSvx); + return true; +} + +OUString SvxJustifyMethodItem::GetValueText( SvxCellJustifyMethod nVal ) +{ + assert(nVal <= SvxCellJustifyMethod::Distribute && "enum overflow!"); + return EditResId(RID_SVXITEMS_JUSTMETHOD[static_cast<size_t>(nVal)]); +} + +SvxJustifyMethodItem* SvxJustifyMethodItem::Clone( SfxItemPool* ) const +{ + return new SvxJustifyMethodItem( *this ); +} + +sal_uInt16 SvxJustifyMethodItem::GetValueCount() const +{ + return static_cast<sal_uInt16>(SvxCellJustifyMethod::Distribute) + 1; // Last Enum value + 1 +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/legacyitem.cxx b/editeng/source/items/legacyitem.cxx new file mode 100644 index 000000000..89c15dd5a --- /dev/null +++ b/editeng/source/items/legacyitem.cxx @@ -0,0 +1,826 @@ +/* -*- 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 <editeng/legacyitem.hxx> +#include <unotools/fontdefs.hxx> +#include <tools/tenccvt.hxx> +#include <tools/stream.hxx> +#include <comphelper/fileformat.h> +#include <vcl/graph.hxx> +#include <vcl/GraphicObject.hxx> +#include <vcl/TypeSerializer.hxx> +#include <osl/diagnose.h> +#include <tools/urlobj.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/borderline.hxx> +#include <editeng/lineitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/editerr.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/shaditem.hxx> +#include <tools/GenericTypeSerializer.hxx> + + +void Create_legacy_direct_set(SvxFontHeightItem& rItem, sal_uInt32 nH, sal_uInt16 nP, MapUnit eP) +{ + rItem.legacy_direct_set(nH, nP, eP); +} + +namespace legacy +{ + namespace SvxFont + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxFontItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt8 _eFamily, eFontPitch, eFontTextEncoding; + OUString aName, aStyle; + rStrm.ReadUChar( _eFamily ); + rStrm.ReadUChar( eFontPitch ); + rStrm.ReadUChar( eFontTextEncoding ); + + // UNICODE: rStrm >> aName; + aName = rStrm.ReadUniOrByteString(rStrm.GetStreamCharSet()); + + // UNICODE: rStrm >> aStyle; + aStyle = rStrm.ReadUniOrByteString(rStrm.GetStreamCharSet()); + + // Set the "correct" textencoding + eFontTextEncoding = static_cast<sal_uInt8>(GetSOLoadTextEncoding( eFontTextEncoding )); + + // at some point, the StarBats changes from ANSI font to SYMBOL font + if ( RTL_TEXTENCODING_SYMBOL != eFontTextEncoding && aName == "StarBats" ) + eFontTextEncoding = RTL_TEXTENCODING_SYMBOL; + + // Check if we have stored unicode + sal_uInt64 const nStreamPos = rStrm.Tell(); + // #define STORE_UNICODE_MAGIC_MARKER 0xFE331188 + sal_uInt32 nMagic = 0xFE331188; + rStrm.ReadUInt32( nMagic ); + if ( nMagic == 0xFE331188 ) + { + aName = rStrm.ReadUniOrByteString( RTL_TEXTENCODING_UNICODE ); + aStyle = rStrm.ReadUniOrByteString( RTL_TEXTENCODING_UNICODE ); + } + else + { + rStrm.Seek( nStreamPos ); + } + + rItem.SetFamilyName(aName); + rItem.SetStyleName(aStyle); + rItem.SetFamily(static_cast<FontFamily>(_eFamily)); + rItem.SetPitch(static_cast<FontPitch>(eFontPitch)); + rItem.SetCharSet(static_cast<rtl_TextEncoding>(eFontTextEncoding)); + } + + SvStream& Store(const SvxFontItem& rItem, SvStream& rStrm, sal_uInt16) + { + const bool bToBats(IsStarSymbol(rItem.GetFamilyName())); + + rStrm.WriteUChar(rItem.GetFamily()).WriteUChar(rItem.GetPitch()).WriteUChar(bToBats ? + RTL_TEXTENCODING_SYMBOL : + GetSOStoreTextEncoding(rItem.GetCharSet())); + + const OUString aStoreFamilyName(bToBats ? "StarBats" : rItem.GetFamilyName()); + + rStrm.WriteUniOrByteString(aStoreFamilyName, rStrm.GetStreamCharSet()); + rStrm.WriteUniOrByteString(rItem.GetStyleName(), rStrm.GetStreamCharSet()); + + return rStrm; + } + } + + namespace SvxFontHeight + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + return (nFileFormatVersion <= SOFFICE_FILEFORMAT_40) + ? FONTHEIGHT_16_VERSION + : FONTHEIGHT_UNIT_VERSION; + } + + void Create(SvxFontHeightItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + sal_uInt16 nsize, nprop = 0; + MapUnit nPropUnit = MapUnit::MapRelative; + + rStrm.ReadUInt16( nsize ); + + if( FONTHEIGHT_16_VERSION <= nItemVersion ) + rStrm.ReadUInt16( nprop ); + else + { + sal_uInt8 nP; + rStrm .ReadUChar( nP ); + nprop = static_cast<sal_uInt16>(nP); + } + + if( FONTHEIGHT_UNIT_VERSION <= nItemVersion ) + { + sal_uInt16 nTmp; + rStrm.ReadUInt16( nTmp ); + nPropUnit = static_cast<MapUnit>(nTmp); + } + + Create_legacy_direct_set(rItem, nsize, nprop, nPropUnit); + } + + SvStream& Store(const SvxFontHeightItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + rStrm.WriteUInt16( rItem.GetHeight() ); + + if( FONTHEIGHT_UNIT_VERSION <= nItemVersion ) + rStrm.WriteUInt16( rItem.GetProp() ).WriteUInt16( static_cast<sal_uInt16>(rItem.GetPropUnit()) ); + else + { + // When exporting to the old versions the relative information is lost + // when there is no percentage + sal_uInt16 _nProp = rItem.GetProp(); + if( MapUnit::MapRelative != rItem.GetPropUnit() ) + _nProp = 100; + rStrm.WriteUInt16( _nProp ); + } + return rStrm; + } + } + + namespace SvxWeight + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxWeightItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt8 nWeight(0); + rStrm.ReadUChar(nWeight); + rItem.SetValue(static_cast<FontWeight>(nWeight)); + } + + SvStream& Store(const SvxWeightItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUChar(rItem.GetValue()); + return rStrm; + } + } + + namespace SvxPosture + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxPostureItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt8 nPosture(0); + rStrm.ReadUChar(nPosture); + rItem.SetValue(static_cast<FontItalic>(nPosture)); + } + + SvStream& Store(const SvxPostureItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUChar( rItem.GetValue() ); + return rStrm; + } + } + + namespace SvxTextLine // SvxUnderlineItem, SvxOverlineItem -> SvxTextLineItem + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxTextLineItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt8 nState(0); + rStrm.ReadUChar(nState); + rItem.SetValue(static_cast<FontLineStyle>(nState)); + // GetColor() is *not* saved/loaded ?!? + } + + SvStream& Store(const SvxTextLineItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUChar(rItem.GetValue()); + // GetColor() is *not* saved/loaded ?!? + return rStrm; + } + } + + namespace SvxCrossedOut + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxCrossedOutItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt8 eCross(0); + rStrm.ReadUChar(eCross); + rItem.SetValue(static_cast<FontStrikeout>(eCross)); + } + + SvStream& Store(const SvxCrossedOutItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUChar(rItem.GetValue()); + return rStrm; + } + } + + namespace SvxColor + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + DBG_ASSERT( SOFFICE_FILEFORMAT_31==nFileFormatVersion || + SOFFICE_FILEFORMAT_40==nFileFormatVersion || + SOFFICE_FILEFORMAT_50==nFileFormatVersion, + "SvxColorItem: Is there a new file format? "); + return SOFFICE_FILEFORMAT_50 >= nFileFormatVersion ? VERSION_USEAUTOCOLOR : 0; + } + + void Create(SvxColorItem& rItem, SvStream& rStrm, sal_uInt16) + { + Color aColor(COL_AUTO); + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.readColor(aColor); + rItem.SetValue(aColor); + } + + SvStream& Store(const SvxColorItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + tools::GenericTypeSerializer aSerializer(rStrm); + if( VERSION_USEAUTOCOLOR == nItemVersion && COL_AUTO == rItem.GetValue() ) + aSerializer.writeColor(COL_BLACK); + else + aSerializer.writeColor(rItem.GetValue()); + return rStrm; + } + } + + namespace SvxBox + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + DBG_ASSERT( SOFFICE_FILEFORMAT_31==nFileFormatVersion || + SOFFICE_FILEFORMAT_40==nFileFormatVersion || + SOFFICE_FILEFORMAT_50==nFileFormatVersion, + "SvxBoxItem: Is there a new file format?" ); + return SOFFICE_FILEFORMAT_31==nFileFormatVersion || + SOFFICE_FILEFORMAT_40==nFileFormatVersion ? 0 : BOX_BORDER_STYLE_VERSION; + } + + /// Item version for saved border lines. The old version saves the line without style information. + const int BORDER_LINE_OLD_VERSION = 0; + /// Item version for saved border lies. The new version includes line style. + const int BORDER_LINE_WITH_STYLE_VERSION = 1; + + /// Creates a border line from a stream. + static ::editeng::SvxBorderLine CreateBorderLine(SvStream &stream, sal_uInt16 version) + { + sal_uInt16 nOutline, nInline, nDistance; + sal_uInt16 nStyle = css::table::BorderLineStyle::NONE; + Color aColor; + tools::GenericTypeSerializer aSerializer(stream); + aSerializer.readColor(aColor); + stream.ReadUInt16( nOutline ).ReadUInt16( nInline ).ReadUInt16( nDistance ); + + if (version >= BORDER_LINE_WITH_STYLE_VERSION) + stream.ReadUInt16( nStyle ); + + ::editeng::SvxBorderLine border(&aColor); + border.GuessLinesWidths(static_cast<SvxBorderLineStyle>(nStyle), nOutline, nInline, nDistance); + return border; + } + + /// Retrieves a BORDER_LINE_* version from a BOX_BORDER_* version. + static sal_uInt16 BorderLineVersionFromBoxVersion(sal_uInt16 boxVersion) + { + return (boxVersion >= BOX_BORDER_STYLE_VERSION)? BORDER_LINE_WITH_STYLE_VERSION : BORDER_LINE_OLD_VERSION; + } + + void Create(SvxBoxItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + sal_uInt16 nDistance(0); + rStrm.ReadUInt16( nDistance ); + SvxBoxItemLine aLineMap[4] = { SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, + SvxBoxItemLine::RIGHT, SvxBoxItemLine::BOTTOM }; + sal_Int8 cLine(0); + + while (rStrm.good()) + { + rStrm.ReadSChar( cLine ); + + if( cLine > 3 ) + break; + + ::editeng::SvxBorderLine aBorder = CreateBorderLine(rStrm, BorderLineVersionFromBoxVersion(nItemVersion)); + rItem.SetLine( &aBorder, aLineMap[cLine] ); + } + + if( nItemVersion >= BOX_4DISTS_VERSION && (cLine&0x10) != 0 ) + { + for(const SvxBoxItemLine & i : aLineMap) + { + sal_uInt16 nDist; + rStrm.ReadUInt16( nDist ); + rItem.SetDistance( nDist, i ); + } + } + else + { + rItem.SetAllDistances(nDistance); + } + } + + /// Store a border line to a stream. + static SvStream& StoreBorderLine(SvStream &stream, const ::editeng::SvxBorderLine &l, sal_uInt16 version) + { + tools::GenericTypeSerializer aSerializer(stream); + aSerializer.writeColor(l.GetColor()); + + stream.WriteUInt16( l.GetOutWidth() ) + .WriteUInt16( l.GetInWidth() ) + .WriteUInt16( l.GetDistance() ); + + if (version >= BORDER_LINE_WITH_STYLE_VERSION) + stream.WriteUInt16( static_cast<sal_uInt16>(l.GetBorderLineStyle()) ); + + return stream; + } + + SvStream& Store(const SvxBoxItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + rStrm.WriteUInt16( rItem.GetSmallestDistance() ); + const ::editeng::SvxBorderLine* pLine[ 4 ]; // top, left, right, bottom + pLine[ 0 ] = rItem.GetTop(); + pLine[ 1 ] = rItem.GetLeft(); + pLine[ 2 ] = rItem.GetRight(); + pLine[ 3 ] = rItem.GetBottom(); + + for( int i = 0; i < 4; i++ ) + { + const ::editeng::SvxBorderLine* l = pLine[ i ]; + if( l ) + { + rStrm.WriteSChar(i); + StoreBorderLine(rStrm, *l, BorderLineVersionFromBoxVersion(nItemVersion)); + } + } + sal_Int8 cLine = 4; + const sal_uInt16 nTopDist(rItem.GetDistance(SvxBoxItemLine::TOP)); + const sal_uInt16 nLeftDist(rItem.GetDistance(SvxBoxItemLine::LEFT)); + const sal_uInt16 nRightDist(rItem.GetDistance(SvxBoxItemLine::RIGHT)); + const sal_uInt16 nBottomDist(rItem.GetDistance(SvxBoxItemLine::BOTTOM)); + + if( nItemVersion >= BOX_4DISTS_VERSION && + !(nTopDist == nLeftDist && + nTopDist == nRightDist && + nTopDist == nBottomDist) ) + { + cLine |= 0x10; + } + + rStrm.WriteSChar( cLine ); + + if( nItemVersion >= BOX_4DISTS_VERSION && (cLine & 0x10) != 0 ) + { + rStrm.WriteUInt16( nTopDist ) + .WriteUInt16( nLeftDist ) + .WriteUInt16( nRightDist ) + .WriteUInt16( nBottomDist ); + } + + return rStrm; + } + } + + namespace SvxLine + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxLineItem& rItem, SvStream& rStrm, sal_uInt16) + { + short nOutline, nInline, nDistance; + Color aColor; + + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.readColor(aColor); + rStrm.ReadInt16( nOutline ).ReadInt16( nInline ).ReadInt16( nDistance ); + if( nOutline ) + { + ::editeng::SvxBorderLine aLine( &aColor ); + aLine.GuessLinesWidths(SvxBorderLineStyle::NONE, nOutline, nInline, nDistance); + rItem.SetLine( &aLine ); + } + } + + SvStream& Store(const SvxLineItem& rItem, SvStream& rStrm, sal_uInt16) + { + const ::editeng::SvxBorderLine* pLine(rItem.GetLine()); + + if(nullptr != pLine) + { + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.writeColor(pLine->GetColor()); + rStrm.WriteInt16( pLine->GetOutWidth() ) + .WriteInt16( pLine->GetInWidth() ) + .WriteInt16( pLine->GetDistance() ); + } + else + { + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.writeColor(Color()); + rStrm.WriteInt16( 0 ).WriteInt16( 0 ).WriteInt16( 0 ); + } + + return rStrm; + } + } + + namespace SvxBrush + { + sal_uInt16 GetVersion(sal_uInt16) + { + return BRUSH_GRAPHIC_VERSION; + } + + const sal_uInt16 LOAD_GRAPHIC = (sal_uInt16(0x0001)); + const sal_uInt16 LOAD_LINK = (sal_uInt16(0x0002)); + const sal_uInt16 LOAD_FILTER = (sal_uInt16(0x0004)); + + void Create(SvxBrushItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + bool bTrans; + Color aTempColor; + Color aTempFillColor; + sal_Int8 nStyle; + + rStrm.ReadCharAsBool( bTrans ); + TypeSerializer aSerializer(rStrm); + aSerializer.readColor(aTempColor); + aSerializer.readColor(aTempFillColor); + rStrm.ReadSChar( nStyle ); + + switch ( nStyle ) + { + case 8: // BRUSH_25: + { + sal_uInt32 nRed = aTempColor.GetRed(); + sal_uInt32 nGreen = aTempColor.GetGreen(); + sal_uInt32 nBlue = aTempColor.GetBlue(); + nRed += static_cast<sal_uInt32>(aTempFillColor.GetRed())*2; + nGreen += static_cast<sal_uInt32>(aTempFillColor.GetGreen())*2; + nBlue += static_cast<sal_uInt32>(aTempFillColor.GetBlue())*2; + rItem.SetColor(Color( static_cast<sal_Int8>(nRed/3), static_cast<sal_Int8>(nGreen/3), static_cast<sal_Int8>(nBlue/3) )); + } + break; + + case 9: // BRUSH_50: + { + sal_uInt32 nRed = aTempColor.GetRed(); + sal_uInt32 nGreen = aTempColor.GetGreen(); + sal_uInt32 nBlue = aTempColor.GetBlue(); + nRed += static_cast<sal_uInt32>(aTempFillColor.GetRed()); + nGreen += static_cast<sal_uInt32>(aTempFillColor.GetGreen()); + nBlue += static_cast<sal_uInt32>(aTempFillColor.GetBlue()); + rItem.SetColor(Color( static_cast<sal_Int8>(nRed/2), static_cast<sal_Int8>(nGreen/2), static_cast<sal_Int8>(nBlue/2) )); + } + break; + + case 10: // BRUSH_75: + { + sal_uInt32 nRed = aTempColor.GetRed()*2; + sal_uInt32 nGreen = aTempColor.GetGreen()*2; + sal_uInt32 nBlue = aTempColor.GetBlue()*2; + nRed += static_cast<sal_uInt32>(aTempFillColor.GetRed()); + nGreen += static_cast<sal_uInt32>(aTempFillColor.GetGreen()); + nBlue += static_cast<sal_uInt32>(aTempFillColor.GetBlue()); + rItem.SetColor(Color( static_cast<sal_Int8>(nRed/3), static_cast<sal_Int8>(nGreen/3), static_cast<sal_Int8>(nBlue/3) )); + } + break; + + case 0: // BRUSH_NULL: + rItem.SetColor(COL_TRANSPARENT); + break; + + default: + rItem.SetColor(aTempColor); + } + + if ( nItemVersion < BRUSH_GRAPHIC_VERSION ) + return; + + sal_uInt16 nDoLoad = 0; + sal_Int8 nPos; + + rStrm.ReadUInt16( nDoLoad ); + + if ( nDoLoad & LOAD_GRAPHIC ) + { + Graphic aGraphic; + aSerializer.readGraphic(aGraphic); + rItem.SetGraphicObject(GraphicObject(aGraphic)); + + if( SVSTREAM_FILEFORMAT_ERROR == rStrm.GetError() ) + { + rStrm.ResetError(); + rStrm.SetError( ERRCODE_SVX_GRAPHIC_WRONG_FILEFORMAT.MakeWarning() ); + } + } + + if ( nDoLoad & LOAD_LINK ) + { + // UNICODE: rStrm >> aRel; + OUString aRel = rStrm.ReadUniOrByteString(rStrm.GetStreamCharSet()); + + // TODO/MBA: how can we get a BaseURL here?! + OSL_FAIL("No BaseURL!"); + OUString aAbs = INetURLObject::GetAbsURL( u"", aRel ); + DBG_ASSERT( !aAbs.isEmpty(), "Invalid URL!" ); + rItem.SetGraphicLink(aAbs); + } + + if ( nDoLoad & LOAD_FILTER ) + { + // UNICODE: rStrm >> maStrFilter; + rItem.SetGraphicFilter(rStrm.ReadUniOrByteString(rStrm.GetStreamCharSet())); + } + + rStrm.ReadSChar( nPos ); + + rItem.SetGraphicPos(static_cast<SvxGraphicPosition>(nPos)); + } + + SvStream& Store(const SvxBrushItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteBool( false ); + TypeSerializer aSerializer(rStrm); + aSerializer.writeColor(rItem.GetColor()); + aSerializer.writeColor(rItem.GetColor()); + rStrm.WriteSChar( rItem.GetColor().IsTransparent() ? 0 : 1 ); //BRUSH_NULL : BRUSH_SOLID + + sal_uInt16 nDoLoad = 0; + const GraphicObject* pGraphicObject(rItem.GetGraphicObject()); + + if (nullptr != pGraphicObject && rItem.GetGraphicLink().isEmpty()) + nDoLoad |= LOAD_GRAPHIC; + if ( !rItem.GetGraphicLink().isEmpty() ) + nDoLoad |= LOAD_LINK; + if ( !rItem.GetGraphicFilter().isEmpty() ) + nDoLoad |= LOAD_FILTER; + rStrm.WriteUInt16( nDoLoad ); + + if (nullptr != pGraphicObject && rItem.GetGraphicLink().isEmpty()) + { + aSerializer.writeGraphic(pGraphicObject->GetGraphic()); + } + if ( !rItem.GetGraphicLink().isEmpty() ) + { + OSL_FAIL("No BaseURL!"); + // TODO/MBA: how to get a BaseURL?! + OUString aRel = INetURLObject::GetRelURL( u"", rItem.GetGraphicLink() ); + // UNICODE: rStrm << aRel; + rStrm.WriteUniOrByteString(aRel, rStrm.GetStreamCharSet()); + } + if ( !rItem.GetGraphicFilter().isEmpty() ) + { + // UNICODE: rStrm << rItem.GetGraphicFilter(); + rStrm.WriteUniOrByteString(rItem.GetGraphicFilter(), rStrm.GetStreamCharSet()); + } + rStrm.WriteSChar( rItem.GetGraphicPos() ); + return rStrm; + } + } + + namespace SvxAdjust + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + return (nFileFormatVersion == SOFFICE_FILEFORMAT_31) + ? 0 : ADJUST_LASTBLOCK_VERSION; + } + + void Create(SvxAdjustItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + char eAdjustment; + rStrm.ReadChar(eAdjustment); + rItem.SetAdjust(static_cast<::SvxAdjust>(eAdjustment)); + + if( nItemVersion >= ADJUST_LASTBLOCK_VERSION ) + { + sal_Int8 nFlags; + rStrm.ReadSChar( nFlags ); + rItem.SetAsFlags(nFlags); + } + } + + SvStream& Store(const SvxAdjustItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + rStrm.WriteChar( static_cast<char>(rItem.GetAdjust()) ); + if ( nItemVersion >= ADJUST_LASTBLOCK_VERSION ) + { + const sal_Int8 nFlags(rItem.GetAsFlags()); + rStrm.WriteSChar( nFlags ); + } + return rStrm; + } + } + + namespace SvxHorJustify + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxHorJustifyItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt16 nVal(0); + rStrm.ReadUInt16( nVal ); + rItem.SetValue(static_cast<::SvxCellHorJustify>(nVal)); + } + + SvStream& Store(const SvxHorJustifyItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUInt16( static_cast<sal_uInt16>(rItem.GetValue()) ); + return rStrm; + } + } + + namespace SvxVerJustify + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxVerJustifyItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt16 nVal(0); + rStrm.ReadUInt16( nVal ); + rItem.SetValue(static_cast<::SvxCellVerJustify>(nVal)); + } + + SvStream& Store(const SvxVerJustifyItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUInt16( static_cast<sal_uInt16>(rItem.GetValue()) ); + return rStrm; + } + } + + namespace SvxFrameDirection + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + return SOFFICE_FILEFORMAT_50 > nFileFormatVersion ? USHRT_MAX : 0; + } + + void Create(SvxFrameDirectionItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt16 nVal(0); + rStrm.ReadUInt16( nVal ); + rItem.SetValue(static_cast<::SvxFrameDirection>(nVal)); + } + + SvStream& Store(const SvxFrameDirectionItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUInt16( static_cast<sal_uInt16>(rItem.GetValue()) ); + return rStrm; + } + } + + namespace SvxFormatBreak + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + DBG_ASSERT( SOFFICE_FILEFORMAT_31==nFileFormatVersion || + SOFFICE_FILEFORMAT_40==nFileFormatVersion || + SOFFICE_FILEFORMAT_50==nFileFormatVersion, + "SvxFormatBreakItem: Is there a new file format? "); + return SOFFICE_FILEFORMAT_31==nFileFormatVersion || + SOFFICE_FILEFORMAT_40==nFileFormatVersion ? 0 : FMTBREAK_NOAUTO; + } + + void Create(SvxFormatBreakItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + sal_Int8 eBreak, bDummy; + rStrm.ReadSChar( eBreak ); + if( FMTBREAK_NOAUTO > nItemVersion ) + rStrm.ReadSChar( bDummy ); + rItem.SetValue(static_cast<::SvxBreak>(eBreak)); + } + + SvStream& Store(const SvxFormatBreakItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + rStrm.WriteSChar( rItem.GetEnumValue() ); + if( FMTBREAK_NOAUTO > nItemVersion ) + rStrm.WriteSChar( 0x01 ); + return rStrm; + } + } + + namespace SvxFormatKeep + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxFormatKeepItem& rItem, SvStream& rStrm, sal_uInt16) + { + // derived from SfxBoolItem, but that uses + // rStream.ReadCharAsBool( tmp ); + sal_Int8 bIsKeep; + rStrm.ReadSChar( bIsKeep ); + rItem.SetValue(static_cast<bool>(bIsKeep)); + } + + SvStream& Store(const SvxFormatKeepItem& rItem, SvStream& rStrm, sal_uInt16) + { + // derived from SfxBoolItem, but that uses + // rStream.WriteBool( m_bValue ); // not bool for serialization! + rStrm.WriteSChar( static_cast<sal_Int8>(rItem.GetValue()) ); + return rStrm; + } + } + + namespace SvxShadow + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxShadowItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_Int8 cLoc; + sal_uInt16 _nWidth; + bool bTrans; + Color aColor; + Color aFillColor; + sal_Int8 nStyle; + rStrm.ReadSChar( cLoc ).ReadUInt16( _nWidth ).ReadCharAsBool( bTrans ); + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.readColor(aColor); + aSerializer.readColor(aFillColor); + rStrm.ReadSChar(nStyle); + aColor.SetAlpha(bTrans ? 0 : 255); + + rItem.SetLocation(static_cast<SvxShadowLocation>(cLoc)); + rItem.SetWidth(_nWidth); + rItem.SetColor(aColor); + } + + SvStream& Store(const SvxShadowItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteSChar( static_cast<sal_uInt8>(rItem.GetLocation()) ) + .WriteUInt16( rItem.GetWidth() ) + .WriteBool( rItem.GetColor().IsTransparent() ); + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.writeColor(rItem.GetColor()); + aSerializer.writeColor(rItem.GetColor()); + rStrm.WriteSChar( rItem.GetColor().IsTransparent() ? 0 : 1 ); //BRUSH_NULL : BRUSH_SOLID + return rStrm; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/numitem.cxx b/editeng/source/items/numitem.cxx new file mode 100644 index 000000000..cbbd7efb9 --- /dev/null +++ b/editeng/source/items/numitem.cxx @@ -0,0 +1,1129 @@ +/* -*- 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 <algorithm> + +#include <editeng/numitem.hxx> + +#include <com/sun/star/text/VertOrientation.hpp> +#include <comphelper/propertyvalue.hxx> +#include <editeng/brushitem.hxx> +#include <rtl/ustrbuf.hxx> +#include <vcl/font.hxx> +#include <vcl/settings.hxx> +#include <editeng/editids.hrc> +#include <editeng/numdef.hxx> +#include <vcl/graph.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <com/sun/star/text/XNumberingFormatter.hpp> +#include <com/sun/star/text/DefaultNumberingProvider.hpp> +#include <com/sun/star/text/XDefaultNumberingProvider.hpp> +#include <com/sun/star/style/NumberingType.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <comphelper/fileformat.h> +#include <comphelper/processfactory.hxx> +#include <tools/mapunit.hxx> +#include <tools/stream.hxx> +#include <tools/debug.hxx> +#include <tools/GenericTypeSerializer.hxx> +#include <unotools/configmgr.hxx> +#include <libxml/xmlwriter.h> +#include <editeng/unonrule.hxx> +#include <sal/log.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <editeng/legacyitem.hxx> + +constexpr sal_Int32 DEF_WRITER_LSPACE = 500; //Standard Indentation +constexpr sal_Int32 DEF_DRAW_LSPACE = 800; //Standard Indentation + +constexpr sal_uInt16 NUMITEM_VERSION_03 = 0x03; +constexpr sal_uInt16 NUMITEM_VERSION_04 = 0x04; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::text; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::style; + +sal_Int32 SvxNumberType::nRefCount = 0; +css::uno::Reference<css::text::XNumberingFormatter> SvxNumberType::xFormatter; +static void lcl_getFormatter(css::uno::Reference<css::text::XNumberingFormatter>& _xFormatter) +{ + if(_xFormatter.is()) + return; + + try + { + Reference<XComponentContext> xContext( ::comphelper::getProcessComponentContext() ); + Reference<XDefaultNumberingProvider> xRet = text::DefaultNumberingProvider::create(xContext); + _xFormatter.set(xRet, UNO_QUERY); + } + catch(const Exception&) + { + SAL_WARN("editeng", "service missing: \"com.sun.star.text.DefaultNumberingProvider\""); + } +} + +SvxNumberType::SvxNumberType(SvxNumType nType) : + nNumType(nType), + bShowSymbol(true) +{ + nRefCount++; +} + +SvxNumberType::SvxNumberType(const SvxNumberType& rType) : + nNumType(rType.nNumType), + bShowSymbol(rType.bShowSymbol) +{ + nRefCount++; +} + +SvxNumberType::~SvxNumberType() +{ + if(!--nRefCount) + xFormatter = nullptr; +} + +OUString SvxNumberType::GetNumStr( sal_Int32 nNo ) const +{ + LanguageTag aLang = utl::ConfigManager::IsFuzzing() ? + LanguageTag("en-US") : + Application::GetSettings().GetLanguageTag(); + return GetNumStr( nNo, aLang.getLocale() ); +} + +OUString SvxNumberType::GetNumStr( sal_Int32 nNo, const css::lang::Locale& rLocale ) const +{ + lcl_getFormatter(xFormatter); + if(!xFormatter.is()) + return OUString(); + + if(bShowSymbol) + { + switch(nNumType) + { + case NumberingType::CHAR_SPECIAL: + case NumberingType::BITMAP: + break; + default: + { + // '0' allowed for ARABIC numberings + if(NumberingType::ARABIC == nNumType && 0 == nNo ) + return OUString('0'); + else + { + static constexpr OUStringLiteral sNumberingType = u"NumberingType"; + static constexpr OUStringLiteral sValue = u"Value"; + Sequence< PropertyValue > aProperties + { + comphelper::makePropertyValue(sNumberingType, static_cast<sal_uInt16>(nNumType)), + comphelper::makePropertyValue(sValue, nNo) + }; + + try + { + return xFormatter->makeNumberingString( aProperties, rLocale ); + } + catch(const Exception&) + { + } + } + } + } + } + return OUString(); +} + +void SvxNumberType::dumpAsXml( xmlTextWriterPtr pWriter ) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxNumberType")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("NumType"), BAD_CAST(OString::number(nNumType).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +SvxNumberFormat::SvxNumberFormat( SvxNumType eType ) + : SvxNumberType(eType), + eNumAdjust(SvxAdjust::Left), + nInclUpperLevels(1), + nStart(1), + cBullet(SVX_DEF_BULLET), + nBulletRelSize(100), + nBulletColor(COL_BLACK), + mePositionAndSpaceMode( LABEL_WIDTH_AND_POSITION ), + nFirstLineOffset(0), + nAbsLSpace(0), + nCharTextDistance(0), + meLabelFollowedBy( LISTTAB ), + mnListtabPos( 0 ), + mnFirstLineIndent( 0 ), + mnIndentAt( 0 ), + eVertOrient(text::VertOrientation::NONE) +{ +} + +SvxNumberFormat::SvxNumberFormat(const SvxNumberFormat& rFormat) : + SvxNumberType(rFormat), + mePositionAndSpaceMode( rFormat.mePositionAndSpaceMode ) +{ + *this = rFormat; +} + +SvxNumberFormat::SvxNumberFormat( SvStream &rStream ) + : nStart(0) + , nBulletRelSize(100) + , nFirstLineOffset(0) + , nAbsLSpace(0) + , nCharTextDistance(0) +{ + sal_uInt16 nTmp16(0); + sal_Int32 nTmp32(0); + rStream.ReadUInt16( nTmp16 ); // Version number + + rStream.ReadUInt16( nTmp16 ); SetNumberingType( static_cast<SvxNumType>(nTmp16) ); + rStream.ReadUInt16( nTmp16 ); eNumAdjust = static_cast<SvxAdjust>(nTmp16); + rStream.ReadUInt16( nTmp16 ); nInclUpperLevels = nTmp16; + rStream.ReadUInt16( nStart ); + rStream.ReadUInt16( nTmp16 ); cBullet = static_cast<sal_Unicode>(nTmp16); + + sal_Int16 temp = 0; + rStream.ReadInt16( temp ); + nFirstLineOffset = temp; + temp = 0; + rStream.ReadInt16( temp ); + nAbsLSpace = temp; + rStream.SeekRel(2); //skip old now unused nLSpace; + + rStream.ReadInt16( nCharTextDistance ); + + sPrefix = rStream.ReadUniOrByteString( rStream.GetStreamCharSet() ); + sSuffix = rStream.ReadUniOrByteString( rStream.GetStreamCharSet() ); + sCharStyleName = rStream.ReadUniOrByteString( rStream.GetStreamCharSet() ); + + sal_uInt16 hasGraphicBrush = 0; + rStream.ReadUInt16( hasGraphicBrush ); + if ( hasGraphicBrush ) + { + pGraphicBrush.reset(new SvxBrushItem(SID_ATTR_BRUSH)); + legacy::SvxBrush::Create(*pGraphicBrush, rStream, BRUSH_GRAPHIC_VERSION); + } + else pGraphicBrush = nullptr; + rStream.ReadUInt16( nTmp16 ); eVertOrient = nTmp16; + + sal_uInt16 hasBulletFont = 0; + rStream.ReadUInt16( hasBulletFont ); + if ( hasBulletFont ) + { + pBulletFont.emplace(); + ReadFont( rStream, *pBulletFont ); + } + else pBulletFont.reset(); + + tools::GenericTypeSerializer aSerializer(rStream); + aSerializer.readSize(aGraphicSize); + aSerializer.readColor(nBulletColor); + + rStream.ReadUInt16( nBulletRelSize ); + rStream.ReadUInt16( nTmp16 ); SetShowSymbol( nTmp16 != 0 ); + + rStream.ReadUInt16( nTmp16 ); mePositionAndSpaceMode = static_cast<SvxNumPositionAndSpaceMode>(nTmp16); + rStream.ReadUInt16( nTmp16 ); meLabelFollowedBy = static_cast<LabelFollowedBy>(nTmp16); + rStream.ReadInt32( nTmp32 ); mnListtabPos = nTmp32; + rStream.ReadInt32( nTmp32 ); mnFirstLineIndent = nTmp32; + rStream.ReadInt32( nTmp32 ); mnIndentAt = nTmp32; +} + +SvxNumberFormat::~SvxNumberFormat() +{ +} + +void SvxNumberFormat::Store(SvStream &rStream, FontToSubsFontConverter pConverter) +{ + if(pConverter && pBulletFont) + { + cBullet = ConvertFontToSubsFontChar(pConverter, cBullet); + OUString sFontName = GetFontToSubsFontName(pConverter); + pBulletFont->SetFamilyName(sFontName); + } + + tools::GenericTypeSerializer aSerializer(rStream); + + rStream.WriteUInt16( NUMITEM_VERSION_04 ); + + rStream.WriteUInt16( GetNumberingType() ); + rStream.WriteUInt16( static_cast<sal_uInt16>(eNumAdjust) ); + rStream.WriteUInt16( nInclUpperLevels ); + rStream.WriteUInt16( nStart ); + rStream.WriteUInt16( cBullet ); + + rStream.WriteInt16( + sal_Int16(std::clamp<sal_Int32>(nFirstLineOffset, SAL_MIN_INT16, SAL_MAX_INT16)) ); + //TODO: better way to handle out-of-bounds value? + rStream.WriteInt16( + sal_Int16(std::clamp<sal_Int32>(nAbsLSpace, SAL_MIN_INT16, SAL_MAX_INT16)) ); + //TODO: better way to handle out-of-bounds value? + rStream.WriteInt16( 0 ); // write a dummy for old now unused nLSpace + + rStream.WriteInt16( nCharTextDistance ); + rtl_TextEncoding eEnc = osl_getThreadTextEncoding(); + rStream.WriteUniOrByteString(sPrefix, eEnc); + rStream.WriteUniOrByteString(sSuffix, eEnc); + rStream.WriteUniOrByteString(sCharStyleName, eEnc); + if(pGraphicBrush) + { + rStream.WriteUInt16( 1 ); + + // in SD or SI force bullet itself to be stored, + // for that purpose throw away link when link and graphic + // are present, so Brush save is forced + if(!pGraphicBrush->GetGraphicLink().isEmpty() && pGraphicBrush->GetGraphic()) + { + pGraphicBrush->SetGraphicLink(""); + } + + legacy::SvxBrush::Store(*pGraphicBrush, rStream, BRUSH_GRAPHIC_VERSION); + } + else + rStream.WriteUInt16( 0 ); + + rStream.WriteUInt16( eVertOrient ); + if(pBulletFont) + { + rStream.WriteUInt16( 1 ); + WriteFont( rStream, *pBulletFont ); + } + else + rStream.WriteUInt16( 0 ); + + aSerializer.writeSize(aGraphicSize); + + Color nTempColor = nBulletColor; + if(COL_AUTO == nBulletColor) + nTempColor = COL_BLACK; + + aSerializer.writeColor(nTempColor); + rStream.WriteUInt16( nBulletRelSize ); + rStream.WriteUInt16( sal_uInt16(IsShowSymbol()) ); + + rStream.WriteUInt16( mePositionAndSpaceMode ); + rStream.WriteUInt16( meLabelFollowedBy ); + rStream.WriteInt32( mnListtabPos ); + rStream.WriteInt32( mnFirstLineIndent ); + rStream.WriteInt32( mnIndentAt ); +} + +SvxNumberFormat& SvxNumberFormat::operator=( const SvxNumberFormat& rFormat ) +{ + if (& rFormat == this) { return *this; } + + SvxNumberType::SetNumberingType(rFormat.GetNumberingType()); + eNumAdjust = rFormat.eNumAdjust ; + nInclUpperLevels = rFormat.nInclUpperLevels ; + nStart = rFormat.nStart ; + cBullet = rFormat.cBullet ; + mePositionAndSpaceMode = rFormat.mePositionAndSpaceMode; + nFirstLineOffset = rFormat.nFirstLineOffset; + nAbsLSpace = rFormat.nAbsLSpace ; + nCharTextDistance = rFormat.nCharTextDistance ; + meLabelFollowedBy = rFormat.meLabelFollowedBy; + mnListtabPos = rFormat.mnListtabPos; + mnFirstLineIndent = rFormat.mnFirstLineIndent; + mnIndentAt = rFormat.mnIndentAt; + eVertOrient = rFormat.eVertOrient; + sPrefix = rFormat.sPrefix; + sSuffix = rFormat.sSuffix; + sListFormat = rFormat.sListFormat; + aGraphicSize = rFormat.aGraphicSize ; + nBulletColor = rFormat.nBulletColor ; + nBulletRelSize = rFormat.nBulletRelSize; + SetShowSymbol(rFormat.IsShowSymbol()); + sCharStyleName = rFormat.sCharStyleName; + pGraphicBrush.reset(); + if(rFormat.pGraphicBrush) + { + pGraphicBrush.reset( new SvxBrushItem(*rFormat.pGraphicBrush) ); + } + pBulletFont.reset(); + if(rFormat.pBulletFont) + pBulletFont = *rFormat.pBulletFont; + return *this; +} + +bool SvxNumberFormat::operator==( const SvxNumberFormat& rFormat) const +{ + if( GetNumberingType() != rFormat.GetNumberingType() || + eNumAdjust != rFormat.eNumAdjust || + nInclUpperLevels != rFormat.nInclUpperLevels || + nStart != rFormat.nStart || + cBullet != rFormat.cBullet || + mePositionAndSpaceMode != rFormat.mePositionAndSpaceMode || + nFirstLineOffset != rFormat.nFirstLineOffset || + nAbsLSpace != rFormat.nAbsLSpace || + nCharTextDistance != rFormat.nCharTextDistance || + meLabelFollowedBy != rFormat.meLabelFollowedBy || + mnListtabPos != rFormat.mnListtabPos || + mnFirstLineIndent != rFormat.mnFirstLineIndent || + mnIndentAt != rFormat.mnIndentAt || + eVertOrient != rFormat.eVertOrient || + sPrefix != rFormat.sPrefix || + sSuffix != rFormat.sSuffix || + sListFormat != rFormat.sListFormat || + aGraphicSize != rFormat.aGraphicSize || + nBulletColor != rFormat.nBulletColor || + nBulletRelSize != rFormat.nBulletRelSize || + IsShowSymbol() != rFormat.IsShowSymbol() || + sCharStyleName != rFormat.sCharStyleName + ) + return false; + if ( + (pGraphicBrush && !rFormat.pGraphicBrush) || + (!pGraphicBrush && rFormat.pGraphicBrush) || + (pGraphicBrush && *pGraphicBrush != *rFormat.pGraphicBrush) + ) + { + return false; + } + if ( + (pBulletFont && !rFormat.pBulletFont) || + (!pBulletFont && rFormat.pBulletFont) || + (pBulletFont && *pBulletFont != *rFormat.pBulletFont) + ) + { + return false; + } + return true; +} + +void SvxNumberFormat::SetGraphicBrush( const SvxBrushItem* pBrushItem, + const Size* pSize, const sal_Int16* pOrient) +{ + if (!pBrushItem) + pGraphicBrush.reset(); + else if ( !pGraphicBrush || (*pBrushItem != *pGraphicBrush) ) + pGraphicBrush.reset(pBrushItem->Clone()); + + if(pOrient) + eVertOrient = *pOrient; + else + eVertOrient = text::VertOrientation::NONE; + if(pSize) + aGraphicSize = *pSize; + else + { + aGraphicSize.setWidth(0); + aGraphicSize.setHeight(0); + } +} + +void SvxNumberFormat::SetGraphic( const OUString& rName ) +{ + if( pGraphicBrush && pGraphicBrush->GetGraphicLink() == rName ) + return ; + + pGraphicBrush.reset( new SvxBrushItem( rName, "", GPOS_AREA, 0 ) ); + if( eVertOrient == text::VertOrientation::NONE ) + eVertOrient = text::VertOrientation::TOP; + + aGraphicSize.setWidth(0); + aGraphicSize.setHeight(0); +} + +sal_Int16 SvxNumberFormat::GetVertOrient() const +{ + return eVertOrient; +} + +void SvxNumberFormat::SetBulletFont(const vcl::Font* pFont) +{ + if (pFont) + pBulletFont = *pFont; + else + pBulletFont.reset(); +} + +void SvxNumberFormat::SetPositionAndSpaceMode( SvxNumPositionAndSpaceMode ePositionAndSpaceMode ) +{ + mePositionAndSpaceMode = ePositionAndSpaceMode; +} + +sal_Int32 SvxNumberFormat::GetAbsLSpace() const +{ + return mePositionAndSpaceMode == LABEL_WIDTH_AND_POSITION + ? nAbsLSpace + : static_cast<sal_Int32>( GetFirstLineIndent() + GetIndentAt() ); +} +sal_Int32 SvxNumberFormat::GetFirstLineOffset() const +{ + return mePositionAndSpaceMode == LABEL_WIDTH_AND_POSITION + ? nFirstLineOffset + : static_cast<sal_Int32>( GetFirstLineIndent() ); +} +short SvxNumberFormat::GetCharTextDistance() const +{ + return mePositionAndSpaceMode == LABEL_WIDTH_AND_POSITION ? nCharTextDistance : 0; +} + +void SvxNumberFormat::SetLabelFollowedBy( const LabelFollowedBy eLabelFollowedBy ) +{ + meLabelFollowedBy = eLabelFollowedBy; +} + +OUString SvxNumberFormat::GetLabelFollowedByAsString() const +{ + switch (meLabelFollowedBy) + { + case LISTTAB: + return "\t"; + case SPACE: + return " "; + case NEWLINE: + return "\n"; + case NOTHING: + // intentionally left blank. + return OUString(); + default: + SAL_WARN("editeng", "Unknown SvxNumberFormat::GetLabelFollowedBy() return value"); + assert(false); + } + return OUString(); +} + +void SvxNumberFormat::SetListtabPos( const tools::Long nListtabPos ) +{ + mnListtabPos = nListtabPos; +} +void SvxNumberFormat::SetFirstLineIndent( const tools::Long nFirstLineIndent ) +{ + mnFirstLineIndent = nFirstLineIndent; +} +void SvxNumberFormat::SetIndentAt( const tools::Long nIndentAt ) +{ + mnIndentAt = nIndentAt; +} + +Size SvxNumberFormat::GetGraphicSizeMM100(const Graphic* pGraphic) +{ + const MapMode aMapMM100( MapUnit::Map100thMM ); + const Size& rSize = pGraphic->GetPrefSize(); + Size aRetSize; + if ( pGraphic->GetPrefMapMode().GetMapUnit() == MapUnit::MapPixel ) + { + OutputDevice* pOutDev = Application::GetDefaultDevice(); + MapMode aOldMap( pOutDev->GetMapMode() ); + pOutDev->SetMapMode( aMapMM100 ); + aRetSize = pOutDev->PixelToLogic( rSize ); + pOutDev->SetMapMode( aOldMap ); + } + else + aRetSize = OutputDevice::LogicToLogic( rSize, pGraphic->GetPrefMapMode(), aMapMM100 ); + return aRetSize; +} + +OUString SvxNumberFormat::CreateRomanString( sal_Int32 nNo, bool bUpper ) +{ + OUStringBuffer sRet; + + constexpr char romans[][13] = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}; + constexpr sal_Int32 values[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; + + for (size_t i = 0; i < std::size(romans); ++i) + { + while(nNo - values[i] >= 0) + { + sRet.appendAscii(romans[i]); + nNo -= values[i]; + } + } + + return bUpper ? sRet.makeStringAndClear() + : sRet.makeStringAndClear().toAsciiLowerCase(); +} + +void SvxNumberFormat::SetListFormat(const OUString& rPrefix, const OUString& rSuffix, int nLevel) +{ + sPrefix = rPrefix; + sSuffix = rSuffix; + + // Generate list format + sListFormat = std::make_optional(sPrefix); + + for (int i = 1; i <= nInclUpperLevels; i++) + { + int nLevelId = nLevel - nInclUpperLevels + i; + if (nLevelId < 0) + // There can be cases with current level 1, but request to show 10 upper levels. Trim it + continue; + + *sListFormat += "%"; + *sListFormat += OUString::number(nLevelId + 1); + *sListFormat += "%"; + if (i != nInclUpperLevels) + *sListFormat += "."; // Default separator for older ODT + } + + *sListFormat += sSuffix; +} + +void SvxNumberFormat::SetListFormat(std::optional<OUString> oSet) +{ + sPrefix.clear(); + sSuffix.clear(); + + if (!oSet.has_value()) + { + return; + } + + sListFormat = oSet; + + // For backward compatibility and UI we should create something looking like + // a prefix, suffix and included levels also. This is not possible in general case + // since level format string is much more flexible. But for most cases is okay + sal_Int32 nFirstReplacement = sListFormat->indexOf('%'); + sal_Int32 nLastReplacement = sListFormat->lastIndexOf('%') + 1; + if (nFirstReplacement > 0) + // Everything before first '%' will be prefix + sPrefix = sListFormat->copy(0, nFirstReplacement); + if (nLastReplacement >= 0 && nLastReplacement < sListFormat->getLength()) + // Everything beyond last '%' is a suffix + sSuffix = sListFormat->copy(nLastReplacement); + + sal_uInt8 nPercents = 0; + for (sal_Int32 i = 0; i < sListFormat->getLength(); i++) + { + if ((*sListFormat)[i] == '%') + nPercents++; + } + nInclUpperLevels = nPercents/2; + if (nInclUpperLevels < 1) + { + // There should be always at least one level. This will be not required + // in future (when we get rid of prefix/suffix), but nowadays there + // are too many conversions "list format" <-> "prefix/suffix/inclUpperLevel" + nInclUpperLevels = 1; + } +} + +OUString SvxNumberFormat::GetListFormat(bool bIncludePrefixSuffix /*= true*/) const +{ + assert(sListFormat.has_value()); + + if (bIncludePrefixSuffix) + return *sListFormat; + + // Strip prefix & suffix from string + return sListFormat->copy(sPrefix.getLength(), sListFormat->getLength() - sPrefix.getLength() - sSuffix.getLength()); +} + +OUString SvxNumberFormat::GetCharFormatName()const +{ + return sCharStyleName; +} + +sal_Int32 SvxNumRule::nRefCount = 0; +static SvxNumberFormat* pStdNumFmt = nullptr; +static SvxNumberFormat* pStdOutlineNumFmt = nullptr; +SvxNumRule::SvxNumRule( SvxNumRuleFlags nFeatures, + sal_uInt16 nLevels, + bool bCont, + SvxNumRuleType eType, + SvxNumberFormat::SvxNumPositionAndSpaceMode + eDefaultNumberFormatPositionAndSpaceMode ) + : nLevelCount(nLevels), + nFeatureFlags(nFeatures), + eNumberingType(eType), + bContinuousNumbering(bCont) +{ + ++nRefCount; + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(i < nLevels) + { + aFmts[i].reset( new SvxNumberFormat(SVX_NUM_CHARS_UPPER_LETTER) ); + // It is a distinction between writer and draw + if(nFeatures & SvxNumRuleFlags::CONTINUOUS) + { + if ( eDefaultNumberFormatPositionAndSpaceMode == + SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFmts[i]->SetAbsLSpace(o3tl::toTwips(DEF_WRITER_LSPACE * (i+1), o3tl::Length::mm100)); + aFmts[i]->SetFirstLineOffset(o3tl::toTwips(-DEF_WRITER_LSPACE, o3tl::Length::mm100)); + } + else if ( eDefaultNumberFormatPositionAndSpaceMode == + SvxNumberFormat::LABEL_ALIGNMENT ) + { + // first line indent of general numbering in inch: -0,25 inch + constexpr tools::Long cFirstLineIndent = o3tl::toTwips(-0.25, o3tl::Length::in); + // indent values of general numbering in inch: + // 0,5 0,75 1,0 1,25 1,5 + // 1,75 2,0 2,25 2,5 2,75 + constexpr tools::Long cIndentAt = o3tl::toTwips(0.25, o3tl::Length::in); + aFmts[i]->SetPositionAndSpaceMode( SvxNumberFormat::LABEL_ALIGNMENT ); + aFmts[i]->SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFmts[i]->SetListtabPos( cIndentAt * (i+2) ); + aFmts[i]->SetFirstLineIndent( cFirstLineIndent ); + aFmts[i]->SetIndentAt( cIndentAt * (i+2) ); + } + } + else + { + aFmts[i]->SetAbsLSpace( DEF_DRAW_LSPACE * i ); + } + } + else + aFmts[i] = nullptr; + aFmtsSet[i] = false; + } +} + +SvxNumRule::SvxNumRule(const SvxNumRule& rCopy) +{ + ++nRefCount; + nLevelCount = rCopy.nLevelCount ; + nFeatureFlags = rCopy.nFeatureFlags ; + bContinuousNumbering = rCopy.bContinuousNumbering; + eNumberingType = rCopy.eNumberingType; + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(rCopy.aFmts[i]) + aFmts[i].reset( new SvxNumberFormat(*rCopy.aFmts[i]) ); + else + aFmts[i].reset(); + aFmtsSet[i] = rCopy.aFmtsSet[i]; + } +} + +SvxNumRule::SvxNumRule(SvxNumRule&& rCopy) noexcept +{ + ++nRefCount; + nLevelCount = rCopy.nLevelCount ; + nFeatureFlags = rCopy.nFeatureFlags ; + bContinuousNumbering = rCopy.bContinuousNumbering; + eNumberingType = rCopy.eNumberingType; + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(rCopy.aFmts[i]) + aFmts[i] = std::move(rCopy.aFmts[i]); + aFmtsSet[i] = rCopy.aFmtsSet[i]; + } +} + +SvxNumRule::SvxNumRule( SvStream &rStream ) + : nLevelCount(0) +{ + sal_uInt16 nTmp16(0); + rStream.ReadUInt16( nTmp16 ); // NUM_ITEM_VERSION + rStream.ReadUInt16( nLevelCount ); + + if (nLevelCount > SVX_MAX_NUM) + { + SAL_WARN("editeng", "nLevelCount: " << nLevelCount << " greater than max of: " << SVX_MAX_NUM); + nLevelCount = SVX_MAX_NUM; + } + + // first nFeatureFlags of old Versions + rStream.ReadUInt16( nTmp16 ); nFeatureFlags = static_cast<SvxNumRuleFlags>(nTmp16); + rStream.ReadUInt16( nTmp16 ); bContinuousNumbering = nTmp16; + rStream.ReadUInt16( nTmp16 ); eNumberingType = static_cast<SvxNumRuleType>(nTmp16); + + for (sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + rStream.ReadUInt16( nTmp16 ); + bool hasNumberingFormat = nTmp16 & 1; + aFmtsSet[i] = nTmp16 & 2; // fdo#68648 reset flag + if ( hasNumberingFormat ){ + aFmts[i].reset( new SvxNumberFormat( rStream ) ); + } + else + { + aFmts[i].reset(); + aFmtsSet[i] = false; // actually only false is valid + } + } + //second nFeatureFlags for new versions + rStream.ReadUInt16( nTmp16 ); nFeatureFlags = static_cast<SvxNumRuleFlags>(nTmp16); +} + +void SvxNumRule::Store( SvStream &rStream ) +{ + rStream.WriteUInt16( NUMITEM_VERSION_03 ); + rStream.WriteUInt16( nLevelCount ); + //first save of nFeatureFlags for old versions + rStream.WriteUInt16( static_cast<sal_uInt16>(nFeatureFlags) ); + rStream.WriteUInt16( sal_uInt16(bContinuousNumbering) ); + rStream.WriteUInt16( static_cast<sal_uInt16>(eNumberingType) ); + + FontToSubsFontConverter pConverter = nullptr; + bool bConvertBulletFont = ( rStream.GetVersion() <= SOFFICE_FILEFORMAT_50 ) && ( rStream.GetVersion() ); + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + sal_uInt16 nSetFlag(aFmtsSet[i] ? 2 : 0); // fdo#68648 store that too + if(aFmts[i]) + { + rStream.WriteUInt16( 1 | nSetFlag ); + if(bConvertBulletFont && aFmts[i]->GetBulletFont()) + { + if(!pConverter) + pConverter = + CreateFontToSubsFontConverter(aFmts[i]->GetBulletFont()->GetFamilyName(), + FontToSubsFontFlags::EXPORT); + } + aFmts[i]->Store(rStream, pConverter); + } + else + rStream.WriteUInt16( 0 | nSetFlag ); + } + //second save of nFeatureFlags for new versions + rStream.WriteUInt16( static_cast<sal_uInt16>(nFeatureFlags) ); +} + +void SvxNumRule::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxNumRule")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("levelCount"), BAD_CAST(OString::number(nLevelCount).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("continuousNumbering"), BAD_CAST(OString::boolean(bContinuousNumbering).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("numberingType"), BAD_CAST(OString::number(static_cast<int>(eNumberingType)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("featureFlags"), BAD_CAST(OString::number(static_cast<int>(nFeatureFlags)).getStr())); + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(aFmts[i]) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("aFmts")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("i"), BAD_CAST(OString::number(i).getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", aFmts[i].get()); + (void)xmlTextWriterEndElement(pWriter); + } + } + (void)xmlTextWriterEndElement(pWriter); +} + + +SvxNumRule::~SvxNumRule() +{ + if(!--nRefCount) + { + delete pStdNumFmt; + pStdNumFmt = nullptr; + delete pStdOutlineNumFmt; + pStdOutlineNumFmt = nullptr; + } +} + +SvxNumRule& SvxNumRule::operator=( const SvxNumRule& rCopy ) +{ + if (this != &rCopy) + { + nLevelCount = rCopy.nLevelCount; + nFeatureFlags = rCopy.nFeatureFlags; + bContinuousNumbering = rCopy.bContinuousNumbering; + eNumberingType = rCopy.eNumberingType; + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(rCopy.aFmts[i]) + aFmts[i].reset( new SvxNumberFormat(*rCopy.aFmts[i]) ); + else + aFmts[i].reset(); + aFmtsSet[i] = rCopy.aFmtsSet[i]; + } + } + return *this; +} + +SvxNumRule& SvxNumRule::operator=( SvxNumRule&& rCopy ) noexcept +{ + if (this != &rCopy) + { + nLevelCount = rCopy.nLevelCount; + nFeatureFlags = rCopy.nFeatureFlags; + bContinuousNumbering = rCopy.bContinuousNumbering; + eNumberingType = rCopy.eNumberingType; + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(rCopy.aFmts[i]) + aFmts[i] = std::move(rCopy.aFmts[i]); + aFmtsSet[i] = rCopy.aFmtsSet[i]; + } + } + return *this; +} + +bool SvxNumRule::operator==( const SvxNumRule& rCopy) const +{ + if(nLevelCount != rCopy.nLevelCount || + nFeatureFlags != rCopy.nFeatureFlags || + bContinuousNumbering != rCopy.bContinuousNumbering || + eNumberingType != rCopy.eNumberingType) + return false; + for(sal_uInt16 i = 0; i < nLevelCount; i++) + { + if ( + (aFmtsSet[i] != rCopy.aFmtsSet[i]) || + (!aFmts[i] && rCopy.aFmts[i]) || + (aFmts[i] && !rCopy.aFmts[i]) || + (aFmts[i] && *aFmts[i] != *rCopy.aFmts[i]) + ) + { + return false; + } + } + return true; +} + +const SvxNumberFormat* SvxNumRule::Get(sal_uInt16 nLevel)const +{ + DBG_ASSERT(nLevel < SVX_MAX_NUM, "Wrong Level" ); + if( nLevel < SVX_MAX_NUM ) + return aFmtsSet[nLevel] ? aFmts[nLevel].get() : nullptr; + else + return nullptr; +} + +const SvxNumberFormat& SvxNumRule::GetLevel(sal_uInt16 nLevel)const +{ + if(!pStdNumFmt) + { + pStdNumFmt = new SvxNumberFormat(SVX_NUM_ARABIC); + pStdOutlineNumFmt = new SvxNumberFormat(SVX_NUM_NUMBER_NONE); + } + + DBG_ASSERT(nLevel < SVX_MAX_NUM, "Wrong Level" ); + + return ( ( nLevel < SVX_MAX_NUM ) && aFmts[nLevel] ) ? + *aFmts[nLevel] : eNumberingType == SvxNumRuleType::NUMBERING ? + *pStdNumFmt : *pStdOutlineNumFmt; +} + +void SvxNumRule::SetLevel( sal_uInt16 i, const SvxNumberFormat& rNumFmt, bool bIsValid ) +{ + DBG_ASSERT(i < SVX_MAX_NUM, "Wrong Level" ); + + if( i >= SVX_MAX_NUM ) + return; + + bool bReplace = !aFmtsSet[i]; + if (!bReplace) + { + const SvxNumberFormat *pFmt = Get(i); + bReplace = pFmt == nullptr || rNumFmt != *pFmt; + } + + if (bReplace) + { + aFmts[i].reset( new SvxNumberFormat(rNumFmt) ); + aFmtsSet[i] = bIsValid; + } +} + +void SvxNumRule::SetLevel(sal_uInt16 nLevel, const SvxNumberFormat* pFmt) +{ + DBG_ASSERT(nLevel < SVX_MAX_NUM, "Wrong Level" ); + + if( nLevel < SVX_MAX_NUM ) + { + aFmtsSet[nLevel] = nullptr != pFmt; + if(pFmt) + SetLevel(nLevel, *pFmt); + else + { + aFmts[nLevel].reset(); + } + } +} + +OUString SvxNumRule::MakeNumString( const SvxNodeNum& rNum ) const +{ + OUStringBuffer aStr; + if( SVX_NO_NUM > rNum.GetLevel() && !( SVX_NO_NUMLEVEL & rNum.GetLevel() ) ) + { + const SvxNumberFormat& rMyNFmt = GetLevel( rNum.GetLevel() ); + aStr.append(rMyNFmt.GetPrefix()); + if( SVX_NUM_NUMBER_NONE != rMyNFmt.GetNumberingType() ) + { + sal_uInt8 i = rNum.GetLevel(); + + if( !IsContinuousNumbering() && + 1 < rMyNFmt.GetIncludeUpperLevels() ) // only on own level? + { + sal_uInt8 n = rMyNFmt.GetIncludeUpperLevels(); + if( 1 < n ) + { + if( i+1 >= n ) + i -= n - 1; + else + i = 0; + } + } + + for( ; i <= rNum.GetLevel(); ++i ) + { + const SvxNumberFormat& rNFmt = GetLevel( i ); + if( SVX_NUM_NUMBER_NONE == rNFmt.GetNumberingType() ) + { + continue; + } + + bool bDot = true; + if( rNum.GetLevelVal()[ i ] ) + { + if(SVX_NUM_BITMAP != rNFmt.GetNumberingType()) + { + const LanguageTag& rLang = Application::GetSettings().GetLanguageTag(); + aStr.append(rNFmt.GetNumStr( rNum.GetLevelVal()[ i ], rLang.getLocale() )); + } + else + bDot = false; + } + else + aStr.append("0"); // all 0-levels are a 0 + if( i != rNum.GetLevel() && bDot) + aStr.append("."); + } + } + + aStr.append(rMyNFmt.GetSuffix()); + } + return aStr.makeStringAndClear(); +} + +// changes linked to embedded bitmaps +void SvxNumRule::UnLinkGraphics() +{ + for(sal_uInt16 i = 0; i < GetLevelCount(); i++) + { + SvxNumberFormat aFmt(GetLevel(i)); + const SvxBrushItem* pBrush = aFmt.GetBrush(); + if(SVX_NUM_BITMAP == aFmt.GetNumberingType()) + { + if(pBrush && !pBrush->GetGraphicLink().isEmpty()) + { + const Graphic* pGraphic = pBrush->GetGraphic(); + if (pGraphic) + { + SvxBrushItem aTempItem(*pBrush); + aTempItem.SetGraphicLink(""); + aTempItem.SetGraphic(*pGraphic); + sal_Int16 eOrient = aFmt.GetVertOrient(); + aFmt.SetGraphicBrush( &aTempItem, &aFmt.GetGraphicSize(), &eOrient ); + } + } + } + else if((SVX_NUM_BITMAP|LINK_TOKEN) == static_cast<int>(aFmt.GetNumberingType())) + aFmt.SetNumberingType(SVX_NUM_BITMAP); + SetLevel(i, aFmt); + } +} + +SvxNumBulletItem::SvxNumBulletItem(SvxNumRule const & rRule) : + SfxPoolItem(SID_ATTR_NUMBERING_RULE), + maNumRule(rRule) +{ +} + +SvxNumBulletItem::SvxNumBulletItem(SvxNumRule && rRule) : + SfxPoolItem(SID_ATTR_NUMBERING_RULE), + maNumRule(std::move(rRule)) +{ +} + +SvxNumBulletItem::SvxNumBulletItem(SvxNumRule const & rRule, sal_uInt16 _nWhich ) : + SfxPoolItem(_nWhich), + maNumRule(rRule) +{ +} + +SvxNumBulletItem::SvxNumBulletItem(SvxNumRule && rRule, sal_uInt16 _nWhich ) : + SfxPoolItem(_nWhich), + maNumRule(std::move(rRule)) +{ +} + +SvxNumBulletItem::SvxNumBulletItem(const SvxNumBulletItem& rCopy) : + SfxPoolItem(rCopy), + maNumRule(rCopy.maNumRule) +{ +} + +SvxNumBulletItem::~SvxNumBulletItem() +{ +} + +bool SvxNumBulletItem::operator==( const SfxPoolItem& rCopy) const +{ + return SfxPoolItem::operator==(rCopy) && + maNumRule == static_cast<const SvxNumBulletItem&>(rCopy).maNumRule; +} + +SvxNumBulletItem* SvxNumBulletItem::Clone( SfxItemPool * ) const +{ + return new SvxNumBulletItem(*this); +} + +bool SvxNumBulletItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + rVal <<= SvxCreateNumRule( maNumRule ); + return true; +} + +bool SvxNumBulletItem::PutValue( const css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + uno::Reference< container::XIndexReplace > xRule; + if( rVal >>= xRule ) + { + try + { + SvxNumRule aNewRule( SvxGetNumRule( xRule ) ); + if( aNewRule.GetLevelCount() != maNumRule.GetLevelCount() || + aNewRule.GetNumRuleType() != maNumRule.GetNumRuleType() ) + { + aNewRule = SvxConvertNumRule( aNewRule, maNumRule.GetLevelCount(), maNumRule.GetNumRuleType() ); + } + maNumRule = std::move( aNewRule ); + return true; + } + catch(const lang::IllegalArgumentException&) + { + } + } + return false; +} + +void SvxNumBulletItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxNumBulletItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + maNumRule.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +SvxNumRule SvxConvertNumRule( const SvxNumRule& rRule, sal_uInt16 nLevels, SvxNumRuleType eType ) +{ + const sal_uInt16 nSrcLevels = rRule.GetLevelCount(); + SvxNumRule aNewRule(rRule.GetFeatureFlags(), nLevels, rRule.IsContinuousNumbering(), eType ); + + for( sal_uInt16 nLevel = 0; (nLevel < nLevels) && (nLevel < nSrcLevels); nLevel++ ) + aNewRule.SetLevel( nLevel, rRule.GetLevel( nLevel ) ); + + return aNewRule; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/optitems.cxx b/editeng/source/items/optitems.cxx new file mode 100644 index 000000000..254da79d9 --- /dev/null +++ b/editeng/source/items/optitems.cxx @@ -0,0 +1,63 @@ +/* -*- 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 <editeng/optitems.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/editrids.hrc> + + +// class SfxHyphenRegionItem ----------------------------------------------- + +SfxHyphenRegionItem::SfxHyphenRegionItem( const sal_uInt16 nId ) : + + SfxPoolItem( nId ) +{ + nMinLead = nMinTrail = 0; +} + +bool SfxHyphenRegionItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return ( ( static_cast<const SfxHyphenRegionItem&>( rAttr ).nMinLead == nMinLead ) && + ( static_cast<const SfxHyphenRegionItem&>( rAttr ).nMinTrail == nMinTrail ) ); +} + +SfxHyphenRegionItem* SfxHyphenRegionItem::Clone( SfxItemPool* ) const +{ + return new SfxHyphenRegionItem( *this ); +} + +bool SfxHyphenRegionItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit , + MapUnit , + OUString& rText, + const IntlWrapper& +) const +{ + rText += EditResId(RID_SVXITEMS_HYPHEN_MINLEAD).replaceAll("%1", OUString::number(nMinLead)) + + "," + + EditResId(RID_SVXITEMS_HYPHEN_MINTRAIL).replaceAll("%1", OUString::number(nMinTrail)); + return true; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/paperinf.cxx b/editeng/source/items/paperinf.cxx new file mode 100644 index 000000000..86401e63f --- /dev/null +++ b/editeng/source/items/paperinf.cxx @@ -0,0 +1,121 @@ +/* -*- 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/print.hxx> +#include <editeng/paperinf.hxx> + +/*-------------------------------------------------------------------- + Description: Is the printer valid + --------------------------------------------------------------------*/ + +static bool IsValidPrinter( const Printer* pPtr ) +{ + return !pPtr->GetName().isEmpty(); +} + + +Size SvxPaperInfo::GetPaperSize( Paper ePaper, MapUnit eUnit ) +{ + PaperInfo aInfo(ePaper); + Size aRet(aInfo.getWidth(), aInfo.getHeight()); // in 100thMM + return eUnit == MapUnit::Map100thMM + ? aRet + : OutputDevice::LogicToLogic(aRet, MapMode(MapUnit::Map100thMM), MapMode(eUnit)); +} + +/*------------------------------------------------------------------------ + Description: Return the paper size of the printer, aligned to our + own sizes. If no Printer is set in the system, A4 portrait + will be delivered as the default paper size. +------------------------------------------------------------------------*/ + +//Is this method may be confused about the units it returns ? +//Always returns TWIPS for known paper sizes or on failure. +//But in the case of PAPER_USER paper and with a Printer with a mapmode set +//will return in those printer units ? +Size SvxPaperInfo::GetPaperSize( const Printer* pPrinter ) +{ + if ( !IsValidPrinter(pPrinter) ) + return GetPaperSize( PAPER_A4 ); + const Paper ePaper = pPrinter->GetPaper(); + + if ( ePaper == PAPER_USER ) + { + // Orientation not take into account, as the right size has + // been already set by SV + Size aPaperSize = pPrinter->GetPaperSize(); + const Size aInvalidSize; + + if ( aPaperSize == aInvalidSize ) + return GetPaperSize(PAPER_A4); + const MapMode& aMap1 = pPrinter->GetMapMode(); + MapMode aMap2; + + if ( aMap1 == aMap2 ) + aPaperSize = + pPrinter->PixelToLogic( aPaperSize, MapMode( MapUnit::MapTwip ) ); + return aPaperSize; + } + + const Orientation eOrient = pPrinter->GetOrientation(); + Size aSize( GetPaperSize( ePaper ) ); + // for Landscape exchange the pages, has already been done by SV + if ( eOrient == Orientation::Landscape ) + Swap( aSize ); + return aSize; +} + + +Paper SvxPaperInfo::GetSvxPaper( const Size &rSize, MapUnit eUnit ) +{ + Size aSize(eUnit == MapUnit::Map100thMM ? rSize : OutputDevice::LogicToLogic(rSize, MapMode(eUnit), MapMode(MapUnit::Map100thMM))); + PaperInfo aInfo(aSize.Width(), aSize.Height()); + aInfo.doSloppyFit(); + return aInfo.getPaper(); +} + + +tools::Long SvxPaperInfo::GetSloppyPaperDimension( tools::Long nSize ) +{ + nSize = o3tl::convert(nSize, o3tl::Length::twip, o3tl::Length::mm100); + nSize = PaperInfo::sloppyFitPageDimension(nSize); + return o3tl::convert(nSize, o3tl::Length::mm100, o3tl::Length::twip); +} + + +Size SvxPaperInfo::GetDefaultPaperSize( MapUnit eUnit ) +{ + PaperInfo aInfo(PaperInfo::getSystemDefaultPaper()); + Size aRet(aInfo.getWidth(), aInfo.getHeight()); + return eUnit == MapUnit::Map100thMM + ? aRet + : OutputDevice::LogicToLogic(aRet, MapMode(MapUnit::Map100thMM), MapMode(eUnit)); +} + +/*------------------------------------------------------------------------ + Description: String representation for the SV-defines of paper size +------------------------------------------------------------------------*/ + +OUString SvxPaperInfo::GetName( Paper ePaper ) +{ + return Printer::GetPaperName( ePaper ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/paraitem.cxx b/editeng/source/items/paraitem.cxx new file mode 100644 index 000000000..9368dfdf3 --- /dev/null +++ b/editeng/source/items/paraitem.cxx @@ -0,0 +1,1285 @@ +/* -*- 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/style/TabStop.hpp> +#include <com/sun/star/style/LineSpacing.hpp> +#include <com/sun/star/style/LineSpacingMode.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <libxml/xmlwriter.h> +#include <comphelper/extract.hxx> +#include <osl/diagnose.h> +#include <unotools/localedatawrapper.hxx> +#include <unotools/syslocale.hxx> +#include <tools/mapunit.hxx> +#include <tools/UnitConversion.hxx> +#include <svl/itempool.hxx> +#include <svl/memberid.h> +#include <editeng/editrids.hrc> +#include <editeng/lspcitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/orphitem.hxx> +#include <editeng/widwitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/pmdlitem.hxx> +#include <editeng/spltitem.hxx> +#include <editeng/hyphenzoneitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <editeng/hngpnctitem.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include <editeng/paravertalignitem.hxx> +#include <editeng/pgrditem.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <editeng/memberids.h> +#include <editeng/itemtype.hxx> +#include <editeng/eerdll.hxx> + +using namespace ::com::sun::star; + + +SfxPoolItem* SvxLineSpacingItem::CreateDefault() { return new SvxLineSpacingItem(LINE_SPACE_DEFAULT_HEIGHT, 0);} +SfxPoolItem* SvxAdjustItem::CreateDefault() { return new SvxAdjustItem(SvxAdjust::Left, 0);} +SfxPoolItem* SvxWidowsItem::CreateDefault() { return new SvxWidowsItem(0, 0);} +SfxPoolItem* SvxOrphansItem::CreateDefault() { return new SvxOrphansItem(0, 0);} +SfxPoolItem* SvxHyphenZoneItem::CreateDefault() { return new SvxHyphenZoneItem(false, 0);} +SfxPoolItem* SvxTabStopItem::CreateDefault() { return new SvxTabStopItem(0);} +SfxPoolItem* SvxFormatSplitItem::CreateDefault() { return new SvxFormatSplitItem(false, 0);} +SfxPoolItem* SvxPageModelItem::CreateDefault() { return new SvxPageModelItem(0);} +SfxPoolItem* SvxParaVertAlignItem::CreateDefault() { return new SvxParaVertAlignItem(Align::Automatic, 0);} + +namespace { + +enum class SvxSpecialLineSpace +{ + User, + OneLine, + OnePointFiveLines, + TwoLines, + End +}; + +} + +SvxLineSpacingItem::SvxLineSpacingItem( sal_uInt16 nHeight, const sal_uInt16 nId ) + : SfxEnumItemInterface( nId ) +{ + nPropLineSpace = 100; + nInterLineSpace = 0; + nLineHeight = nHeight; + eLineSpaceRule = SvxLineSpaceRule::Auto; + eInterLineSpaceRule = SvxInterLineSpaceRule::Off; +} + + +bool SvxLineSpacingItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxLineSpacingItem& rLineSpace = static_cast<const SvxLineSpacingItem&>(rAttr); + return + // Same Linespacing Rule? + (eLineSpaceRule == rLineSpace.eLineSpaceRule) + // For maximum and minimum Linespacing be the size must coincide. + && (eLineSpaceRule == SvxLineSpaceRule::Auto || + nLineHeight == rLineSpace.nLineHeight) + // Same Linespacing Rule? + && ( eInterLineSpaceRule == rLineSpace.eInterLineSpaceRule ) + // Either set proportional or additive. + && (( eInterLineSpaceRule == SvxInterLineSpaceRule::Off) + || (eInterLineSpaceRule == SvxInterLineSpaceRule::Prop + && nPropLineSpace == rLineSpace.nPropLineSpace) + || (eInterLineSpaceRule == SvxInterLineSpaceRule::Fix + && (nInterLineSpace == rLineSpace.nInterLineSpace))); +} + +/* Who does still know why the LineSpacingItem is so complicated? + We can not use it for UNO since there are only two values: + - a sal_uInt16 for the mode + - a sal_uInt32 for all values (distance, height, rel. detail) +*/ +bool SvxLineSpacingItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + style::LineSpacing aLSp; + switch( eLineSpaceRule ) + { + case SvxLineSpaceRule::Auto: + if(eInterLineSpaceRule == SvxInterLineSpaceRule::Fix) + { + aLSp.Mode = style::LineSpacingMode::LEADING; + aLSp.Height = ( bConvert ? static_cast<short>(convertTwipToMm100(nInterLineSpace)) : nInterLineSpace); + } + else if(eInterLineSpaceRule == SvxInterLineSpaceRule::Off) + { + aLSp.Mode = style::LineSpacingMode::PROP; + aLSp.Height = 100; + } + else + { + aLSp.Mode = style::LineSpacingMode::PROP; + aLSp.Height = nPropLineSpace; + } + break; + case SvxLineSpaceRule::Fix : + case SvxLineSpaceRule::Min : + aLSp.Mode = eLineSpaceRule == SvxLineSpaceRule::Fix ? style::LineSpacingMode::FIX : style::LineSpacingMode::MINIMUM; + aLSp.Height = ( bConvert ? static_cast<short>(convertTwipToMm100(nLineHeight)) : nLineHeight ); + break; + default: + ;//prevent warning about SvxLineSpaceRule::End + } + + switch ( nMemberId ) + { + case 0 : rVal <<= aLSp; break; + case MID_LINESPACE : rVal <<= aLSp.Mode; break; + case MID_HEIGHT : rVal <<= aLSp.Height; break; + default: OSL_FAIL("Wrong MemberId!"); break; + } + + return true; +} + +bool SvxLineSpacingItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + // fill with current data + style::LineSpacing aLSp; + uno::Any aAny; + bool bRet = QueryValue( aAny, bConvert ? CONVERT_TWIPS : 0 ) && ( aAny >>= aLSp ); + + // get new data + switch ( nMemberId ) + { + case 0 : bRet = (rVal >>= aLSp); break; + case MID_LINESPACE : bRet = (rVal >>= aLSp.Mode); break; + case MID_HEIGHT : bRet = (rVal >>= aLSp.Height); break; + default: OSL_FAIL("Wrong MemberId!"); break; + } + + if( bRet ) + { + nLineHeight = aLSp.Height; + switch( aLSp.Mode ) + { + case style::LineSpacingMode::LEADING: + { + eInterLineSpaceRule = SvxInterLineSpaceRule::Fix; + eLineSpaceRule = SvxLineSpaceRule::Auto; + nInterLineSpace = aLSp.Height; + if(bConvert) + nInterLineSpace = o3tl::toTwips(nInterLineSpace, o3tl::Length::mm100); + + } + break; + case style::LineSpacingMode::PROP: + { + eLineSpaceRule = SvxLineSpaceRule::Auto; + nPropLineSpace = aLSp.Height; + if(100 == aLSp.Height) + eInterLineSpaceRule = SvxInterLineSpaceRule::Off; + else + eInterLineSpaceRule = SvxInterLineSpaceRule::Prop; + } + break; + case style::LineSpacingMode::FIX: + case style::LineSpacingMode::MINIMUM: + { + eInterLineSpaceRule = SvxInterLineSpaceRule::Off; + eLineSpaceRule = aLSp.Mode == style::LineSpacingMode::FIX ? SvxLineSpaceRule::Fix : SvxLineSpaceRule::Min; + nLineHeight = aLSp.Height; + if(bConvert) + nLineHeight = o3tl::toTwips(nLineHeight, o3tl::Length::mm100); + } + break; + } + } + + return bRet; +} + +SvxLineSpacingItem* SvxLineSpacingItem::Clone( SfxItemPool * ) const +{ + return new SvxLineSpacingItem( *this ); +} + +bool SvxLineSpacingItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + case SfxItemPresentation::Complete: + { + switch( GetLineSpaceRule() ) + { + case SvxLineSpaceRule::Auto: + { + SvxInterLineSpaceRule eInter = GetInterLineSpaceRule(); + + switch( eInter ) + { + // Default single line spacing + case SvxInterLineSpaceRule::Off: + rText = EditResId(RID_SVXITEMS_LINESPACING_SINGLE); + break; + + // Default single line spacing + case SvxInterLineSpaceRule::Prop: + if ( 100 == GetPropLineSpace() ) + { + rText = EditResId(RID_SVXITEMS_LINESPACING_SINGLE); + break; + } + // 1.15 line spacing + if ( 115 == GetPropLineSpace() ) + { + rText = EditResId(RID_SVXITEMS_LINESPACING_115); + break; + } + // 1.5 line spacing + if ( 150 == GetPropLineSpace() ) + { + rText = EditResId(RID_SVXITEMS_LINESPACING_15); + break; + } + // double line spacing + if ( 200 == GetPropLineSpace() ) + { + rText = EditResId(RID_SVXITEMS_LINESPACING_DOUBLE); + break; + } + // the set per cent value + rText = EditResId(RID_SVXITEMS_LINESPACING_PROPORTIONAL) + " " + OUString::number(GetPropLineSpace()) + "%"; + break; + + case SvxInterLineSpaceRule::Fix: + rText = EditResId(RID_SVXITEMS_LINESPACING_LEADING) + + " " + GetMetricText(GetInterLineSpace(), eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)); + break; + default: ;//prevent warning + } + } + break; + case SvxLineSpaceRule::Fix: + rText = EditResId(RID_SVXITEMS_LINESPACING_FIXED) + + " " + GetMetricText(GetLineHeight(), eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)); + break; + + case SvxLineSpaceRule::Min: + rText = EditResId(RID_SVXITEMS_LINESPACING_MIN) + + " " + GetMetricText(GetLineHeight(), eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)); + break; + default: ;//prevent warning + } + } + } + return true; +} + +sal_uInt16 SvxLineSpacingItem::GetValueCount() const +{ + return sal_uInt16(SvxSpecialLineSpace::End); // SvxSpecialLineSpace::TwoLines + 1 +} + + +sal_uInt16 SvxLineSpacingItem::GetEnumValue() const +{ + SvxSpecialLineSpace nVal; + switch ( nPropLineSpace ) + { + case 100: nVal = SvxSpecialLineSpace::OneLine; break; + case 150: nVal = SvxSpecialLineSpace::OnePointFiveLines; break; + case 200: nVal = SvxSpecialLineSpace::TwoLines; break; + default: nVal = SvxSpecialLineSpace::User; break; + } + return static_cast<sal_uInt16>(nVal); +} + + +void SvxLineSpacingItem::SetEnumValue( sal_uInt16 nVal ) +{ + switch ( static_cast<SvxSpecialLineSpace>(nVal) ) + { + case SvxSpecialLineSpace::OneLine: nPropLineSpace = 100; break; + case SvxSpecialLineSpace::OnePointFiveLines: nPropLineSpace = 150; break; + case SvxSpecialLineSpace::TwoLines: nPropLineSpace = 200; break; + default: break; + } +} + +// class SvxAdjustItem --------------------------------------------------- + +SvxAdjustItem::SvxAdjustItem(const SvxAdjust eAdjst, const sal_uInt16 nId ) + : SfxEnumItemInterface( nId ), + bOneBlock( false ), bLastCenter( false ), bLastBlock( false ) +{ + SetAdjust( eAdjst ); +} + + +bool SvxAdjustItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxAdjustItem& rItem = static_cast<const SvxAdjustItem&>(rAttr); + return GetAdjust() == rItem.GetAdjust() && + bOneBlock == rItem.bOneBlock && + bLastCenter == rItem.bLastCenter && + bLastBlock == rItem.bLastBlock; +} + +bool SvxAdjustItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_PARA_ADJUST : rVal <<= static_cast<sal_Int16>(GetAdjust()); break; + case MID_LAST_LINE_ADJUST : rVal <<= static_cast<sal_Int16>(GetLastBlock()); break; + case MID_EXPAND_SINGLE : + { + rVal <<= bOneBlock; + break; + } + default: ;//prevent warning + } + return true; +} + +bool SvxAdjustItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_PARA_ADJUST : + case MID_LAST_LINE_ADJUST : + { + sal_Int32 eVal = - 1; + ::cppu::enum2int(eVal,rVal); + if(eVal >= 0 && eVal <= 4) + { + SvxAdjust eAdjust = static_cast<SvxAdjust>(eVal); + if(MID_LAST_LINE_ADJUST == nMemberId && + eAdjust != SvxAdjust::Left && + eAdjust != SvxAdjust::Block && + eAdjust != SvxAdjust::Center) + return false; + nMemberId == MID_PARA_ADJUST ? SetAdjust(eAdjust) : SetLastBlock(eAdjust); + } + } + break; + case MID_EXPAND_SINGLE : + bOneBlock = Any2Bool(rVal); + break; + } + return true; +} + +SvxAdjustItem* SvxAdjustItem::Clone( SfxItemPool * ) const +{ + return new SvxAdjustItem( *this ); +} + +bool SvxAdjustItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + case SfxItemPresentation::Complete: + rText = GetValueTextByPos( static_cast<sal_uInt16>(GetAdjust()) ); + return true; + default: ;//prevent warning + } + return false; +} + + +sal_uInt16 SvxAdjustItem::GetValueCount() const +{ + return sal_uInt16(SvxAdjust::End); // SvxAdjust::BlockLine + 1 +} + +OUString SvxAdjustItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_ADJUST[] = + { + RID_SVXITEMS_ADJUST_LEFT, + RID_SVXITEMS_ADJUST_RIGHT, + RID_SVXITEMS_ADJUST_BLOCK, + RID_SVXITEMS_ADJUST_CENTER, + RID_SVXITEMS_ADJUST_BLOCKLINE + }; + static_assert(SAL_N_ELEMENTS(RID_SVXITEMS_ADJUST) - 1 == size_t(SvxAdjust::BlockLine), "unexpected size"); + assert(nPos <= sal_uInt16(SvxAdjust::BlockLine) && "enum overflow!"); + return EditResId(RID_SVXITEMS_ADJUST[nPos]); +} + +sal_uInt16 SvxAdjustItem::GetEnumValue() const +{ + return static_cast<sal_uInt16>(GetAdjust()); +} + + +void SvxAdjustItem::SetEnumValue( sal_uInt16 nVal ) +{ + SetAdjust( static_cast<SvxAdjust>(nVal) ); +} + + +// class SvxWidowsItem --------------------------------------------------- + +SvxWidowsItem::SvxWidowsItem(const sal_uInt8 nL, const sal_uInt16 nId ) : + SfxByteItem( nId, nL ) +{ +} + +SvxWidowsItem* SvxWidowsItem::Clone( SfxItemPool * ) const +{ + return new SvxWidowsItem( *this ); +} + +bool SvxWidowsItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + rText = EditResId(RID_SVXITEMS_LINES); + break; + } + + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_WIDOWS_COMPLETE) + " " + EditResId(RID_SVXITEMS_LINES); + break; + } + + default: + { + SAL_WARN( "editeng.items", "SvxWidowsItem::GetPresentation(): unknown SfxItemPresentation" ); + } + } + + rText = rText.replaceFirst( "%1", OUString::number( GetValue() ) ); + return true; +} + +// class SvxOrphansItem -------------------------------------------------- + +SvxOrphansItem::SvxOrphansItem(const sal_uInt8 nL, const sal_uInt16 nId ) : + SfxByteItem( nId, nL ) +{ +} + +SvxOrphansItem* SvxOrphansItem::Clone( SfxItemPool * ) const +{ + return new SvxOrphansItem( *this ); +} + +bool SvxOrphansItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + rText = EditResId(RID_SVXITEMS_LINES); + break; + } + + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_ORPHANS_COMPLETE) + " " + EditResId(RID_SVXITEMS_LINES); + break; + } + + default: + { + SAL_WARN( "editeng.items", "SvxOrphansItem::GetPresentation(): unknown SfxItemPresentation" ); + } + } + + rText = rText.replaceFirst( "%1", OUString::number( GetValue() ) ); + return true; +} + +// class SvxHyphenZoneItem ----------------------------------------------- + +SvxHyphenZoneItem::SvxHyphenZoneItem( const bool bHyph, const sal_uInt16 nId ) : + SfxPoolItem( nId ), + bHyphen(bHyph), + bPageEnd(true), + bNoCapsHyphenation(false), + bNoLastWordHyphenation(false), + nMinLead(0), + nMinTrail(0), + nMaxHyphens(255), + nMinWordLength(0), + nTextHyphenZone(0) +{ +} + + +bool SvxHyphenZoneItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_IS_HYPHEN: + rVal <<= bHyphen; + break; + case MID_HYPHEN_MIN_LEAD: + rVal <<= static_cast<sal_Int16>(nMinLead); + break; + case MID_HYPHEN_MIN_TRAIL: + rVal <<= static_cast<sal_Int16>(nMinTrail); + break; + case MID_HYPHEN_MAX_HYPHENS: + rVal <<= static_cast<sal_Int16>(nMaxHyphens); + break; + case MID_HYPHEN_NO_CAPS: + rVal <<= bNoCapsHyphenation; + break; + case MID_HYPHEN_NO_LAST_WORD: + rVal <<= bNoLastWordHyphenation; + break; + case MID_HYPHEN_MIN_WORD_LENGTH: + rVal <<= static_cast<sal_Int16>(nMinWordLength); + break; + case MID_HYPHEN_ZONE: + rVal <<= static_cast<sal_Int16>(nTextHyphenZone); + break; + } + return true; +} + +bool SvxHyphenZoneItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + sal_Int16 nNewVal = 0; + + if( nMemberId != MID_IS_HYPHEN && nMemberId != MID_HYPHEN_NO_CAPS && + nMemberId != MID_HYPHEN_NO_LAST_WORD ) + { + if(!(rVal >>= nNewVal)) + return false; + } + + switch(nMemberId) + { + case MID_IS_HYPHEN: + bHyphen = Any2Bool(rVal); + break; + case MID_HYPHEN_MIN_LEAD: + nMinLead = static_cast<sal_uInt8>(nNewVal); + break; + case MID_HYPHEN_MIN_TRAIL: + nMinTrail = static_cast<sal_uInt8>(nNewVal); + break; + case MID_HYPHEN_MAX_HYPHENS: + nMaxHyphens = static_cast<sal_uInt8>(nNewVal); + break; + case MID_HYPHEN_NO_CAPS: + bNoCapsHyphenation = Any2Bool(rVal); + break; + case MID_HYPHEN_NO_LAST_WORD: + bNoLastWordHyphenation = Any2Bool(rVal); + break; + case MID_HYPHEN_MIN_WORD_LENGTH: + nMinWordLength = static_cast<sal_uInt8>(nNewVal); + break; + case MID_HYPHEN_ZONE: + nTextHyphenZone = nNewVal; + break; + } + return true; +} + + +bool SvxHyphenZoneItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxHyphenZoneItem& rItem = static_cast<const SvxHyphenZoneItem&>(rAttr); + return ( rItem.bHyphen == bHyphen + && rItem.bNoCapsHyphenation == bNoCapsHyphenation + && rItem.bNoLastWordHyphenation == bNoLastWordHyphenation + && rItem.bPageEnd == bPageEnd + && rItem.nMinLead == nMinLead + && rItem.nMinTrail == nMinTrail + && rItem.nMaxHyphens == nMaxHyphens + && rItem.nMinWordLength == nMinWordLength + && rItem.nTextHyphenZone == nTextHyphenZone ); +} + +SvxHyphenZoneItem* SvxHyphenZoneItem::Clone( SfxItemPool * ) const +{ + return new SvxHyphenZoneItem( *this ); +} + +bool SvxHyphenZoneItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + OUString cpDelimTmp(cpDelim); + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + TranslateId pId = RID_SVXITEMS_HYPHEN_FALSE; + + if ( bHyphen ) + pId = RID_SVXITEMS_HYPHEN_TRUE; + rText = EditResId(pId) + cpDelimTmp; + pId = RID_SVXITEMS_PAGE_END_FALSE; + + if ( bPageEnd ) + pId = RID_SVXITEMS_PAGE_END_TRUE; + rText += EditResId(pId) + cpDelimTmp + + OUString::number( nMinLead ) + cpDelimTmp + + OUString::number( nMinTrail ) + cpDelimTmp + + OUString::number( nMaxHyphens ) + cpDelimTmp + + OUString::number( nMinWordLength ) + cpDelimTmp + + GetMetricText( nTextHyphenZone, eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + + if ( bNoCapsHyphenation ) + rText += cpDelimTmp + EditResId(RID_SVXITEMS_HYPHEN_NO_CAPS_TRUE); + + if ( bNoLastWordHyphenation ) + rText += cpDelimTmp + EditResId(RID_SVXITEMS_HYPHEN_LAST_WORD_TRUE); + + return true; + } + case SfxItemPresentation::Complete: + { + TranslateId pId = RID_SVXITEMS_HYPHEN_FALSE; + + if ( bHyphen ) + pId = RID_SVXITEMS_HYPHEN_TRUE; + rText = EditResId(pId) + cpDelimTmp; + pId = RID_SVXITEMS_PAGE_END_FALSE; + + if ( bPageEnd ) + pId = RID_SVXITEMS_PAGE_END_TRUE; + rText += EditResId(pId) + + cpDelimTmp + + EditResId(RID_SVXITEMS_HYPHEN_MINLEAD).replaceAll("%1", OUString::number(nMinLead)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_HYPHEN_MINTRAIL).replaceAll("%1", OUString::number(nMinTrail)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_HYPHEN_MAX).replaceAll("%1", OUString::number(nMaxHyphens)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_HYPHEN_MINWORDLEN).replaceAll("%1", OUString::number(nMinWordLength)); + + if ( nTextHyphenZone > 0 ) + { + rText += cpDelimTmp + EditResId(RID_SVXITEMS_HYPHEN_ZONE) + + GetMetricText( nTextHyphenZone, eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + + if ( bNoCapsHyphenation ) + rText += cpDelimTmp + EditResId(RID_SVXITEMS_HYPHEN_NO_CAPS_TRUE); + + if ( bNoLastWordHyphenation ) + rText += cpDelimTmp + EditResId(RID_SVXITEMS_HYPHEN_LAST_WORD_TRUE); + + return true; + } + default: ;//prevent warning + } + return false; +} + + +// class SvxTabStop ------------------------------------------------------ + +SvxTabStop::SvxTabStop() +{ + nTabPos = 0; + eAdjustment = SvxTabAdjust::Left; + m_cDecimal = cDfltDecimalChar; + cFill = cDfltFillChar; +} + + +SvxTabStop::SvxTabStop( const sal_Int32 nPos, const SvxTabAdjust eAdjst, + const sal_Unicode cDec, const sal_Unicode cFil ) +{ + nTabPos = nPos; + eAdjustment = eAdjst; + m_cDecimal = cDec; + cFill = cFil; +} + +void SvxTabStop::fillDecimal() const +{ + if ( cDfltDecimalChar == m_cDecimal ) + m_cDecimal = SvtSysLocale().GetLocaleData().getNumDecimalSep()[0]; +} + +void SvxTabStop::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxTabStop")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nTabPos"), + BAD_CAST(OString::number(nTabPos).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eAdjustment"), + BAD_CAST(OString::number(static_cast<int>(eAdjustment)).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxTabStopItem -------------------------------------------------- + +SvxTabStopItem::SvxTabStopItem( sal_uInt16 _nWhich ) : + SfxPoolItem( _nWhich ) +{ + const sal_uInt16 nTabs = SVX_TAB_DEFCOUNT, nDist = SVX_TAB_DEFDIST; + const SvxTabAdjust eAdjst= SvxTabAdjust::Default; + + for (sal_uInt16 i = 0; i < nTabs; ++i) + { + SvxTabStop aTab( (i + 1) * nDist, eAdjst ); + maTabStops.insert( aTab ); + } +} + + +SvxTabStopItem::SvxTabStopItem( const sal_uInt16 nTabs, + const sal_uInt16 nDist, + const SvxTabAdjust eAdjst, + sal_uInt16 _nWhich ) : + SfxPoolItem( _nWhich ) +{ + for ( sal_uInt16 i = 0; i < nTabs; ++i ) + { + SvxTabStop aTab( (i + 1) * nDist, eAdjst ); + maTabStops.insert( aTab ); + } +} + + +sal_uInt16 SvxTabStopItem::GetPos( const SvxTabStop& rTab ) const +{ + SvxTabStopArr::const_iterator it = maTabStops.find( rTab ); + return it != maTabStops.end() ? it - maTabStops.begin() : SVX_TAB_NOTFOUND; +} + + +sal_uInt16 SvxTabStopItem::GetPos( const sal_Int32 nPos ) const +{ + SvxTabStopArr::const_iterator it = maTabStops.find( SvxTabStop( nPos ) ); + return it != maTabStops.end() ? it - maTabStops.begin() : SVX_TAB_NOTFOUND; +} + + +bool SvxTabStopItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_TABSTOPS: + { + sal_uInt16 nCount = Count(); + uno::Sequence< style::TabStop> aSeq(nCount); + style::TabStop* pArr = aSeq.getArray(); + for(sal_uInt16 i = 0; i < nCount; i++) + { + const SvxTabStop& rTab = (*this)[i]; + pArr[i].Position = bConvert ? convertTwipToMm100(rTab.GetTabPos()) : rTab.GetTabPos(); + switch(rTab.GetAdjustment()) + { + case SvxTabAdjust::Left : pArr[i].Alignment = style::TabAlign_LEFT; break; + case SvxTabAdjust::Right : pArr[i].Alignment = style::TabAlign_RIGHT; break; + case SvxTabAdjust::Decimal: pArr[i].Alignment = style::TabAlign_DECIMAL; break; + case SvxTabAdjust::Center : pArr[i].Alignment = style::TabAlign_CENTER; break; + default: //SvxTabAdjust::Default + pArr[i].Alignment = style::TabAlign_DEFAULT; + + } + pArr[i].DecimalChar = rTab.GetDecimal(); + pArr[i].FillChar = rTab.GetFill(); + } + rVal <<= aSeq; + break; + } + case MID_STD_TAB: + { + const SvxTabStop &rTab = maTabStops.front(); + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(rTab.GetTabPos()) : rTab.GetTabPos()); + break; + } + } + return true; +} + +bool SvxTabStopItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_TABSTOPS: + { + uno::Sequence< style::TabStop> aSeq; + if(!(rVal >>= aSeq)) + { + uno::Sequence < uno::Sequence < uno::Any > > aAnySeq; + if (!(rVal >>= aAnySeq)) + return false; + auto aAnySeqRange = asNonConstRange(aAnySeq); + sal_Int32 nLength = aAnySeq.getLength(); + aSeq.realloc( nLength ); + auto pSeq = aSeq.getArray(); + for ( sal_Int32 n=0; n<nLength; n++ ) + { + uno::Sequence < uno::Any >& rAnySeq = aAnySeqRange[n]; + if ( rAnySeq.getLength() == 4 ) + { + if (!(rAnySeq[0] >>= pSeq[n].Position)) return false; + if (!(rAnySeq[1] >>= pSeq[n].Alignment)) + { + sal_Int32 nVal = 0; + if (rAnySeq[1] >>= nVal) + pSeq[n].Alignment = static_cast<css::style::TabAlign>(nVal); + else + return false; + } + if (!(rAnySeq[2] >>= pSeq[n].DecimalChar)) + { + OUString aVal; + if ( (rAnySeq[2] >>= aVal) && aVal.getLength() == 1 ) + pSeq[n].DecimalChar = aVal.toChar(); + else + return false; + } + if (!(rAnySeq[3] >>= pSeq[n].FillChar)) + { + OUString aVal; + if ( (rAnySeq[3] >>= aVal) && aVal.getLength() == 1 ) + pSeq[n].FillChar = aVal.toChar(); + else + return false; + } + } + else + return false; + } + } + + maTabStops.clear(); + const style::TabStop* pArr = aSeq.getConstArray(); + const sal_uInt16 nCount = static_cast<sal_uInt16>(aSeq.getLength()); + for(sal_uInt16 i = 0; i < nCount ; i++) + { + SvxTabAdjust eAdjust = SvxTabAdjust::Default; + switch(pArr[i].Alignment) + { + case style::TabAlign_LEFT : eAdjust = SvxTabAdjust::Left; break; + case style::TabAlign_CENTER : eAdjust = SvxTabAdjust::Center; break; + case style::TabAlign_RIGHT : eAdjust = SvxTabAdjust::Right; break; + case style::TabAlign_DECIMAL: eAdjust = SvxTabAdjust::Decimal; break; + default: ;//prevent warning + } + sal_Unicode cFill = pArr[i].FillChar; + sal_Unicode cDecimal = pArr[i].DecimalChar; + SvxTabStop aTab( bConvert ? o3tl::toTwips(pArr[i].Position, o3tl::Length::mm100) : pArr[i].Position, + eAdjust, + cDecimal, + cFill ); + Insert(aTab); + } + break; + } + case MID_STD_TAB: + { + sal_Int32 nNewPos = 0; + if (!(rVal >>= nNewPos) ) + return false; + if (bConvert) + nNewPos = o3tl::toTwips(nNewPos, o3tl::Length::mm100); + if (nNewPos <= 0) + return false; + const SvxTabStop& rTab = maTabStops.front(); + SvxTabStop aNewTab ( nNewPos, rTab.GetAdjustment(), rTab.GetDecimal(), rTab.GetFill() ); + Remove( 0 ); + Insert( aNewTab ); + break; + } + } + return true; +} + + +bool SvxTabStopItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxTabStopItem& rTSI = static_cast<const SvxTabStopItem&>(rAttr); + + if ( Count() != rTSI.Count() ) + return false; + + for ( sal_uInt16 i = 0; i < Count(); ++i ) + if( (*this)[i] != rTSI[i] ) + return false; + return true; +} + +SvxTabStopItem* SvxTabStopItem::Clone( SfxItemPool * ) const +{ + return new SvxTabStopItem( *this ); +} + +bool SvxTabStopItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + rText.clear(); + + bool bComma = false; + + for ( sal_uInt16 i = 0; i < Count(); ++i ) + { + if ( SvxTabAdjust::Default != ((*this)[i]).GetAdjustment() ) + { + if ( bComma ) + rText += ","; + rText += GetMetricText( + ((*this)[i]).GetTabPos(), eCoreUnit, ePresUnit, &rIntl ); + if ( SfxItemPresentation::Complete == ePres ) + { + rText += " " + EditResId(GetMetricId(ePresUnit)); + } + bComma = true; + } + } + return true; +} + + +bool SvxTabStopItem::Insert( const SvxTabStop& rTab ) +{ + sal_uInt16 nTabPos = GetPos(rTab); + if(SVX_TAB_NOTFOUND != nTabPos ) + Remove(nTabPos); + return maTabStops.insert( rTab ).second; +} + +void SvxTabStopItem::Insert( const SvxTabStopItem* pTabs ) +{ + for( sal_uInt16 i = 0; i < pTabs->Count(); i++ ) + { + const SvxTabStop& rTab = (*pTabs)[i]; + sal_uInt16 nTabPos = GetPos(rTab); + if(SVX_TAB_NOTFOUND != nTabPos) + Remove(nTabPos); + } + for( sal_uInt16 i = 0; i < pTabs->Count(); i++ ) + { + maTabStops.insert( (*pTabs)[i] ); + } +} + +void SvxTabStopItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxTabStopItem")); + for (const auto& rTabStop : maTabStops) + rTabStop.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxFormatSplitItem ------------------------------------------------- +SvxFormatSplitItem::~SvxFormatSplitItem() +{ +} + +SvxFormatSplitItem* SvxFormatSplitItem::Clone( SfxItemPool * ) const +{ + return new SvxFormatSplitItem( *this ); +} + +bool SvxFormatSplitItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + TranslateId pId = RID_SVXITEMS_FMTSPLIT_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_FMTSPLIT_TRUE; + rText = EditResId(pId); + return true; +} + +SvxPageModelItem* SvxPageModelItem::Clone( SfxItemPool* ) const +{ + return new SvxPageModelItem( *this ); +} + +bool SvxPageModelItem::QueryValue( css::uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + + switch ( nMemberId ) + { + case MID_AUTO: rVal <<= bAuto; break; + case MID_NAME: rVal <<= GetValue(); break; + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + return true; +} + +bool SvxPageModelItem::PutValue( const css::uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet; + OUString aStr; + switch ( nMemberId ) + { + case MID_AUTO: bRet = ( rVal >>= bAuto ); break; + case MID_NAME: bRet = ( rVal >>= aStr ); if ( bRet ) SetValue(aStr); break; + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + return bRet; +} + +bool SvxPageModelItem::operator==( const SfxPoolItem& rAttr ) const +{ + return SfxStringItem::operator==(rAttr) && + bAuto == static_cast<const SvxPageModelItem&>( rAttr ).bAuto; +} + +bool SvxPageModelItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + rText.clear(); + bool bSet = !GetValue().isEmpty(); + + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + if ( bSet ) + rText = GetValue(); + return true; + + case SfxItemPresentation::Complete: + if ( bSet ) + { + rText = EditResId(RID_SVXITEMS_PAGEMODEL_COMPLETE) + GetValue(); + } + return true; + default: ;//prevent warning + } + return false; +} + + +SvxScriptSpaceItem::SvxScriptSpaceItem( bool bOn, const sal_uInt16 nId ) + : SfxBoolItem( nId, bOn ) +{ +} + +SvxScriptSpaceItem* SvxScriptSpaceItem::Clone( SfxItemPool * ) const +{ + return new SvxScriptSpaceItem( *this ); +} + +bool SvxScriptSpaceItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& /*rIntl*/ ) const +{ + rText = EditResId( !GetValue() + ? RID_SVXITEMS_SCRPTSPC_OFF + : RID_SVXITEMS_SCRPTSPC_ON ); + return true; +} + + +SvxHangingPunctuationItem::SvxHangingPunctuationItem( + bool bOn, const sal_uInt16 nId ) + : SfxBoolItem( nId, bOn ) +{ +} + +SvxHangingPunctuationItem* SvxHangingPunctuationItem::Clone( SfxItemPool * ) const +{ + return new SvxHangingPunctuationItem( *this ); +} + +bool SvxHangingPunctuationItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& /*rIntl*/ ) const +{ + rText = EditResId( !GetValue() + ? RID_SVXITEMS_HNGPNCT_OFF + : RID_SVXITEMS_HNGPNCT_ON ); + return true; +} + + +SvxForbiddenRuleItem::SvxForbiddenRuleItem( + bool bOn, const sal_uInt16 nId ) + : SfxBoolItem( nId, bOn ) +{ +} + +SvxForbiddenRuleItem* SvxForbiddenRuleItem::Clone( SfxItemPool * ) const +{ + return new SvxForbiddenRuleItem( *this ); +} + +bool SvxForbiddenRuleItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& /*rIntl*/ ) const +{ + rText = EditResId( !GetValue() + ? RID_SVXITEMS_FORBIDDEN_RULE_OFF + : RID_SVXITEMS_FORBIDDEN_RULE_ON ); + return true; +} + +/************************************************************************* +|* class SvxParaVertAlignItem +*************************************************************************/ + +SvxParaVertAlignItem::SvxParaVertAlignItem( Align nValue, + const sal_uInt16 nW ) + : SfxUInt16Item( nW, static_cast<sal_uInt16>(nValue) ) +{ +} + +SvxParaVertAlignItem* SvxParaVertAlignItem::Clone( SfxItemPool* ) const +{ + return new SvxParaVertAlignItem( *this ); +} + +bool SvxParaVertAlignItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& ) const +{ + TranslateId pTmp; + switch( GetValue() ) + { + case Align::Automatic: pTmp = RID_SVXITEMS_PARAVERTALIGN_AUTO; break; + case Align::Top: pTmp = RID_SVXITEMS_PARAVERTALIGN_TOP; break; + case Align::Center: pTmp = RID_SVXITEMS_PARAVERTALIGN_CENTER; break; + case Align::Bottom: pTmp = RID_SVXITEMS_PARAVERTALIGN_BOTTOM; break; + default: pTmp = RID_SVXITEMS_PARAVERTALIGN_BASELINE; break; + } + rText = EditResId(pTmp); + return true; +} + +bool SvxParaVertAlignItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 /*nMemberId*/ ) const +{ + rVal <<= static_cast<sal_Int16>(GetValue()); + return true; +} + +bool SvxParaVertAlignItem::PutValue( const css::uno::Any& rVal, + sal_uInt8 /*nMemberId*/ ) +{ + sal_Int16 nVal = sal_Int16(); + if((rVal >>= nVal) && nVal >=0 && nVal <= sal_uInt16(Align::Bottom) ) + { + SetValue( static_cast<Align>(nVal) ); + return true; + } + else + return false; +} + +SvxParaGridItem::SvxParaGridItem( bool bOn, const sal_uInt16 nId ) + : SfxBoolItem( nId, bOn ) +{ +} + +SvxParaGridItem* SvxParaGridItem::Clone( SfxItemPool * ) const +{ + return new SvxParaGridItem( *this ); +} + +bool SvxParaGridItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& /*rIntl*/ ) const +{ + rText = GetValue() ? + EditResId( RID_SVXITEMS_PARASNAPTOGRID_ON ) : + EditResId( RID_SVXITEMS_PARASNAPTOGRID_OFF ); + + return true; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/svdfield.cxx b/editeng/source/items/svdfield.cxx new file mode 100644 index 000000000..a9b78148c --- /dev/null +++ b/editeng/source/items/svdfield.cxx @@ -0,0 +1,34 @@ +/* -*- 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 <editeng/measfld.hxx> + +SdrMeasureField::~SdrMeasureField() {} + +std::unique_ptr<SvxFieldData> SdrMeasureField::Clone() const +{ + return std::make_unique<SdrMeasureField>(*this); +} + +bool SdrMeasureField::operator==(const SvxFieldData& rSrc) const +{ + return eMeasureFieldKind == static_cast<const SdrMeasureField&>(rSrc).GetMeasureFieldKind(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/svxfont.cxx b/editeng/source/items/svxfont.cxx new file mode 100644 index 000000000..ca8c5f3fd --- /dev/null +++ b/editeng/source/items/svxfont.cxx @@ -0,0 +1,860 @@ +/* -*- 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 <editeng/svxfont.hxx> + +#include <vcl/glyphitemcache.hxx> +#include <vcl/metric.hxx> +#include <vcl/outdev.hxx> +#include <vcl/print.hxx> +#include <tools/debug.hxx> +#include <tools/gen.hxx> +#include <tools/poly.hxx> +#include <unotools/charclass.hxx> +#include <com/sun/star/i18n/KCharacterType.hpp> +#include <editeng/escapementitem.hxx> +#include <sal/log.hxx> +#include <limits> + +static tools::Long GetTextArray( const OutputDevice* pOut, const OUString& rStr, std::vector<sal_Int32>* pDXAry, + sal_Int32 nIndex, sal_Int32 nLen ) + +{ + const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(pOut, rStr, nIndex, nLen); + return pOut->GetTextArray( rStr, pDXAry, nIndex, nLen, nullptr, layoutGlyphs); +} + +SvxFont::SvxFont() +{ + nKern = nEsc = 0; + nPropr = 100; + eCaseMap = SvxCaseMap::NotMapped; + SetLanguage(LANGUAGE_SYSTEM); +} + +SvxFont::SvxFont( const vcl::Font &rFont ) + : Font( rFont ) +{ + nKern = nEsc = 0; + nPropr = 100; + eCaseMap = SvxCaseMap::NotMapped; + SetLanguage(LANGUAGE_SYSTEM); +} + +SvxFont::SvxFont( const SvxFont &rFont ) + : Font( rFont ) +{ + nKern = rFont.GetFixKerning(); + nEsc = rFont.GetEscapement(); + nPropr = rFont.GetPropr(); + eCaseMap = rFont.GetCaseMap(); + SetLanguage(rFont.GetLanguage()); +} + +void SvxFont::SetNonAutoEscapement(short nNewEsc, const OutputDevice* pOutDev) +{ + nEsc = nNewEsc; + if ( abs(nEsc) == DFLT_ESC_AUTO_SUPER ) + { + double fAutoAscent = .8; + double fAutoDescent = .2; + if ( pOutDev ) + { + const FontMetric& rFontMetric = pOutDev->GetFontMetric(); + double fFontHeight = rFontMetric.GetAscent() + rFontMetric.GetDescent(); + if ( fFontHeight ) + { + fAutoAscent = rFontMetric.GetAscent() / fFontHeight; + fAutoDescent = rFontMetric.GetDescent() / fFontHeight; + } + } + + if ( nEsc == DFLT_ESC_AUTO_SUPER ) + nEsc = fAutoAscent * (100 - nPropr); + else //DFLT_ESC_AUTO_SUB + nEsc = fAutoDescent * -(100 - nPropr); + } + + if ( nEsc > MAX_ESC_POS ) + nEsc = MAX_ESC_POS; + else if ( nEsc < -MAX_ESC_POS ) + nEsc = -MAX_ESC_POS; +} + +tools::Polygon SvxFont::DrawArrow( OutputDevice &rOut, const tools::Rectangle& rRect, + const Size& rSize, const Color& rCol, bool bLeftOrTop, bool bVertical ) +{ + tools::Polygon aPoly; + Point aTmp; + Point aNxt; + if (bVertical) + { + tools::Long nLeft = ((rRect.Left() + rRect.Right()) / 2) - (rSize.Height() / 2); + tools::Long nRight = ((rRect.Left() + rRect.Right()) / 2) + (rSize.Height() / 2); + tools::Long nMid = (rRect.Left() + rRect.Right()) / 2; + tools::Long nTop = ((rRect.Top() + rRect.Bottom()) / 2) - (rSize.Height() / 2); + tools::Long nBottom = nTop + rSize.Height(); + if (nTop < rRect.Top()) + { + if (bLeftOrTop) + { + nTop = rRect.Top(); + nBottom = rRect.Bottom(); + } + else + { + nTop = rRect.Bottom(); + nBottom = rRect.Bottom() - (rSize.Height() / 2); + } + } + aTmp.setX(nRight); + aTmp.setY(nBottom); + aNxt.setX(nMid); + aNxt.setY(nTop); + aPoly.Insert(0, aTmp); + aPoly.Insert(0, aNxt); + aTmp.setX(nLeft); + aPoly.Insert(0, aTmp); + } + else + { + tools::Long nLeft = (rRect.Left() + rRect.Right() - rSize.Width()) / 2; + tools::Long nRight = nLeft + rSize.Width(); + tools::Long nMid = (rRect.Top() + rRect.Bottom()) / 2; + tools::Long nTop = nMid - rSize.Height() / 2; + tools::Long nBottom = nTop + rSize.Height(); + if (nLeft < rRect.Left()) + { + nLeft = rRect.Left(); + nRight = rRect.Right(); + } + aTmp.setX(bLeftOrTop ? nLeft : nRight); + aTmp.setY(nMid); + aNxt.setX(bLeftOrTop ? nRight : nLeft); + aNxt.setY(nTop); + aPoly.Insert(0, aTmp); + aPoly.Insert(0, aNxt); + aNxt.setY(nBottom); + aPoly.Insert(0, aNxt); + } + Color aOldLineColor = rOut.GetLineColor(); + Color aOldFillColor = rOut.GetFillColor(); + rOut.SetFillColor( rCol ); + rOut.SetLineColor( COL_BLACK ); + rOut.DrawPolygon( aPoly ); + rOut.DrawLine( aTmp, aNxt ); + rOut.SetLineColor( aOldLineColor ); + rOut.SetFillColor( aOldFillColor ); + return aPoly; +} + +OUString SvxFont::CalcCaseMap(const OUString &rTxt) const +{ + if (!IsCaseMap() || rTxt.isEmpty()) + return rTxt; + OUString aTxt(rTxt); + // I still have to get the language + const LanguageType eLang = LANGUAGE_DONTKNOW == GetLanguage() + ? LANGUAGE_SYSTEM : GetLanguage(); + + CharClass aCharClass(( LanguageTag(eLang) )); + + switch( eCaseMap ) + { + case SvxCaseMap::SmallCaps: + case SvxCaseMap::Uppercase: + { + aTxt = aCharClass.uppercase( aTxt ); + break; + } + + case SvxCaseMap::Lowercase: + { + aTxt = aCharClass.lowercase( aTxt ); + break; + } + case SvxCaseMap::Capitalize: + { + // Every beginning of a word is capitalized, the rest of the word + // is taken over as is. + // Bug: if the attribute starts in the middle of the word. + bool bBlank = true; + + for (sal_Int32 i = 0; i < aTxt.getLength(); ++i) + { + if( aTxt[i] == ' ' || aTxt[i] == '\t') + bBlank = true; + else + { + if (bBlank) + { + OUString sTitle(aCharClass.uppercase(OUString(aTxt[i]))); + aTxt = aTxt.replaceAt(i, 1, sTitle); + } + bBlank = false; + } + } + break; + } + default: + { + SAL_WARN( "editeng", "SvxFont::CaseMapTxt: unknown casemap"); + break; + } + } + return aTxt; +} + +/************************************************************************* + * class SvxDoCapitals + * The virtual Method Do si called by SvxFont::DoOnCapitals alternately + * the uppercase and lowercase parts. The derivate of SvxDoCapitals fills + * this method with life. + *************************************************************************/ + +class SvxDoCapitals +{ +protected: + VclPtr<OutputDevice> pOut; + const OUString &rTxt; + const sal_Int32 nIdx; + const sal_Int32 nLen; + +public: + SvxDoCapitals( OutputDevice *_pOut, const OUString &_rTxt, + const sal_Int32 _nIdx, const sal_Int32 _nLen ) + : pOut(_pOut), rTxt(_rTxt), nIdx(_nIdx), nLen(_nLen) + { } + + virtual ~SvxDoCapitals() {} + + virtual void DoSpace( const bool bDraw ); + virtual void SetSpace(); + virtual void Do( const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen, + const bool bUpper ) = 0; + + const OUString &GetTxt() const { return rTxt; } + sal_Int32 GetIdx() const { return nIdx; } + sal_Int32 GetLen() const { return nLen; } +}; + +void SvxDoCapitals::DoSpace( const bool /*bDraw*/ ) { } + +void SvxDoCapitals::SetSpace() { } + +/************************************************************************* + * SvxFont::DoOnCapitals() const + * Decomposes the String into uppercase and lowercase letters and then + * calls the method SvxDoCapitals::Do( ). + *************************************************************************/ + +void SvxFont::DoOnCapitals(SvxDoCapitals &rDo) const +{ + const OUString &rTxt = rDo.GetTxt(); + const sal_Int32 nIdx = rDo.GetIdx(); + const sal_Int32 nLen = rDo.GetLen(); + + const OUString aTxt( CalcCaseMap( rTxt ) ); + const sal_Int32 nTxtLen = std::min( rTxt.getLength(), nLen ); + sal_Int32 nPos = 0; + sal_Int32 nOldPos = nPos; + + // Test if string length differ between original and CaseMapped + bool bCaseMapLengthDiffers(aTxt.getLength() != rTxt.getLength()); + + const LanguageType eLang = LANGUAGE_DONTKNOW == GetLanguage() + ? LANGUAGE_SYSTEM : GetLanguage(); + + CharClass aCharClass(( LanguageTag(eLang) )); + OUString aCharString; + + while( nPos < nTxtLen ) + { + // first in turn are the uppercase letters + + // There are characters that are both upper- and lower-case L (eg blank) + // Such ambiguities lead to chaos, this is why these characters are + // allocated to the lowercase characters! + + while( nPos < nTxtLen ) + { + aCharString = rTxt.copy( nPos + nIdx, 1 ); + sal_Int32 nCharacterType = aCharClass.getCharacterType( aCharString, 0 ); + if ( nCharacterType & css::i18n::KCharacterType::LOWER ) + break; + if ( ! ( nCharacterType & css::i18n::KCharacterType::UPPER ) ) + break; + ++nPos; + } + if( nOldPos != nPos ) + { + if(bCaseMapLengthDiffers) + { + // If strings differ work preparing the necessary snippet to address that + // potential difference + const OUString aSnippet = rTxt.copy(nIdx + nOldPos, nPos-nOldPos); + OUString aNewText = CalcCaseMap(aSnippet); + + rDo.Do( aNewText, 0, aNewText.getLength(), true ); + } + else + { + rDo.Do( aTxt, nIdx + nOldPos, nPos-nOldPos, true ); + } + + nOldPos = nPos; + } + // Now the lowercase are processed (without blanks) + while( nPos < nTxtLen ) + { + sal_uInt32 nCharacterType = aCharClass.getCharacterType( aCharString, 0 ); + if ( nCharacterType & css::i18n::KCharacterType::UPPER ) + break; + if ( aCharString == " " ) + break; + if( ++nPos < nTxtLen ) + aCharString = rTxt.copy( nPos + nIdx, 1 ); + } + if( nOldPos != nPos ) + { + if(bCaseMapLengthDiffers) + { + // If strings differ work preparing the necessary snippet to address that + // potential difference + const OUString aSnippet = rTxt.copy(nIdx + nOldPos, nPos - nOldPos); + OUString aNewText = CalcCaseMap(aSnippet); + + rDo.Do( aNewText, 0, aNewText.getLength(), false ); + } + else + { + rDo.Do( aTxt, nIdx + nOldPos, nPos-nOldPos, false ); + } + + nOldPos = nPos; + } + // Now the blanks are<processed + while( nPos < nTxtLen && aCharString == " " && ++nPos < nTxtLen ) + aCharString = rTxt.copy( nPos + nIdx, 1 ); + + if( nOldPos != nPos ) + { + rDo.DoSpace( false ); + + if(bCaseMapLengthDiffers) + { + // If strings differ work preparing the necessary snippet to address that + // potential difference + const OUString aSnippet = rTxt.copy(nIdx + nOldPos, nPos - nOldPos); + OUString aNewText = CalcCaseMap(aSnippet); + + rDo.Do( aNewText, 0, aNewText.getLength(), false ); + } + else + { + rDo.Do( aTxt, nIdx + nOldPos, nPos - nOldPos, false ); + } + + nOldPos = nPos; + rDo.SetSpace(); + } + } + rDo.DoSpace( true ); +} + + +void SvxFont::SetPhysFont(OutputDevice& rOut) const +{ + const vcl::Font& rCurrentFont = rOut.GetFont(); + if ( nPropr == 100 ) + { + if ( !rCurrentFont.IsSameInstance( *this ) ) + rOut.SetFont( *this ); + } + else + { + Font aNewFont( *this ); + Size aSize( aNewFont.GetFontSize() ); + aNewFont.SetFontSize( Size( aSize.Width() * nPropr / 100, + aSize.Height() * nPropr / 100 ) ); + if ( !rCurrentFont.IsSameInstance( aNewFont ) ) + rOut.SetFont( aNewFont ); + } +} + +vcl::Font SvxFont::ChgPhysFont(OutputDevice& rOut) const +{ + vcl::Font aOldFont(rOut.GetFont()); + SetPhysFont(rOut); + return aOldFont; +} + +Size SvxFont::GetPhysTxtSize( const OutputDevice *pOut, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen ) const +{ + if ( !IsCaseMap() && !IsKern() ) + return Size( pOut->GetTextWidth( rTxt, nIdx, nLen ), + pOut->GetTextHeight() ); + + Size aTxtSize; + aTxtSize.setHeight( pOut->GetTextHeight() ); + if ( !IsCaseMap() ) + aTxtSize.setWidth( pOut->GetTextWidth( rTxt, nIdx, nLen ) ); + else + { + const OUString aNewText = CalcCaseMap(rTxt); + bool bCaseMapLengthDiffers(aNewText.getLength() != rTxt.getLength()); + sal_Int32 nWidth(0); + + if(bCaseMapLengthDiffers) + { + // If strings differ work preparing the necessary snippet to address that + // potential difference + const OUString aSnippet = rTxt.copy(nIdx, nLen); + OUString _aNewText = CalcCaseMap(aSnippet); + nWidth = pOut->GetTextWidth( _aNewText, 0, _aNewText.getLength() ); + } + else + { + nWidth = pOut->GetTextWidth( aNewText, nIdx, nLen ); + } + + aTxtSize.setWidth(nWidth); + } + + if( IsKern() && ( nLen > 1 ) ) + { + std::vector<sal_Int32> aDXArray(nLen); + GetTextArray(pOut, rTxt, &aDXArray, nIdx, nLen); + tools::Long nOldValue = aDXArray[0]; + sal_Int32 nSpaceCount = 0; + for(sal_Int32 i = 1; i < nLen; ++i) + { + if (aDXArray[i] != nOldValue) + { + nOldValue = aDXArray[i]; + ++nSpaceCount; + } + } + aTxtSize.AdjustWidth( nSpaceCount * tools::Long( nKern ) ); + } + + return aTxtSize; +} + +Size SvxFont::GetPhysTxtSize( const OutputDevice *pOut ) +{ + if ( !IsCaseMap() && !IsKern() ) + return Size( pOut->GetTextWidth( "" ), pOut->GetTextHeight() ); + + Size aTxtSize; + aTxtSize.setHeight( pOut->GetTextHeight() ); + if ( !IsCaseMap() ) + aTxtSize.setWidth( pOut->GetTextWidth( "" ) ); + else + aTxtSize.setWidth( pOut->GetTextWidth( CalcCaseMap( "" ) ) ); + + return aTxtSize; +} + +Size SvxFont::QuickGetTextSize( const OutputDevice *pOut, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen, std::vector<sal_Int32>* pDXArray ) const +{ + if ( !IsCaseMap() && !IsKern() ) + return Size( GetTextArray( pOut, rTxt, pDXArray, nIdx, nLen ), + pOut->GetTextHeight() ); + + std::vector<sal_Int32> aDXArray; + + // We always need pDXArray to count the number of kern spaces + if (!pDXArray && IsKern() && nLen > 1) + { + pDXArray = &aDXArray; + aDXArray.reserve(nLen); + } + + Size aTxtSize; + aTxtSize.setHeight( pOut->GetTextHeight() ); + if ( !IsCaseMap() ) + aTxtSize.setWidth( GetTextArray( pOut, rTxt, pDXArray, nIdx, nLen ) ); + else + aTxtSize.setWidth( GetTextArray( pOut, CalcCaseMap( rTxt ), + pDXArray, nIdx, nLen ) ); + + if( IsKern() && ( nLen > 1 ) ) + { + tools::Long nOldValue = (*pDXArray)[0]; + tools::Long nSpaceSum = nKern; + (*pDXArray)[0] += nSpaceSum; + + for ( sal_Int32 i = 1; i < nLen; i++ ) + { + if ( (*pDXArray)[i] != nOldValue ) + { + nOldValue = (*pDXArray)[i]; + nSpaceSum += nKern; + } + (*pDXArray)[i] += nSpaceSum; + } + + // The last one is a nKern too big: + nOldValue = (*pDXArray)[nLen - 1]; + tools::Long nNewValue = nOldValue - nKern; + for ( sal_Int32 i = nLen - 1; i >= 0 && (*pDXArray)[i] == nOldValue; --i) + (*pDXArray)[i] = nNewValue; + + aTxtSize.AdjustWidth(nSpaceSum - nKern); + } + + return aTxtSize; +} + +Size SvxFont::GetTextSize(const OutputDevice& rOut, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen) const +{ + sal_Int32 nTmp = nLen; + if ( nTmp == SAL_MAX_INT32 ) // already initialized? + nTmp = rTxt.getLength(); + Font aOldFont( ChgPhysFont(const_cast<OutputDevice&>(rOut))); + Size aTxtSize; + if( IsCapital() && !rTxt.isEmpty() ) + { + aTxtSize = GetCapitalSize(&rOut, rTxt, nIdx, nTmp); + } + else aTxtSize = GetPhysTxtSize(&rOut,rTxt,nIdx,nTmp); + const_cast<OutputDevice&>(rOut).SetFont(aOldFont); + return aTxtSize; +} + +static void DrawTextArray( OutputDevice* pOut, const Point& rStartPt, const OUString& rStr, + o3tl::span<const sal_Int32> pDXAry, + sal_Int32 nIndex, sal_Int32 nLen ) +{ + const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(pOut, rStr, nIndex, nLen); + pOut->DrawTextArray(rStartPt, rStr, pDXAry, nIndex, nLen, SalLayoutFlags::NONE, layoutGlyphs); +} + +void SvxFont::QuickDrawText( OutputDevice *pOut, + const Point &rPos, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen, o3tl::span<const sal_Int32> pDXArray ) const +{ + + // Font has to be selected in OutputDevice... + if ( !IsCaseMap() && !IsCapital() && !IsKern() && !IsEsc() ) + { + DrawTextArray( pOut, rPos, rTxt, pDXArray, nIdx, nLen ); + return; + } + + Point aPos( rPos ); + + if ( nEsc ) + { + tools::Long nDiff = GetFontSize().Height(); + nDiff *= nEsc; + nDiff /= 100; + + if ( !IsVertical() ) + aPos.AdjustY( -nDiff ); + else + aPos.AdjustX(nDiff ); + } + + if( IsCapital() ) + { + DBG_ASSERT( pDXArray.empty(), "DrawCapital not for TextArray!" ); + DrawCapital( pOut, aPos, rTxt, nIdx, nLen ); + } + else + { + if ( IsKern() && pDXArray.empty() ) + { + Size aSize = GetPhysTxtSize( pOut, rTxt, nIdx, nLen ); + + if ( !IsCaseMap() ) + pOut->DrawStretchText( aPos, aSize.Width(), rTxt, nIdx, nLen ); + else + pOut->DrawStretchText( aPos, aSize.Width(), CalcCaseMap( rTxt ), nIdx, nLen ); + } + else + { + if ( !IsCaseMap() ) + DrawTextArray( pOut, aPos, rTxt, pDXArray, nIdx, nLen ); + else + DrawTextArray( pOut, aPos, CalcCaseMap( rTxt ), pDXArray, nIdx, nLen ); + } + } +} + + +void SvxFont::DrawPrev( OutputDevice *pOut, Printer* pPrinter, + const Point &rPos, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen ) const +{ + if ( !nLen || rTxt.isEmpty() ) + return; + sal_Int32 nTmp = nLen; + + if ( nTmp == SAL_MAX_INT32 ) // already initialized? + nTmp = rTxt.getLength(); + Point aPos( rPos ); + + if ( nEsc ) + { + short nTmpEsc; + if( DFLT_ESC_AUTO_SUPER == nEsc ) + { + nTmpEsc = .8 * (100 - nPropr); + assert (nTmpEsc == DFLT_ESC_SUPER && "I'm sure this formula needs to be changed, but how to confirm that???"); + nTmpEsc = DFLT_ESC_SUPER; + } + else if( DFLT_ESC_AUTO_SUB == nEsc ) + { + nTmpEsc = .2 * -(100 - nPropr); + assert (nTmpEsc == -20 && "I'm sure this formula needs to be changed, but how to confirm that???"); + nTmpEsc = -20; + } + else + nTmpEsc = nEsc; + Size aSize = GetFontSize(); + aPos.AdjustY( -(( nTmpEsc * aSize.Height() ) / 100) ); + } + Font aOldFont( ChgPhysFont(*pOut) ); + Font aOldPrnFont( ChgPhysFont(*pPrinter) ); + + if ( IsCapital() ) + DrawCapital( pOut, aPos, rTxt, nIdx, nTmp ); + else + { + Size aSize = GetPhysTxtSize( pPrinter, rTxt, nIdx, nTmp ); + + if ( !IsCaseMap() ) + pOut->DrawStretchText( aPos, aSize.Width(), rTxt, nIdx, nTmp ); + else + { + const OUString aNewText = CalcCaseMap(rTxt); + bool bCaseMapLengthDiffers(aNewText.getLength() != rTxt.getLength()); + + if(bCaseMapLengthDiffers) + { + // If strings differ work preparing the necessary snippet to address that + // potential difference + const OUString aSnippet(rTxt.copy( nIdx, nTmp)); + OUString _aNewText = CalcCaseMap(aSnippet); + + pOut->DrawStretchText( aPos, aSize.Width(), _aNewText, 0, _aNewText.getLength() ); + } + else + { + pOut->DrawStretchText( aPos, aSize.Width(), CalcCaseMap( rTxt ), nIdx, nTmp ); + } + } + } + pOut->SetFont(aOldFont); + pPrinter->SetFont( aOldPrnFont ); +} + + +SvxFont& SvxFont::operator=( const vcl::Font& rFont ) +{ + Font::operator=( rFont ); + return *this; +} + +SvxFont& SvxFont::operator=( const SvxFont& rFont ) +{ + Font::operator=( rFont ); + eCaseMap = rFont.eCaseMap; + nEsc = rFont.nEsc; + nPropr = rFont.nPropr; + nKern = rFont.nKern; + return *this; +} + +namespace { + +class SvxDoGetCapitalSize : public SvxDoCapitals +{ +protected: + SvxFont* pFont; + Size aTxtSize; + short nKern; +public: + SvxDoGetCapitalSize( SvxFont *_pFnt, const OutputDevice *_pOut, + const OUString &_rTxt, const sal_Int32 _nIdx, + const sal_Int32 _nLen, const short _nKrn ) + : SvxDoCapitals( const_cast<OutputDevice*>(_pOut), _rTxt, _nIdx, _nLen ), + pFont( _pFnt ), + nKern( _nKrn ) + { } + + virtual void Do( const OUString &rTxt, const sal_Int32 nIdx, + const sal_Int32 nLen, const bool bUpper ) override; + + const Size &GetSize() const { return aTxtSize; }; +}; + +} + +void SvxDoGetCapitalSize::Do( const OUString &_rTxt, const sal_Int32 _nIdx, + const sal_Int32 _nLen, const bool bUpper ) +{ + Size aPartSize; + if ( !bUpper ) + { + sal_uInt8 nProp = pFont->GetPropr(); + pFont->SetProprRel( SMALL_CAPS_PERCENTAGE ); + pFont->SetPhysFont( *pOut ); + aPartSize.setWidth( pOut->GetTextWidth( _rTxt, _nIdx, _nLen ) ); + aPartSize.setHeight( pOut->GetTextHeight() ); + aTxtSize.setHeight( aPartSize.Height() ); + pFont->SetPropr( nProp ); + pFont->SetPhysFont( *pOut ); + } + else + { + aPartSize.setWidth( pOut->GetTextWidth( _rTxt, _nIdx, _nLen ) ); + aPartSize.setHeight( pOut->GetTextHeight() ); + } + aTxtSize.AdjustWidth(aPartSize.Width() ); + aTxtSize.AdjustWidth( _nLen * tools::Long( nKern ) ); +} + +Size SvxFont::GetCapitalSize( const OutputDevice *pOut, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen) const +{ + // Start: + SvxDoGetCapitalSize aDo( const_cast<SvxFont *>(this), pOut, rTxt, nIdx, nLen, nKern ); + DoOnCapitals( aDo ); + Size aTxtSize( aDo.GetSize() ); + + // End: + if( !aTxtSize.Height() ) + { + aTxtSize.setWidth( 0 ); + aTxtSize.setHeight( pOut->GetTextHeight() ); + } + return aTxtSize; +} + +namespace { + +class SvxDoDrawCapital : public SvxDoCapitals +{ +protected: + SvxFont *pFont; + Point aPos; + Point aSpacePos; + short nKern; +public: + SvxDoDrawCapital( SvxFont *pFnt, OutputDevice *_pOut, const OUString &_rTxt, + const sal_Int32 _nIdx, const sal_Int32 _nLen, + const Point &rPos, const short nKrn ) + : SvxDoCapitals( _pOut, _rTxt, _nIdx, _nLen ), + pFont( pFnt ), + aPos( rPos ), + aSpacePos( rPos ), + nKern( nKrn ) + { } + virtual void DoSpace( const bool bDraw ) override; + virtual void SetSpace() override; + virtual void Do( const OUString &rTxt, const sal_Int32 nIdx, + const sal_Int32 nLen, const bool bUpper ) override; +}; + +} + +void SvxDoDrawCapital::DoSpace( const bool bDraw ) +{ + if ( !(bDraw || pFont->IsWordLineMode()) ) + return; + + sal_uLong nDiff = static_cast<sal_uLong>(aPos.X() - aSpacePos.X()); + if ( nDiff ) + { + bool bWordWise = pFont->IsWordLineMode(); + bool bTrans = pFont->IsTransparent(); + pFont->SetWordLineMode( false ); + pFont->SetTransparent( true ); + pFont->SetPhysFont(*pOut); + pOut->DrawStretchText( aSpacePos, nDiff, " ", 0, 2 ); + pFont->SetWordLineMode( bWordWise ); + pFont->SetTransparent( bTrans ); + pFont->SetPhysFont(*pOut); + } +} + +void SvxDoDrawCapital::SetSpace() +{ + if ( pFont->IsWordLineMode() ) + aSpacePos.setX( aPos.X() ); +} + +void SvxDoDrawCapital::Do( const OUString &_rTxt, const sal_Int32 _nIdx, + const sal_Int32 _nLen, const bool bUpper) +{ + sal_uInt8 nProp = 0; + Size aPartSize; + + // Set the desired font + FontLineStyle eUnder = pFont->GetUnderline(); + FontStrikeout eStrike = pFont->GetStrikeout(); + pFont->SetUnderline( LINESTYLE_NONE ); + pFont->SetStrikeout( STRIKEOUT_NONE ); + if ( !bUpper ) + { + nProp = pFont->GetPropr(); + pFont->SetProprRel( SMALL_CAPS_PERCENTAGE ); + } + pFont->SetPhysFont(*pOut); + + aPartSize.setWidth( pOut->GetTextWidth( _rTxt, _nIdx, _nLen ) ); + aPartSize.setHeight( pOut->GetTextHeight() ); + tools::Long nWidth = aPartSize.Width(); + if ( nKern ) + { + aPos.AdjustX(nKern/2); + if ( _nLen ) nWidth += (_nLen*tools::Long(nKern)); + } + pOut->DrawStretchText(aPos,nWidth-nKern,_rTxt,_nIdx,_nLen); + + // Restore Font + pFont->SetUnderline( eUnder ); + pFont->SetStrikeout( eStrike ); + if ( !bUpper ) + pFont->SetPropr( nProp ); + pFont->SetPhysFont(*pOut); + + aPos.AdjustX(nWidth-(nKern/2) ); +} + +/************************************************************************* + * SvxFont::DrawCapital() draws the uppercase letter. + *************************************************************************/ + +void SvxFont::DrawCapital( OutputDevice *pOut, + const Point &rPos, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen ) const +{ + SvxDoDrawCapital aDo( const_cast<SvxFont *>(this),pOut,rTxt,nIdx,nLen,rPos,nKern ); + DoOnCapitals( aDo ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/textitem.cxx b/editeng/source/items/textitem.cxx new file mode 100644 index 000000000..147fa301d --- /dev/null +++ b/editeng/source/items/textitem.cxx @@ -0,0 +1,2774 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/style/CaseMap.hpp> +#include <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/frame/status/FontHeight.hpp> +#include <math.h> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <unotools/fontdefs.hxx> +#include <unotools/intlwrapper.hxx> +#include <unotools/syslocale.hxx> +#include <utility> +#include <vcl/outdev.hxx> +#include <vcl/unohelp.hxx> +#include <svtools/unitconv.hxx> + +#include <editeng/editids.hrc> +#include <editeng/editrids.hrc> +#include <tools/bigint.hxx> +#include <tools/mapunit.hxx> +#include <tools/UnitConversion.hxx> + +#include <rtl/math.hxx> +#include <rtl/ustring.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <svl/itemset.hxx> + +#include <svtools/langtab.hxx> +#include <svl/itempool.hxx> +#include <svtools/ctrltool.hxx> +#include <com/sun/star/awt/FontSlant.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/text/FontEmphasis.hpp> +#include <editeng/rsiditem.hxx> +#include <editeng/memberids.h> +#include <editeng/flstitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/nhypitem.hxx> +#include <editeng/blinkitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/twolinesitem.hxx> +#include <editeng/scripttypeitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/itemtype.hxx> +#include <editeng/eerdll.hxx> +#include <libxml/xmlwriter.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::text; + +SfxPoolItem* SvxFontItem::CreateDefault() {return new SvxFontItem(0);} +SfxPoolItem* SvxPostureItem::CreateDefault() { return new SvxPostureItem(ITALIC_NONE, 0);} +SfxPoolItem* SvxWeightItem::CreateDefault() {return new SvxWeightItem(WEIGHT_NORMAL, 0);} +SfxPoolItem* SvxFontHeightItem::CreateDefault() {return new SvxFontHeightItem(240, 100, 0);} +SfxPoolItem* SvxUnderlineItem::CreateDefault() {return new SvxUnderlineItem(LINESTYLE_NONE, 0);} +SfxPoolItem* SvxOverlineItem::CreateDefault() {return new SvxOverlineItem(LINESTYLE_NONE, 0);} +SfxPoolItem* SvxCrossedOutItem::CreateDefault() {return new SvxCrossedOutItem(STRIKEOUT_NONE, 0);} +SfxPoolItem* SvxShadowedItem::CreateDefault() {return new SvxShadowedItem(false, 0);} +SfxPoolItem* SvxAutoKernItem::CreateDefault() {return new SvxAutoKernItem(false, 0);} +SfxPoolItem* SvxWordLineModeItem::CreateDefault() {return new SvxWordLineModeItem(false, 0);} +SfxPoolItem* SvxContourItem::CreateDefault() {return new SvxContourItem(false, 0);} +SfxPoolItem* SvxColorItem::CreateDefault() {return new SvxColorItem(0);} +SfxPoolItem* SvxKerningItem::CreateDefault() {return new SvxKerningItem(0, 0);} +SfxPoolItem* SvxCaseMapItem::CreateDefault() {return new SvxCaseMapItem(SvxCaseMap::NotMapped, 0);} +SfxPoolItem* SvxEscapementItem::CreateDefault() {return new SvxEscapementItem(0);} +SfxPoolItem* SvxLanguageItem::CreateDefault() {return new SvxLanguageItem(LANGUAGE_GERMAN, 0);} +SfxPoolItem* SvxEmphasisMarkItem::CreateDefault() {return new SvxEmphasisMarkItem(FontEmphasisMark::NONE, 0);} +SfxPoolItem* SvxCharRotateItem::CreateDefault() {return new SvxCharRotateItem(0_deg10, false, 0);} +SfxPoolItem* SvxCharScaleWidthItem::CreateDefault() {return new SvxCharScaleWidthItem(100, 0);} +SfxPoolItem* SvxCharReliefItem::CreateDefault() {return new SvxCharReliefItem(FontRelief::NONE, 0);} + + +// class SvxFontListItem ------------------------------------------------- + +SvxFontListItem::SvxFontListItem( const FontList* pFontLst, + const sal_uInt16 nId ) : + SfxPoolItem( nId ), + pFontList( pFontLst ) +{ + if ( pFontList ) + { + sal_Int32 nCount = pFontList->GetFontNameCount(); + aFontNameSeq.realloc( nCount ); + auto pFontNameSeq = aFontNameSeq.getArray(); + + for ( sal_Int32 i = 0; i < nCount; i++ ) + pFontNameSeq[i] = pFontList->GetFontName(i).GetFamilyName(); + } +} + +SvxFontListItem* SvxFontListItem::Clone( SfxItemPool* ) const +{ + return new SvxFontListItem( *this ); +} + +bool SvxFontListItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return( pFontList == static_cast<const SvxFontListItem&>(rAttr).pFontList ); +} + +bool SvxFontListItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + rVal <<= aFontNameSeq; + return true; +} + + +bool SvxFontListItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText.clear(); + return false; +} + +// class SvxFontItem ----------------------------------------------------- + +namespace +{ +sal_Int32 CompareTo(sal_Int32 nA, sal_Int32 nB) +{ + if (nA < nB) + { + return -1; + } + + if (nA > nB) + { + return 1; + } + + return 0; +} +} + +SvxFontItem::SvxFontItem( const sal_uInt16 nId ) : + SfxPoolItem( nId ) +{ + eFamily = FAMILY_SWISS; + ePitch = PITCH_VARIABLE; + eTextEncoding = RTL_TEXTENCODING_DONTKNOW; +} + + +SvxFontItem::SvxFontItem( const FontFamily eFam, OUString aName, + OUString aStName, const FontPitch eFontPitch, + const rtl_TextEncoding eFontTextEncoding, const sal_uInt16 nId ) : + + SfxPoolItem( nId ), + + aFamilyName(std::move(aName)), + aStyleName(std::move(aStName)) +{ + eFamily = eFam; + ePitch = eFontPitch; + eTextEncoding = eFontTextEncoding; +} + + +bool SvxFontItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case 0: + { + css::awt::FontDescriptor aFontDescriptor; + aFontDescriptor.Name = aFamilyName; + aFontDescriptor.StyleName = aStyleName; + aFontDescriptor.Family = static_cast<sal_Int16>(eFamily); + aFontDescriptor.CharSet = static_cast<sal_Int16>(eTextEncoding); + aFontDescriptor.Pitch = static_cast<sal_Int16>(ePitch); + rVal <<= aFontDescriptor; + } + break; + case MID_FONT_FAMILY_NAME: + rVal <<= aFamilyName; + break; + case MID_FONT_STYLE_NAME: + rVal <<= aStyleName; + break; + case MID_FONT_FAMILY : rVal <<= static_cast<sal_Int16>(eFamily); break; + case MID_FONT_CHAR_SET : rVal <<= static_cast<sal_Int16>(eTextEncoding); break; + case MID_FONT_PITCH : rVal <<= static_cast<sal_Int16>(ePitch); break; + } + return true; +} + +bool SvxFontItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId) +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case 0: + { + css::awt::FontDescriptor aFontDescriptor; + if ( !( rVal >>= aFontDescriptor )) + return false; + + aFamilyName = aFontDescriptor.Name; + aStyleName = aFontDescriptor.StyleName; + eFamily = static_cast<FontFamily>(aFontDescriptor.Family); + eTextEncoding = static_cast<rtl_TextEncoding>(aFontDescriptor.CharSet); + ePitch = static_cast<FontPitch>(aFontDescriptor.Pitch); + } + break; + case MID_FONT_FAMILY_NAME : + { + OUString aStr; + if(!(rVal >>= aStr)) + return false; + aFamilyName = aStr; + } + break; + case MID_FONT_STYLE_NAME: + { + OUString aStr; + if(!(rVal >>= aStr)) + return false; + aStyleName = aStr; + } + break; + case MID_FONT_FAMILY : + { + sal_Int16 nFamily = sal_Int16(); + if(!(rVal >>= nFamily)) + return false; + eFamily = static_cast<FontFamily>(nFamily); + } + break; + case MID_FONT_CHAR_SET : + { + sal_Int16 nSet = sal_Int16(); + if(!(rVal >>= nSet)) + return false; + eTextEncoding = static_cast<rtl_TextEncoding>(nSet); + } + break; + case MID_FONT_PITCH : + { + sal_Int16 nPitch = sal_Int16(); + if(!(rVal >>= nPitch)) + return false; + ePitch = static_cast<FontPitch>(nPitch); + } + break; + } + return true; +} + + +bool SvxFontItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxFontItem& rItem = static_cast<const SvxFontItem&>(rAttr); + + bool bRet = ( eFamily == rItem.eFamily && + aFamilyName == rItem.aFamilyName && + aStyleName == rItem.aStyleName ); + + if ( bRet ) + { + if ( ePitch != rItem.ePitch || eTextEncoding != rItem.eTextEncoding ) + { + bRet = false; + SAL_INFO( "editeng.items", "FontItem::operator==(): only pitch or rtl_TextEncoding different "); + } + } + return bRet; +} + +bool SvxFontItem::operator<(const SfxPoolItem& rCmp) const +{ + const auto& rOther = static_cast<const SvxFontItem&>(rCmp); + sal_Int32 nRet = GetFamilyName().compareTo(rOther.GetFamilyName()); + if (nRet != 0) + { + return nRet < 0; + } + + nRet = GetStyleName().compareTo(rOther.GetStyleName()); + if (nRet != 0) + { + return nRet < 0; + } + + nRet = CompareTo(GetFamily(), rOther.GetFamily()); + if (nRet != 0) + { + return nRet < 0; + } + + nRet = CompareTo(GetPitch(), rOther.GetPitch()); + if (nRet != 0) + { + return nRet < 0; + } + + return GetCharSet() < rOther.GetCharSet(); +} + +SvxFontItem* SvxFontItem::Clone( SfxItemPool * ) const +{ + return new SvxFontItem( *this ); +} + +bool SvxFontItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = aFamilyName; + return true; +} + + +void SvxFontItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxFontItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("familyName"), BAD_CAST(aFamilyName.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("styleName"), BAD_CAST(aStyleName.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("family"), BAD_CAST(OString::number(eFamily).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pitch"), BAD_CAST(OString::number(ePitch).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("textEncoding"), BAD_CAST(OString::number(eTextEncoding).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxPostureItem -------------------------------------------------- + +SvxPostureItem::SvxPostureItem( const FontItalic ePosture, const sal_uInt16 nId ) : + SfxEnumItem( nId, ePosture ) +{ +} + +SvxPostureItem* SvxPostureItem::Clone( SfxItemPool * ) const +{ + return new SvxPostureItem( *this ); +} + +sal_uInt16 SvxPostureItem::GetValueCount() const +{ + return ITALIC_NORMAL + 1; // ITALIC_NONE also belongs here +} + + +bool SvxPostureItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( GetValue() ); + return true; +} + + +OUString SvxPostureItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + DBG_ASSERT( nPos <= sal_uInt16(ITALIC_NORMAL), "enum overflow!" ); + + FontItalic eItalic = static_cast<FontItalic>(nPos); + TranslateId pId; + + switch ( eItalic ) + { + case ITALIC_NONE: pId = RID_SVXITEMS_ITALIC_NONE; break; + case ITALIC_OBLIQUE: pId = RID_SVXITEMS_ITALIC_OBLIQUE; break; + case ITALIC_NORMAL: pId = RID_SVXITEMS_ITALIC_NORMAL; break; + default: ;//prevent warning + } + + return pId ? EditResId(pId) : OUString(); +} + +bool SvxPostureItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_ITALIC: + rVal <<= GetBoolValue(); + break; + case MID_POSTURE: + rVal <<= vcl::unohelper::ConvertFontSlant(GetValue()); + break; + } + return true; +} + +bool SvxPostureItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_ITALIC: + SetBoolValue(Any2Bool(rVal)); + break; + case MID_POSTURE: + { + awt::FontSlant eSlant; + if(!(rVal >>= eSlant)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + + eSlant = static_cast<awt::FontSlant>(nValue); + } + SetValue(vcl::unohelper::ConvertFontSlant(eSlant)); + } + } + return true; +} + +bool SvxPostureItem::HasBoolValue() const +{ + return true; +} + +bool SvxPostureItem::GetBoolValue() const +{ + return ( GetValue() >= ITALIC_OBLIQUE ); +} + +void SvxPostureItem::SetBoolValue( bool bVal ) +{ + SetValue( bVal ? ITALIC_NORMAL : ITALIC_NONE ); +} + +void SvxPostureItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxPostureItem")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("whichId"), "%d", Which()); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("value"), "%d", GetValue()); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(GetValueTextByPos(GetValue()).toUtf8().getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxWeightItem --------------------------------------------------- + +SvxWeightItem::SvxWeightItem( const FontWeight eWght, const sal_uInt16 nId ) : + SfxEnumItem( nId, eWght ) +{ +} + + +bool SvxWeightItem::HasBoolValue() const +{ + return true; +} + + +bool SvxWeightItem::GetBoolValue() const +{ + return GetValue() >= WEIGHT_BOLD; +} + + +void SvxWeightItem::SetBoolValue( bool bVal ) +{ + SetValue( bVal ? WEIGHT_BOLD : WEIGHT_NORMAL ); +} + + +sal_uInt16 SvxWeightItem::GetValueCount() const +{ + return WEIGHT_BLACK; // WEIGHT_DONTKNOW does not belong +} + +SvxWeightItem* SvxWeightItem::Clone( SfxItemPool * ) const +{ + return new SvxWeightItem( *this ); +} + +bool SvxWeightItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( GetValue() ); + return true; +} + +OUString SvxWeightItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_WEIGHTS[] = + { + RID_SVXITEMS_WEIGHT_DONTKNOW, + RID_SVXITEMS_WEIGHT_THIN, + RID_SVXITEMS_WEIGHT_ULTRALIGHT, + RID_SVXITEMS_WEIGHT_LIGHT, + RID_SVXITEMS_WEIGHT_SEMILIGHT, + RID_SVXITEMS_WEIGHT_NORMAL, + RID_SVXITEMS_WEIGHT_MEDIUM, + RID_SVXITEMS_WEIGHT_SEMIBOLD, + RID_SVXITEMS_WEIGHT_BOLD, + RID_SVXITEMS_WEIGHT_ULTRABOLD, + RID_SVXITEMS_WEIGHT_BLACK + }; + + static_assert(SAL_N_ELEMENTS(RID_SVXITEMS_WEIGHTS) - 1 == WEIGHT_BLACK, "must match"); + assert(nPos <= sal_uInt16(WEIGHT_BLACK) && "enum overflow!" ); + return EditResId(RID_SVXITEMS_WEIGHTS[nPos]); +} + +bool SvxWeightItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_BOLD : + rVal <<= GetBoolValue(); + break; + case MID_WEIGHT: + { + rVal <<= vcl::unohelper::ConvertFontWeight( GetValue() ); + } + break; + } + return true; +} + +bool SvxWeightItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_BOLD : + SetBoolValue(Any2Bool(rVal)); + break; + case MID_WEIGHT: + { + double fValue = 0; + if(!(rVal >>= fValue)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + fValue = static_cast<float>(nValue); + } + SetValue( vcl::unohelper::ConvertFontWeight(static_cast<float>(fValue)) ); + } + break; + } + return true; +} + +void SvxWeightItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxWeightItem")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("whichId"), "%d", Which()); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("value"), "%d", GetValue()); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(GetValueTextByPos(GetValue()).toUtf8().getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxFontHeightItem ----------------------------------------------- + +SvxFontHeightItem::SvxFontHeightItem( const sal_uInt32 nSz, + const sal_uInt16 nPrp, + const sal_uInt16 nId ) : + SfxPoolItem( nId ) +{ + SetHeight( nSz,nPrp ); // calculate in percentage +} + +SvxFontHeightItem* SvxFontHeightItem::Clone( SfxItemPool * ) const +{ + return new SvxFontHeightItem( *this ); +} + +bool SvxFontHeightItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + return GetHeight() == static_cast<const SvxFontHeightItem&>(rItem).GetHeight() && + GetProp() == static_cast<const SvxFontHeightItem&>(rItem).GetProp() && + GetPropUnit() == static_cast<const SvxFontHeightItem&>(rItem).GetPropUnit(); +} + +bool SvxFontHeightItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // In StarOne is the uno::Any always 1/100mm. Through the MemberId it is + // controlled if the value in the Item should be 1/100mm or Twips. + + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case 0: + { + css::frame::status::FontHeight aFontHeight; + + // Point (i.e. Twips) is asked for, thus re-calculate if + // CONVERT_TWIPS is not set. + if( bConvert ) + { + aFontHeight.Height = o3tl::convert<double>(nHeight, o3tl::Length::twip, o3tl::Length::pt); + } + else + { + double fPoints = o3tl::convert<double>(nHeight, o3tl::Length::mm100, o3tl::Length::pt); + aFontHeight.Height = rtl::math::round(fPoints, 1); + } + + aFontHeight.Prop = MapUnit::MapRelative == ePropUnit ? nProp : 100; + + float fRet = nProp; + switch( ePropUnit ) + { + case MapUnit::MapRelative: + fRet = 0.; + break; + case MapUnit::Map100thMM: + fRet = o3tl::convert(fRet, o3tl::Length::mm100, o3tl::Length::pt); + break; + case MapUnit::MapPoint: + + break; + case MapUnit::MapTwip: + fRet = o3tl::convert(fRet, o3tl::Length::twip, o3tl::Length::pt); + break; + default: ;//prevent warning + } + aFontHeight.Diff = fRet; + rVal <<= aFontHeight; + } + break; + case MID_FONTHEIGHT: + { + // Point (i.e. Twips) is asked for, thus re-calculate if + // CONVERT_TWIPS is not set. + if( bConvert ) + { + rVal <<= static_cast<float>(o3tl::convert<double>(nHeight, o3tl::Length::twip, o3tl::Length::pt)); + } + else + { + double fPoints = o3tl::convert<double>(nHeight, o3tl::Length::mm100, o3tl::Length::pt); + rVal <<= static_cast<float>(::rtl::math::round(fPoints, 1)); + } + } + break; + case MID_FONTHEIGHT_PROP: + rVal <<= static_cast<sal_Int16>(MapUnit::MapRelative == ePropUnit ? nProp : 100); + break; + case MID_FONTHEIGHT_DIFF: + { + float fRet = nProp; + switch( ePropUnit ) + { + case MapUnit::MapRelative: + fRet = 0.; + break; + case MapUnit::Map100thMM: + fRet = o3tl::convert(fRet, o3tl::Length::mm100, o3tl::Length::pt); + break; + case MapUnit::MapPoint: + + break; + case MapUnit::MapTwip: + fRet = o3tl::convert(fRet, o3tl::Length::twip, o3tl::Length::pt); + break; + default: ;//prevent warning + } + rVal <<= fRet; + } + break; + } + return true; +} + +// Try to reconstruct the original height input value from the modified height +// and the prop data; this seems somewhat futile given the various ways how the +// modified height is calculated (with and without conversion between twips and +// 100th mm; with an additional eCoreMetric input in one of the SetHeight +// overloads), and indeed known to occasionally produce nRet values that would +// be negative, so just guard against negative results here and throw the hands +// up in despair: +static sal_uInt32 lcl_GetRealHeight_Impl(sal_uInt32 nHeight, sal_uInt16 nProp, MapUnit eProp, bool bCoreInTwip) +{ + sal_uInt32 nRet = nHeight; + short nDiff = 0; + switch( eProp ) + { + case MapUnit::MapRelative: + if (nProp) + { + nRet *= 100; + nRet /= nProp; + } + break; + case MapUnit::MapPoint: + { + short nTemp = static_cast<short>(nProp); + nDiff = nTemp * 20; + if(!bCoreInTwip) + nDiff = static_cast<short>(convertTwipToMm100(static_cast<tools::Long>(nDiff))); + break; + } + case MapUnit::Map100thMM: + //then the core is surely also in 1/100 mm + nDiff = static_cast<short>(nProp); + break; + case MapUnit::MapTwip: + // Here surely TWIP + nDiff = static_cast<short>(nProp); + break; + default: + break; + } + nRet = (nDiff < 0 || nRet >= o3tl::make_unsigned(nDiff)) + ? nRet - nDiff : 0; + //TODO: overflow in case nDiff < 0 and nRet - nDiff > SAL_MAX_UINT32 + + return nRet; +} + +bool SvxFontHeightItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case 0: + { + css::frame::status::FontHeight aFontHeight; + if ( rVal >>= aFontHeight ) + { + // Height + ePropUnit = MapUnit::MapRelative; + nProp = 100; + double fPoint = aFontHeight.Height; + if( fPoint < 0. || fPoint > 10000. ) + return false; + + nHeight = static_cast<tools::Long>( fPoint * 20.0 + 0.5 ); // Twips + if (!bConvert) + nHeight = convertTwipToMm100(nHeight); // Convert, if the item contains 1/100mm + + nProp = aFontHeight.Prop; + } + else + return false; + } + break; + case MID_FONTHEIGHT: + { + ePropUnit = MapUnit::MapRelative; + nProp = 100; + double fPoint = 0; + if(!(rVal >>= fPoint)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + fPoint = static_cast<float>(nValue); + } + if(fPoint < 0. || fPoint > 10000.) + return false; + + nHeight = static_cast<tools::Long>( fPoint * 20.0 + 0.5 ); // Twips + if (!bConvert) + nHeight = convertTwipToMm100(nHeight); // Convert, if the item contains 1/100mm + } + break; + case MID_FONTHEIGHT_PROP: + { + sal_Int16 nNew = sal_Int16(); + if(!(rVal >>= nNew)) + return true; + + nHeight = lcl_GetRealHeight_Impl(nHeight, nProp, ePropUnit, bConvert); + + nHeight *= nNew; + nHeight /= 100; + nProp = nNew; + ePropUnit = MapUnit::MapRelative; + } + break; + case MID_FONTHEIGHT_DIFF: + { + nHeight = lcl_GetRealHeight_Impl(nHeight, nProp, ePropUnit, bConvert); + float fValue = 0; + if(!(rVal >>= fValue)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + fValue = static_cast<float>(nValue); + } + sal_Int16 nCoreDiffValue = static_cast<sal_Int16>(fValue * 20.); + nHeight += bConvert ? nCoreDiffValue : convertTwipToMm100(nCoreDiffValue); + nProp = static_cast<sal_uInt16>(static_cast<sal_Int16>(fValue)); + ePropUnit = MapUnit::MapPoint; + } + break; + } + return true; +} + + +bool SvxFontHeightItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit eCoreUnit, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& rIntl +) const +{ + if( MapUnit::MapRelative != ePropUnit ) + { + rText = OUString::number( static_cast<short>(nProp) ) + + " " + EditResId( GetMetricId( ePropUnit ) ); + if( 0 <= static_cast<short>(nProp) ) + rText = "+" + rText; + } + else if( 100 == nProp ) + { + rText = GetMetricText( static_cast<tools::Long>(nHeight), + eCoreUnit, MapUnit::MapPoint, &rIntl ) + + " " + EditResId(GetMetricId(MapUnit::MapPoint)); + } + else + rText = OUString::number( nProp ) + "%"; + return true; +} + + +void SvxFontHeightItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + nHeight = static_cast<sal_uInt32>(BigInt::Scale( nHeight, nMult, nDiv )); +} + + +bool SvxFontHeightItem::HasMetrics() const +{ + return true; +} + +void SvxFontHeightItem::SetHeight( sal_uInt32 nNewHeight, const sal_uInt16 nNewProp, + MapUnit eUnit ) +{ + DBG_ASSERT( GetRefCount() == 0, "SetValue() with pooled item" ); + + if( MapUnit::MapRelative != eUnit ) + nHeight = nNewHeight + ::ItemToControl( short(nNewProp), eUnit, + FieldUnit::TWIP ); + else if( 100 != nNewProp ) + nHeight = sal_uInt32(( nNewHeight * nNewProp ) / 100 ); + else + nHeight = nNewHeight; + + nProp = nNewProp; + ePropUnit = eUnit; +} + +void SvxFontHeightItem::SetHeight( sal_uInt32 nNewHeight, sal_uInt16 nNewProp, + MapUnit eMetric, MapUnit eCoreMetric ) +{ + DBG_ASSERT( GetRefCount() == 0, "SetValue() with pooled item" ); + + if( MapUnit::MapRelative != eMetric ) + nHeight = nNewHeight + + ::ControlToItem( ::ItemToControl(static_cast<short>(nNewProp), eMetric, + FieldUnit::TWIP ), FieldUnit::TWIP, + eCoreMetric ); + else if( 100 != nNewProp ) + nHeight = sal_uInt32(( nNewHeight * nNewProp ) / 100 ); + else + nHeight = nNewHeight; + + nProp = nNewProp; + ePropUnit = eMetric; +} + +void SvxFontHeightItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxFontHeightItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("height"), BAD_CAST(OString::number(nHeight).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("prop"), BAD_CAST(OString::number(nProp).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("propUnit"), BAD_CAST(OString::number(static_cast<int>(ePropUnit)).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxTextLineItem ------------------------------------------------ + +SvxTextLineItem::SvxTextLineItem( const FontLineStyle eSt, const sal_uInt16 nId ) + : SfxEnumItem( nId, eSt ), mColor( COL_TRANSPARENT ) +{ +} + + +bool SvxTextLineItem::HasBoolValue() const +{ + return true; +} + + +bool SvxTextLineItem::GetBoolValue() const +{ + return GetValue() != LINESTYLE_NONE; +} + + +void SvxTextLineItem::SetBoolValue( bool bVal ) +{ + SetValue( bVal ? LINESTYLE_SINGLE : LINESTYLE_NONE ); +} + +SvxTextLineItem* SvxTextLineItem::Clone( SfxItemPool * ) const +{ + return new SvxTextLineItem( *this ); +} + +sal_uInt16 SvxTextLineItem::GetValueCount() const +{ + return LINESTYLE_DOTTED + 1; // LINESTYLE_NONE also belongs here +} + + +bool SvxTextLineItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( GetValue() ); + if( !mColor.IsTransparent() ) + rText += cpDelim + ::GetColorString( mColor ); + return true; +} + + +OUString SvxTextLineItem::GetValueTextByPos( sal_uInt16 /*nPos*/ ) const +{ + OSL_FAIL("SvxTextLineItem::GetValueTextByPos: Pure virtual method"); + return OUString(); +} + +bool SvxTextLineItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_TEXTLINED: + rVal <<= GetBoolValue(); + break; + case MID_TL_STYLE: + rVal <<= static_cast<sal_Int16>(GetValue()); + break; + case MID_TL_COLOR: + rVal <<= mColor; + break; + case MID_TL_HASCOLOR: + rVal <<= mColor.GetAlpha() == 255; + break; + } + return true; + +} + +bool SvxTextLineItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch(nMemberId) + { + case MID_TEXTLINED: + SetBoolValue(Any2Bool(rVal)); + break; + case MID_TL_STYLE: + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + bRet = false; + else + SetValue(static_cast<FontLineStyle>(nValue)); + } + break; + case MID_TL_COLOR: + { + Color nCol; + if( !( rVal >>= nCol ) ) + bRet = false; + else + { + // Keep transparence, because it contains the information + // whether the font color or the stored color should be used + sal_uInt8 nAlpha = mColor.GetAlpha(); + mColor = nCol; + mColor.SetAlpha( nAlpha ); + } + } + break; + case MID_TL_HASCOLOR: + mColor.SetAlpha( Any2Bool( rVal ) ? 255 : 0 ); + break; + } + return bRet; +} + +bool SvxTextLineItem::operator==( const SfxPoolItem& rItem ) const +{ + return SfxEnumItem::operator==( rItem ) && + GetColor() == static_cast<const SvxTextLineItem&>(rItem).GetColor(); +} + +// class SvxUnderlineItem ------------------------------------------------ + + +SvxUnderlineItem::SvxUnderlineItem( const FontLineStyle eSt, const sal_uInt16 nId ) + : SvxTextLineItem( eSt, nId ) +{ +} + +SvxUnderlineItem* SvxUnderlineItem::Clone( SfxItemPool * ) const +{ + return new SvxUnderlineItem( *this ); +} + +OUString SvxUnderlineItem::GetValueTextByPos( sal_uInt16 nPos ) const +{ + static TranslateId RID_SVXITEMS_UL[] = + { + RID_SVXITEMS_UL_NONE, + RID_SVXITEMS_UL_SINGLE, + RID_SVXITEMS_UL_DOUBLE, + RID_SVXITEMS_UL_DOTTED, + RID_SVXITEMS_UL_DONTKNOW, + RID_SVXITEMS_UL_DASH, + RID_SVXITEMS_UL_LONGDASH, + RID_SVXITEMS_UL_DASHDOT, + RID_SVXITEMS_UL_DASHDOTDOT, + RID_SVXITEMS_UL_SMALLWAVE, + RID_SVXITEMS_UL_WAVE, + RID_SVXITEMS_UL_DOUBLEWAVE, + RID_SVXITEMS_UL_BOLD, + RID_SVXITEMS_UL_BOLDDOTTED, + RID_SVXITEMS_UL_BOLDDASH, + RID_SVXITEMS_UL_BOLDLONGDASH, + RID_SVXITEMS_UL_BOLDDASHDOT, + RID_SVXITEMS_UL_BOLDDASHDOTDOT, + RID_SVXITEMS_UL_BOLDWAVE + }; + static_assert(SAL_N_ELEMENTS(RID_SVXITEMS_UL) - 1 == LINESTYLE_BOLDWAVE, "must match"); + assert(nPos <= sal_uInt16(LINESTYLE_BOLDWAVE) && "enum overflow!"); + return EditResId(RID_SVXITEMS_UL[nPos]); +} + +// class SvxOverlineItem ------------------------------------------------ + +SvxOverlineItem::SvxOverlineItem( const FontLineStyle eSt, const sal_uInt16 nId ) + : SvxTextLineItem( eSt, nId ) +{ +} + +SvxOverlineItem* SvxOverlineItem::Clone( SfxItemPool * ) const +{ + return new SvxOverlineItem( *this ); +} + +OUString SvxOverlineItem::GetValueTextByPos( sal_uInt16 nPos ) const +{ + static TranslateId RID_SVXITEMS_OL[] = + { + RID_SVXITEMS_OL_NONE, + RID_SVXITEMS_OL_SINGLE, + RID_SVXITEMS_OL_DOUBLE, + RID_SVXITEMS_OL_DOTTED, + RID_SVXITEMS_OL_DONTKNOW, + RID_SVXITEMS_OL_DASH, + RID_SVXITEMS_OL_LONGDASH, + RID_SVXITEMS_OL_DASHDOT, + RID_SVXITEMS_OL_DASHDOTDOT, + RID_SVXITEMS_OL_SMALLWAVE, + RID_SVXITEMS_OL_WAVE, + RID_SVXITEMS_OL_DOUBLEWAVE, + RID_SVXITEMS_OL_BOLD, + RID_SVXITEMS_OL_BOLDDOTTED, + RID_SVXITEMS_OL_BOLDDASH, + RID_SVXITEMS_OL_BOLDLONGDASH, + RID_SVXITEMS_OL_BOLDDASHDOT, + RID_SVXITEMS_OL_BOLDDASHDOTDOT, + RID_SVXITEMS_OL_BOLDWAVE + }; + static_assert(SAL_N_ELEMENTS(RID_SVXITEMS_OL) - 1 == LINESTYLE_BOLDWAVE, "must match"); + assert(nPos <= sal_uInt16(LINESTYLE_BOLDWAVE) && "enum overflow!"); + return EditResId(RID_SVXITEMS_OL[nPos]); +} + +// class SvxCrossedOutItem ----------------------------------------------- + +SvxCrossedOutItem::SvxCrossedOutItem( const FontStrikeout eSt, const sal_uInt16 nId ) + : SfxEnumItem( nId, eSt ) +{ +} + + +bool SvxCrossedOutItem::HasBoolValue() const +{ + return true; +} + + +bool SvxCrossedOutItem::GetBoolValue() const +{ + return GetValue() != STRIKEOUT_NONE; +} + + +void SvxCrossedOutItem::SetBoolValue( bool bVal ) +{ + SetValue( bVal ? STRIKEOUT_SINGLE : STRIKEOUT_NONE ); +} + + +sal_uInt16 SvxCrossedOutItem::GetValueCount() const +{ + return STRIKEOUT_DOUBLE + 1; // STRIKEOUT_NONE belongs also here +} + +SvxCrossedOutItem* SvxCrossedOutItem::Clone( SfxItemPool * ) const +{ + return new SvxCrossedOutItem( *this ); +} + +bool SvxCrossedOutItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( GetValue() ); + return true; +} + +OUString SvxCrossedOutItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_STRIKEOUT[] = + { + RID_SVXITEMS_STRIKEOUT_NONE, + RID_SVXITEMS_STRIKEOUT_SINGLE, + RID_SVXITEMS_STRIKEOUT_DOUBLE, + RID_SVXITEMS_STRIKEOUT_DONTKNOW, + RID_SVXITEMS_STRIKEOUT_BOLD, + RID_SVXITEMS_STRIKEOUT_SLASH, + RID_SVXITEMS_STRIKEOUT_X + }; + static_assert(SAL_N_ELEMENTS(RID_SVXITEMS_STRIKEOUT) - 1 == STRIKEOUT_X, "must match"); + assert(nPos <= sal_uInt16(STRIKEOUT_X) && "enum overflow!"); + return EditResId(RID_SVXITEMS_STRIKEOUT[nPos]); +} + +bool SvxCrossedOutItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_CROSSED_OUT: + rVal <<= GetBoolValue(); + break; + case MID_CROSS_OUT: + rVal <<= static_cast<sal_Int16>(GetValue()); + break; + } + return true; +} + +bool SvxCrossedOutItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_CROSSED_OUT: + SetBoolValue(Any2Bool(rVal)); + break; + case MID_CROSS_OUT: + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + SetValue(static_cast<FontStrikeout>(nValue)); + } + break; + } + return true; +} +// class SvxShadowedItem ------------------------------------------------- + +SvxShadowedItem::SvxShadowedItem( const bool bShadowed, const sal_uInt16 nId ) : + SfxBoolItem( nId, bShadowed ) +{ +} + +SvxShadowedItem* SvxShadowedItem::Clone( SfxItemPool * ) const +{ + return new SvxShadowedItem( *this ); +} + +bool SvxShadowedItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_SHADOWED_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_SHADOWED_TRUE; + rText = EditResId(pId); + return true; +} + +// class SvxAutoKernItem ------------------------------------------------- + +SvxAutoKernItem::SvxAutoKernItem( const bool bAutoKern, const sal_uInt16 nId ) : + SfxBoolItem( nId, bAutoKern ) +{ +} + +SvxAutoKernItem* SvxAutoKernItem::Clone( SfxItemPool * ) const +{ + return new SvxAutoKernItem( *this ); +} + +bool SvxAutoKernItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_AUTOKERN_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_AUTOKERN_TRUE; + rText = EditResId(pId); + return true; +} + +// class SvxWordLineModeItem --------------------------------------------- + +SvxWordLineModeItem::SvxWordLineModeItem( const bool bWordLineMode, + const sal_uInt16 nId ) : + SfxBoolItem( nId, bWordLineMode ) +{ +} + +SvxWordLineModeItem* SvxWordLineModeItem::Clone( SfxItemPool * ) const +{ + return new SvxWordLineModeItem( *this ); +} + +bool SvxWordLineModeItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_WORDLINE_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_WORDLINE_TRUE; + rText = EditResId(pId); + return true; +} + +// class SvxContourItem -------------------------------------------------- + +SvxContourItem::SvxContourItem( const bool bContoured, const sal_uInt16 nId ) : + SfxBoolItem( nId, bContoured ) +{ +} + +SvxContourItem* SvxContourItem::Clone( SfxItemPool * ) const +{ + return new SvxContourItem( *this ); +} + +bool SvxContourItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_CONTOUR_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_CONTOUR_TRUE; + rText = EditResId(pId); + return true; +} + +SvxThemeColor::SvxThemeColor() + : maThemeIndex(-1), + mnLumMod(10000), + mnLumOff(0) +{ +} + +bool SvxThemeColor::operator==(const SvxThemeColor& rThemeColor) const +{ + return maThemeIndex == rThemeColor.maThemeIndex && + mnLumMod == rThemeColor.mnLumMod && + mnLumOff == rThemeColor.mnLumOff; +} + +void SvxThemeColor::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxThemeColor")); + + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("theme-index"), + BAD_CAST(OString::number(maThemeIndex).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("lum-mod"), + BAD_CAST(OString::number(mnLumMod).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("lum-off"), + BAD_CAST(OString::number(mnLumOff).getStr())); + + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxColorItem ---------------------------------------------------- +SvxColorItem::SvxColorItem( const sal_uInt16 nId ) : + SfxPoolItem(nId), + mColor( COL_BLACK ), + maTintShade(0) +{ +} + +SvxColorItem::SvxColorItem( const Color& rCol, const sal_uInt16 nId ) : + SfxPoolItem( nId ), + mColor( rCol ), + maTintShade(0) +{ +} + +SvxColorItem::~SvxColorItem() +{ +} + +bool SvxColorItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + const SvxColorItem& rColorItem = static_cast<const SvxColorItem&>(rAttr); + + return mColor == rColorItem.mColor && + maThemeColor == rColorItem.maThemeColor && + maTintShade == rColorItem.maTintShade; +} + +bool SvxColorItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch (nMemberId) + { + case MID_COLOR_ALPHA: + { + auto fTransparency = static_cast<double>(255 - mColor.GetAlpha()) * 100 / 255; + rVal <<= static_cast<sal_Int16>(basegfx::fround(fTransparency)); + break; + } + case MID_GRAPHIC_TRANSPARENT: + { + rVal <<= mColor.GetAlpha() == 0; + break; + } + case MID_COLOR_THEME_INDEX: + { + rVal <<= maThemeColor.GetThemeIndex(); + break; + } + case MID_COLOR_TINT_OR_SHADE: + { + rVal <<= maTintShade; + break; + } + case MID_COLOR_LUM_MOD: + { + rVal <<= maThemeColor.GetLumMod(); + break; + } + case MID_COLOR_LUM_OFF: + { + rVal <<= maThemeColor.GetLumOff(); + break; + } + default: + { + rVal <<= mColor; + break; + } + } + return true; +} + +bool SvxColorItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_COLOR_ALPHA: + { + sal_Int16 nTransparency = 0; + bool bRet = rVal >>= nTransparency; + if (bRet) + { + auto fTransparency = static_cast<double>(nTransparency) * 255 / 100; + mColor.SetAlpha(255 - static_cast<sal_uInt8>(basegfx::fround(fTransparency))); + } + return bRet; + } + case MID_GRAPHIC_TRANSPARENT: + { + mColor.SetAlpha( Any2Bool( rVal ) ? 0 : 255 ); + return true; + } + case MID_COLOR_THEME_INDEX: + { + sal_Int16 nIndex = -1; + if (!(rVal >>= nIndex)) + return false; + maThemeColor.SetThemeIndex(nIndex); + } + break; + case MID_COLOR_TINT_OR_SHADE: + { + sal_Int16 nTintShade = -1; + if (!(rVal >>= nTintShade)) + return false; + maTintShade = nTintShade; + } + break; + case MID_COLOR_LUM_MOD: + { + sal_Int16 nLumMod = -1; + if (!(rVal >>= nLumMod)) + return false; + maThemeColor.SetLumMod(nLumMod); + } + break; + case MID_COLOR_LUM_OFF: + { + sal_Int16 nLumOff = -1; + if (!(rVal >>= nLumOff)) + return false; + maThemeColor.SetLumOff(nLumOff); + } + break; + default: + { + return rVal >>= mColor; + } + break; + } + return true; +} + +SvxColorItem* SvxColorItem::Clone( SfxItemPool * ) const +{ + return new SvxColorItem( *this ); +} + +bool SvxColorItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = ::GetColorString( mColor ); + return true; +} + +void SvxColorItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxColorItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + + std::stringstream ss; + ss << mColor; + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(ss.str().c_str())); + + OUString aStr; + IntlWrapper aIntlWrapper(SvtSysLocale().GetUILanguageTag()); + GetPresentation( SfxItemPresentation::Complete, MapUnit::Map100thMM, MapUnit::Map100thMM, aStr, aIntlWrapper); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(OUStringToOString(aStr, RTL_TEXTENCODING_UTF8).getStr())); + + maThemeColor.dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + + + +void SvxColorItem::SetValue( const Color& rNewCol ) +{ + mColor = rNewCol; +} + +// class SvxKerningItem -------------------------------------------------- + +SvxKerningItem::SvxKerningItem( const short nKern, const sal_uInt16 nId ) : + SfxInt16Item( nId, nKern ) +{ +} + +SvxKerningItem* SvxKerningItem::Clone( SfxItemPool * ) const +{ + return new SvxKerningItem( *this ); +} + +void SvxKerningItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + SetValue( static_cast<sal_Int16>(BigInt::Scale( GetValue(), nMult, nDiv )) ); +} + + +bool SvxKerningItem::HasMetrics() const +{ + return true; +} + + +bool SvxKerningItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + rText = GetMetricText( static_cast<tools::Long>(GetValue()), eCoreUnit, MapUnit::MapPoint, &rIntl ) + + " " + EditResId(GetMetricId(MapUnit::MapPoint)); + return true; + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_KERNING_COMPLETE); + TranslateId pId; + + if ( GetValue() > 0 ) + pId = RID_SVXITEMS_KERNING_EXPANDED; + else if ( GetValue() < 0 ) + pId = RID_SVXITEMS_KERNING_CONDENSED; + + if (pId) + rText += EditResId(pId); + rText += GetMetricText( static_cast<tools::Long>(GetValue()), eCoreUnit, MapUnit::MapPoint, &rIntl ) + + " " + EditResId(GetMetricId(MapUnit::MapPoint)); + return true; + } + default: ; //prevent warning + } + return false; +} + +bool SvxKerningItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + sal_Int16 nVal = GetValue(); + if(nMemberId & CONVERT_TWIPS) + nVal = static_cast<sal_Int16>(convertTwipToMm100(nVal)); + rVal <<= nVal; + return true; +} + +bool SvxKerningItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId) +{ + sal_Int16 nVal = sal_Int16(); + if(!(rVal >>= nVal)) + return false; + if(nMemberId & CONVERT_TWIPS) + nVal = o3tl::toTwips(nVal, o3tl::Length::mm100); + SetValue(nVal); + return true; +} + +// class SvxCaseMapItem -------------------------------------------------- + +SvxCaseMapItem::SvxCaseMapItem( const SvxCaseMap eMap, const sal_uInt16 nId ) : + SfxEnumItem( nId, eMap ) +{ +} + +sal_uInt16 SvxCaseMapItem::GetValueCount() const +{ + return sal_uInt16(SvxCaseMap::End); // SvxCaseMap::SmallCaps + 1 +} + +SvxCaseMapItem* SvxCaseMapItem::Clone( SfxItemPool * ) const +{ + return new SvxCaseMapItem( *this ); +} + +bool SvxCaseMapItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( static_cast<sal_uInt16>(GetValue()) ); + return true; +} + +OUString SvxCaseMapItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_CASEMAP[] = + { + RID_SVXITEMS_CASEMAP_NONE, + RID_SVXITEMS_CASEMAP_UPPERCASE, + RID_SVXITEMS_CASEMAP_LOWERCASE, + RID_SVXITEMS_CASEMAP_TITLE, + RID_SVXITEMS_CASEMAP_SMALLCAPS + }; + + static_assert(SAL_N_ELEMENTS(RID_SVXITEMS_CASEMAP) == size_t(SvxCaseMap::End), "must match"); + assert(nPos < sal_uInt16(SvxCaseMap::End) && "enum overflow!"); + return EditResId(RID_SVXITEMS_CASEMAP[nPos]); +} + +bool SvxCaseMapItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + sal_Int16 nRet = style::CaseMap::NONE; + switch( GetValue() ) + { + case SvxCaseMap::Uppercase : nRet = style::CaseMap::UPPERCASE; break; + case SvxCaseMap::Lowercase : nRet = style::CaseMap::LOWERCASE; break; + case SvxCaseMap::Capitalize : nRet = style::CaseMap::TITLE ; break; + case SvxCaseMap::SmallCaps: nRet = style::CaseMap::SMALLCAPS; break; + default: break; + } + rVal <<= nRet; + return true; +} + +bool SvxCaseMapItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + sal_uInt16 nVal = sal_uInt16(); + if(!(rVal >>= nVal)) + return false; + + SvxCaseMap eVal; + switch( nVal ) + { + case style::CaseMap::NONE : eVal = SvxCaseMap::NotMapped; break; + case style::CaseMap::UPPERCASE: eVal = SvxCaseMap::Uppercase; break; + case style::CaseMap::LOWERCASE: eVal = SvxCaseMap::Lowercase; break; + case style::CaseMap::TITLE : eVal = SvxCaseMap::Capitalize; break; + case style::CaseMap::SMALLCAPS: eVal = SvxCaseMap::SmallCaps; break; + default: return false; + } + SetValue(eVal); + return true; +} + +// class SvxEscapementItem ----------------------------------------------- + +SvxEscapementItem::SvxEscapementItem( const sal_uInt16 nId ) : + SfxEnumItemInterface( nId ), + + nEsc ( 0 ), + nProp ( 100 ) +{ +} + + +SvxEscapementItem::SvxEscapementItem( const SvxEscapement eEscape, + const sal_uInt16 nId ) : + SfxEnumItemInterface( nId ), + nProp( 100 ) +{ + SetEscapement( eEscape ); + if( nEsc ) + nProp = DFLT_ESC_PROP; +} + + +SvxEscapementItem::SvxEscapementItem( const short _nEsc, + const sal_uInt8 _nProp, + const sal_uInt16 nId ) : + SfxEnumItemInterface( nId ), + nEsc ( _nEsc ), + nProp ( _nProp ) +{ +} + + +bool SvxEscapementItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return( nEsc == static_cast<const SvxEscapementItem&>(rAttr).nEsc && + nProp == static_cast<const SvxEscapementItem&>(rAttr).nProp ); +} + +SvxEscapementItem* SvxEscapementItem::Clone( SfxItemPool * ) const +{ + return new SvxEscapementItem( *this ); +} + +sal_uInt16 SvxEscapementItem::GetValueCount() const +{ + return sal_uInt16(SvxEscapement::End); // SvxEscapement::Subscript + 1 +} + + +bool SvxEscapementItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( GetEnumValue() ); + + if ( nEsc != 0 ) + { + if( DFLT_ESC_AUTO_SUPER == nEsc || DFLT_ESC_AUTO_SUB == nEsc ) + rText += EditResId(RID_SVXITEMS_ESCAPEMENT_AUTO); + else + rText += OUString::number( nEsc ) + "%"; + } + return true; +} + +OUString SvxEscapementItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_ESCAPEMENT[] = + { + RID_SVXITEMS_ESCAPEMENT_OFF, + RID_SVXITEMS_ESCAPEMENT_SUPER, + RID_SVXITEMS_ESCAPEMENT_SUB + }; + + static_assert(SAL_N_ELEMENTS(RID_SVXITEMS_ESCAPEMENT) == size_t(SvxEscapement::End), "must match"); + assert(nPos < sal_uInt16(SvxEscapement::End) && "enum overflow!"); + return EditResId(RID_SVXITEMS_ESCAPEMENT[nPos]); +} + +sal_uInt16 SvxEscapementItem::GetEnumValue() const +{ + if ( nEsc < 0 ) + return sal_uInt16(SvxEscapement::Subscript); + else if ( nEsc > 0 ) + return sal_uInt16(SvxEscapement::Superscript); + return sal_uInt16(SvxEscapement::Off); +} + + +void SvxEscapementItem::SetEnumValue( sal_uInt16 nVal ) +{ + SetEscapement( static_cast<SvxEscapement>(nVal) ); +} + +bool SvxEscapementItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_ESC: + rVal <<= static_cast<sal_Int16>(nEsc); + break; + case MID_ESC_HEIGHT: + rVal <<= static_cast<sal_Int8>(nProp); + break; + case MID_AUTO_ESC: + rVal <<= (DFLT_ESC_AUTO_SUB == nEsc || DFLT_ESC_AUTO_SUPER == nEsc); + break; + } + return true; +} + +bool SvxEscapementItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_ESC: + { + sal_Int16 nVal = sal_Int16(); + if( (rVal >>= nVal) && (std::abs(nVal) <= MAX_ESC_POS+1)) + nEsc = nVal; + else + return false; + } + break; + case MID_ESC_HEIGHT: + { + sal_Int8 nVal = sal_Int8(); + if( (rVal >>= nVal) && (nVal <= 100)) + nProp = nVal; + else + return false; + } + break; + case MID_AUTO_ESC: + { + bool bVal = Any2Bool(rVal); + if(bVal) + { + if(nEsc < 0) + nEsc = DFLT_ESC_AUTO_SUB; + else + nEsc = DFLT_ESC_AUTO_SUPER; + } + else + if(DFLT_ESC_AUTO_SUPER == nEsc ) + --nEsc; + else if(DFLT_ESC_AUTO_SUB == nEsc) + ++nEsc; + } + break; + } + return true; +} + +// class SvxLanguageItem ------------------------------------------------- + +SvxLanguageItem::SvxLanguageItem( const LanguageType eLang, const sal_uInt16 nId ) + : SvxLanguageItem_Base( nId , eLang ) +{ +} + + +sal_uInt16 SvxLanguageItem::GetValueCount() const +{ + // #i50205# got rid of class International + SAL_WARN( "editeng.items", "SvxLanguageItem::GetValueCount: supposed to return a count of what?"); + // Could be SvtLanguageTable::GetEntryCount() (all locales with resource string)? + // Could be LocaleDataWrapper::getInstalledLanguageTypes() (all locales with locale data)? + return 0; +} + +SvxLanguageItem* SvxLanguageItem::Clone( SfxItemPool * ) const +{ + return new SvxLanguageItem( *this ); +} + +bool SvxLanguageItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = SvtLanguageTable::GetLanguageString( GetValue() ); + return true; +} + +bool SvxLanguageItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_LANG_INT: // for basic conversions! + rVal <<= static_cast<sal_Int16>(static_cast<sal_uInt16>(GetValue())); + break; + case MID_LANG_LOCALE: + lang::Locale aRet( LanguageTag::convertToLocale( GetValue(), false)); + rVal <<= aRet; + break; + } + return true; +} + +bool SvxLanguageItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_LANG_INT: // for basic conversions! + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + + SetValue(LanguageType(nValue)); + } + break; + case MID_LANG_LOCALE: + { + lang::Locale aLocale; + if(!(rVal >>= aLocale)) + return false; + + SetValue( LanguageTag::convertToLanguageType( aLocale, false)); + } + break; + } + return true; +} + +// class SvxNoHyphenItem ------------------------------------------------- + +SvxNoHyphenItem::SvxNoHyphenItem( const sal_uInt16 nId ) : + SfxBoolItem( nId , true ) +{ +} + +SvxNoHyphenItem* SvxNoHyphenItem::Clone( SfxItemPool* ) const +{ + return new SvxNoHyphenItem( *this ); +} + +bool SvxNoHyphenItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText.clear(); + return false; +} + +/* + * Dummy item for ToolBox controls: + * + */ + + +// class SvxBlinkItem ------------------------------------------------- + + +SvxBlinkItem::SvxBlinkItem( const bool bBlink, const sal_uInt16 nId ) : + SfxBoolItem( nId, bBlink ) +{ +} + +SvxBlinkItem* SvxBlinkItem::Clone( SfxItemPool * ) const +{ + return new SvxBlinkItem( *this ); +} + +bool SvxBlinkItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_BLINK_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_BLINK_TRUE; + rText = EditResId(pId); + return true; +} + +// class SvxEmphaisMarkItem --------------------------------------------------- + +SvxEmphasisMarkItem::SvxEmphasisMarkItem( const FontEmphasisMark nValue, + const sal_uInt16 nId ) + : SfxUInt16Item( nId, static_cast<sal_uInt16>(nValue) ) +{ +} + +SvxEmphasisMarkItem* SvxEmphasisMarkItem::Clone( SfxItemPool * ) const +{ + return new SvxEmphasisMarkItem( *this ); +} + +bool SvxEmphasisMarkItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, + const IntlWrapper& /*rIntl*/ +) const +{ + static TranslateId RID_SVXITEMS_EMPHASIS[] = + { + RID_SVXITEMS_EMPHASIS_NONE_STYLE, + RID_SVXITEMS_EMPHASIS_DOT_STYLE, + RID_SVXITEMS_EMPHASIS_CIRCLE_STYLE, + RID_SVXITEMS_EMPHASIS_DISC_STYLE, + RID_SVXITEMS_EMPHASIS_ACCENT_STYLE + }; + + FontEmphasisMark nVal = GetEmphasisMark(); + rText = EditResId(RID_SVXITEMS_EMPHASIS[ + static_cast<sal_uInt16>(static_cast<FontEmphasisMark>( nVal & FontEmphasisMark::Style ))]); + TranslateId pId = ( FontEmphasisMark::PosAbove & nVal ) + ? RID_SVXITEMS_EMPHASIS_ABOVE_POS + : ( FontEmphasisMark::PosBelow & nVal ) + ? RID_SVXITEMS_EMPHASIS_BELOW_POS + : TranslateId(); + if( pId ) + rText += EditResId( pId ); + return true; +} + +bool SvxEmphasisMarkItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_EMPHASIS: + { + FontEmphasisMark nValue = GetEmphasisMark(); + sal_Int16 nRet = 0; + switch(nValue & FontEmphasisMark::Style) + { + case FontEmphasisMark::NONE : nRet = FontEmphasis::NONE; break; + case FontEmphasisMark::Dot : nRet = FontEmphasis::DOT_ABOVE; break; + case FontEmphasisMark::Circle : nRet = FontEmphasis::CIRCLE_ABOVE; break; + case FontEmphasisMark::Disc : nRet = FontEmphasis::DISK_ABOVE; break; + case FontEmphasisMark::Accent : nRet = FontEmphasis::ACCENT_ABOVE; break; + default: break; + } + if(nRet && nValue & FontEmphasisMark::PosBelow) + nRet += 10; + rVal <<= nRet; + } + break; + } + return true; +} + +bool SvxEmphasisMarkItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_EMPHASIS: + { + sal_Int32 nValue = -1; + rVal >>= nValue; + FontEmphasisMark nMark; + switch(nValue) + { + case FontEmphasis::NONE : nMark = FontEmphasisMark::NONE; break; + case FontEmphasis::DOT_ABOVE : nMark = FontEmphasisMark::Dot|FontEmphasisMark::PosAbove; break; + case FontEmphasis::CIRCLE_ABOVE: nMark = FontEmphasisMark::Circle|FontEmphasisMark::PosAbove; break; + case FontEmphasis::DISK_ABOVE : nMark = FontEmphasisMark::Disc|FontEmphasisMark::PosAbove; break; + case FontEmphasis::ACCENT_ABOVE: nMark = FontEmphasisMark::Accent|FontEmphasisMark::PosAbove; break; + case FontEmphasis::DOT_BELOW : nMark = FontEmphasisMark::Dot|FontEmphasisMark::PosBelow; break; + case FontEmphasis::CIRCLE_BELOW: nMark = FontEmphasisMark::Circle|FontEmphasisMark::PosBelow; break; + case FontEmphasis::DISK_BELOW : nMark = FontEmphasisMark::Disc|FontEmphasisMark::PosBelow; break; + case FontEmphasis::ACCENT_BELOW: nMark = FontEmphasisMark::Accent|FontEmphasisMark::PosBelow; break; + default: return false; + } + SetValue( static_cast<sal_Int16>(nMark) ); + } + break; + } + return true; +} + +/************************************************************************* +|* class SvxTwoLinesItem +*************************************************************************/ + +SvxTwoLinesItem::SvxTwoLinesItem( bool bFlag, sal_Unicode nStartBracket, + sal_Unicode nEndBracket, sal_uInt16 nW ) + : SfxPoolItem( nW ), + cStartBracket( nStartBracket ), cEndBracket( nEndBracket ), bOn( bFlag ) +{ +} + +SvxTwoLinesItem::~SvxTwoLinesItem() +{ +} + +bool SvxTwoLinesItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return bOn == static_cast<const SvxTwoLinesItem&>(rAttr).bOn && + cStartBracket == static_cast<const SvxTwoLinesItem&>(rAttr).cStartBracket && + cEndBracket == static_cast<const SvxTwoLinesItem&>(rAttr).cEndBracket; +} + +SvxTwoLinesItem* SvxTwoLinesItem::Clone( SfxItemPool* ) const +{ + return new SvxTwoLinesItem( *this ); +} + +bool SvxTwoLinesItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch( nMemberId ) + { + case MID_TWOLINES: + rVal <<= bOn; + break; + case MID_START_BRACKET: + { + OUString s; + if( cStartBracket ) + s = OUString( cStartBracket ); + rVal <<= s; + } + break; + case MID_END_BRACKET: + { + OUString s; + if( cEndBracket ) + s = OUString( cEndBracket ); + rVal <<= s; + } + break; + default: + bRet = false; + break; + } + return bRet; +} + +bool SvxTwoLinesItem::PutValue( const css::uno::Any& rVal, + sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = false; + OUString s; + switch( nMemberId ) + { + case MID_TWOLINES: + bOn = Any2Bool( rVal ); + bRet = true; + break; + case MID_START_BRACKET: + if( rVal >>= s ) + { + cStartBracket = s.isEmpty() ? 0 : s[ 0 ]; + bRet = true; + } + break; + case MID_END_BRACKET: + if( rVal >>= s ) + { + cEndBracket = s.isEmpty() ? 0 : s[ 0 ]; + bRet = true; + } + break; + } + return bRet; +} + +bool SvxTwoLinesItem::GetPresentation( SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& /*rIntl*/ ) const +{ + if( !GetValue() ) + rText = EditResId( RID_SVXITEMS_TWOLINES_OFF ); + else + { + rText = EditResId( RID_SVXITEMS_TWOLINES ); + if( GetStartBracket() ) + rText = OUStringChar(GetStartBracket()) + rText; + if( GetEndBracket() ) + rText += OUStringChar(GetEndBracket()); + } + return true; +} + + +/************************************************************************* +|* class SvxTextRotateItem +*************************************************************************/ + +SvxTextRotateItem::SvxTextRotateItem(Degree10 nValue, const sal_uInt16 nW) + : SfxUInt16Item(nW, nValue.get()) +{ +} + +SvxTextRotateItem* SvxTextRotateItem::Clone(SfxItemPool*) const +{ + return new SvxTextRotateItem(*this); +} + +bool SvxTextRotateItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper&) const +{ + if (!GetValue()) + rText = EditResId(RID_SVXITEMS_TEXTROTATE_OFF); + else + { + rText = EditResId(RID_SVXITEMS_TEXTROTATE); + rText = rText.replaceFirst("$(ARG1)", + OUString::number(toDegrees(GetValue()))); + } + return true; +} + +bool SvxTextRotateItem::QueryValue(css::uno::Any& rVal, + sal_uInt8 nMemberId) const +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch (nMemberId) + { + case MID_ROTATE: + rVal <<= static_cast<sal_Int16>(GetValue()); + break; + default: + bRet = false; + break; + } + return bRet; +} + +bool SvxTextRotateItem::PutValue(const css::uno::Any& rVal, sal_uInt8 nMemberId) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch (nMemberId) + { + case MID_ROTATE: + { + sal_Int16 nVal = 0; + if ((rVal >>= nVal) && (0 == nVal || 900 == nVal || 2700 == nVal)) + SetValue(Degree10(nVal)); + else + bRet = false; + break; + } + default: + bRet = false; + } + return bRet; +} + +void SvxTextRotateItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxTextRotateItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::number(GetValue().get()).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + + +/************************************************************************* +|* class SvxCharRotateItem +*************************************************************************/ + +SvxCharRotateItem::SvxCharRotateItem( Degree10 nValue, + bool bFitIntoLine, + const sal_uInt16 nW ) + : SvxTextRotateItem(nValue, nW), bFitToLine( bFitIntoLine ) +{ +} + +SvxCharRotateItem* SvxCharRotateItem::Clone( SfxItemPool* ) const +{ + return new SvxCharRotateItem( *this ); +} + +bool SvxCharRotateItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper&) const +{ + if( !GetValue() ) + rText = EditResId( RID_SVXITEMS_CHARROTATE_OFF ); + else + { + rText = EditResId( RID_SVXITEMS_CHARROTATE ); + rText = rText.replaceFirst( "$(ARG1)", + OUString::number( toDegrees(GetValue()) )); + if( IsFitToLine() ) + rText += EditResId( RID_SVXITEMS_CHARROTATE_FITLINE ); + } + return true; +} + +bool SvxCharRotateItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 nMemberId ) const +{ + bool bRet = true; + switch(nMemberId & ~CONVERT_TWIPS) + { + case MID_ROTATE: + SvxTextRotateItem::QueryValue(rVal, nMemberId); + break; + case MID_FITTOLINE: + rVal <<= IsFitToLine(); + break; + default: + bRet = false; + break; + } + return bRet; +} + +bool SvxCharRotateItem::PutValue( const css::uno::Any& rVal, + sal_uInt8 nMemberId ) +{ + bool bRet = true; + switch(nMemberId & ~CONVERT_TWIPS) + { + case MID_ROTATE: + { + bRet = SvxTextRotateItem::PutValue(rVal, nMemberId); + break; + } + + case MID_FITTOLINE: + SetFitToLine( Any2Bool( rVal ) ); + break; + default: + bRet = false; + } + return bRet; +} + +bool SvxCharRotateItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + return SvxTextRotateItem::operator==( rItem ) && + IsFitToLine() == static_cast<const SvxCharRotateItem&>(rItem).IsFitToLine(); +} + +void SvxCharRotateItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxCharRotateItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::number(GetValue().get()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fitToLine"), BAD_CAST(OString::boolean(IsFitToLine()).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +/************************************************************************* +|* class SvxCharScaleItem +*************************************************************************/ + +SvxCharScaleWidthItem::SvxCharScaleWidthItem( sal_uInt16 nValue, + const sal_uInt16 nW ) + : SfxUInt16Item( nW, nValue ) +{ +} + +SvxCharScaleWidthItem* SvxCharScaleWidthItem::Clone( SfxItemPool* ) const +{ + return new SvxCharScaleWidthItem( *this ); +} + +bool SvxCharScaleWidthItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper&) const +{ + if( !GetValue() ) + rText = EditResId( RID_SVXITEMS_CHARSCALE_OFF ); + else + { + rText = EditResId( RID_SVXITEMS_CHARSCALE ); + rText = rText.replaceFirst( "$(ARG1)", + OUString::number( GetValue() )); + } + return true; +} + +bool SvxCharScaleWidthItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + // SfxUInt16Item::QueryValue returns sal_Int32 in Any now... (srx642w) + // where we still want this to be a sal_Int16 + sal_Int16 nValue = sal_Int16(); + if (rVal >>= nValue) + { + SetValue( static_cast<sal_uInt16>(nValue) ); + return true; + } + + SAL_WARN("editeng.items", "SvxCharScaleWidthItem::PutValue - Wrong type!" ); + return false; +} + +bool SvxCharScaleWidthItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + // SfxUInt16Item::QueryValue returns sal_Int32 in Any now... (srx642w) + // where we still want this to be a sal_Int16 + rVal <<= static_cast<sal_Int16>(GetValue()); + return true; +} + +/************************************************************************* +|* class SvxCharReliefItem +*************************************************************************/ + +SvxCharReliefItem::SvxCharReliefItem( FontRelief eValue, + const sal_uInt16 nId ) + : SfxEnumItem( nId, eValue ) +{ +} + +SvxCharReliefItem* SvxCharReliefItem::Clone( SfxItemPool * ) const +{ + return new SvxCharReliefItem( *this ); +} + +static TranslateId RID_SVXITEMS_RELIEF[] = +{ + RID_SVXITEMS_RELIEF_NONE, + RID_SVXITEMS_RELIEF_EMBOSSED, + RID_SVXITEMS_RELIEF_ENGRAVED +}; + +OUString SvxCharReliefItem::GetValueTextByPos(sal_uInt16 nPos) +{ + assert(nPos < SAL_N_ELEMENTS(RID_SVXITEMS_RELIEF) && "enum overflow"); + return EditResId(RID_SVXITEMS_RELIEF[nPos]); +} + +sal_uInt16 SvxCharReliefItem::GetValueCount() const +{ + return SAL_N_ELEMENTS(RID_SVXITEMS_RELIEF) - 1; +} + +bool SvxCharReliefItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( static_cast<sal_uInt16>(GetValue()) ); + return true; +} + +bool SvxCharReliefItem::PutValue( const css::uno::Any& rVal, + sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch( nMemberId ) + { + case MID_RELIEF: + { + sal_Int16 nVal = -1; + rVal >>= nVal; + if(nVal >= 0 && nVal <= sal_Int16(FontRelief::Engraved)) + SetValue( static_cast<FontRelief>(nVal) ); + else + bRet = false; + } + break; + default: + bRet = false; + break; + } + return bRet; +} + +bool SvxCharReliefItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch( nMemberId ) + { + case MID_RELIEF: + rVal <<= static_cast<sal_Int16>(GetValue()); + break; + default: + bRet = false; + break; + } + return bRet; +} + +/************************************************************************* +|* class SvxScriptSetItem +*************************************************************************/ + +SvxScriptSetItem::SvxScriptSetItem( sal_uInt16 nSlotId, SfxItemPool& rPool ) + : SfxSetItem( nSlotId, SfxItemSet( rPool, + svl::Items<SID_ATTR_CHAR_FONT, SID_ATTR_CHAR_FONT> )) +{ + sal_uInt16 nLatin, nAsian, nComplex; + GetWhichIds( nLatin, nAsian, nComplex ); + GetItemSet().MergeRange( nLatin, nLatin ); + GetItemSet().MergeRange( nAsian, nAsian ); + GetItemSet().MergeRange( nComplex, nComplex ); +} + +SvxScriptSetItem* SvxScriptSetItem::Clone( SfxItemPool * ) const +{ + SvxScriptSetItem* p = new SvxScriptSetItem( Which(), *GetItemSet().GetPool() ); + p->GetItemSet().Put( GetItemSet(), false ); + return p; +} + +const SfxPoolItem* SvxScriptSetItem::GetItemOfScriptSet( + const SfxItemSet& rSet, sal_uInt16 nId ) +{ + const SfxPoolItem* pI; + SfxItemState eSt = rSet.GetItemState( nId, false, &pI ); + if( SfxItemState::SET != eSt ) + pI = SfxItemState::DEFAULT == eSt ? &rSet.Get( nId ) : nullptr; + return pI; +} + +const SfxPoolItem* SvxScriptSetItem::GetItemOfScript( sal_uInt16 nSlotId, const SfxItemSet& rSet, SvtScriptType nScript ) +{ + sal_uInt16 nLatin, nAsian, nComplex; + GetWhichIds( nSlotId, rSet, nLatin, nAsian, nComplex ); + + const SfxPoolItem *pRet, *pAsn, *pCmplx; + if (nScript == SvtScriptType::ASIAN) + { + pRet = GetItemOfScriptSet( rSet, nAsian ); + } else if (nScript == SvtScriptType::COMPLEX) + { + pRet = GetItemOfScriptSet( rSet, nComplex ); + } else if (nScript == (SvtScriptType::LATIN|SvtScriptType::ASIAN)) + { + if( nullptr == (pRet = GetItemOfScriptSet( rSet, nLatin )) || + nullptr == (pAsn = GetItemOfScriptSet( rSet, nAsian )) || + *pRet != *pAsn ) + pRet = nullptr; + } else if (nScript == (SvtScriptType::LATIN|SvtScriptType::COMPLEX)) + { + if( nullptr == (pRet = GetItemOfScriptSet( rSet, nLatin )) || + nullptr == (pCmplx = GetItemOfScriptSet( rSet, nComplex )) || + *pRet != *pCmplx ) + pRet = nullptr; + } else if (nScript == (SvtScriptType::ASIAN|SvtScriptType::COMPLEX)) + { + if( nullptr == (pRet = GetItemOfScriptSet( rSet, nAsian )) || + nullptr == (pCmplx = GetItemOfScriptSet( rSet, nComplex )) || + *pRet != *pCmplx ) + pRet = nullptr; + } else if (nScript == (SvtScriptType::LATIN|SvtScriptType::ASIAN|SvtScriptType::COMPLEX)) + { + if( nullptr == (pRet = GetItemOfScriptSet( rSet, nLatin )) || + nullptr == (pAsn = GetItemOfScriptSet( rSet, nAsian )) || + nullptr == (pCmplx = GetItemOfScriptSet( rSet, nComplex )) || + *pRet != *pAsn || *pRet != *pCmplx ) + pRet = nullptr; + } else { + //no one valid -> match to latin + pRet = GetItemOfScriptSet( rSet, nLatin ); + } + return pRet; +} + +const SfxPoolItem* SvxScriptSetItem::GetItemOfScript( SvtScriptType nScript ) const +{ + return GetItemOfScript( Which(), GetItemSet(), nScript ); +} + +void SvxScriptSetItem::PutItemForScriptType( SvtScriptType nScriptType, + const SfxPoolItem& rItem ) +{ + sal_uInt16 nLatin, nAsian, nComplex; + GetWhichIds( nLatin, nAsian, nComplex ); + + if( SvtScriptType::LATIN & nScriptType ) + { + GetItemSet().Put( rItem.CloneSetWhich(nLatin) ); + } + if( SvtScriptType::ASIAN & nScriptType ) + { + GetItemSet().Put( rItem.CloneSetWhich(nAsian) ); + } + if( SvtScriptType::COMPLEX & nScriptType ) + { + GetItemSet().Put( rItem.CloneSetWhich(nComplex) ); + } +} + +void SvxScriptSetItem::GetWhichIds( sal_uInt16 nSlotId, const SfxItemSet& rSet, sal_uInt16& rLatin, sal_uInt16& rAsian, sal_uInt16& rComplex ) +{ + const SfxItemPool& rPool = *rSet.GetPool(); + GetSlotIds( nSlotId, rLatin, rAsian, rComplex ); + rLatin = rPool.GetWhich( rLatin ); + rAsian = rPool.GetWhich( rAsian ); + rComplex = rPool.GetWhich( rComplex ); +} + +void SvxScriptSetItem::GetWhichIds( sal_uInt16& rLatin, sal_uInt16& rAsian, + sal_uInt16& rComplex ) const +{ + GetWhichIds( Which(), GetItemSet(), rLatin, rAsian, rComplex ); +} + +void SvxScriptSetItem::GetSlotIds( sal_uInt16 nSlotId, sal_uInt16& rLatin, + sal_uInt16& rAsian, sal_uInt16& rComplex ) +{ + switch( nSlotId ) + { + default: + SAL_WARN( "editeng.items", "wrong SlotId for class SvxScriptSetItem" ); + [[fallthrough]]; // default to font - Id Range !! + + case SID_ATTR_CHAR_FONT: + rLatin = SID_ATTR_CHAR_FONT; + rAsian = SID_ATTR_CHAR_CJK_FONT; + rComplex = SID_ATTR_CHAR_CTL_FONT; + break; + case SID_ATTR_CHAR_FONTHEIGHT: + rLatin = SID_ATTR_CHAR_FONTHEIGHT; + rAsian = SID_ATTR_CHAR_CJK_FONTHEIGHT; + rComplex = SID_ATTR_CHAR_CTL_FONTHEIGHT; + break; + case SID_ATTR_CHAR_WEIGHT: + rLatin = SID_ATTR_CHAR_WEIGHT; + rAsian = SID_ATTR_CHAR_CJK_WEIGHT; + rComplex = SID_ATTR_CHAR_CTL_WEIGHT; + break; + case SID_ATTR_CHAR_POSTURE: + rLatin = SID_ATTR_CHAR_POSTURE; + rAsian = SID_ATTR_CHAR_CJK_POSTURE; + rComplex = SID_ATTR_CHAR_CTL_POSTURE; + break; + case SID_ATTR_CHAR_LANGUAGE: + rLatin = SID_ATTR_CHAR_LANGUAGE; + rAsian = SID_ATTR_CHAR_CJK_LANGUAGE; + rComplex = SID_ATTR_CHAR_CTL_LANGUAGE; + break; + case SID_ATTR_CHAR_SHADOWED: + rLatin = SID_ATTR_CHAR_SHADOWED; + rAsian = SID_ATTR_CHAR_SHADOWED; + rComplex = SID_ATTR_CHAR_SHADOWED; + break; + case SID_ATTR_CHAR_STRIKEOUT: + rLatin = SID_ATTR_CHAR_STRIKEOUT; + rAsian = SID_ATTR_CHAR_STRIKEOUT; + rComplex = SID_ATTR_CHAR_STRIKEOUT; + break; + } +} + +void GetDefaultFonts( SvxFontItem& rLatin, SvxFontItem& rAsian, SvxFontItem& rComplex ) +{ + const sal_uInt16 nItemCnt = 3; + + static struct + { + DefaultFontType nFontType; + LanguageType nLanguage; + } + const aOutTypeArr[ nItemCnt ] = + { + { DefaultFontType::LATIN_TEXT, LANGUAGE_ENGLISH_US }, + { DefaultFontType::CJK_TEXT, LANGUAGE_ENGLISH_US }, + { DefaultFontType::CTL_TEXT, LANGUAGE_ARABIC_SAUDI_ARABIA } + }; + + SvxFontItem* aItemArr[ nItemCnt ] = { &rLatin, &rAsian, &rComplex }; + + for ( sal_uInt16 n = 0; n < nItemCnt; ++n ) + { + vcl::Font aFont( OutputDevice::GetDefaultFont( aOutTypeArr[ n ].nFontType, + aOutTypeArr[ n ].nLanguage, + GetDefaultFontFlags::OnlyOne ) ); + SvxFontItem* pItem = aItemArr[ n ]; + pItem->SetFamily( aFont.GetFamilyType() ); + pItem->SetFamilyName( aFont.GetFamilyName() ); + pItem->SetStyleName( OUString() ); + pItem->SetPitch( aFont.GetPitch()); + pItem->SetCharSet(aFont.GetCharSet()); + } +} + + +bool SvxRsidItem::QueryValue( uno::Any& rVal, sal_uInt8 ) const +{ + rVal <<= GetValue(); + return true; +} + +bool SvxRsidItem::PutValue( const uno::Any& rVal, sal_uInt8 ) +{ + sal_uInt32 nRsid = 0; + if( !( rVal >>= nRsid ) ) + return false; + + SetValue( nRsid ); + return true; +} + +SvxRsidItem* SvxRsidItem::Clone( SfxItemPool * ) const +{ + return new SvxRsidItem( *this ); +} + +bool SvxRsidItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText.clear(); + return false; +} + +void SvxRsidItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxRsidItem")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("whichId"), "%d", Which()); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("value"), "%" SAL_PRIuUINT32, GetValue()); + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/writingmodeitem.cxx b/editeng/source/items/writingmodeitem.cxx new file mode 100644 index 000000000..252c8c055 --- /dev/null +++ b/editeng/source/items/writingmodeitem.cxx @@ -0,0 +1,94 @@ +/* -*- 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 <editeng/writingmodeitem.hxx> +#include <editeng/frmdir.hxx> +#include <editeng/eerdll.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::text; + + +SvxWritingModeItem::SvxWritingModeItem( WritingMode eValue, sal_uInt16 _nWhich ) + : SfxUInt16Item( _nWhich, static_cast<sal_uInt16>(eValue) ) +{ +} + +SvxWritingModeItem::~SvxWritingModeItem() +{ +} + +SvxWritingModeItem* SvxWritingModeItem::Clone( SfxItemPool * ) const +{ + return new SvxWritingModeItem( *this ); +} + +bool SvxWritingModeItem::GetPresentation( SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, + MapUnit /*ePresMetric*/, + OUString &rText, + const IntlWrapper& ) const +{ + rText = EditResId(getFrmDirResId(static_cast<int>(GetValue()))); + return true; +} + +bool SvxWritingModeItem::PutValue( const css::uno::Any& rVal, sal_uInt8 ) +{ + sal_Int32 nVal = 0; + bool bRet = ( rVal >>= nVal ); + + if( !bRet ) + { + WritingMode eMode; + bRet = rVal >>= eMode; + + if( bRet ) + { + nVal = static_cast<sal_Int32>(eMode); + } + } + + if( bRet ) + { + switch( static_cast<WritingMode>(nVal) ) + { + case WritingMode_LR_TB: + case WritingMode_RL_TB: + case WritingMode_TB_RL: + SetValue( static_cast<sal_uInt16>(nVal) ); + bRet = true; + break; + default: + bRet = false; + break; + } + } + + return bRet; +} + +bool SvxWritingModeItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 ) const +{ + rVal <<= GetValue(); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/xmlcnitm.cxx b/editeng/source/items/xmlcnitm.cxx new file mode 100644 index 000000000..50733ade4 --- /dev/null +++ b/editeng/source/items/xmlcnitm.cxx @@ -0,0 +1,206 @@ +/* -*- 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/servicehelper.hxx> +#include <com/sun/star/xml/AttributeData.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <o3tl/any.hxx> +#include <xmloff/xmlcnimp.hxx> +#include <xmloff/unoatrcn.hxx> +#include <editeng/xmlcnitm.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::xml; + + +SvXMLAttrContainerItem::SvXMLAttrContainerItem( sal_uInt16 _nWhich ) : + SfxPoolItem( _nWhich ) +{ +} + +SvXMLAttrContainerItem::SvXMLAttrContainerItem( + const SvXMLAttrContainerItem& rItem ) : + SfxPoolItem( rItem ), + maContainerData( rItem.maContainerData ) +{ +} + +SvXMLAttrContainerItem::~SvXMLAttrContainerItem() +{ +} + +bool SvXMLAttrContainerItem::operator==( const SfxPoolItem& rItem ) const +{ + return SfxPoolItem::operator==(rItem) && + maContainerData == static_cast<const SvXMLAttrContainerItem&>(rItem).maContainerData; +} + +bool SvXMLAttrContainerItem::GetPresentation( + SfxItemPresentation /*ePresentation*/, + MapUnit /*eCoreMetric*/, + MapUnit /*ePresentationMetric*/, + OUString & /*rText*/, + const IntlWrapper& /*rIntlWrapper*/ ) const +{ + return false; +} + +bool SvXMLAttrContainerItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + Reference<XNameContainer> xContainer + = new SvUnoAttributeContainer(std::make_unique<SvXMLAttrContainerData>(maContainerData)); + + rVal <<= xContainer; + return true; +} + +bool SvXMLAttrContainerItem::PutValue( const css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + Reference<XUnoTunnel> xTunnel(rVal, UNO_QUERY); + if (auto pContainer = comphelper::getFromUnoTunnel<SvUnoAttributeContainer>(xTunnel)) + { + maContainerData = *pContainer->GetContainerImpl(); + } + else + { + SvXMLAttrContainerData aNewImpl; + + try + { + Reference<XNameContainer> xContainer( rVal, UNO_QUERY ); + if( !xContainer.is() ) + return false; + + const Sequence< OUString > aNameSequence( xContainer->getElementNames() ); + const OUString* pNames = aNameSequence.getConstArray(); + const sal_Int32 nCount = aNameSequence.getLength(); + Any aAny; + sal_Int32 nAttr; + + for( nAttr = 0; nAttr < nCount; nAttr++ ) + { + const OUString aName( *pNames++ ); + + aAny = xContainer->getByName( aName ); + auto pData = o3tl::tryAccess<AttributeData>(aAny); + if( !pData ) + return false; + + sal_Int32 pos = aName.indexOf( ':' ); + if( pos != -1 ) + { + const OUString aPrefix( aName.copy( 0, pos )); + const OUString aLName( aName.copy( pos+1 )); + + if( pData->Namespace.isEmpty() ) + { + if( !aNewImpl.AddAttr( aPrefix, aLName, pData->Value ) ) + break; + } + else + { + if( !aNewImpl.AddAttr( aPrefix, pData->Namespace, aLName, pData->Value ) ) + break; + } + } + else + { + if( !aNewImpl.AddAttr( aName, pData->Value ) ) + break; + } + } + + if( nAttr == nCount ) + maContainerData = std::move(aNewImpl); + else + return false; + } + catch(...) + { + return false; + } + } + return true; +} + + +bool SvXMLAttrContainerItem::AddAttr( const OUString& rLName, + const OUString& rValue ) +{ + return maContainerData.AddAttr( rLName, rValue ); +} + +bool SvXMLAttrContainerItem::AddAttr( const OUString& rPrefix, + const OUString& rNamespace, const OUString& rLName, + const OUString& rValue ) +{ + return maContainerData.AddAttr( rPrefix, rNamespace, rLName, rValue ); +} + +sal_uInt16 SvXMLAttrContainerItem::GetAttrCount() const +{ + return static_cast<sal_uInt16>(maContainerData.GetAttrCount()); +} + +OUString SvXMLAttrContainerItem::GetAttrNamespace( sal_uInt16 i ) const +{ + return maContainerData.GetAttrNamespace( i ); +} + +OUString SvXMLAttrContainerItem::GetAttrPrefix( sal_uInt16 i ) const +{ + return maContainerData.GetAttrPrefix( i ); +} + +const OUString& SvXMLAttrContainerItem::GetAttrLName( sal_uInt16 i ) const +{ + return maContainerData.GetAttrLName( i ); +} + +const OUString& SvXMLAttrContainerItem::GetAttrValue( sal_uInt16 i ) const +{ + return maContainerData.GetAttrValue( i ); +} + + +sal_uInt16 SvXMLAttrContainerItem::GetFirstNamespaceIndex() const +{ + return maContainerData.GetFirstNamespaceIndex(); +} + +sal_uInt16 SvXMLAttrContainerItem::GetNextNamespaceIndex( sal_uInt16 nIdx ) const +{ + return maContainerData.GetNextNamespaceIndex( nIdx ); +} + +const OUString& SvXMLAttrContainerItem::GetNamespace( sal_uInt16 i ) const +{ + return maContainerData.GetNamespace( i ); +} + +const OUString& SvXMLAttrContainerItem::GetPrefix( sal_uInt16 i ) const +{ + return maContainerData.GetPrefix( i ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/lookuptree/Trie.cxx b/editeng/source/lookuptree/Trie.cxx new file mode 100644 index 000000000..c994fa7cf --- /dev/null +++ b/editeng/source/lookuptree/Trie.cxx @@ -0,0 +1,189 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <string_view> + +#include <editeng/Trie.hxx> + +namespace editeng +{ + +/* TrieNode */ + +struct TrieNode final +{ + static const int LATIN_ARRAY_SIZE = 26; + + sal_Unicode mCharacter; + bool mMarker; + std::vector<std::unique_ptr<TrieNode>> mChildren; + std::unique_ptr<TrieNode> mLatinArray[LATIN_ARRAY_SIZE]; + + explicit TrieNode(sal_Unicode aCharacter = '\0'); + + void markWord(); + TrieNode* findChild(sal_Unicode aCharacter); + TrieNode* traversePath(const OUString& sPath); + void addNewChild(TrieNode* pChild); + void collectSuggestions(std::u16string_view sPath, std::vector<OUString>& rSuggestionList); + static void collectSuggestionsForCurrentNode(TrieNode* pCurrent, std::u16string_view sPath, std::vector<OUString>& rSuggestionList); +}; + +TrieNode::TrieNode(sal_Unicode aCharacter) : + mCharacter(aCharacter), + mMarker(false) +{ + for (auto & i : mLatinArray) + { + i = nullptr; + } +} + +void TrieNode::markWord() +{ + mMarker = true; +} + +void TrieNode::addNewChild(TrieNode* pChild) +{ + if (pChild->mCharacter >= 'a' && + pChild->mCharacter <= 'z') + { + mLatinArray[pChild->mCharacter - u'a'].reset(pChild); + } + else + { + mChildren.push_back(std::unique_ptr<TrieNode>(pChild)); + } +} + +TrieNode* TrieNode::findChild(sal_Unicode aInputCharacter) +{ + if (aInputCharacter >= 'a' && + aInputCharacter <= 'z') + { + return mLatinArray[aInputCharacter - u'a'].get(); + } + + for(auto const & pCurrent : mChildren) + { + if ( pCurrent->mCharacter == aInputCharacter ) + return pCurrent.get(); + } + + return nullptr; +} + +void TrieNode::collectSuggestions(std::u16string_view sPath, std::vector<OUString>& rSuggestionList) +{ + // first traverse nodes for alphabet characters + for (auto const & pCurrent : mLatinArray) + { + if (pCurrent != nullptr) + collectSuggestionsForCurrentNode(pCurrent.get(), sPath, rSuggestionList); + } + + // traverse nodes for other characters + for(auto const & pCurrent : mChildren) + { + if (pCurrent != nullptr) + collectSuggestionsForCurrentNode(pCurrent.get(), sPath, rSuggestionList); + } +} + +void TrieNode::collectSuggestionsForCurrentNode(TrieNode* pCurrent, std::u16string_view sPath, std::vector<OUString>& rSuggestionList) +{ + OUString aStringPath = sPath + OUStringChar(pCurrent->mCharacter); + if(pCurrent->mMarker) + { + rSuggestionList.push_back(aStringPath); + } + // recursively descend tree + pCurrent->collectSuggestions(aStringPath, rSuggestionList); +} + +TrieNode* TrieNode::traversePath(const OUString& sPath) +{ + TrieNode* pCurrent = this; + + for ( sal_Int32 i = 0; i < sPath.getLength(); i++ ) + { + sal_Unicode aCurrentChar = sPath[i]; + pCurrent = pCurrent->findChild(aCurrentChar); + if ( pCurrent == nullptr ) + return nullptr; + } + + return pCurrent; +} + +/* TRIE */ + +Trie::Trie() : + mRoot(new TrieNode()) +{} + +Trie::~Trie() +{} + +void Trie::insert(const OUString& sInputString) const +{ + // adding an empty word is not allowed + if ( sInputString.isEmpty() ) + { + return; + } + + // traverse the input string and modify the tree with new nodes / characters + + TrieNode* pCurrent = mRoot.get(); + sal_Unicode aCurrentChar; + + for ( sal_Int32 i = 0; i < sInputString.getLength(); i++ ) + { + aCurrentChar = sInputString[i]; + TrieNode* pChild = pCurrent->findChild(aCurrentChar); + if ( pChild == nullptr ) + { + TrieNode* pNewNode = new TrieNode(aCurrentChar); + pCurrent->addNewChild(pNewNode); + pCurrent = pNewNode; + } + else + { + pCurrent = pChild; + } + } + + pCurrent->markWord(); +} + +void Trie::findSuggestions(const OUString& sWordPart, std::vector<OUString>& rSuggestionList) const +{ + TrieNode* pNode = mRoot->traversePath(sWordPart); + + if (pNode != nullptr) + { + pNode->collectSuggestions(sWordPart, rSuggestionList); + } +} + +size_t Trie::size() const +{ + if (!mRoot) + return 0; + std::vector<OUString> entries; + mRoot->collectSuggestions(std::u16string_view(), entries); + return entries.size(); +} + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectExport.cxx b/editeng/source/misc/SvXMLAutoCorrectExport.cxx new file mode 100644 index 000000000..8faf43411 --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectExport.cxx @@ -0,0 +1,110 @@ +/* -*- 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 "SvXMLAutoCorrectExport.hxx" + +#include <com/sun/star/util/MeasureUnit.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <xmloff/namespacemap.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <xmloff/xmltoken.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +SvXMLAutoCorrectExport::SvXMLAutoCorrectExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvxAutocorrWordList * pNewAutocorr_List, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler) +: SvXMLExport( xContext, "", rFileName, util::MeasureUnit::CM, rHandler ), + pAutocorr_List( pNewAutocorr_List ) +{ + GetNamespaceMap_().Add( GetXMLToken ( XML_NP_BLOCK_LIST), + GetXMLToken ( XML_N_BLOCK_LIST ), + XML_NAMESPACE_BLOCKLIST ); +} + +ErrCode SvXMLAutoCorrectExport::exportDoc(enum XMLTokenEnum /*eClass*/) +{ + GetDocHandler()->startDocument(); + + addChaffWhenEncryptedStorage(); + + AddAttribute ( XML_NAMESPACE_NONE, + GetNamespaceMap_().GetAttrNameByKey ( XML_NAMESPACE_BLOCKLIST ), + GetNamespaceMap_().GetNameByKey ( XML_NAMESPACE_BLOCKLIST ) ); + { + SvXMLElementExport aRoot (*this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK_LIST, true, true); + const SvxAutocorrWordList::AutocorrWordSetType& rContent = pAutocorr_List->getSortedContent(); + for (auto const& content : rContent) + { + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_ABBREVIATED_NAME, + content.GetShort()); + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_NAME, + content.IsTextOnly() ? content.GetLong() : content.GetShort()); + + SvXMLElementExport aBlock( *this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK, true, true); + } + } + GetDocHandler()->endDocument(); + return ERRCODE_NONE; +} + +SvXMLExceptionListExport::SvXMLExceptionListExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvStringsISortDtor &rNewList, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler) +: SvXMLExport( xContext, "", rFileName, util::MeasureUnit::CM, rHandler ), + rList( rNewList ) +{ + GetNamespaceMap_().Add( GetXMLToken ( XML_NP_BLOCK_LIST ), + GetXMLToken ( XML_N_BLOCK_LIST ), + XML_NAMESPACE_BLOCKLIST ); +} + +ErrCode SvXMLExceptionListExport::exportDoc(enum XMLTokenEnum /*eClass*/) +{ + GetDocHandler()->startDocument(); + + addChaffWhenEncryptedStorage(); + + AddAttribute ( XML_NAMESPACE_NONE, + GetNamespaceMap_().GetAttrNameByKey ( XML_NAMESPACE_BLOCKLIST ), + GetNamespaceMap_().GetNameByKey ( XML_NAMESPACE_BLOCKLIST ) ); + { + SvXMLElementExport aRoot (*this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK_LIST, true, true); + sal_uInt16 nBlocks= rList.size(); + for ( sal_uInt16 i = 0; i < nBlocks; i++) + { + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_ABBREVIATED_NAME, + rList[i] ); + SvXMLElementExport aBlock( *this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK, true, true); + } + } + GetDocHandler()->endDocument(); + return ERRCODE_NONE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectExport.hxx b/editeng/source/misc/SvXMLAutoCorrectExport.hxx new file mode 100644 index 000000000..58570e3ad --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectExport.hxx @@ -0,0 +1,60 @@ +/* -*- 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 <xmloff/xmlexp.hxx> +#include <editeng/svxacorr.hxx> + +class SvXMLAutoCorrectExport : public SvXMLExport +{ +private: + const SvxAutocorrWordList *pAutocorr_List; +public: + SvXMLAutoCorrectExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvxAutocorrWordList * pNewAutocorr_List, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler); + + ErrCode exportDoc(enum ::xmloff::token::XMLTokenEnum eClass = ::xmloff::token::XML_TOKEN_INVALID) override; + void ExportAutoStyles_() override {} + void ExportMasterStyles_ () override {} + void ExportContent_() override {} +}; + +class SvStringsISortDtor; + +class SvXMLExceptionListExport : public SvXMLExport +{ +private: + const SvStringsISortDtor & rList; +public: + SvXMLExceptionListExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvStringsISortDtor &rNewList, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler); + + ErrCode exportDoc(enum ::xmloff::token::XMLTokenEnum eClass = ::xmloff::token::XML_TOKEN_INVALID) override; + void ExportAutoStyles_() override {} + void ExportMasterStyles_ () override {} + void ExportContent_() override {} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectImport.cxx b/editeng/source/misc/SvXMLAutoCorrectImport.cxx new file mode 100644 index 000000000..baeef8861 --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectImport.cxx @@ -0,0 +1,163 @@ +/* -*- 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 <utility> + +#include "SvXMLAutoCorrectImport.hxx" +#include "SvXMLAutoCorrectTokenHandler.hxx" + +using namespace css; +using namespace css::xml::sax; + +SvXMLAutoCorrectImport::SvXMLAutoCorrectImport( + const uno::Reference< uno::XComponentContext > & xContext, + SvxAutocorrWordList *pNewAutocorr_List, + SvxAutoCorrect &rNewAutoCorrect, + css::uno::Reference < css::embed::XStorage > xNewStorage) +: SvXMLImport( xContext, "" ), + pAutocorr_List (pNewAutocorr_List), + rAutoCorrect ( rNewAutoCorrect ), + xStorage (std::move( xNewStorage )) +{ +} + +SvXMLAutoCorrectImport::~SvXMLAutoCorrectImport() noexcept +{ +} + +SvXMLImportContext *SvXMLAutoCorrectImport::CreateFastContext( sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & /*xAttrList*/ ) +{ + if( Element == SvXMLAutoCorrectToken::BLOCKLIST ) + return new SvXMLWordListContext( *this ); + return nullptr; +} + +SvXMLWordListContext::SvXMLWordListContext( + SvXMLAutoCorrectImport& rImport ) : + SvXMLImportContext ( rImport ), + rLocalRef(rImport) +{ + rLocalRef.rAutoCorrect.refreshBlockList( rLocalRef.xStorage ); +} + +css::uno::Reference<XFastContextHandler> SAL_CALL SvXMLWordListContext::createFastChildContext( + sal_Int32 Element, const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) +{ + if ( Element == SvXMLAutoCorrectToken::BLOCK ) + return new SvXMLWordContext (rLocalRef, xAttrList); + return nullptr; +} + +SvXMLWordListContext::~SvXMLWordListContext() +{ +} + +SvXMLWordContext::SvXMLWordContext( + SvXMLAutoCorrectImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) : + SvXMLImportContext ( rImport ) +{ + OUString sWrong, sRight; + if ( xAttrList.is() && xAttrList->hasAttribute( SvXMLAutoCorrectToken::ABBREVIATED_NAME ) ) + sWrong = xAttrList->getValue( SvXMLAutoCorrectToken::ABBREVIATED_NAME ); + + if ( xAttrList.is() && xAttrList->hasAttribute( SvXMLAutoCorrectToken::NAME ) ) + sRight = xAttrList->getValue( SvXMLAutoCorrectToken::NAME ); + + if ( sWrong.isEmpty() || sRight.isEmpty()) + return; + + bool bOnlyTxt = sRight != sWrong; + if( !bOnlyTxt ) + { + const OUString sLongSave( sRight ); + if( !rImport.rAutoCorrect.GetLongText( sWrong, sRight ) && + !sLongSave.isEmpty() ) + { + sRight = sLongSave; + bOnlyTxt = true; + } + } + rImport.pAutocorr_List->LoadEntry( sWrong, sRight, bOnlyTxt ); +} + +SvXMLWordContext::~SvXMLWordContext() +{ +} + +SvXMLExceptionListImport::SvXMLExceptionListImport( + const uno::Reference< uno::XComponentContext > & xContext, + SvStringsISortDtor & rNewList ) +: SvXMLImport( xContext, "" ), + rList (rNewList) +{ +} + +SvXMLExceptionListImport::~SvXMLExceptionListImport() noexcept +{ +} + +SvXMLImportContext *SvXMLExceptionListImport::CreateFastContext(sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & /*xAttrList*/ ) +{ + if( Element == SvXMLAutoCorrectToken::BLOCKLIST ) + return new SvXMLExceptionListContext( *this ); + return nullptr; +} + +SvXMLExceptionListContext::SvXMLExceptionListContext( + SvXMLExceptionListImport& rImport ) : + SvXMLImportContext ( rImport ), + rLocalRef(rImport) +{ +} + +css::uno::Reference<xml::sax::XFastContextHandler> SAL_CALL SvXMLExceptionListContext::createFastChildContext( + sal_Int32 Element, const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) +{ + if ( Element == SvXMLAutoCorrectToken::BLOCK ) + return new SvXMLExceptionContext (rLocalRef, xAttrList); + return nullptr; +} + +SvXMLExceptionListContext::~SvXMLExceptionListContext() +{ +} + +SvXMLExceptionContext::SvXMLExceptionContext( + SvXMLExceptionListImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) : + SvXMLImportContext ( rImport ) +{ + OUString sWord; + if( xAttrList.is() && xAttrList->hasAttribute( SvXMLAutoCorrectToken::ABBREVIATED_NAME ) ) + sWord = xAttrList->getValue( SvXMLAutoCorrectToken::ABBREVIATED_NAME ); + + if (sWord.isEmpty()) + return; + + rImport.rList.insert( sWord ); +} + +SvXMLExceptionContext::~SvXMLExceptionContext() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectImport.hxx b/editeng/source/misc/SvXMLAutoCorrectImport.hxx new file mode 100644 index 000000000..961e6963d --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectImport.hxx @@ -0,0 +1,113 @@ +/* -*- 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 <sot/storage.hxx> +#include <xmloff/xmlictxt.hxx> +#include <xmloff/xmlimp.hxx> +#include <editeng/svxacorr.hxx> + +class SvXMLAutoCorrectImport : public SvXMLImport +{ +protected: + + // This method is called after the namespace map has been updated, but + // before a context for the current element has been pushed. + virtual SvXMLImportContext *CreateFastContext( sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; + +public: + SvxAutocorrWordList *pAutocorr_List; + SvxAutoCorrect &rAutoCorrect; + css::uno::Reference < css::embed::XStorage > xStorage; + + SvXMLAutoCorrectImport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + SvxAutocorrWordList *pNewAutocorr_List, + SvxAutoCorrect &rNewAutoCorrect, + css::uno::Reference < css::embed::XStorage > xNewStorage); + + virtual ~SvXMLAutoCorrectImport() noexcept override; +}; + +class SvXMLWordListContext : public SvXMLImportContext +{ +private: + SvXMLAutoCorrectImport & rLocalRef; +public: + SvXMLWordListContext ( SvXMLAutoCorrectImport& rImport ); + + virtual css::uno::Reference<XFastContextHandler> SAL_CALL createFastChildContext( sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; + + virtual ~SvXMLWordListContext() override; +}; + +class SvXMLWordContext : public SvXMLImportContext +{ +public: + SvXMLWordContext ( SvXMLAutoCorrectImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ); + + virtual ~SvXMLWordContext() override; +}; + + +class SvXMLExceptionListImport : public SvXMLImport +{ +protected: + + // This method is called after the namespace map has been updated, but + // before a context for the current element has been pushed. + virtual SvXMLImportContext *CreateFastContext( sal_Int32 Element, const css::uno::Reference< + css::xml::sax::XFastAttributeList > & xAttrList ) override; +public: + SvStringsISortDtor &rList; + + SvXMLExceptionListImport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + SvStringsISortDtor & rNewList ); + + virtual ~SvXMLExceptionListImport() noexcept override; +}; + +class SvXMLExceptionListContext : public SvXMLImportContext +{ +private: + SvXMLExceptionListImport & rLocalRef; +public: + SvXMLExceptionListContext ( SvXMLExceptionListImport& rImport ); + + virtual css::uno::Reference<XFastContextHandler> SAL_CALL createFastChildContext( sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; + + virtual ~SvXMLExceptionListContext() override; +}; + +class SvXMLExceptionContext : public SvXMLImportContext +{ +public: + SvXMLExceptionContext ( SvXMLExceptionListImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ); + + virtual ~SvXMLExceptionContext() override; +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectTokenHandler.cxx b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.cxx new file mode 100644 index 000000000..4bdadcdcd --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.cxx @@ -0,0 +1,54 @@ +/* -*- 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/. + */ + +#include "SvXMLAutoCorrectTokenHandler.hxx" +#include <xmloff/xmltoken.hxx> +#if defined __clang__ +#if __has_warning("-Wdeprecated-register") +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-register" +#endif +#endif +#include <tokens.cxx> +#if defined __clang__ +#if __has_warning("-Wdeprecated-register") +#pragma GCC diagnostic pop +#endif +#endif + +using namespace css::uno; +using namespace ::xmloff::token; + +SvXMLAutoCorrectTokenHandler::SvXMLAutoCorrectTokenHandler() +{ +} + +SvXMLAutoCorrectTokenHandler::~SvXMLAutoCorrectTokenHandler() +{ +} + +sal_Int32 SAL_CALL SvXMLAutoCorrectTokenHandler::getTokenFromUTF8( const Sequence< sal_Int8 >& Identifier ) +{ + return getTokenDirect( reinterpret_cast< const char* >( Identifier.getConstArray() ), Identifier.getLength() ); +} + +Sequence< sal_Int8 > SAL_CALL SvXMLAutoCorrectTokenHandler::getUTF8Identifier( sal_Int32 ) +{ + return Sequence< sal_Int8 >(); +} + +sal_Int32 SvXMLAutoCorrectTokenHandler::getTokenDirect( const char *pTag, sal_Int32 nLength ) const +{ + if( !nLength ) + nLength = strlen( pTag ); + const struct xmltoken* pToken = Perfect_Hash::in_word_set( pTag, nLength ); + return pToken ? pToken->nToken : XML_TOKEN_INVALID; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectTokenHandler.hxx b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.hxx new file mode 100644 index 000000000..352f77385 --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.hxx @@ -0,0 +1,49 @@ +/* -*- 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/. + */ + +#ifndef EDITENG_SOURCE_MISC_SVXMLAUTOCORRECTTOKENHANDLER_HXX +#define EDITENG_SOURCE_MISC_SVXMLAUTOCORRECTTOKENHANDLER_HXX + +#include <sal/types.h> +#include <xmloff/xmltoken.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <com/sun/star/xml/sax/FastToken.hpp> +#include <sax/fastattribs.hxx> + +using namespace css::xml::sax; +using namespace ::xmloff::token; + +enum SvXMLAutoCorrectToken : sal_Int32 +{ + NAMESPACE = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST, //65553 + ABBREVIATED_NAME = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_ABBREVIATED_NAME, //65655 + BLOCK = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_BLOCK, //65791 + BLOCKLIST = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_BLOCK_LIST, //65792 + NAME = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_NAME //66737 +}; + +class SvXMLAutoCorrectTokenHandler : + public sax_fastparser::FastTokenHandlerBase +{ +public: + explicit SvXMLAutoCorrectTokenHandler(); + virtual ~SvXMLAutoCorrectTokenHandler() override; + + //XFastTokenHandler + virtual sal_Int32 SAL_CALL getTokenFromUTF8( const css::uno::Sequence< sal_Int8 >& Identifier ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getUTF8Identifier( sal_Int32 Token ) override; + + // Much faster direct C++ shortcut to the method that matters + virtual sal_Int32 getTokenDirect( const char *pToken, sal_Int32 nLength ) const override; +}; + +#endif // EDITENG_SOURCE_MISC_SVXMLAUTOCORRECTTOKENHANDLER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ + diff --git a/editeng/source/misc/acorrcfg.cxx b/editeng/source/misc/acorrcfg.cxx new file mode 100644 index 000000000..4508a1f6e --- /dev/null +++ b/editeng/source/misc/acorrcfg.cxx @@ -0,0 +1,608 @@ +/* -*- 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 <comphelper/processfactory.hxx> +#include <editeng/acorrcfg.hxx> +#include <o3tl/any.hxx> +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/ucbhelper.hxx> + +#include <editeng/svxacorr.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +using namespace utl; +using namespace com::sun::star; +using namespace com::sun::star::uno; + + +SvxAutoCorrCfg::SvxAutoCorrCfg() : + aBaseConfig(*this), + aSwConfig(*this), + bFileRel(true), + bNetRel(true), + bAutoTextTip(true), + bAutoTextPreview(false), + bAutoFmtByInput(true), + bSearchInAllCategories(false) +{ + SvtPathOptions aPathOpt; + OUString sSharePath, sUserPath; + OUString const & sAutoPath( aPathOpt.GetAutoCorrectPath() ); + + sSharePath = sAutoPath.getToken(0, ';'); + sUserPath = sAutoPath.getToken(1, ';'); + + //fdo#67743 ensure the userdir exists so that any later attempt to copy the + //shared autocorrect file into the user dir will succeed + ::ucbhelper::Content aContent; + Reference < ucb::XCommandEnvironment > xEnv; + ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, sUserPath, aContent); + + for( OUString* pS : { &sSharePath, &sUserPath } ) + { + INetURLObject aPath( *pS ); + aPath.insertName(u"acor"); + *pS = aPath.GetMainURL(INetURLObject::DecodeMechanism::ToIUri); + } + pAutoCorrect.reset( new SvxAutoCorrect( sSharePath, sUserPath ) ); + + aBaseConfig.Load(true); + aSwConfig.Load(true); +} + +SvxAutoCorrCfg::~SvxAutoCorrCfg() +{ +} + +void SvxAutoCorrCfg::SetAutoCorrect(SvxAutoCorrect *const pNew) +{ + if (pNew != pAutoCorrect.get()) + { + if (pNew && (pAutoCorrect->GetFlags() != pNew->GetFlags())) + { + aBaseConfig.SetModified(); + aSwConfig.SetModified(); + } + pAutoCorrect.reset( pNew ); + } +} + +Sequence<OUString> SvxBaseAutoCorrCfg::GetPropertyNames() +{ + static const char* aPropNames[] = + { + "Exceptions/TwoCapitalsAtStart", // 0 + "Exceptions/CapitalAtStartSentence", // 1 + "UseReplacementTable", // 2 + "TwoCapitalsAtStart", // 3 + "CapitalAtStartSentence", // 4 + "ChangeUnderlineWeight", // 5 + "SetInetAttribute", // 6 + "ChangeOrdinalNumber", // 7 + "AddNonBreakingSpace", // 8 + "ChangeDash", // 9 + "RemoveDoubleSpaces", // 10 + "ReplaceSingleQuote", // 11 + "SingleQuoteAtStart", // 12 + "SingleQuoteAtEnd", // 13 + "ReplaceDoubleQuote", // 14 + "DoubleQuoteAtStart", // 15 + "DoubleQuoteAtEnd", // 16 + "CorrectAccidentalCapsLock", // 17 + "TransliterateRTL", // 18 + "ChangeAngleQuotes" // 19 + }; + const int nCount = 20; + Sequence<OUString> aNames(nCount); + OUString* pNames = aNames.getArray(); + for(int i = 0; i < nCount; i++) + pNames[i] = OUString::createFromAscii(aPropNames[i]); + return aNames; +} + +void SvxBaseAutoCorrCfg::Load(bool bInit) +{ + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues = GetProperties(aNames); + if(bInit) + EnableNotification(aNames); + const Any* pValues = aValues.getConstArray(); + DBG_ASSERT(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() != aNames.getLength()) + return; + + ACFlags nFlags = ACFlags::NONE; // default all off + sal_Int32 nTemp = 0; + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case 0: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::SaveWordCplSttLst; + break;//"Exceptions/TwoCapitalsAtStart", + case 1: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::SaveWordWordStartLst; + break;//"Exceptions/CapitalAtStartSentence", + case 2: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::Autocorrect; + break;//"UseReplacementTable", + case 3: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::CapitalStartWord; + break;//"TwoCapitalsAtStart", + case 4: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::CapitalStartSentence; + break;//"CapitalAtStartSentence", + case 5: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgWeightUnderl; + break;//"ChangeUnderlineWeight", + case 6: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::SetINetAttr; + break;//"SetInetAttribute", + case 7: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgOrdinalNumber; + break;//"ChangeOrdinalNumber", + case 8: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::AddNonBrkSpace; + break;//"AddNonBreakingSpace" + case 9: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgToEnEmDash; + break;//"ChangeDash", + case 10: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::IgnoreDoubleSpace; + break;//"RemoveDoubleSpaces", + case 11: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgSglQuotes; + break;//"ReplaceSingleQuote", + case 12: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetStartSingleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"SingleQuoteAtStart", + case 13: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetEndSingleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"SingleQuoteAtEnd", + case 14: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgQuotes; + break;//"ReplaceDoubleQuote", + case 15: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetStartDoubleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"DoubleQuoteAtStart", + case 16: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetEndDoubleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"DoubleQuoteAtEnd" + case 17: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::CorrectCapsLock; + break;//"CorrectAccidentalCapsLock" + case 18: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::TransliterateRTL; + break;//"TransliterateRTL" + case 19: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgAngleQuotes; + break;//"ChangeAngleQuotes" + } + } + } + if( nFlags != ACFlags::NONE ) + rParent.pAutoCorrect->SetAutoCorrFlag( nFlags ); + rParent.pAutoCorrect->SetAutoCorrFlag( ( static_cast<ACFlags>(0xffff) & ~nFlags ), false ); +} + +SvxBaseAutoCorrCfg::SvxBaseAutoCorrCfg(SvxAutoCorrCfg& rPar) : + utl::ConfigItem("Office.Common/AutoCorrect"), + rParent(rPar) +{ +} + +SvxBaseAutoCorrCfg::~SvxBaseAutoCorrCfg() +{ +} + +void SvxBaseAutoCorrCfg::ImplCommit() +{ + const ACFlags nFlags = rParent.pAutoCorrect->GetFlags(); + PutProperties( + GetPropertyNames(), + {css::uno::Any(bool(nFlags & ACFlags::SaveWordCplSttLst)), + // "Exceptions/TwoCapitalsAtStart" + css::uno::Any(bool(nFlags & ACFlags::SaveWordWordStartLst)), + // "Exceptions/CapitalAtStartSentence" + css::uno::Any(bool(nFlags & ACFlags::Autocorrect)), // "UseReplacementTable" + css::uno::Any(bool(nFlags & ACFlags::CapitalStartWord)), + // "TwoCapitalsAtStart" + css::uno::Any(bool(nFlags & ACFlags::CapitalStartSentence)), + // "CapitalAtStartSentence" + css::uno::Any(bool(nFlags & ACFlags::ChgWeightUnderl)), + // "ChangeUnderlineWeight" + css::uno::Any(bool(nFlags & ACFlags::SetINetAttr)), // "SetInetAttribute" + css::uno::Any(bool(nFlags & ACFlags::ChgOrdinalNumber)), + // "ChangeOrdinalNumber" + css::uno::Any(bool(nFlags & ACFlags::AddNonBrkSpace)), // "AddNonBreakingSpace" + css::uno::Any(bool(nFlags & ACFlags::ChgToEnEmDash)), // "ChangeDash" + css::uno::Any(bool(nFlags & ACFlags::IgnoreDoubleSpace)), + // "RemoveDoubleSpaces" + css::uno::Any(bool(nFlags & ACFlags::ChgSglQuotes)), // "ReplaceSingleQuote" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetStartSingleQuote())), + // "SingleQuoteAtStart" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetEndSingleQuote())), + // "SingleQuoteAtEnd" + css::uno::Any(bool(nFlags & ACFlags::ChgQuotes)), // "ReplaceDoubleQuote" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetStartDoubleQuote())), + // "DoubleQuoteAtStart" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetEndDoubleQuote())), + // "DoubleQuoteAtEnd" + css::uno::Any(bool(nFlags & ACFlags::CorrectCapsLock)), + // "CorrectAccidentalCapsLock" + css::uno::Any(bool(nFlags & ACFlags::TransliterateRTL)), + // "TransliterateRTL" + css::uno::Any(bool(nFlags & ACFlags::ChgAngleQuotes))}); + // "ChangeAngleQuotes" + +} + +void SvxBaseAutoCorrCfg::Notify( const Sequence<OUString>& /* aPropertyNames */) +{ + Load(false); +} + +Sequence<OUString> SvxSwAutoCorrCfg::GetPropertyNames() +{ + static const char* aPropNames[] = + { + "Text/FileLinks", // 0 + "Text/InternetLinks", // 1 + "Text/ShowPreview", // 2 + "Text/ShowToolTip", // 3 + "Text/SearchInAllCategories", // 4 + "Format/Option/UseReplacementTable", // 5 + "Format/Option/TwoCapitalsAtStart", // 6 + "Format/Option/CapitalAtStartSentence", // 7 + "Format/Option/ChangeUnderlineWeight", // 8 + "Format/Option/SetInetAttribute", // 9 + "Format/Option/ChangeOrdinalNumber", //10 + "Format/Option/AddNonBreakingSpace", //11 + "Format/Option/ChangeDash", //12 + "Format/Option/DelEmptyParagraphs", //13 + "Format/Option/ReplaceUserStyle", //14 + "Format/Option/ChangeToBullets/Enable", //15 + "Format/Option/ChangeToBullets/SpecialCharacter/Char", //16 + "Format/Option/ChangeToBullets/SpecialCharacter/Font", //17 + "Format/Option/ChangeToBullets/SpecialCharacter/FontFamily", //18 + "Format/Option/ChangeToBullets/SpecialCharacter/FontCharset", //19 + "Format/Option/ChangeToBullets/SpecialCharacter/FontPitch", //20 + "Format/Option/CombineParagraphs", //21 + "Format/Option/CombineValue", //22 + "Format/Option/DelSpacesAtStartEnd", //23 + "Format/Option/DelSpacesBetween", //24 + "Format/ByInput/Enable", //25 + "Format/ByInput/ChangeDash", //26 + "Format/ByInput/ApplyNumbering/Enable", //27 + "Format/ByInput/ChangeToBorders", //28 + "Format/ByInput/ChangeToTable", //29 + "Format/ByInput/ReplaceStyle", //30 + "Format/ByInput/DelSpacesAtStartEnd", //31 + "Format/ByInput/DelSpacesBetween", //32 + "Completion/Enable", //33 + "Completion/MinWordLen", //34 + "Completion/MaxListLen", //35 + "Completion/CollectWords", //36 + "Completion/EndlessList", //37 + "Completion/AppendBlank", //38 + "Completion/ShowAsTip", //39 + "Completion/AcceptKey", //40 + "Completion/KeepList", //41 + "Format/ByInput/ApplyNumbering/SpecialCharacter/Char", //42 + "Format/ByInput/ApplyNumbering/SpecialCharacter/Font", //43 + "Format/ByInput/ApplyNumbering/SpecialCharacter/FontFamily", //44 + "Format/ByInput/ApplyNumbering/SpecialCharacter/FontCharset", //45 + "Format/ByInput/ApplyNumbering/SpecialCharacter/FontPitch", //46 + }; + const int nCount = 47; + Sequence<OUString> aNames(nCount); + OUString* pNames = aNames.getArray(); + for(int i = 0; i < nCount; i++) + pNames[i] = OUString::createFromAscii(aPropNames[i]); + return aNames; +} + +void SvxSwAutoCorrCfg::Load(bool bInit) +{ + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues = GetProperties(aNames); + if(bInit) + EnableNotification(aNames); + const Any* pValues = aValues.getConstArray(); + DBG_ASSERT(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() != aNames.getLength()) + return; + + SvxSwAutoFormatFlags& rSwFlags = rParent.pAutoCorrect->GetSwFlags(); + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case 0: rParent.bFileRel = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/FileLinks", + case 1: rParent.bNetRel = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/InternetLinks", + case 2: rParent.bAutoTextPreview = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/ShowPreview", + case 3: rParent.bAutoTextTip = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/ShowToolTip", + case 4: rParent.bSearchInAllCategories = *o3tl::doAccess<bool>(pValues[nProp]); break; //"Text/SearchInAllCategories" + case 5: rSwFlags.bAutoCorrect = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/UseReplacementTable", + case 6: rSwFlags.bCapitalStartSentence = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/TwoCapitalsAtStart", + case 7: rSwFlags.bCapitalStartWord = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/CapitalAtStartSentence", + case 8: rSwFlags.bChgWeightUnderl = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeUnderlineWeight", + case 9: rSwFlags.bSetINetAttr = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/SetInetAttribute", + case 10: rSwFlags.bChgOrdinalNumber = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeOrdinalNumber", + case 11: rSwFlags.bAddNonBrkSpace = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/AddNonBreakingSpace", +// it doesn't exist here - the common flags are used for that -> LM +// case 12: rSwFlags.bChgToEnEmDash = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeDash", + case 13: rSwFlags.bDelEmptyNode = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/DelEmptyParagraphs", + case 14: rSwFlags.bChgUserColl = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ReplaceUserStyle", + case 15: rSwFlags.bChgEnumNum = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeToBullets/Enable", + case 16: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.cBullet = + sal::static_int_cast< sal_Unicode >(nVal); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/Char", + case 17: + { + OUString sTemp; pValues[nProp] >>= sTemp; + rSwFlags.aBulletFont.SetFamilyName(sTemp); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/Font", + case 18: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aBulletFont.SetFamily(FontFamily(nVal)); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/FontFamily", + case 19: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aBulletFont.SetCharSet(rtl_TextEncoding(nVal)); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/FontCharset", + case 20: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aBulletFont.SetPitch(FontPitch(nVal)); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/FontPitch", + case 21: rSwFlags.bRightMargin = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/CombineParagraphs", + case 22: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nRightMargin = + sal::static_int_cast< sal_uInt8 >(nVal); + } + break; // "Format/Option/CombineValue", + case 23: rSwFlags.bAFormatDelSpacesAtSttEnd = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/DelSpacesAtStartEnd", + case 24: rSwFlags.bAFormatDelSpacesBetweenLines = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/DelSpacesBetween", + case 25: rParent.bAutoFmtByInput = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/Enable", + case 26: rSwFlags.bChgToEnEmDash = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ChangeDash", + case 27: rSwFlags.bSetNumRule = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ApplyNumbering/Enable", + case 28: rSwFlags.bSetBorder = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ChangeToBorders", + case 29: rSwFlags.bCreateTable = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ChangeToTable", + case 30: rSwFlags.bReplaceStyles = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ReplaceStyle", + case 31: rSwFlags.bAFormatByInpDelSpacesAtSttEnd = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/DelSpacesAtStartEnd", + case 32: rSwFlags.bAFormatByInpDelSpacesBetweenLines = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/DelSpacesBetween", + case 33: rSwFlags.bAutoCompleteWords = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/Enable", + case 34: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nAutoCmpltWordLen = + sal::static_int_cast< sal_uInt16 >(nVal); + } + break; // "Completion/MinWordLen", + case 35: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nAutoCmpltListLen = + sal::static_int_cast< sal_uInt16 >(nVal); + } + break; // "Completion/MaxListLen", + case 36: rSwFlags.bAutoCmpltCollectWords = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/CollectWords", + case 37: rSwFlags.bAutoCmpltEndless = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/EndlessList", + case 38: rSwFlags.bAutoCmpltAppendBlank = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/AppendBlank", + case 39: rSwFlags.bAutoCmpltShowAsTip = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/ShowAsTip", + case 40: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nAutoCmpltExpandKey = + sal::static_int_cast< sal_uInt16 >(nVal); + } + break; // "Completion/AcceptKey" + case 41 :rSwFlags.bAutoCmpltKeepList = *o3tl::doAccess<bool>(pValues[nProp]); break;//"Completion/KeepList" + case 42 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.cByInputBullet = + sal::static_int_cast< sal_Unicode >(nVal); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/Char", + case 43 : + { + OUString sTemp; pValues[nProp] >>= sTemp; + rSwFlags.aByInputBulletFont.SetFamilyName(sTemp); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/Font", + case 44 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aByInputBulletFont.SetFamily(FontFamily(nVal)); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/FontFamily", + case 45 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aByInputBulletFont.SetCharSet(rtl_TextEncoding(nVal)); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/FontCharset", + case 46 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aByInputBulletFont.SetPitch(FontPitch(nVal)); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/FontPitch", + } + } + } +} + +SvxSwAutoCorrCfg::SvxSwAutoCorrCfg(SvxAutoCorrCfg& rPar) : + utl::ConfigItem("Office.Writer/AutoFunction"), + rParent(rPar) +{ +} + +SvxSwAutoCorrCfg::~SvxSwAutoCorrCfg() +{ +} + +void SvxSwAutoCorrCfg::ImplCommit() +{ + SvxSwAutoFormatFlags& rSwFlags = rParent.pAutoCorrect->GetSwFlags(); + PutProperties( + GetPropertyNames(), + {css::uno::Any(rParent.bFileRel), // "Text/FileLinks" + css::uno::Any(rParent.bNetRel), // "Text/InternetLinks" + css::uno::Any(rParent.bAutoTextPreview), // "Text/ShowPreview" + css::uno::Any(rParent.bAutoTextTip), // "Text/ShowToolTip" + css::uno::Any(rParent.bSearchInAllCategories), + // "Text/SearchInAllCategories" + css::uno::Any(rSwFlags.bAutoCorrect), + // "Format/Option/UseReplacementTable" + css::uno::Any(rSwFlags.bCapitalStartSentence), + // "Format/Option/TwoCapitalsAtStart" + css::uno::Any(rSwFlags.bCapitalStartWord), + // "Format/Option/CapitalAtStartSentence" + css::uno::Any(rSwFlags.bChgWeightUnderl), + // "Format/Option/ChangeUnderlineWeight" + css::uno::Any(rSwFlags.bSetINetAttr), + // "Format/Option/SetInetAttribute" + css::uno::Any(rSwFlags.bChgOrdinalNumber), + // "Format/Option/ChangeOrdinalNumber" + css::uno::Any(rSwFlags.bAddNonBrkSpace), + // "Format/Option/AddNonBreakingSpace" + css::uno::Any(true), + // "Format/Option/ChangeDash"; it doesn't exist here - the common + // flags are used for that -> LM + css::uno::Any(rSwFlags.bDelEmptyNode), + // "Format/Option/DelEmptyParagraphs" + css::uno::Any(rSwFlags.bChgUserColl), + // "Format/Option/ReplaceUserStyle" + css::uno::Any(rSwFlags.bChgEnumNum), + // "Format/Option/ChangeToBullets/Enable" + css::uno::Any(sal_Int32(rSwFlags.cBullet)), + // "Format/Option/ChangeToBullets/SpecialCharacter/Char" + css::uno::Any(rSwFlags.aBulletFont.GetFamilyName()), + // "Format/Option/ChangeToBullets/SpecialCharacter/Font" + css::uno::Any(sal_Int32(rSwFlags.aBulletFont.GetFamilyType())), + // "Format/Option/ChangeToBullets/SpecialCharacter/FontFamily" + css::uno::Any(sal_Int32(rSwFlags.aBulletFont.GetCharSet())), + // "Format/Option/ChangeToBullets/SpecialCharacter/FontCharset" + css::uno::Any(sal_Int32(rSwFlags.aBulletFont.GetPitch())), + // "Format/Option/ChangeToBullets/SpecialCharacter/FontPitch" + css::uno::Any(rSwFlags.bRightMargin), + // "Format/Option/CombineParagraphs" + css::uno::Any(sal_Int32(rSwFlags.nRightMargin)), + // "Format/Option/CombineValue" + css::uno::Any(rSwFlags.bAFormatDelSpacesAtSttEnd), + // "Format/Option/DelSpacesAtStartEnd" + css::uno::Any(rSwFlags.bAFormatDelSpacesBetweenLines), + // "Format/Option/DelSpacesBetween" + css::uno::Any(rParent.bAutoFmtByInput), // "Format/ByInput/Enable" + css::uno::Any(rSwFlags.bChgToEnEmDash), // "Format/ByInput/ChangeDash" + css::uno::Any(rSwFlags.bSetNumRule), + // "Format/ByInput/ApplyNumbering/Enable" + css::uno::Any(rSwFlags.bSetBorder), // "Format/ByInput/ChangeToBorders" + css::uno::Any(rSwFlags.bCreateTable), // "Format/ByInput/ChangeToTable" + css::uno::Any(rSwFlags.bReplaceStyles), + // "Format/ByInput/ReplaceStyle" + css::uno::Any(rSwFlags.bAFormatByInpDelSpacesAtSttEnd), + // "Format/ByInput/DelSpacesAtStartEnd" + css::uno::Any(rSwFlags.bAFormatByInpDelSpacesBetweenLines), + // "Format/ByInput/DelSpacesBetween" + css::uno::Any(rSwFlags.bAutoCompleteWords), // "Completion/Enable" + css::uno::Any(sal_Int32(rSwFlags.nAutoCmpltWordLen)), + // "Completion/MinWordLen" + css::uno::Any(sal_Int32(rSwFlags.nAutoCmpltListLen)), + // "Completion/MaxListLen" + css::uno::Any(rSwFlags.bAutoCmpltCollectWords), + // "Completion/CollectWords" + css::uno::Any(rSwFlags.bAutoCmpltEndless), // "Completion/EndlessList" + css::uno::Any(rSwFlags.bAutoCmpltAppendBlank), + // "Completion/AppendBlank" + css::uno::Any(rSwFlags.bAutoCmpltShowAsTip), // "Completion/ShowAsTip" + css::uno::Any(sal_Int32(rSwFlags.nAutoCmpltExpandKey)), + // "Completion/AcceptKey" + css::uno::Any(rSwFlags.bAutoCmpltKeepList), // "Completion/KeepList" + css::uno::Any(sal_Int32(rSwFlags.cByInputBullet)), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/Char" + css::uno::Any(rSwFlags.aByInputBulletFont.GetFamilyName()), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/Font" + css::uno::Any(sal_Int32(rSwFlags.aByInputBulletFont.GetFamilyType())), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/FontFamily" + css::uno::Any(sal_Int32(rSwFlags.aByInputBulletFont.GetCharSet())), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/FontCharset" + css::uno::Any(sal_Int32(rSwFlags.aByInputBulletFont.GetPitch()))}); + // "Format/ByInput/ApplyNumbering/SpecialCharacter/FontPitch" +} + +void SvxSwAutoCorrCfg::Notify( const Sequence<OUString>& /* aPropertyNames */ ) +{ + Load(false); +} + +SvxAutoCorrCfg& SvxAutoCorrCfg::Get() +{ + static SvxAutoCorrCfg theSvxAutoCorrCfg; + return theSvxAutoCorrCfg; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/edtdlg.cxx b/editeng/source/misc/edtdlg.cxx new file mode 100644 index 000000000..a3f4390ab --- /dev/null +++ b/editeng/source/misc/edtdlg.cxx @@ -0,0 +1,29 @@ +/* -*- 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 <editeng/edtdlg.hxx> + +EditAbstractDialogFactory* EditAbstractDialogFactory::Create() +{ + return dynamic_cast<EditAbstractDialogFactory*>(VclAbstractDialogFactory::Create()); +} + +EditAbstractDialogFactory::~EditAbstractDialogFactory() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/forbiddencharacterstable.cxx b/editeng/source/misc/forbiddencharacterstable.cxx new file mode 100644 index 000000000..7276da584 --- /dev/null +++ b/editeng/source/misc/forbiddencharacterstable.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 <editeng/forbiddencharacterstable.hxx> + +#include <unotools/localedatawrapper.hxx> +#include <utility> + +SvxForbiddenCharactersTable::SvxForbiddenCharactersTable( + css::uno::Reference<css::uno::XComponentContext> xContext) + : m_xContext(std::move(xContext)) +{ +} + +std::shared_ptr<SvxForbiddenCharactersTable> +SvxForbiddenCharactersTable::makeForbiddenCharactersTable( + const css::uno::Reference<css::uno::XComponentContext>& rxContext) +{ + return std::shared_ptr<SvxForbiddenCharactersTable>(new SvxForbiddenCharactersTable(rxContext)); +} + +const css::i18n::ForbiddenCharacters* +SvxForbiddenCharactersTable::GetForbiddenCharacters(LanguageType nLanguage, bool bGetDefault) +{ + css::i18n::ForbiddenCharacters* pForbiddenCharacters = nullptr; + Map::iterator it = maMap.find(nLanguage); + if (it != maMap.end()) + pForbiddenCharacters = &(it->second); + else if (bGetDefault && m_xContext.is()) + { + LocaleDataWrapper aWrapper(m_xContext, LanguageTag(nLanguage)); + maMap[nLanguage] = aWrapper.getForbiddenCharacters(); + pForbiddenCharacters = &maMap[nLanguage]; + } + return pForbiddenCharacters; +} + +void SvxForbiddenCharactersTable::SetForbiddenCharacters( + LanguageType nLanguage, const css::i18n::ForbiddenCharacters& rForbiddenChars) +{ + maMap[nLanguage] = rForbiddenChars; +} + +void SvxForbiddenCharactersTable::ClearForbiddenCharacters(LanguageType nLanguage) +{ + maMap.erase(nLanguage); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/hangulhanja.cxx b/editeng/source/misc/hangulhanja.cxx new file mode 100644 index 000000000..186a7ccb2 --- /dev/null +++ b/editeng/source/misc/hangulhanja.cxx @@ -0,0 +1,1002 @@ +/* -*- 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 <editeng/hangulhanja.hxx> +#include <unotools/lingucfg.hxx> +#include <unotools/linguprops.hxx> + +#include <set> +#include <map> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/UnicodeScript.hpp> +#include <com/sun/star/i18n/TextConversion.hpp> +#include <com/sun/star/i18n/XExtendedTextConversion.hpp> +#include <com/sun/star/i18n/TextConversionType.hpp> +#include <com/sun/star/i18n/TextConversionOption.hpp> +#include <vcl/weld.hxx> +#include <unotools/charclass.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> + +#include <editeng/edtdlg.hxx> + +#define HHC HangulHanjaConversion + + +namespace editeng +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::i18n; + using namespace ::com::sun::star::i18n::TextConversionOption; + using namespace ::com::sun::star::i18n::TextConversionType; + + class HangulHanjaConversion_Impl + { + private: + typedef std::set<OUString> StringBag; + typedef std::map<OUString, OUString> StringMap; + + private: + StringBag m_sIgnoreList; + StringMap m_aChangeList; + static StringMap m_aRecentlyUsedList; + + // general + VclPtr<AbstractHangulHanjaConversionDialog> + m_pConversionDialog; // the dialog to display for user interaction + weld::Widget* m_pUIParent; // the parent window for any UI we raise + Reference< XComponentContext > + m_xContext; // the service factory to use + Reference< XExtendedTextConversion > + m_xConverter; // the text conversion service + lang::Locale m_aSourceLocale; // the locale we're working with + + // additions for Chinese simplified / traditional conversion + HHC::ConversionType m_eConvType; // conversion type (Hangul/Hanja, simplified/traditional Chinese,...) + LanguageType m_nSourceLang; // just a 'copy' of m_aSourceLocale in order to + // save the applications from always converting to this + // type in their implementations + LanguageType m_nTargetLang; // target language of new replacement text + const vcl::Font* m_pTargetFont; // target font of new replacement text + sal_Int32 m_nConvOptions; // text conversion options (as used by 'getConversions') + bool m_bIsInteractive; // specifies if the conversion requires user interaction + // (and likely a specialised dialog) or if it is to run + // automatically without any user interaction. + // True for Hangul / Hanja conversion + // False for Chinese simplified / traditional conversion + + HangulHanjaConversion* m_pAntiImpl; // our "anti-impl" instance + + // options + bool m_bByCharacter; // are we in "by character" mode currently? + HHC::ConversionFormat m_eConversionFormat; // the current format for the conversion + HHC::ConversionDirection m_ePrimaryConversionDirection; // the primary conversion direction + HHC::ConversionDirection m_eCurrentConversionDirection; // the primary conversion direction + + //options from Hangul/Hanja Options dialog (also saved to configuration) + bool m_bIgnorePostPositionalWord; + bool m_bShowRecentlyUsedFirst; + bool m_bAutoReplaceUnique; + + // state + OUString m_sCurrentPortion; // the text which we are currently working on + LanguageType m_nCurrentPortionLang; // language of m_sCurrentPortion found + sal_Int32 m_nCurrentStartIndex; // the start index within m_sCurrentPortion of the current convertible portion + sal_Int32 m_nCurrentEndIndex; // the end index (excluding) within m_sCurrentPortion of the current convertible portion + sal_Int32 m_nReplacementBaseIndex;// index which ReplaceUnit-calls need to be relative to + sal_Int32 m_nCurrentConversionOption; + sal_Int16 m_nCurrentConversionType; + Sequence< OUString > + m_aCurrentSuggestions; // the suggestions for the current unit + // (means for the text [m_nCurrentStartIndex, m_nCurrentEndIndex) in m_sCurrentPortion) + bool m_bTryBothDirections; // specifies if other conversion directions should be tried when looking for convertible characters + + + public: + HangulHanjaConversion_Impl( + weld::Widget* pUIParent, + const Reference< XComponentContext >& rxContext, + const lang::Locale& _rSourceLocale, + const lang::Locale& _rTargetLocale, + const vcl::Font* _pTargetFont, + sal_Int32 _nConvOptions, + bool _bIsInteractive, + HangulHanjaConversion* _pAntiImpl ); + + public: + void DoDocumentConversion( ); + + bool IsValid() const { return m_xConverter.is(); } + + weld::Widget* GetUIParent() const { return m_pUIParent; } + LanguageType GetSourceLang() const { return m_nSourceLang; } + LanguageType GetTargetLang() const { return m_nTargetLang; } + const vcl::Font * GetTargetFont() const { return m_pTargetFont; } + sal_Int32 GetConvOptions() const { return m_nConvOptions; } + bool IsInteractive() const { return m_bIsInteractive; } + + protected: + void createDialog(); + + /** continue with the conversion, return <TRUE/> if and only if the complete conversion is done + @param _bRepeatCurrentUnit + if <TRUE/>, an implNextConvertible will be called initially to advance to the next convertible. + if <FALSE/>, the method will initially work with the current convertible unit + */ + bool ContinueConversion( bool _bRepeatCurrentUnit ); + + private: + DECL_LINK( OnOptionsChanged, LinkParamNone*, void ); + DECL_LINK( OnIgnore, weld::Button&, void ); + DECL_LINK( OnIgnoreAll, weld::Button&, void ); + DECL_LINK( OnChange, weld::Button&, void ); + DECL_LINK( OnChangeAll, weld::Button&, void ); + DECL_LINK( OnByCharClicked, weld::Toggleable&, void ); + DECL_LINK( OnConversionTypeChanged, weld::Toggleable&, void ); + DECL_LINK( OnFind, weld::Button&, void ); + + /** proceed, after the current convertible has been handled + + <p><b>Attention:</b> + When returning from this method, the dialog may have been deleted!</p> + + @param _bRepeatCurrentUnit + will be passed to the <member>ContinueConversion</member> call + */ + void implProceed( bool _bRepeatCurrentUnit ); + + // change the current convertible, and do _not_ proceed + void implChange( const OUString& _rChangeInto ); + + /** find the next convertible piece of text, with possibly advancing to the next portion + + @see HangulHanjaConversion::GetNextPortion + */ + bool implNextConvertible( bool _bRepeatUnit ); + + /** find the next convertible unit within the current portion + @param _bRepeatUnit + if <TRUE/>, the search will start at the beginning of the current unit, + if <FALSE/>, it will start at the end of the current unit + */ + bool implNextConvertibleUnit( const sal_Int32 _nStartAt ); + + /** retrieves the next portion, with setting the index members properly + @return + <TRUE/> if and only if there is a next portion + */ + bool implRetrieveNextPortion( ); + + /** determine the ConversionDirection for m_sCurrentPortion + @return + <FALSE/> if and only if something went wrong + */ + bool implGetConversionDirectionForCurrentPortion( HHC::ConversionDirection& rDirection ); + + /** member m_aCurrentSuggestions and m_nCurrentEndIndex are updated according to the other settings and current dictionaries + + if _bAllowSearchNextConvertibleText is true _nStartAt is used as starting point to search the next + convertible text portion. This may result in changing of the member m_nCurrentStartIndex additionally. + + @return + <TRUE/> if Suggestions were found + */ + bool implUpdateSuggestions( const bool _bAllowSearchNextConvertibleText=false, const sal_Int32 _nStartAt=-1 ); + + /** reads the options from Hangul/Hanja Options dialog that are saved to configuration + */ + void implReadOptionsFromConfiguration(); + + /** get the string currently considered to be replaced or ignored + */ + OUString GetCurrentUnit() const; + + /** read options from configuration, update suggestion list and dialog content + */ + void implUpdateData(); + + /** get the conversion direction dependent from m_eConvType and m_eCurrentConversionDirection + in case of switching the direction is allowed this can be triggered with parameter bSwitchDirection + */ + sal_Int16 implGetConversionType( bool bSwitchDirection=false ) const; + }; + + HangulHanjaConversion_Impl::StringMap HangulHanjaConversion_Impl::m_aRecentlyUsedList = HangulHanjaConversion_Impl::StringMap(); + + HangulHanjaConversion_Impl::HangulHanjaConversion_Impl( weld::Widget* pUIParent, + const Reference< XComponentContext >& rxContext, + const lang::Locale& _rSourceLocale, + const lang::Locale& _rTargetLocale, + const vcl::Font* _pTargetFont, + sal_Int32 _nOptions, + bool _bIsInteractive, + HangulHanjaConversion* _pAntiImpl ) + : m_pUIParent( pUIParent ) + , m_xContext( rxContext ) + , m_aSourceLocale( _rSourceLocale ) + , m_nSourceLang( LanguageTag::convertToLanguageType( _rSourceLocale ) ) + , m_nTargetLang( LanguageTag::convertToLanguageType( _rTargetLocale ) ) + , m_pTargetFont( _pTargetFont ) + , m_nConvOptions(_nOptions) + , m_bIsInteractive( _bIsInteractive ) + , m_pAntiImpl( _pAntiImpl ) + , m_bByCharacter((_nOptions & CHARACTER_BY_CHARACTER) != 0) + , m_eConversionFormat( HHC::eSimpleConversion) + , m_ePrimaryConversionDirection( HHC::eHangulToHanja) // used for eConvHangulHanja + , m_eCurrentConversionDirection( HHC::eHangulToHanja) // used for eConvHangulHanja + , m_nCurrentPortionLang( LANGUAGE_NONE ) + , m_nCurrentStartIndex( 0 ) + , m_nCurrentEndIndex( 0 ) + , m_nReplacementBaseIndex( 0 ) + , m_nCurrentConversionOption( TextConversionOption::NONE ) + , m_nCurrentConversionType( -1 ) // not yet known + , m_bTryBothDirections( true ) + { + implReadOptionsFromConfiguration(); + + DBG_ASSERT( m_xContext.is(), "HangulHanjaConversion_Impl::HangulHanjaConversion_Impl: no ORB!" ); + + // determine conversion type + if (m_nSourceLang == LANGUAGE_KOREAN && m_nTargetLang == LANGUAGE_KOREAN) + m_eConvType = HHC::eConvHangulHanja; + else if ( (m_nSourceLang == LANGUAGE_CHINESE_TRADITIONAL && m_nTargetLang == LANGUAGE_CHINESE_SIMPLIFIED) || + (m_nSourceLang == LANGUAGE_CHINESE_SIMPLIFIED && m_nTargetLang == LANGUAGE_CHINESE_TRADITIONAL) ) + m_eConvType = HHC::eConvSimplifiedTraditional; + else + { + m_eConvType = HHC::eConvHangulHanja; + OSL_FAIL( "failed to determine conversion type from languages" ); + } + + m_xConverter = TextConversion::create( m_xContext ); + } + + void HangulHanjaConversion_Impl::createDialog() + { + DBG_ASSERT( m_bIsInteractive, "createDialog when the conversion should not be interactive?" ); + if ( !m_bIsInteractive || m_pConversionDialog ) + return; + + EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create(); + m_pConversionDialog = pFact->CreateHangulHanjaConversionDialog(m_pUIParent); + + m_pConversionDialog->EnableRubySupport( m_pAntiImpl->HasRubySupport() ); + + m_pConversionDialog->SetByCharacter( m_bByCharacter ); + m_pConversionDialog->SetConversionFormat( m_eConversionFormat ); + m_pConversionDialog->SetConversionDirectionState( m_bTryBothDirections, m_ePrimaryConversionDirection ); + + // the handlers + m_pConversionDialog->SetOptionsChangedHdl( LINK( this, HangulHanjaConversion_Impl, OnOptionsChanged ) ); + m_pConversionDialog->SetIgnoreHdl( LINK( this, HangulHanjaConversion_Impl, OnIgnore ) ); + m_pConversionDialog->SetIgnoreAllHdl( LINK( this, HangulHanjaConversion_Impl, OnIgnoreAll ) ); + m_pConversionDialog->SetChangeHdl( LINK( this, HangulHanjaConversion_Impl, OnChange ) ); + m_pConversionDialog->SetChangeAllHdl( LINK( this, HangulHanjaConversion_Impl, OnChangeAll ) ); + m_pConversionDialog->SetClickByCharacterHdl( LINK( this, HangulHanjaConversion_Impl, OnByCharClicked ) ); + m_pConversionDialog->SetConversionFormatChangedHdl( LINK( this, HangulHanjaConversion_Impl, OnConversionTypeChanged ) ); + m_pConversionDialog->SetFindHdl( LINK( this, HangulHanjaConversion_Impl, OnFind ) ); + } + + sal_Int16 HangulHanjaConversion_Impl::implGetConversionType( bool bSwitchDirection ) const + { + sal_Int16 nConversionType = -1; + if (m_eConvType == HHC::eConvHangulHanja) + nConversionType = ( HHC::eHangulToHanja == m_eCurrentConversionDirection && !bSwitchDirection ) ? TO_HANJA : TO_HANGUL; + else if (m_eConvType == HHC::eConvSimplifiedTraditional) + nConversionType = LANGUAGE_CHINESE_SIMPLIFIED == m_nTargetLang ? TO_SCHINESE : TO_TCHINESE; + DBG_ASSERT( nConversionType != -1, "unexpected conversion type" ); + return nConversionType; + } + + bool HangulHanjaConversion_Impl::implUpdateSuggestions( bool _bAllowSearchNextConvertibleText, const sal_Int32 _nStartAt ) + { + // parameters for the converter + sal_Int32 nStartSearch = m_nCurrentStartIndex; + if( _bAllowSearchNextConvertibleText ) + nStartSearch = _nStartAt; + + sal_Int32 nLength = m_sCurrentPortion.getLength() - nStartSearch; + m_nCurrentConversionType = implGetConversionType(); + m_nCurrentConversionOption = m_bByCharacter ? CHARACTER_BY_CHARACTER : css::i18n::TextConversionOption::NONE; + if( m_bIgnorePostPositionalWord ) + m_nCurrentConversionOption = m_nCurrentConversionOption | IGNORE_POST_POSITIONAL_WORD; + + // no need to check both directions for chinese conversion (saves time) + if (m_eConvType == HHC::eConvSimplifiedTraditional) + m_bTryBothDirections = false; + + bool bFoundAny = true; + try + { + TextConversionResult aResult = m_xConverter->getConversions( + m_sCurrentPortion, + nStartSearch, + nLength, + m_aSourceLocale, + m_nCurrentConversionType, + m_nCurrentConversionOption + ); + const bool bFoundPrimary = aResult.Boundary.startPos < aResult.Boundary.endPos; + bFoundAny = bFoundPrimary; + + if ( m_bTryBothDirections ) + { // see if we find another convertible when assuming the other direction + TextConversionResult aSecondResult = m_xConverter->getConversions( + m_sCurrentPortion, + nStartSearch, + nLength, + m_aSourceLocale, + implGetConversionType( true ), // switched! + m_nCurrentConversionOption + ); + if ( aSecondResult.Boundary.startPos < aSecondResult.Boundary.endPos ) + { // we indeed found such a convertible + + // in case the first attempt (with the original conversion direction) + // didn't find anything + if ( !bFoundPrimary + // or if the second location is _before_ the first one + || ( aSecondResult.Boundary.startPos < aResult.Boundary.startPos ) + ) + { + // then use the second finding + aResult = aSecondResult; + + // our current conversion direction changed now + m_eCurrentConversionDirection = ( HHC::eHangulToHanja == m_eCurrentConversionDirection ) + ? HHC::eHanjaToHangul : HHC::eHangulToHanja; + bFoundAny = true; + } + } + } + + if( _bAllowSearchNextConvertibleText ) + { + //this might change the current position + m_aCurrentSuggestions = aResult.Candidates; + m_nCurrentStartIndex = aResult.Boundary.startPos; + m_nCurrentEndIndex = aResult.Boundary.endPos; + } + else + { + //the change of starting position is not allowed + if( m_nCurrentStartIndex == aResult.Boundary.startPos + && aResult.Boundary.endPos != aResult.Boundary.startPos ) + { + m_aCurrentSuggestions = aResult.Candidates; + m_nCurrentEndIndex = aResult.Boundary.endPos; + } + else + { + m_aCurrentSuggestions.realloc( 0 ); + if( m_sCurrentPortion.getLength() >= m_nCurrentStartIndex+1 ) + m_nCurrentEndIndex = m_nCurrentStartIndex+1; + } + } + + //put recently used string to front: + if( m_bShowRecentlyUsedFirst && m_aCurrentSuggestions.getLength()>1 ) + { + OUString sCurrentUnit( GetCurrentUnit() ); + StringMap::const_iterator aRecentlyUsed = m_aRecentlyUsedList.find( sCurrentUnit ); + bool bUsedBefore = aRecentlyUsed != m_aRecentlyUsedList.end(); + if( bUsedBefore && m_aCurrentSuggestions[0] != aRecentlyUsed->second ) + { + sal_Int32 nCount = m_aCurrentSuggestions.getLength(); + Sequence< OUString > aTmp(nCount); + auto pTmp = aTmp.getArray(); + pTmp[0]=aRecentlyUsed->second; + sal_Int32 nDiff = 1; + for( sal_Int32 n=1; n<nCount; n++)//we had 0 already + { + if( nDiff && m_aCurrentSuggestions[n-nDiff]==aRecentlyUsed->second ) + nDiff=0; + pTmp[n]=m_aCurrentSuggestions[n-nDiff]; + } + m_aCurrentSuggestions = aTmp; + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::implNextConvertibleUnit" ); + + //!!! at least we want to move on in the text in order + //!!! to avoid an endless loop... + return false; + } + return bFoundAny; + } + + bool HangulHanjaConversion_Impl::implNextConvertibleUnit( const sal_Int32 _nStartAt ) + { + m_aCurrentSuggestions.realloc( 0 ); + + // ask the TextConversion service for the next convertible piece of text + + // get current values from dialog + if( m_eConvType == HHC::eConvHangulHanja && m_pConversionDialog ) + { + m_bTryBothDirections = m_pConversionDialog->GetUseBothDirections(); + HHC::ConversionDirection eDialogDirection = m_pConversionDialog->GetDirection( HHC::eHangulToHanja ); + + if( !m_bTryBothDirections && eDialogDirection != m_eCurrentConversionDirection ) + { + m_eCurrentConversionDirection = eDialogDirection; + } + + // save currently used value for possible later use + HangulHanjaConversion::m_bTryBothDirectionsSave = m_bTryBothDirections; + HangulHanjaConversion::m_ePrimaryConversionDirectionSave = m_eCurrentConversionDirection; + } + + bool bFoundAny = implUpdateSuggestions( true, _nStartAt ); + + return bFoundAny && + (m_nCurrentStartIndex < m_sCurrentPortion.getLength()); + } + + bool HangulHanjaConversion_Impl::implRetrieveNextPortion( ) + { + const bool bAllowImplicitChanges = m_eConvType == HHC::eConvSimplifiedTraditional; + + m_sCurrentPortion.clear(); + m_nCurrentPortionLang = LANGUAGE_NONE; + m_pAntiImpl->GetNextPortion( m_sCurrentPortion, m_nCurrentPortionLang, bAllowImplicitChanges ); + m_nReplacementBaseIndex = 0; + m_nCurrentStartIndex = m_nCurrentEndIndex = 0; + + bool bRet = !m_sCurrentPortion.isEmpty(); + + if (m_eConvType == HHC::eConvHangulHanja && m_bTryBothDirections) + implGetConversionDirectionForCurrentPortion( m_eCurrentConversionDirection ); + + return bRet; + } + + bool HangulHanjaConversion_Impl::implNextConvertible( bool _bRepeatUnit ) + { + if ( _bRepeatUnit || ( m_nCurrentEndIndex < m_sCurrentPortion.getLength() ) ) + { + if ( implNextConvertibleUnit( + _bRepeatUnit + ? m_nCurrentStartIndex + : m_nCurrentEndIndex + ) ) + return true; + } + + // no convertible text in the current portion anymore + // -> advance to the next portion + do + { + // next portion + if ( implRetrieveNextPortion( ) ) + { // there is a next portion + // -> find the next convertible unit in the current portion + if ( implNextConvertibleUnit( 0 ) ) + return true; + } + } + while ( !m_sCurrentPortion.isEmpty() ); + + // no more portions + return false; + } + + OUString HangulHanjaConversion_Impl::GetCurrentUnit() const + { + DBG_ASSERT( m_nCurrentStartIndex < m_sCurrentPortion.getLength(), + "HangulHanjaConversion_Impl::GetCurrentUnit: invalid index into current portion!" ); + DBG_ASSERT( m_nCurrentEndIndex <= m_sCurrentPortion.getLength(), + "HangulHanjaConversion_Impl::GetCurrentUnit: invalid index into current portion!" ); + DBG_ASSERT( m_nCurrentStartIndex <= m_nCurrentEndIndex, + "HangulHanjaConversion_Impl::GetCurrentUnit: invalid interval!" ); + + OUString sCurrentUnit = m_sCurrentPortion.copy( m_nCurrentStartIndex, m_nCurrentEndIndex - m_nCurrentStartIndex ); + return sCurrentUnit; + } + + bool HangulHanjaConversion_Impl::ContinueConversion( bool _bRepeatCurrentUnit ) + { + while ( implNextConvertible( _bRepeatCurrentUnit ) ) + { + OUString sCurrentUnit( GetCurrentUnit() ); + + // do we need to ignore it? + const bool bAlwaysIgnoreThis = m_sIgnoreList.end() != m_sIgnoreList.find( sCurrentUnit ); + + // do we need to change it? + StringMap::const_iterator aChangeListPos = m_aChangeList.find( sCurrentUnit ); + const bool bAlwaysChangeThis = m_aChangeList.end() != aChangeListPos; + + // do we automatically change this? + const bool bAutoChange = m_bAutoReplaceUnique && m_aCurrentSuggestions.getLength() == 1; + + if (!m_bIsInteractive) + { + // silent conversion (e.g. for simplified/traditional Chinese)... + if(m_aCurrentSuggestions.hasElements()) + implChange( m_aCurrentSuggestions.getConstArray()[0] ); + } + else if (bAutoChange) + { + implChange( m_aCurrentSuggestions.getConstArray()[0] ); + } + else if ( bAlwaysChangeThis ) + { + implChange( aChangeListPos->second ); + } + else if ( !bAlwaysIgnoreThis ) + { + // here we need to ask the user for what to do with the text + // for this, allow derivees to highlight the current text unit in a possible document view + m_pAntiImpl->HandleNewUnit( m_nCurrentStartIndex - m_nReplacementBaseIndex, m_nCurrentEndIndex - m_nReplacementBaseIndex ); + + DBG_ASSERT( m_pConversionDialog, "we should always have a dialog here!" ); + if( m_pConversionDialog ) + m_pConversionDialog->SetCurrentString( sCurrentUnit, m_aCurrentSuggestions ); + + // do not look for the next convertible: We have to wait for the user to interactively + // decide what happens with the current convertible + return false; + } + } + + return true; + } + + bool HangulHanjaConversion_Impl::implGetConversionDirectionForCurrentPortion( HHC::ConversionDirection& rDirection ) + { + // - For eConvHangulHanja the direction is determined by + // the first encountered Korean character. + // - For eConvSimplifiedTraditional the conversion direction + // is already specified by the source language. + + bool bSuccess = true; + + if (m_eConvType == HHC::eConvHangulHanja) + { + bSuccess = false; + try + { + // get the break iterator service + Reference< XBreakIterator > xBreakIter = i18n::BreakIterator::create( m_xContext ); + sal_Int32 nNextAsianScript = xBreakIter->beginOfScript( m_sCurrentPortion, m_nCurrentStartIndex, css::i18n::ScriptType::ASIAN ); + if ( -1 == nNextAsianScript ) + nNextAsianScript = xBreakIter->nextScript( m_sCurrentPortion, m_nCurrentStartIndex, css::i18n::ScriptType::ASIAN ); + if ( ( nNextAsianScript >= m_nCurrentStartIndex ) && ( nNextAsianScript < m_sCurrentPortion.getLength() ) ) + { // found asian text + + // determine if it's Hangul + CharClass aCharClassificaton( m_xContext, LanguageTag( m_aSourceLocale) ); + css::i18n::UnicodeScript nScript = aCharClassificaton.getScript( m_sCurrentPortion, sal::static_int_cast< sal_uInt16 >(nNextAsianScript) ); + if ( ( UnicodeScript_kHangulJamo == nScript ) + || ( UnicodeScript_kHangulCompatibilityJamo == nScript ) + || ( UnicodeScript_kHangulSyllable == nScript ) + ) + { + rDirection = HHC::eHangulToHanja; + } + else + { + rDirection = HHC::eHanjaToHangul; + } + + bSuccess = true; + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::implGetConversionDirectionForCurrentPortion" ); + } + } + + return bSuccess; + } + + void HangulHanjaConversion_Impl::DoDocumentConversion( ) + { + // clear the change-all list - it's to be re-initialized for every single document + StringMap().swap(m_aChangeList); + + // first of all, we need to guess the direction of our conversion - it is determined by the first + // hangul or hanja character in the first text + if ( !implRetrieveNextPortion() ) + { + SAL_INFO( "editeng", "HangulHanjaConversion_Impl::DoDocumentConversion: why did you call me if you do have nothing to convert?" ); + // nothing to do + return; + } + if( m_eConvType == HHC::eConvHangulHanja ) + { + //init conversion direction from saved value + HHC::ConversionDirection eDirection = HHC::eHangulToHanja; + if(!implGetConversionDirectionForCurrentPortion( eDirection )) + // something went wrong, has already been asserted + return; + + if (HangulHanjaConversion::IsUseSavedConversionDirectionState()) + { + m_ePrimaryConversionDirection = HangulHanjaConversion::m_ePrimaryConversionDirectionSave; + m_bTryBothDirections = HangulHanjaConversion::m_bTryBothDirectionsSave; + if( m_bTryBothDirections ) + m_eCurrentConversionDirection = eDirection; + else + m_eCurrentConversionDirection = m_ePrimaryConversionDirection; + } + else + { + m_ePrimaryConversionDirection = eDirection; + m_eCurrentConversionDirection = eDirection; + } + } + + if (m_bIsInteractive && m_eConvType == HHC::eConvHangulHanja) + { + //always open dialog if at least having a hangul or hanja text portion + createDialog(); + if(HangulHanjaConversion::IsUseSavedConversionDirectionState()) + ContinueConversion( false ); + else + implUpdateData(); + m_pConversionDialog->Execute(); + m_pConversionDialog.disposeAndClear(); + } + else + { + const bool bCompletelyDone = ContinueConversion( false ); + DBG_ASSERT( bCompletelyDone, "HangulHanjaConversion_Impl::DoDocumentConversion: ContinueConversion should have returned true here!" ); + } + } + + void HangulHanjaConversion_Impl::implProceed( bool _bRepeatCurrentUnit ) + { + if ( ContinueConversion( _bRepeatCurrentUnit ) ) + { // we're done with the whole document + DBG_ASSERT( !m_bIsInteractive || m_pConversionDialog, "HangulHanjaConversion_Impl::implProceed: we should not reach this here without dialog!" ); + if ( m_pConversionDialog ) + m_pConversionDialog->EndDialog( RET_OK ); + } + } + + void HangulHanjaConversion_Impl::implChange( const OUString& _rChangeInto ) + { + if( _rChangeInto.isEmpty() ) + return; + + // translate the conversion format into a replacement action + // this translation depends on whether we have a Hangul original, or a Hanja original + + HHC::ReplacementAction eAction( HHC::eExchange ); + + if (m_eConvType == HHC::eConvHangulHanja) + { + // is the original we're about to change in Hangul? + const bool bOriginalIsHangul = HHC::eHangulToHanja == m_eCurrentConversionDirection; + + switch ( m_eConversionFormat ) + { + case HHC::eSimpleConversion: eAction = HHC::eExchange; break; + case HHC::eHangulBracketed: eAction = bOriginalIsHangul ? HHC::eOriginalBracketed : HHC::eReplacementBracketed; break; + case HHC::eHanjaBracketed: eAction = bOriginalIsHangul ? HHC::eReplacementBracketed : HHC::eOriginalBracketed; break; + case HHC::eRubyHanjaAbove: eAction = bOriginalIsHangul ? HHC::eReplacementAbove : HHC::eOriginalAbove; break; + case HHC::eRubyHanjaBelow: eAction = bOriginalIsHangul ? HHC::eReplacementBelow : HHC::eOriginalBelow; break; + case HHC::eRubyHangulAbove: eAction = bOriginalIsHangul ? HHC::eOriginalAbove : HHC::eReplacementAbove; break; + case HHC::eRubyHangulBelow: eAction = bOriginalIsHangul ? HHC::eOriginalBelow : HHC::eReplacementBelow; break; + default: + OSL_FAIL( "HangulHanjaConversion_Impl::implChange: invalid/unexpected conversion format!" ); + } + } + + // the proper indices (the wrapper implementation needs indices relative to the + // previous replacement) + DBG_ASSERT( ( m_nReplacementBaseIndex <= m_nCurrentStartIndex ) && ( m_nReplacementBaseIndex <= m_nCurrentEndIndex ), + "HangulHanjaConversion_Impl::implChange: invalid replacement base!" ); + + sal_Int32 nStartIndex = m_nCurrentStartIndex - m_nReplacementBaseIndex; + sal_Int32 nEndIndex = m_nCurrentEndIndex - m_nReplacementBaseIndex; + + //remind this decision + m_aRecentlyUsedList[ GetCurrentUnit() ] = _rChangeInto; + + LanguageType *pNewUnitLang = nullptr; + LanguageType nNewUnitLang = LANGUAGE_NONE; + if (m_eConvType == HHC::eConvSimplifiedTraditional) + { + // check if language needs to be changed + if ( m_pAntiImpl->GetTargetLanguage() == LANGUAGE_CHINESE_TRADITIONAL && + !HangulHanjaConversion::IsTraditional( m_nCurrentPortionLang )) + nNewUnitLang = LANGUAGE_CHINESE_TRADITIONAL; + else if ( m_pAntiImpl->GetTargetLanguage() == LANGUAGE_CHINESE_SIMPLIFIED && + !HangulHanjaConversion::IsSimplified( m_nCurrentPortionLang )) + nNewUnitLang = LANGUAGE_CHINESE_SIMPLIFIED; + if (nNewUnitLang != LANGUAGE_NONE) + pNewUnitLang = &nNewUnitLang; + } + + // according to FT we should not (yet) bother about Hangul/Hanja conversion here + // + // aOffsets is needed in ReplaceUnit below in order to find out + // exactly which characters are really changed in order to keep as much + // from attributation for the text as possible. + Sequence< sal_Int32 > aOffsets; + if (m_eConvType == HHC::eConvSimplifiedTraditional && m_xConverter.is()) + { + try + { + m_xConverter->getConversionWithOffset( + m_sCurrentPortion, + m_nCurrentStartIndex, + m_nCurrentEndIndex - m_nCurrentStartIndex, + m_aSourceLocale, + m_nCurrentConversionType, + m_nCurrentConversionOption, + aOffsets + ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::implChange: caught unexpected exception!" ); + aOffsets.realloc(0); + } + } + + // do the replacement + m_pAntiImpl->ReplaceUnit( nStartIndex, nEndIndex, m_sCurrentPortion, + _rChangeInto, aOffsets, eAction, pNewUnitLang ); + + + // adjust the replacement base + m_nReplacementBaseIndex = m_nCurrentEndIndex; + } + + void HangulHanjaConversion_Impl::implReadOptionsFromConfiguration() + { + SvtLinguConfig aLngCfg; + aLngCfg.GetProperty( UPH_IS_IGNORE_POST_POSITIONAL_WORD ) >>= m_bIgnorePostPositionalWord; + aLngCfg.GetProperty( UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST ) >>= m_bShowRecentlyUsedFirst; + aLngCfg.GetProperty( UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES ) >>= m_bAutoReplaceUnique; + } + + void HangulHanjaConversion_Impl::implUpdateData() + { + implReadOptionsFromConfiguration(); + implUpdateSuggestions(); + + if(m_pConversionDialog) + { + OUString sCurrentUnit( GetCurrentUnit() ); + + m_pConversionDialog->SetCurrentString( sCurrentUnit, m_aCurrentSuggestions ); + m_pConversionDialog->FocusSuggestion(); + } + + m_pAntiImpl->HandleNewUnit( m_nCurrentStartIndex - m_nReplacementBaseIndex, m_nCurrentEndIndex - m_nReplacementBaseIndex ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnOptionsChanged, LinkParamNone*, void) + { + //options and dictionaries might have been changed + //-> update our internal settings and the dialog + implUpdateData(); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnIgnore, weld::Button&, void) + { + // simply ignore, and proceed + implProceed( false ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnIgnoreAll, weld::Button&, void) + { + DBG_ASSERT( m_pConversionDialog, "HangulHanjaConversion_Impl::OnIgnoreAll: no dialog! How this?" ); + + if ( m_pConversionDialog ) + { + OUString sCurrentUnit = m_pConversionDialog->GetCurrentString(); + DBG_ASSERT( m_sIgnoreList.end() == m_sIgnoreList.find( sCurrentUnit ), + "HangulHanjaConversion_Impl, OnIgnoreAll: shouldn't this have been ignored before" ); + + // put into the "ignore all" list + m_sIgnoreList.insert( sCurrentUnit ); + + // and proceed + implProceed( false ); + } + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnChange, weld::Button&, void) + { + // change + DBG_ASSERT( m_pConversionDialog, "we should always have a dialog here!" ); + if( m_pConversionDialog ) + implChange( m_pConversionDialog->GetCurrentSuggestion( ) ); + // and proceed + implProceed( false ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnChangeAll, weld::Button&, void) + { + DBG_ASSERT( m_pConversionDialog, "HangulHanjaConversion_Impl::OnChangeAll: no dialog! How this?" ); + if ( !m_pConversionDialog ) + return; + + OUString sCurrentUnit( m_pConversionDialog->GetCurrentString() ); + OUString sChangeInto( m_pConversionDialog->GetCurrentSuggestion( ) ); + + if( !sChangeInto.isEmpty() ) + { + // change the current occurrence + implChange( sChangeInto ); + + // put into the "change all" list + m_aChangeList.emplace( sCurrentUnit, sChangeInto ); + } + + // and proceed + implProceed( false ); + } + + IMPL_LINK(HangulHanjaConversion_Impl, OnByCharClicked, weld::Toggleable&, rBox, void) + { + m_bByCharacter = rBox.get_active(); + + // continue conversion, without advancing to the next unit, but instead continuing with the current unit + implProceed( true ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnConversionTypeChanged, weld::Toggleable&, void) + { + DBG_ASSERT( m_pConversionDialog, "we should always have a dialog here!" ); + if( m_pConversionDialog ) + m_eConversionFormat = m_pConversionDialog->GetConversionFormat( ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnFind, weld::Button&, void) + { + DBG_ASSERT( m_pConversionDialog, "HangulHanjaConversion_Impl::OnFind: where did this come from?" ); + if ( !m_pConversionDialog ) + return; + + try + { + OUString sNewOriginal( m_pConversionDialog->GetCurrentSuggestion( ) ); + Sequence< OUString > aSuggestions; + + DBG_ASSERT( m_xConverter.is(), "HangulHanjaConversion_Impl::OnFind: no converter!" ); + TextConversionResult aToHanja = m_xConverter->getConversions( + sNewOriginal, + 0, sNewOriginal.getLength(), + m_aSourceLocale, + TextConversionType::TO_HANJA, + TextConversionOption::NONE + ); + TextConversionResult aToHangul = m_xConverter->getConversions( + sNewOriginal, + 0, sNewOriginal.getLength(), + m_aSourceLocale, + TextConversionType::TO_HANGUL, + TextConversionOption::NONE + ); + + bool bHaveToHanja = ( aToHanja.Boundary.startPos < aToHanja.Boundary.endPos ); + bool bHaveToHangul = ( aToHangul.Boundary.startPos < aToHangul.Boundary.endPos ); + + TextConversionResult* pResult = nullptr; + if ( bHaveToHanja && bHaveToHangul ) + { // it found convertibles in both directions -> use the first + if ( aToHangul.Boundary.startPos < aToHanja.Boundary.startPos ) + pResult = &aToHangul; + else + pResult = &aToHanja; + } + else if ( bHaveToHanja ) + { // only found toHanja + pResult = &aToHanja; + } + else + { // only found toHangul + pResult = &aToHangul; + } + if ( pResult ) + aSuggestions = pResult->Candidates; + + m_pConversionDialog->SetCurrentString( sNewOriginal, aSuggestions, false ); + m_pConversionDialog->FocusSuggestion(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::OnFind" ); + } + } + + bool HangulHanjaConversion::m_bUseSavedValues = false; + bool HangulHanjaConversion::m_bTryBothDirectionsSave = false; + HHC::ConversionDirection HangulHanjaConversion::m_ePrimaryConversionDirectionSave = HHC::eHangulToHanja; + + HangulHanjaConversion::HangulHanjaConversion( weld::Widget* pUIParent, + const Reference< XComponentContext >& rxContext, + const lang::Locale& _rSourceLocale, const lang::Locale& _rTargetLocale, + const vcl::Font* _pTargetFont, + sal_Int32 _nOptions, bool _bIsInteractive) + :m_pImpl( new HangulHanjaConversion_Impl( pUIParent, rxContext, _rSourceLocale, _rTargetLocale, _pTargetFont, _nOptions, _bIsInteractive, this ) ) + { + } + + HangulHanjaConversion::~HangulHanjaConversion() COVERITY_NOEXCEPT_FALSE + { + } + + void HangulHanjaConversion::SetUseSavedConversionDirectionState( bool bVal ) + { + m_bUseSavedValues = bVal; + } + + bool HangulHanjaConversion::IsUseSavedConversionDirectionState() + { + return m_bUseSavedValues; + } + + weld::Widget* HangulHanjaConversion::GetUIParent() const + { + return m_pImpl->GetUIParent(); + } + + LanguageType HangulHanjaConversion::GetSourceLanguage( ) const + { + return m_pImpl->GetSourceLang(); + } + + LanguageType HangulHanjaConversion::GetTargetLanguage( ) const + { + return m_pImpl->GetTargetLang(); + } + + const vcl::Font * HangulHanjaConversion::GetTargetFont( ) const + { + return m_pImpl->GetTargetFont(); + } + + sal_Int32 HangulHanjaConversion::GetConversionOptions( ) const + { + return m_pImpl->GetConvOptions(); + } + + bool HangulHanjaConversion::IsInteractive( ) const + { + return m_pImpl->IsInteractive(); + } + + void HangulHanjaConversion::ConvertDocument() + { + if ( m_pImpl->IsValid() ) + m_pImpl->DoDocumentConversion( ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/splwrap.cxx b/editeng/source/misc/splwrap.cxx new file mode 100644 index 000000000..134c7211f --- /dev/null +++ b/editeng/source/misc/splwrap.cxx @@ -0,0 +1,472 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_wasm_strip.h> + +#include <rtl/ustring.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svtools/langtab.hxx> + +#include <vcl/errinf.hxx> +#include <editeng/unolingu.hxx> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/linguistic2/XLinguProperties.hpp> +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> +#include <com/sun/star/linguistic2/XHyphenator.hpp> +#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp> +#include <com/sun/star/linguistic2/XDictionary.hpp> + +#include <editeng/svxenum.hxx> +#include <editeng/splwrap.hxx> +#include <editeng/edtdlg.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/editrids.hrc> +#include <editeng/editerr.hxx> + +#include <map> +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; + + +// misc functions --------------------------------------------- + +void SvxPrepareAutoCorrect( OUString &rOldText, const OUString &rNewText ) +{ + // This function should be used to strip (or add) trailing '.' from + // the strings before passing them on to the autocorrect function in + // order that the autocorrect function will hopefully + // works properly with normal words and abbreviations (with trailing '.') + // independent of if they are at the end of the sentence or not. + // + // rOldText: text to be replaced + // rNewText: replacement text + + sal_Int32 nOldLen = rOldText.getLength(); + sal_Int32 nNewLen = rNewText.getLength(); + if (nOldLen && nNewLen) + { + bool bOldHasDot = '.' == rOldText[ nOldLen - 1 ], + bNewHasDot = '.' == rNewText[ nNewLen - 1 ]; + if (bOldHasDot && !bNewHasDot + /*this is: !(bOldHasDot && bNewHasDot) && bOldHasDot*/) + rOldText = rOldText.copy( 0, nOldLen - 1 ); + } +} + +#define SVX_LANG_NEED_CHECK 0 +#define SVX_LANG_OK 1 +#define SVX_LANG_MISSING 2 +#define SVX_LANG_MISSING_DO_WARN 3 + +typedef std::map< LanguageType, sal_uInt16 > LangCheckState_map_t; + +static LangCheckState_map_t & GetLangCheckState() +{ + static LangCheckState_map_t aLangCheckState; + return aLangCheckState; +} + +void SvxSpellWrapper::ShowLanguageErrors() +{ + // display message boxes for languages not available for + // spellchecking or hyphenation + LangCheckState_map_t &rLCS = GetLangCheckState(); + for (auto const& elem : rLCS) + { + LanguageType nLang = elem.first; + sal_uInt16 nVal = elem.second; + sal_uInt16 nTmpSpell = nVal & 0x00FF; + sal_uInt16 nTmpHyph = (nVal >> 8) & 0x00FF; + + if (SVX_LANG_MISSING_DO_WARN == nTmpSpell) + { + OUString aErr( SvtLanguageTable::GetLanguageString( nLang ) ); + ErrorHandler::HandleError( + *new StringErrorInfo( ERRCODE_SVX_LINGU_LANGUAGENOTEXISTS, aErr ) ); + nTmpSpell = SVX_LANG_MISSING; + } + if (SVX_LANG_MISSING_DO_WARN == nTmpHyph) + { + OUString aErr( SvtLanguageTable::GetLanguageString( nLang ) ); + ErrorHandler::HandleError( + *new StringErrorInfo( ERRCODE_SVX_LINGU_LANGUAGENOTEXISTS, aErr ) ); + nTmpHyph = SVX_LANG_MISSING; + } + + rLCS[ nLang ] = (nTmpHyph << 8) | nTmpSpell; + } + +} + +SvxSpellWrapper::~SvxSpellWrapper() +{ +} + +/*-------------------------------------------------------------------- + * Description: Constructor, the test sequence is determined + * + * !bStart && !bOtherCntnt: BODY_END, BODY_START, OTHER + * !bStart && bOtherCntnt: OTHER, BODY + * bStart && !bOtherCntnt: BODY_END, OTHER + * bStart && bOtherCntnt: OTHER + * + --------------------------------------------------------------------*/ + +SvxSpellWrapper::SvxSpellWrapper( weld::Widget* pWn, + const bool bStart, const bool bIsAllRight ) : + + pWin ( pWn ), + bOtherCntnt ( false ), + bStartChk ( false ), + bRevAllowed ( true ), + bAllRight ( bIsAllRight ) +{ + Reference< linguistic2::XLinguProperties > xProp( LinguMgr::GetLinguPropertySet() ); + bool bWrapReverse = xProp.is() && xProp->getIsWrapReverse(); + bReverse = bWrapReverse; + bStartDone = !bReverse && bStart; + bEndDone = bReverse && bStart; +} + + +SvxSpellWrapper::SvxSpellWrapper( weld::Widget* pWn, + Reference< XHyphenator > const &xHyphenator, + const bool bStart, const bool bOther ) : + pWin ( pWn ), + xHyph ( xHyphenator ), + bOtherCntnt ( bOther ), + bReverse ( false ), + bStartDone ( bOther || bStart ), + bEndDone ( false ), + bStartChk ( bOther ), + bRevAllowed ( false ), + bAllRight ( true ) +{ +} + + +sal_Int16 SvxSpellWrapper::CheckSpellLang( + Reference< XSpellChecker1 > const & xSpell, LanguageType nLang) +{ + LangCheckState_map_t &rLCS = GetLangCheckState(); + + LangCheckState_map_t::iterator aIt( rLCS.find( nLang ) ); + sal_uInt16 nVal = aIt == rLCS.end() ? SVX_LANG_NEED_CHECK : aIt->second; + + if (aIt == rLCS.end()) + rLCS[ nLang ] = nVal; + + if (SVX_LANG_NEED_CHECK == (nVal & 0x00FF)) + { + sal_uInt16 nTmpVal = SVX_LANG_MISSING_DO_WARN; + if (xSpell.is() && xSpell->hasLanguage( static_cast<sal_uInt16>(nLang) )) + nTmpVal = SVX_LANG_OK; + nVal &= 0xFF00; + nVal |= nTmpVal; + + rLCS[ nLang ] = nVal; + } + + return static_cast<sal_Int16>(nVal); +} + +sal_Int16 SvxSpellWrapper::CheckHyphLang( + Reference< XHyphenator > const & xHyph, LanguageType nLang) +{ + LangCheckState_map_t &rLCS = GetLangCheckState(); + + LangCheckState_map_t::iterator aIt( rLCS.find( nLang ) ); + sal_uInt16 nVal = aIt == rLCS.end() ? 0 : aIt->second; + + if (aIt == rLCS.end()) + rLCS[ nLang ] = nVal; + + if (SVX_LANG_NEED_CHECK == ((nVal >> 8) & 0x00FF)) + { + sal_uInt16 nTmpVal = SVX_LANG_MISSING_DO_WARN; + if (xHyph.is() && xHyph->hasLocale( LanguageTag::convertToLocale( nLang ) )) + nTmpVal = SVX_LANG_OK; + nVal &= 0x00FF; + nVal |= nTmpVal << 8; + + rLCS[ nLang ] = nVal; + } + + return static_cast<sal_Int16>(nVal); +} + + +void SvxSpellWrapper::SpellStart( SvxSpellArea /*eSpell*/ ) +{ // Here, the necessary preparations be made for SpellContinue in the +} // given area. + + +bool SvxSpellWrapper::SpellMore() +{ + return false; // Should additional documents be examined? +} + + +void SvxSpellWrapper::SpellEnd() +{ // Area is complete, tidy up if necessary + + // display error for last language not found + ShowLanguageErrors(); +} + +void SvxSpellWrapper::SpellContinue() +{ +} + +void SvxSpellWrapper::ReplaceAll( const OUString & ) +{ // Replace Word from the Replace list +} + +void SvxSpellWrapper::InsertHyphen( const sal_Int32 ) +{ // inserting and deleting Hyphen +} + +// Testing of the document areas in the order specified by the flags +void SvxSpellWrapper::SpellDocument( ) +{ +#if ENABLE_WASM_STRIP_HUNSPELL + return; +#else + if ( bOtherCntnt ) + { + bReverse = false; + SpellStart( SvxSpellArea::Other ); + } + else + { + bStartChk = bReverse; + SpellStart( bReverse ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd ); + } + + if ( !FindSpellError() ) + return; + + Reference< XHyphenatedWord > xHyphWord( GetLast(), UNO_QUERY ); + + if (xHyphWord.is()) + { + EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractHyphenWordDialog> pDlg(pFact->CreateHyphenWordDialog( + pWin, + xHyphWord->getWord(), + LanguageTag( xHyphWord->getLocale() ).getLanguageType(), + xHyph, this )); + pDlg->Execute(); + } +#endif +} + + +// Select the next area + + +bool SvxSpellWrapper::SpellNext( ) +{ + Reference< linguistic2::XLinguProperties > xProp( LinguMgr::GetLinguPropertySet() ); + bool bWrapReverse = xProp.is() && xProp->getIsWrapReverse(); + bool bActRev = bRevAllowed && bWrapReverse; + + // bActRev is the direction after Spell checking, bReverse is the one + // at the beginning. + if( bActRev == bReverse ) + { // No change of direction, thus is the + if( bStartChk ) // desired area ( bStartChk ) + bStartDone = true; // completely processed. + else + bEndDone = true; + } + else if( bReverse == bStartChk ) //For a change of direction, an area can + { // be processed during certain circumstances + if( bStartChk ) // If the first part is spell checked in backwards + bEndDone = true; // and this is reversed in the process, then + else // then the end part is processed (and vice-versa). + bStartDone = true; + } + + bReverse = bActRev; + if( bOtherCntnt && bStartDone && bEndDone ) // Document has been fully checked? + { + if ( SpellMore() ) // spell check another document? + { + bOtherCntnt = false; + bStartDone = !bReverse; + bEndDone = bReverse; + SpellStart( SvxSpellArea::Body ); + return true; + } + return false; + } + + bool bGoOn = false; + + if ( bOtherCntnt ) + { + bStartChk = false; + SpellStart( SvxSpellArea::Body ); + bGoOn = true; + } + else if ( bStartDone && bEndDone ) + { + if ( SpellMore() ) // check another document? + { + bOtherCntnt = false; + bStartDone = !bReverse; + bEndDone = bReverse; + SpellStart( SvxSpellArea::Body ); + return true; + } + } + else + { + // a BODY_area done, ask for the other BODY_area + xWait.reset(); + + TranslateId pResId = bReverse ? RID_SVXSTR_QUERY_BW_CONTINUE : RID_SVXSTR_QUERY_CONTINUE; + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin, + VclMessageType::Question, VclButtonsType::YesNo, + EditResId(pResId))); + if (xBox->run() != RET_YES) + { + // sacrifice the other area if necessary ask for special area + xWait.reset(new weld::WaitObject(pWin)); + bStartDone = bEndDone = true; + return SpellNext(); + } + else + { + bStartChk = !bStartDone; + SpellStart( bStartChk ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd ); + bGoOn = true; + } + xWait.reset(new weld::WaitObject(pWin)); + } + return bGoOn; +} + + +Reference< XDictionary > SvxSpellWrapper::GetAllRightDic() +{ + Reference< XDictionary > xDic; + + Reference< XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() ); + if (xDicList.is()) + { + Sequence< Reference< XDictionary > > aDics( xDicList->getDictionaries() ); + const Reference< XDictionary > *pDic = aDics.getConstArray(); + sal_Int32 nCount = aDics.getLength(); + + sal_Int32 i = 0; + while (!xDic.is() && i < nCount) + { + Reference< XDictionary > xTmp = pDic[i]; + if (xTmp.is()) + { + if ( xTmp->isActive() && + xTmp->getDictionaryType() != DictionaryType_NEGATIVE && + LanguageTag( xTmp->getLocale() ).getLanguageType() == LANGUAGE_NONE ) + { + Reference< frame::XStorable > xStor( xTmp, UNO_QUERY ); + if (xStor.is() && xStor->hasLocation() && !xStor->isReadonly()) + { + xDic = xTmp; + } + } + } + ++i; + } + + if (!xDic.is()) + { + xDic = LinguMgr::GetStandardDic(); + if (xDic.is()) + xDic->setActive( true ); + } + } + + return xDic; +} + + +bool SvxSpellWrapper::FindSpellError() +{ + ShowLanguageErrors(); + + xWait.reset(new weld::WaitObject(pWin)); + bool bSpell = true; + + Reference< XDictionary > xAllRightDic; + if (IsAllRight()) + xAllRightDic = GetAllRightDic(); + + while ( bSpell ) + { + SpellContinue(); + + Reference< XSpellAlternatives > xAlt( GetLast(), UNO_QUERY ); + Reference< XHyphenatedWord > xHyphWord( GetLast(), UNO_QUERY ); + + if (xAlt.is()) + { + if (IsAllRight() && xAllRightDic.is()) + { + xAllRightDic->add( xAlt->getWord(), false, OUString() ); + } + else + { + // look up in ChangeAllList for misspelled word + Reference< XDictionary > xChangeAllList = + LinguMgr::GetChangeAllList(); + Reference< XDictionaryEntry > xEntry; + if (xChangeAllList.is()) + xEntry = xChangeAllList->getEntry( xAlt->getWord() ); + + if (xEntry.is()) + { + // replace word without asking + ReplaceAll( xEntry->getReplacementText() ); + } + else + bSpell = false; + } + } + else if (xHyphWord.is()) + bSpell = false; + else + { + SpellEnd(); + bSpell = SpellNext(); + } + } + xWait.reset(); + return GetLast().is(); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/svxacorr.cxx b/editeng/source/misc/svxacorr.cxx new file mode 100644 index 000000000..ff93ecee8 --- /dev/null +++ b/editeng/source/misc/svxacorr.cxx @@ -0,0 +1,3069 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <string_view> +#include <sal/config.h> + +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <tools/urlobj.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <i18nutil/transliteration.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <svl/fstathelper.hxx> +#include <svl/urihelper.hxx> +#include <unotools/charclass.hxx> +#include <com/sun/star/i18n/UnicodeType.hpp> +#include <unotools/collatorwrapper.hxx> +#include <com/sun/star/i18n/UnicodeScript.hpp> +#include <com/sun/star/i18n/OrdinalSuffix.hpp> +#include <unotools/localedatawrapper.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/storagehelper.hxx> +#include <o3tl/string_view.hxx> +#include <editeng/editids.hrc> +#include <sot/storage.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/svxacorr.hxx> +#include <editeng/unolingu.hxx> +#include <vcl/window.hxx> +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <com/sun/star/xml/sax/FastParser.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/SAXParseException.hpp> +#include <unotools/streamwrap.hxx> +#include "SvXMLAutoCorrectImport.hxx" +#include "SvXMLAutoCorrectExport.hxx" +#include "SvXMLAutoCorrectTokenHandler.hxx" +#include <ucbhelper/content.hxx> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <tools/diagnose_ex.h> +#include <xmloff/xmltoken.hxx> +#include <unordered_map> +#include <rtl/character.hxx> + +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star; +using namespace ::xmloff::token; +using namespace ::utl; + +namespace { + +enum class Flags { + NONE = 0x00, + FullStop = 0x01, + ExclamationMark = 0x02, + QuestionMark = 0x04, +}; + +} + +namespace o3tl { + template<> struct typed_flags<Flags> : is_typed_flags<Flags, 0x07> {}; +} +const sal_Unicode cNonBreakingSpace = 0xA0; // UNICODE code for no break space + +constexpr OUStringLiteral pXMLImplWordStart_ExcptLstStr = u"WordExceptList.xml"; +constexpr OUStringLiteral pXMLImplCplStt_ExcptLstStr = u"SentenceExceptList.xml"; +constexpr OUStringLiteral pXMLImplAutocorr_ListStr = u"DocumentList.xml"; + +const char + /* also at these beginnings - Brackets and all kinds of begin characters */ + sImplSttSkipChars[] = "\"\'([{\x83\x84\x89\x91\x92\x93\x94", + /* also at these ends - Brackets and all kinds of begin characters */ + sImplEndSkipChars[] = "\"\')]}\x83\x84\x89\x91\x92\x93\x94"; + +static OUString EncryptBlockName_Imp(const OUString& rName); + +static bool NonFieldWordDelim( const sal_Unicode c ) +{ + return ' ' == c || '\t' == c || 0x0a == c || + cNonBreakingSpace == c || 0x2011 == c; +} + +static bool IsWordDelim( const sal_Unicode c ) +{ + return c == 0x1 || NonFieldWordDelim(c); +} + + +static bool IsLowerLetter( sal_Int32 nCharType ) +{ + return CharClass::isLetterType( nCharType ) && + ( css::i18n::KCharacterType::LOWER & nCharType); +} + +static bool IsUpperLetter( sal_Int32 nCharType ) +{ + return CharClass::isLetterType( nCharType ) && + ( css::i18n::KCharacterType::UPPER & nCharType); +} + +static bool lcl_IsUnsupportedUnicodeChar( CharClass const & rCC, const OUString& rTxt, + sal_Int32 nStt, sal_Int32 nEnd ) +{ + for( ; nStt < nEnd; ++nStt ) + { + css::i18n::UnicodeScript nScript = rCC.getScript( rTxt, nStt ); + switch( nScript ) + { + case css::i18n::UnicodeScript_kCJKRadicalsSupplement: + case css::i18n::UnicodeScript_kHangulJamo: + case css::i18n::UnicodeScript_kCJKSymbolPunctuation: + case css::i18n::UnicodeScript_kHiragana: + case css::i18n::UnicodeScript_kKatakana: + case css::i18n::UnicodeScript_kHangulCompatibilityJamo: + case css::i18n::UnicodeScript_kEnclosedCJKLetterMonth: + case css::i18n::UnicodeScript_kCJKCompatibility: + case css::i18n::UnicodeScript_kCJKUnifiedIdeographsExtensionA: + case css::i18n::UnicodeScript_kCJKUnifiedIdeograph: + case css::i18n::UnicodeScript_kHangulSyllable: + case css::i18n::UnicodeScript_kCJKCompatibilityIdeograph: + case css::i18n::UnicodeScript_kHalfwidthFullwidthForm: + return true; + default: ; //do nothing + } + } + return false; +} + +static bool lcl_IsSymbolChar( CharClass const & rCC, const OUString& rTxt, + sal_Int32 nStt, sal_Int32 nEnd ) +{ + for( ; nStt < nEnd; ++nStt ) + { + if( css::i18n::UnicodeType::PRIVATE_USE == rCC.getType( rTxt, nStt )) + return true; + } + return false; +} + +static bool lcl_IsInAsciiArr( const char* pArr, const sal_Unicode c ) +{ + // tdf#54409 check also typographical quotation marks in the case of skipped ASCII quotation marks + if ( 0x2018 <= c && c <= 0x201F && (pArr == sImplSttSkipChars || pArr == sImplEndSkipChars) ) + return true; + + bool bRet = false; + for( ; *pArr; ++pArr ) + if( *pArr == c ) + { + bRet = true; + break; + } + return bRet; +} + +SvxAutoCorrDoc::~SvxAutoCorrDoc() +{ +} + +// Called by the functions: +// - FnCapitalStartWord +// - FnCapitalStartSentence +// after the exchange of characters. Then the words, if necessary, can be inserted +// into the exception list. +void SvxAutoCorrDoc::SaveCpltSttWord( ACFlags, sal_Int32, const OUString&, + sal_Unicode ) +{ +} + +LanguageType SvxAutoCorrDoc::GetLanguage( sal_Int32 ) const +{ + return LANGUAGE_SYSTEM; +} + +static const LanguageTag& GetAppLang() +{ + return Application::GetSettings().GetLanguageTag(); +} + +/// Never use an unresolved LANGUAGE_SYSTEM. +static LanguageType GetDocLanguage( const SvxAutoCorrDoc& rDoc, sal_Int32 nPos ) +{ + LanguageType eLang = rDoc.GetLanguage( nPos ); + if (eLang == LANGUAGE_SYSTEM) + eLang = GetAppLang().getLanguageType(); // the current work locale + return eLang; +} + +static LocaleDataWrapper& GetLocaleDataWrapper( LanguageType nLang ) +{ + static std::unique_ptr<LocaleDataWrapper> xLclDtWrp; + LanguageTag aLcl( nLang ); + if (!xLclDtWrp || xLclDtWrp->getLoadedLanguageTag() != aLcl) + xLclDtWrp.reset(new LocaleDataWrapper(std::move(aLcl))); + return *xLclDtWrp; +} +static TransliterationWrapper& GetIgnoreTranslWrapper() +{ + static int bIsInit = 0; + static TransliterationWrapper aWrp( ::comphelper::getProcessComponentContext(), + TransliterationFlags::IGNORE_KANA | + TransliterationFlags::IGNORE_WIDTH ); + if( !bIsInit ) + { + aWrp.loadModuleIfNeeded( GetAppLang().getLanguageType() ); + bIsInit = 1; + } + return aWrp; +} +static CollatorWrapper& GetCollatorWrapper() +{ + static CollatorWrapper aCollWrp = []() + { + CollatorWrapper tmp( ::comphelper::getProcessComponentContext() ); + tmp.loadDefaultCollator( GetAppLang().getLocale(), 0 ); + return tmp; + }(); + return aCollWrp; +} + +bool SvxAutoCorrect::IsAutoCorrectChar( sal_Unicode cChar ) +{ + return cChar == '\0' || cChar == '\t' || cChar == 0x0a || + cChar == ' ' || cChar == '\'' || cChar == '\"' || + cChar == '*' || cChar == '_' || cChar == '%' || + cChar == '.' || cChar == ',' || cChar == ';' || + cChar == ':' || cChar == '?' || cChar == '!' || + cChar == '<' || cChar == '>' || + cChar == '/' || cChar == '-'; +} + +namespace +{ + bool IsCompoundWordDelimChar(sal_Unicode cChar) + { + return cChar == '-' || SvxAutoCorrect::IsAutoCorrectChar(cChar); + } +} + +bool SvxAutoCorrect::NeedsHardspaceAutocorr( sal_Unicode cChar ) +{ + return cChar == '%' || cChar == ';' || cChar == ':' || cChar == '?' || cChar == '!' || + cChar == '/' /*case for the urls exception*/; +} + +ACFlags SvxAutoCorrect::GetDefaultFlags() +{ + ACFlags nRet = ACFlags::Autocorrect + | ACFlags::CapitalStartSentence + | ACFlags::CapitalStartWord + | ACFlags::ChgOrdinalNumber + | ACFlags::ChgToEnEmDash + | ACFlags::AddNonBrkSpace + | ACFlags::TransliterateRTL + | ACFlags::ChgAngleQuotes + | ACFlags::ChgWeightUnderl + | ACFlags::SetINetAttr + | ACFlags::ChgQuotes + | ACFlags::SaveWordCplSttLst + | ACFlags::SaveWordWordStartLst + | ACFlags::CorrectCapsLock; + LanguageType eLang = GetAppLang().getLanguageType(); + if( eLang.anyOf( + LANGUAGE_ENGLISH, + LANGUAGE_ENGLISH_US, + LANGUAGE_ENGLISH_UK, + LANGUAGE_ENGLISH_AUS, + LANGUAGE_ENGLISH_CAN, + LANGUAGE_ENGLISH_NZ, + LANGUAGE_ENGLISH_EIRE, + LANGUAGE_ENGLISH_SAFRICA, + LANGUAGE_ENGLISH_JAMAICA, + LANGUAGE_ENGLISH_CARIBBEAN)) + nRet &= ~ACFlags(ACFlags::ChgQuotes|ACFlags::ChgSglQuotes); + return nRet; +} + +constexpr sal_Unicode cEmDash = 0x2014; +constexpr sal_Unicode cEnDash = 0x2013; +constexpr sal_Unicode cApostrophe = 0x2019; +constexpr sal_Unicode cLeftDoubleAngleQuote = 0xAB; +constexpr sal_Unicode cRightDoubleAngleQuote = 0xBB; +constexpr sal_Unicode cLeftSingleAngleQuote = 0x2039; +constexpr sal_Unicode cRightSingleAngleQuote = 0x203A; +// stop characters for searching preceding quotes +// (the first character is also the opening quote we are looking for) +const sal_Unicode aStopDoubleAngleQuoteStart[] = { 0x201E, 0x201D, 0x201C, 0 }; // preceding ,, +const sal_Unicode aStopDoubleAngleQuoteEnd[] = { cRightDoubleAngleQuote, cLeftDoubleAngleQuote, 0x201D, 0x201E, 0 }; // preceding >> +// preceding << for Romanian, handle also alternative primary closing quotation mark U+201C +const sal_Unicode aStopDoubleAngleQuoteEndRo[] = { cLeftDoubleAngleQuote, cRightDoubleAngleQuote, 0x201D, 0x201E, 0x201C, 0 }; +const sal_Unicode aStopSingleQuoteEnd[] = { 0x201A, 0x2018, 0x201C, 0x201E, 0 }; +const sal_Unicode aStopSingleQuoteEndRuUa[] = { 0x201E, 0x201C, cRightDoubleAngleQuote, cLeftDoubleAngleQuote, 0 }; + +SvxAutoCorrect::SvxAutoCorrect( OUString aShareAutocorrFile, + OUString aUserAutocorrFile ) + : sShareAutoCorrFile(std::move( aShareAutocorrFile )) + , sUserAutoCorrFile(std::move( aUserAutocorrFile )) + , eCharClassLang( LANGUAGE_DONTKNOW ) + , nFlags(SvxAutoCorrect::GetDefaultFlags()) + , cStartDQuote( 0 ) + , cEndDQuote( 0 ) + , cStartSQuote( 0 ) + , cEndSQuote( 0 ) +{ +} + +SvxAutoCorrect::SvxAutoCorrect( const SvxAutoCorrect& rCpy ) + : sShareAutoCorrFile( rCpy.sShareAutoCorrFile ) + , sUserAutoCorrFile( rCpy.sUserAutoCorrFile ) + , aSwFlags( rCpy.aSwFlags ) + , eCharClassLang(rCpy.eCharClassLang) + , nFlags( rCpy.nFlags & ~ACFlags(ACFlags::ChgWordLstLoad|ACFlags::CplSttLstLoad|ACFlags::WordStartLstLoad)) + , cStartDQuote( rCpy.cStartDQuote ) + , cEndDQuote( rCpy.cEndDQuote ) + , cStartSQuote( rCpy.cStartSQuote ) + , cEndSQuote( rCpy.cEndSQuote ) +{ +} + + +SvxAutoCorrect::~SvxAutoCorrect() +{ +} + +void SvxAutoCorrect::GetCharClass_( LanguageType eLang ) +{ + pCharClass.reset( new CharClass( LanguageTag( eLang)) ); + eCharClassLang = eLang; +} + +void SvxAutoCorrect::SetAutoCorrFlag( ACFlags nFlag, bool bOn ) +{ + ACFlags nOld = nFlags; + nFlags = bOn ? nFlags | nFlag + : nFlags & ~nFlag; + + if( !bOn ) + { + if( (nOld & ACFlags::CapitalStartSentence) != (nFlags & ACFlags::CapitalStartSentence) ) + nFlags &= ~ACFlags::CplSttLstLoad; + if( (nOld & ACFlags::CapitalStartWord) != (nFlags & ACFlags::CapitalStartWord) ) + nFlags &= ~ACFlags::WordStartLstLoad; + if( (nOld & ACFlags::Autocorrect) != (nFlags & ACFlags::Autocorrect) ) + nFlags &= ~ACFlags::ChgWordLstLoad; + } +} + + +// Correct TWo INitial CApitals +void SvxAutoCorrect::FnCapitalStartWord( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + CharClass& rCC = GetCharClass( eLang ); + + // Delete all non alphanumeric. Test the characters at the beginning/end of + // the word ( recognizes: "(min.", "/min.", and so on.) + for( ; nSttPos < nEndPos; ++nSttPos ) + if( rCC.isLetterNumeric( rTxt, nSttPos )) + break; + for( ; nSttPos < nEndPos; --nEndPos ) + if( rCC.isLetterNumeric( rTxt, nEndPos - 1 )) + break; + + // Is the word a compounded word separated by delimiters? + // If so, keep track of all delimiters so each constituent + // word can be checked for two initial capital letters. + std::deque<sal_Int32> aDelimiters; + + // Always check for two capitals at the beginning + // of the entire word, so start at nSttPos. + aDelimiters.push_back(nSttPos); + + // Find all compound word delimiters + for (sal_Int32 n = nSttPos; n < nEndPos; ++n) + { + if (IsCompoundWordDelimChar(rTxt[ n ])) + { + aDelimiters.push_back( n + 1 ); // Get position of char after delimiter + } + } + + // Decide where to put the terminating delimiter. + // If the last AutoCorrect char was a newline, then the AutoCorrect + // char will not be included in rTxt. + // If the last AutoCorrect char was not a newline, then the AutoCorrect + // character will be the last character in rTxt. + if (!IsCompoundWordDelimChar(rTxt[nEndPos-1])) + aDelimiters.push_back(nEndPos); + + // Iterate through the word and all words that compose it. + // Two capital letters at the beginning of word? + for (size_t nI = 0; nI < aDelimiters.size() - 1; ++nI) + { + nSttPos = aDelimiters[nI]; + nEndPos = aDelimiters[nI + 1]; + + if( nSttPos+2 < nEndPos && + IsUpperLetter( rCC.getCharacterType( rTxt, nSttPos )) && + IsUpperLetter( rCC.getCharacterType( rTxt, ++nSttPos )) && + // Is the third character a lower case + IsLowerLetter( rCC.getCharacterType( rTxt, nSttPos +1 )) && + // Do not replace special attributes + 0x1 != rTxt[ nSttPos ] && 0x2 != rTxt[ nSttPos ]) + { + // test if the word is in an exception list + OUString sWord( rTxt.copy( nSttPos - 1, nEndPos - nSttPos + 1 )); + if( !FindInWordStartExceptList(eLang, sWord) ) + { + // Check that word isn't correctly spelt before correcting: + css::uno::Reference< css::linguistic2::XSpellChecker1 > xSpeller = + LinguMgr::GetSpellChecker(); + if( xSpeller->hasLanguage(static_cast<sal_uInt16>(eLang)) ) + { + Sequence< css::beans::PropertyValue > aEmptySeq; + if (xSpeller->isValid(sWord, static_cast<sal_uInt16>(eLang), aEmptySeq)) + { + return; + } + } + sal_Unicode cSave = rTxt[ nSttPos ]; + OUString sChar = rCC.lowercase( OUString(cSave) ); + if( sChar[0] != cSave && rDoc.ReplaceRange( nSttPos, 1, sChar )) + { + if( ACFlags::SaveWordWordStartLst & nFlags ) + rDoc.SaveCpltSttWord( ACFlags::CapitalStartWord, nSttPos, sWord, cSave ); + } + } + } + } +} + +// Format ordinal numbers suffixes (1st -> 1^st) +bool SvxAutoCorrect::FnChgOrdinalNumber( + SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang) +{ + // 1st, 2nd, 3rd, 4 - 0th + // 201th or 201st + // 12th or 12nd + bool bChg = false; + + // In some languages ordinal suffixes should never be + // changed to superscript. Let's break for those languages. + if (!eLang.anyOf( + LANGUAGE_SWEDISH, + LANGUAGE_SWEDISH_FINLAND)) + { + CharClass& rCC = GetCharClass(eLang); + + for (; nSttPos < nEndPos; ++nSttPos) + if (!lcl_IsInAsciiArr(sImplSttSkipChars, rTxt[nSttPos])) + break; + for (; nSttPos < nEndPos; --nEndPos) + if (!lcl_IsInAsciiArr(sImplEndSkipChars, rTxt[nEndPos - 1])) + break; + + + // Get the last number in the string to check + sal_Int32 nNumEnd = nEndPos; + bool bFoundEnd = false; + bool isValidNumber = true; + sal_Int32 i = nEndPos; + while (i > nSttPos) + { + i--; + bool isDigit = rCC.isDigit(rTxt, i); + if (bFoundEnd) + isValidNumber &= (isDigit || !rCC.isLetter(rTxt, i)); + + if (isDigit && !bFoundEnd) + { + bFoundEnd = true; + nNumEnd = i; + } + } + + if (bFoundEnd && isValidNumber) { + sal_Int32 nNum = o3tl::toInt32(rTxt.subView(nSttPos, nNumEnd - nSttPos + 1)); + + // Check if the characters after that number correspond to the ordinal suffix + uno::Reference< i18n::XOrdinalSuffix > xOrdSuffix + = i18n::OrdinalSuffix::create(comphelper::getProcessComponentContext()); + + const uno::Sequence< OUString > aSuffixes = xOrdSuffix->getOrdinalSuffix(nNum, rCC.getLanguageTag().getLocale()); + for (OUString const & sSuffix : aSuffixes) + { + std::u16string_view sEnd = rTxt.subView(nNumEnd + 1, nEndPos - nNumEnd - 1); + + if (sSuffix == sEnd) + { + // Check if the ordinal suffix has to be set as super script + if (rCC.isLetter(sSuffix)) + { + // Do the change + SvxEscapementItem aSvxEscapementItem(DFLT_ESC_AUTO_SUPER, + DFLT_ESC_PROP, SID_ATTR_CHAR_ESCAPEMENT); + rDoc.SetAttr(nNumEnd + 1, nEndPos, + SID_ATTR_CHAR_ESCAPEMENT, + aSvxEscapementItem); + bChg = true; + } + } + } + } + } + return bChg; +} + +// Replace dashes +bool SvxAutoCorrect::FnChgToEnEmDash( + SvxAutoCorrDoc& rDoc, std::u16string_view rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + bool bRet = false; + CharClass& rCC = GetCharClass( eLang ); + if (eLang == LANGUAGE_SYSTEM) + eLang = GetAppLang().getLanguageType(); + bool bAlwaysUseEmDash = (eLang == LANGUAGE_RUSSIAN || eLang == LANGUAGE_UKRAINIAN); + + // replace " - " or " --" with "enDash" + if( 1 < nSttPos && 1 <= nEndPos - nSttPos ) + { + sal_Unicode cCh = rTxt[ nSttPos ]; + if( '-' == cCh ) + { + if( 1 < nEndPos - nSttPos && + ' ' == rTxt[ nSttPos-1 ] && + '-' == rTxt[ nSttPos+1 ]) + { + sal_Int32 n; + for( n = nSttPos+2; n < nEndPos && lcl_IsInAsciiArr( + sImplSttSkipChars,(cCh = rTxt[ n ])); + ++n ) + ; + + // found: " --[<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) ) ) + { + for( n = nSttPos-1; n && lcl_IsInAsciiArr( + sImplEndSkipChars,(cCh = rTxt[ --n ])); ) + ; + + // found: "[A-z0-9][<AnyEndChars>] --[<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) )) + { + rDoc.Delete( nSttPos, nSttPos + 2 ); + rDoc.Insert( nSttPos, bAlwaysUseEmDash ? OUString(cEmDash) : OUString(cEnDash) ); + bRet = true; + } + } + } + } + else if( 3 < nSttPos && + ' ' == rTxt[ nSttPos-1 ] && + '-' == rTxt[ nSttPos-2 ]) + { + sal_Int32 n, nLen = 1, nTmpPos = nSttPos - 2; + if( '-' == ( cCh = rTxt[ nTmpPos-1 ]) ) + { + --nTmpPos; + ++nLen; + cCh = rTxt[ nTmpPos-1 ]; + } + if( ' ' == cCh ) + { + for( n = nSttPos; n < nEndPos && lcl_IsInAsciiArr( + sImplSttSkipChars,(cCh = rTxt[ n ])); + ++n ) + ; + + // found: " - [<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) ) ) + { + cCh = ' '; + for( n = nTmpPos-1; n && lcl_IsInAsciiArr( + sImplEndSkipChars,(cCh = rTxt[ --n ])); ) + ; + // found: "[A-z0-9][<AnyEndChars>] - [<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) )) + { + rDoc.Delete( nTmpPos, nTmpPos + nLen ); + rDoc.Insert( nTmpPos, bAlwaysUseEmDash ? OUString(cEmDash) : OUString(cEnDash) ); + bRet = true; + } + } + } + } + } + + // Replace [A-z0-9]--[A-z0-9] double dash with "emDash" or "enDash" + // [0-9]--[0-9] double dash always replaced with "enDash" + // Finnish and Hungarian use enDash instead of emDash. + bool bEnDash = (eLang == LANGUAGE_HUNGARIAN || eLang == LANGUAGE_FINNISH); + if( 4 <= nEndPos - nSttPos ) + { + OUString sTmp( rTxt.substr( nSttPos, nEndPos - nSttPos ) ); + sal_Int32 nFndPos = sTmp.indexOf("--"); + if( nFndPos != -1 && nFndPos && + nFndPos + 2 < sTmp.getLength() && + ( rCC.isLetterNumeric( sTmp, nFndPos - 1 ) || + lcl_IsInAsciiArr( sImplEndSkipChars, rTxt[ nFndPos - 1 ] )) && + ( rCC.isLetterNumeric( sTmp, nFndPos + 2 ) || + lcl_IsInAsciiArr( sImplSttSkipChars, rTxt[ nFndPos + 2 ] ))) + { + nSttPos = nSttPos + nFndPos; + rDoc.Delete( nSttPos, nSttPos + 2 ); + rDoc.Insert( nSttPos, (bEnDash || (rCC.isDigit( sTmp, nFndPos - 1 ) && + rCC.isDigit( sTmp, nFndPos + 2 )) ? OUString(cEnDash) : OUString(cEmDash)) ); + bRet = true; + } + } + return bRet; +} + +// Add non-breaking space before specific punctuation marks in French text +bool SvxAutoCorrect::FnAddNonBrkSpace( + SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nEndPos, + LanguageType eLang, bool& io_bNbspRunNext ) +{ + bool bRet = false; + + CharClass& rCC = GetCharClass( eLang ); + + if ( rCC.getLanguageTag().getLanguage() == "fr" ) + { + bool bFrCA = (rCC.getLanguageTag().getCountry() == "CA"); + OUString allChars = ":;?!%"; + OUString chars( allChars ); + if ( bFrCA ) + chars = ":"; + + sal_Unicode cChar = rTxt[ nEndPos ]; + bool bHasSpace = chars.indexOf( cChar ) != -1; + bool bIsSpecial = allChars.indexOf( cChar ) != -1; + if ( bIsSpecial ) + { + // Get the last word delimiter position + sal_Int32 nSttWdPos = nEndPos; + bool bWasWordDelim = false; + while( nSttWdPos ) + { + bWasWordDelim = IsWordDelim( rTxt[ --nSttWdPos ]); + if (bWasWordDelim) + break; + } + + //See if the text is the start of a protocol string, e.g. have text of + //"http" see if it is the start of "http:" and if so leave it alone + sal_Int32 nIndex = nSttWdPos + (bWasWordDelim ? 1 : 0); + sal_Int32 nProtocolLen = nEndPos - nSttWdPos + 1; + if (nIndex + nProtocolLen <= rTxt.getLength()) + { + if (INetURLObject::CompareProtocolScheme(rTxt.subView(nIndex, nProtocolLen)) != INetProtocol::NotValid) + return false; + } + + // Check the presence of "://" in the word + sal_Int32 nStrPos = rTxt.indexOf( "://", nSttWdPos + 1 ); + if ( nStrPos == -1 && nEndPos > 0 ) + { + // Check the previous char + sal_Unicode cPrevChar = rTxt[ nEndPos - 1 ]; + if ( ( chars.indexOf( cPrevChar ) == -1 ) && cPrevChar != '\t' ) + { + // Remove any previous normal space + sal_Int32 nPos = nEndPos - 1; + while ( cPrevChar == ' ' || cPrevChar == cNonBreakingSpace ) + { + if ( nPos == 0 ) break; + nPos--; + cPrevChar = rTxt[ nPos ]; + } + + nPos++; + if ( nEndPos - nPos > 0 ) + rDoc.Delete( nPos, nEndPos ); + + // Add the non-breaking space at the end pos + if ( bHasSpace ) + rDoc.Insert( nPos, OUString(cNonBreakingSpace) ); + io_bNbspRunNext = true; + bRet = true; + } + else if ( chars.indexOf( cPrevChar ) != -1 ) + io_bNbspRunNext = true; + } + } + else if ( cChar == '/' && nEndPos > 1 && rTxt.getLength() > (nEndPos - 1) ) + { + // Remove the hardspace right before to avoid formatting URLs + sal_Unicode cPrevChar = rTxt[ nEndPos - 1 ]; + sal_Unicode cMaybeSpaceChar = rTxt[ nEndPos - 2 ]; + if ( cPrevChar == ':' && cMaybeSpaceChar == cNonBreakingSpace ) + { + rDoc.Delete( nEndPos - 2, nEndPos - 1 ); + bRet = true; + } + } + } + + return bRet; +} + +// URL recognition +bool SvxAutoCorrect::FnSetINetAttr( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + OUString sURL( URIHelper::FindFirstURLInText( rTxt, nSttPos, nEndPos, + GetCharClass( eLang ) )); + bool bRet = !sURL.isEmpty(); + if( bRet ) // so, set attribute: + rDoc.SetINetAttr( nSttPos, nEndPos, sURL ); + return bRet; +} + +// Automatic *bold*, /italic/, -strikeout- and _underline_ +bool SvxAutoCorrect::FnChgWeightUnderl( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nEndPos ) +{ + // Condition: + // at the beginning: _, *, / or ~ after Space with the following !Space + // at the end: _, *, / or ~ before Space (word delimiter?) + + sal_Unicode cInsChar = rTxt[ nEndPos ]; // underline, bold, italic or strikeout + if( ++nEndPos != rTxt.getLength() && + !IsWordDelim( rTxt[ nEndPos ] ) ) + return false; + + --nEndPos; + + bool bAlphaNum = false; + sal_Int32 nPos = nEndPos; + sal_Int32 nFndPos = -1; + CharClass& rCC = GetCharClass( LANGUAGE_SYSTEM ); + + while( nPos ) + { + switch( sal_Unicode c = rTxt[ --nPos ] ) + { + case '_': + case '-': + case '/': + case '*': + if( c == cInsChar ) + { + if( bAlphaNum && nPos+1 < nEndPos && ( !nPos || + IsWordDelim( rTxt[ nPos-1 ])) && + !IsWordDelim( rTxt[ nPos+1 ])) + nFndPos = nPos; + else + // Condition is not satisfied, so cancel + nFndPos = -1; + nPos = 0; + } + break; + default: + if( !bAlphaNum ) + bAlphaNum = rCC.isLetterNumeric( rTxt, nPos ); + } + } + + if( -1 != nFndPos ) + { + // first delete the Character at the end - this allows insertion + // of an empty hint in SetAttr which would be removed by Delete + // (fdo#62536, AUTOFMT in Writer) + rDoc.Delete( nEndPos, nEndPos + 1 ); + rDoc.Delete( nFndPos, nFndPos + 1 ); + // Span the Attribute over the area + // the end. + if( '*' == cInsChar ) // Bold + { + SvxWeightItem aSvxWeightItem( WEIGHT_BOLD, SID_ATTR_CHAR_WEIGHT ); + rDoc.SetAttr( nFndPos, nEndPos - 1, + SID_ATTR_CHAR_WEIGHT, + aSvxWeightItem); + } + else if( '/' == cInsChar ) // Italic + { + SvxPostureItem aSvxPostureItem( ITALIC_NORMAL, SID_ATTR_CHAR_POSTURE ); + rDoc.SetAttr( nFndPos, nEndPos - 1, + SID_ATTR_CHAR_POSTURE, + aSvxPostureItem); + } + else if( '-' == cInsChar ) // Strikeout + { + SvxCrossedOutItem aSvxCrossedOutItem( STRIKEOUT_SINGLE, SID_ATTR_CHAR_STRIKEOUT ); + rDoc.SetAttr( nFndPos, nEndPos - 1, + SID_ATTR_CHAR_STRIKEOUT, + aSvxCrossedOutItem); + } + else // Underline + { + SvxUnderlineItem aSvxUnderlineItem( LINESTYLE_SINGLE, SID_ATTR_CHAR_UNDERLINE ); + rDoc.SetAttr( nFndPos, nEndPos - 1, + SID_ATTR_CHAR_UNDERLINE, + aSvxUnderlineItem); + } + } + + return -1 != nFndPos; +} + +// Capitalize first letter of every sentence +void SvxAutoCorrect::FnCapitalStartSentence( SvxAutoCorrDoc& rDoc, + const OUString& rTxt, bool bNormalPos, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + + if( rTxt.isEmpty() || nEndPos <= nSttPos ) + return; + + CharClass& rCC = GetCharClass( eLang ); + OUString aText( rTxt ); + const sal_Unicode *pStart = aText.getStr(), + *pStr = pStart + nEndPos, + *pWordStt = nullptr, + *pDelim = nullptr; + + bool bAtStart = false; + do { + --pStr; + if (rCC.isLetter(aText, pStr - pStart)) + { + if( !pWordStt ) + pDelim = pStr+1; + pWordStt = pStr; + } + else if (pWordStt && !rCC.isDigit(aText, pStr - pStart)) + { + if( (lcl_IsInAsciiArr( "-'", *pStr ) || *pStr == cApostrophe) && // These characters are allowed in words + pWordStt - 1 == pStr && + // Installation at beginning of paragraph. Replaced < by <= (#i38971#) + (pStart + 1) <= pStr && + rCC.isLetter(aText, pStr-1 - pStart)) + pWordStt = --pStr; + else + break; + } + bAtStart = (pStart == pStr); + } while( !bAtStart ); + + if (!pWordStt) + return; // no character to be replaced + + + if (rCC.isDigit(aText, pStr - pStart)) + return; // already ok + + if (IsUpperLetter(rCC.getCharacterType(aText, pWordStt - pStart))) + return; // already ok + + //See if the text is the start of a protocol string, e.g. have text of + //"http" see if it is the start of "http:" and if so leave it alone + sal_Int32 nIndex = pWordStt - pStart; + sal_Int32 nProtocolLen = pDelim - pWordStt + 1; + if (nIndex + nProtocolLen <= rTxt.getLength()) + { + if (INetURLObject::CompareProtocolScheme(rTxt.subView(nIndex, nProtocolLen)) != INetProtocol::NotValid) + return; // already ok + } + + if (0x1 == *pWordStt || 0x2 == *pWordStt) + return; // already ok + + // Only capitalize, if string before specified characters is long enough + if( *pDelim && 2 >= pDelim - pWordStt && + lcl_IsInAsciiArr( ".-)>", *pDelim ) ) + return; + + // tdf#59666 don't capitalize single Greek letters (except in Greek texts) + if ( 1 == pDelim - pWordStt && 0x03B1 <= *pWordStt && *pWordStt <= 0x03C9 && eLang != LANGUAGE_GREEK ) + return; + + if( !bAtStart ) // Still no beginning of a paragraph? + { + if (NonFieldWordDelim(*pStr)) + { + for (;;) + { + bAtStart = (pStart == pStr--); + if (bAtStart || !NonFieldWordDelim(*pStr)) + break; + } + } + // Asian full stop, full width full stop, full width exclamation mark + // and full width question marks are treated as word delimiters + else if ( 0x3002 != *pStr && 0xFF0E != *pStr && 0xFF01 != *pStr && + 0xFF1F != *pStr ) + return; // no valid separator -> no replacement + } + + // No replacement for words in TWo INitial CApitals or sMALL iNITIAL list + if (FindInWordStartExceptList(eLang, OUString(pWordStt, pDelim - pWordStt))) + return; + + if( bAtStart ) // at the beginning of a paragraph? + { + // Check out the previous paragraph, if it exists. + // If so, then check to paragraph separator at the end. + OUString const*const pPrevPara = rDoc.GetPrevPara(bNormalPos); + if (!pPrevPara) + { + // valid separator -> replace + OUString sChar( *pWordStt ); + sChar = rCC.titlecase(sChar); //see fdo#56740 + if (sChar != OUStringChar(*pWordStt)) + rDoc.ReplaceRange( pWordStt - pStart, 1, sChar ); + return; + } + + aText = *pPrevPara; + bAtStart = false; + pStart = aText.getStr(); + pStr = pStart + aText.getLength(); + + do { // overwrite all blanks + --pStr; + if (!NonFieldWordDelim(*pStr)) + break; + bAtStart = (pStart == pStr); + } while( !bAtStart ); + + if( bAtStart ) + return; // no valid separator -> no replacement + } + + // Found [ \t]+[A-Z0-9]+ until here. Test now on the paragraph separator. + // all three can happen, but not more than once! + const sal_Unicode* pExceptStt = nullptr; + bool bContinue = true; + Flags nFlag = Flags::NONE; + do + { + switch (*pStr) + { + // Western and Asian full stop + case '.': + case 0x3002: + case 0xFF0E: + { + if (pStr >= pStart + 2 && *(pStr - 2) == '.') + { + //e.g. text "f.o.o. word": Now currently considering + //capitalizing word but second last character of + //previous word is a . So probably last word is an + //anagram that ends in . and not truly the end of a + //previous sentence, so don't autocapitalize this word + return; + } + if (nFlag & Flags::FullStop) + return; // no valid separator -> no replacement + nFlag |= Flags::FullStop; + pExceptStt = pStr; + } + break; + case '!': + case 0xFF01: + { + if (nFlag & Flags::ExclamationMark) + return; // no valid separator -> no replacement + nFlag |= Flags::ExclamationMark; + } + break; + case '?': + case 0xFF1F: + { + if (nFlag & Flags::QuestionMark) + return; // no valid separator -> no replacement + nFlag |= Flags::QuestionMark; + } + break; + default: + if (nFlag == Flags::NONE) + return; // no valid separator -> no replacement + else + bContinue = false; + break; + } + + if (bContinue && pStr-- == pStart) + { + return; // no valid separator -> no replacement + } + } while (bContinue); + if (Flags::FullStop != nFlag) + pExceptStt = nullptr; + + // Only capitalize, if string is long enough + if( 2 > ( pStr - pStart ) ) + return; + + if (!rCC.isLetterNumeric(aText, pStr-- - pStart)) + { + bool bValid = false, bAlphaFnd = false; + const sal_Unicode* pTmpStr = pStr; + while( !bValid ) + { + if( rCC.isDigit( aText, pTmpStr - pStart ) ) + { + bValid = true; + pStr = pTmpStr - 1; + } + else if( rCC.isLetter( aText, pTmpStr - pStart ) ) + { + if( bAlphaFnd ) + { + bValid = true; + pStr = pTmpStr; + } + else + bAlphaFnd = true; + } + else if (bAlphaFnd || NonFieldWordDelim(*pTmpStr)) + break; + + if( pTmpStr == pStart ) + break; + + --pTmpStr; + } + + if( !bValid ) + return; // no valid separator -> no replacement + } + + bool bNumericOnly = '0' <= *(pStr+1) && *(pStr+1) <= '9'; + + // Search for the beginning of the word + while (!NonFieldWordDelim(*pStr)) + { + if( bNumericOnly && rCC.isLetter( aText, pStr - pStart ) ) + bNumericOnly = false; + + if( pStart == pStr ) + break; + + --pStr; + } + + if( bNumericOnly ) // consists of only numbers, then not + return; + + if (NonFieldWordDelim(*pStr)) + ++pStr; + + OUString sWord; + + // check on the basis of the exception list + if( pExceptStt ) + { + sWord = OUString(pStr, pExceptStt - pStr + 1); + if( FindInCplSttExceptList(eLang, sWord) ) + return; + + // Delete all non alphanumeric. Test the characters at the + // beginning/end of the word ( recognizes: "(min.", "/min.", and so on.) + OUString sTmp( sWord ); + while( !sTmp.isEmpty() && + !rCC.isLetterNumeric( sTmp, 0 ) ) + sTmp = sTmp.copy(1); + + // Remove all non alphanumeric characters towards the end up until + // the last one. + sal_Int32 nLen = sTmp.getLength(); + while( nLen && !rCC.isLetterNumeric( sTmp, nLen-1 ) ) + --nLen; + if( nLen + 1 < sTmp.getLength() ) + sTmp = sTmp.copy( 0, nLen + 1 ); + + if( !sTmp.isEmpty() && sTmp.getLength() != sWord.getLength() && + FindInCplSttExceptList(eLang, sTmp)) + return; + + if(FindInCplSttExceptList(eLang, sWord, true)) + return; + } + + // Ok, then replace + sal_Unicode cSave = *pWordStt; + nSttPos = pWordStt - rTxt.getStr(); + OUString sChar = rCC.titlecase(OUString(cSave)); //see fdo#56740 + bool bRet = sChar[0] != cSave && rDoc.ReplaceRange( nSttPos, 1, sChar ); + + // Perhaps someone wants to have the word + if( bRet && ACFlags::SaveWordCplSttLst & nFlags ) + rDoc.SaveCpltSttWord( ACFlags::CapitalStartSentence, nSttPos, sWord, cSave ); +} + +// Correct accidental use of cAPS LOCK key +bool SvxAutoCorrect::FnCorrectCapsLock( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + if (nEndPos - nSttPos < 2) + // string must be at least 2-character long. + return false; + + CharClass& rCC = GetCharClass( eLang ); + + // Check the first 2 letters. + if ( !IsLowerLetter(rCC.getCharacterType(rTxt, nSttPos)) ) + return false; + + if ( !IsUpperLetter(rCC.getCharacterType(rTxt, nSttPos+1)) ) + return false; + + OUStringBuffer aConverted; + aConverted.append( rCC.uppercase(OUString(rTxt[nSttPos])) ); + aConverted.append( rCC.lowercase(OUString(rTxt[nSttPos+1])) ); + + // No replacement for words in TWo INitial CApitals or sMALL iNITIAL list + if (FindInWordStartExceptList(eLang, rTxt.copy(nSttPos, nEndPos - nSttPos))) + return false; + + for( sal_Int32 i = nSttPos+2; i < nEndPos; ++i ) + { + if ( IsLowerLetter(rCC.getCharacterType(rTxt, i)) ) + // A lowercase letter disqualifies the whole text. + return false; + + if ( IsUpperLetter(rCC.getCharacterType(rTxt, i)) ) + // Another uppercase letter. Convert it. + aConverted.append( rCC.lowercase(OUString(rTxt[i])) ); + else + // This is not an alphabetic letter. Leave it as-is. + aConverted.append( rTxt[i] ); + } + + // Replace the word. + rDoc.Delete(nSttPos, nEndPos); + rDoc.Insert(nSttPos, aConverted.makeStringAndClear()); + + return true; +} + + +sal_Unicode SvxAutoCorrect::GetQuote( sal_Unicode cInsChar, bool bSttQuote, + LanguageType eLang ) const +{ + sal_Unicode cRet = bSttQuote ? ( '\"' == cInsChar + ? GetStartDoubleQuote() + : GetStartSingleQuote() ) + : ( '\"' == cInsChar + ? GetEndDoubleQuote() + : GetEndSingleQuote() ); + if( !cRet ) + { + // then through the Language find the right character + if( LANGUAGE_NONE == eLang ) + cRet = cInsChar; + else + { + LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); + OUString sRet( bSttQuote + ? ( '\"' == cInsChar + ? rLcl.getDoubleQuotationMarkStart() + : rLcl.getQuotationMarkStart() ) + : ( '\"' == cInsChar + ? rLcl.getDoubleQuotationMarkEnd() + : rLcl.getQuotationMarkEnd() )); + cRet = !sRet.isEmpty() ? sRet[0] : cInsChar; + } + } + return cRet; +} + +void SvxAutoCorrect::InsertQuote( SvxAutoCorrDoc& rDoc, sal_Int32 nInsPos, + sal_Unicode cInsChar, bool bSttQuote, + bool bIns, LanguageType eLang, ACQuotes eType ) const +{ + sal_Unicode cRet; + + if ( eType == ACQuotes::DoubleAngleQuote ) + { + bool bSwiss = eLang == LANGUAGE_FRENCH_SWISS; + // pressing " inside a quotation -> use second level angle quotes + bool bLeftQuote = '\"' == cInsChar && + // start position and Romanian OR + // not start position and Hungarian + bSttQuote == (eLang != LANGUAGE_HUNGARIAN); + cRet = ( '<' == cInsChar || bLeftQuote ) + ? ( bSwiss ? cLeftSingleAngleQuote : cLeftDoubleAngleQuote ) + : ( bSwiss ? cRightSingleAngleQuote : cRightDoubleAngleQuote ); + } + else if ( eType == ACQuotes::UseApostrophe ) + cRet = cApostrophe; + else + cRet = GetQuote( cInsChar, bSttQuote, eLang ); + + OUString sChg( cInsChar ); + if( bIns ) + rDoc.Insert( nInsPos, sChg ); + else + rDoc.Replace( nInsPos, sChg ); + + sChg = OUString(cRet); + + if( eType == ACQuotes::NonBreakingSpace ) + { + if( rDoc.Insert( bSttQuote ? nInsPos+1 : nInsPos, OUStringChar(cNonBreakingSpace) )) + { + if( !bSttQuote ) + ++nInsPos; + } + } + else if( eType == ACQuotes::DoubleAngleQuote && cInsChar != '\"' ) + { + rDoc.Delete( nInsPos-1, nInsPos); + --nInsPos; + } + + rDoc.Replace( nInsPos, sChg ); + + // i' -> I' in English (last step for the Undo) + if( eType == ACQuotes::CapitalizeIAm ) + rDoc.Replace( nInsPos-1, "I" ); +} + +OUString SvxAutoCorrect::GetQuote( SvxAutoCorrDoc const & rDoc, sal_Int32 nInsPos, + sal_Unicode cInsChar, bool bSttQuote ) +{ + const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); + sal_Unicode cRet = GetQuote( cInsChar, bSttQuote, eLang ); + + OUString sRet(cRet); + + if( '\"' == cInsChar ) + { + if (primary(eLang) == primary(LANGUAGE_FRENCH) && eLang != LANGUAGE_FRENCH_SWISS) + { + if( bSttQuote ) + sRet += " "; + else + sRet = " " + sRet; + } + } + return sRet; +} + +// search preceding opening quote in the paragraph before the insert position +static bool lcl_HasPrecedingChar( std::u16string_view rTxt, sal_Int32 nPos, + const sal_Unicode sPrecedingChar, const sal_Unicode* aStopChars ) +{ + sal_Unicode cTmpChar; + + do { + cTmpChar = rTxt[ --nPos ]; + if ( cTmpChar == sPrecedingChar ) + return true; + + for ( const sal_Unicode* pCh = aStopChars; *pCh; ++pCh ) + if ( cTmpChar == *pCh ) + return false; + + } while ( nPos > 0 ); + + return false; +} + +// WARNING: rText may become invalid, see comment below +void SvxAutoCorrect::DoAutoCorrect( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nInsPos, sal_Unicode cChar, + bool bInsert, bool& io_bNbspRunNext, vcl::Window const * pFrameWin ) +{ + bool bIsNextRun = io_bNbspRunNext; + io_bNbspRunNext = false; // if it was set, then it has to be turned off + + do{ // only for middle check loop !! + if( cChar ) + { + // Prevent double space + if( nInsPos && ' ' == cChar && + IsAutoCorrFlag( ACFlags::IgnoreDoubleSpace ) && + ' ' == rTxt[ nInsPos - 1 ]) + { + break; + } + + bool bSingle = '\'' == cChar; + bool bIsReplaceQuote = + (IsAutoCorrFlag( ACFlags::ChgQuotes ) && ('\"' == cChar )) || + (IsAutoCorrFlag( ACFlags::ChgSglQuotes ) && bSingle ); + if( bIsReplaceQuote ) + { + bool bSttQuote = !nInsPos; + ACQuotes eType = ACQuotes::NONE; + const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); + if (!bSttQuote) + { + sal_Unicode cPrev = rTxt[ nInsPos-1 ]; + bSttQuote = NonFieldWordDelim(cPrev) || + lcl_IsInAsciiArr( "([{", cPrev ) || + ( cEmDash == cPrev ) || + ( cEnDash == cPrev ); + // tdf#38394 use opening quotation mark << in French l'<<word>> + if ( !bSingle && !bSttQuote && cPrev == cApostrophe && + primary(eLang) == primary(LANGUAGE_FRENCH) && + ( ( ( nInsPos == 2 || ( nInsPos > 2 && IsWordDelim( rTxt[ nInsPos-3 ] ) ) ) && + // abbreviated form of ce, de, je, la, le, ne, me, te, se or si + OUString("cdjlnmtsCDJLNMTS").indexOf( rTxt[ nInsPos-2 ] ) > -1 ) || + ( ( nInsPos == 3 || (nInsPos > 3 && IsWordDelim( rTxt[ nInsPos-4 ] ) ) ) && + // abbreviated form of que + ( rTxt[ nInsPos-2 ] == 'u' || rTxt[ nInsPos-2 ] == 'U' ) && + ( rTxt[ nInsPos-3 ] == 'q' || rTxt[ nInsPos-3 ] == 'Q' ) ) ) ) + { + bSttQuote = true; + } + // tdf#108423 for capitalization of English i'm + else if ( bSingle && ( cPrev == 'i' ) && + primary(eLang) == primary(LANGUAGE_ENGLISH) && + ( nInsPos == 1 || IsWordDelim( rTxt[ nInsPos-2 ] ) ) ) + { + eType = ACQuotes::CapitalizeIAm; + } + // tdf#133524 support >>Hungarian<< and <<Romanian>> secondary level quotations + else if ( !bSingle && nInsPos && + ( ( eLang == LANGUAGE_HUNGARIAN && + lcl_HasPrecedingChar( rTxt, nInsPos, + bSttQuote ? aStopDoubleAngleQuoteStart[0] : aStopDoubleAngleQuoteEnd[0], + bSttQuote ? aStopDoubleAngleQuoteStart + 1 : aStopDoubleAngleQuoteEnd + 1 ) ) || + ( eLang.anyOf( + LANGUAGE_ROMANIAN, + LANGUAGE_ROMANIAN_MOLDOVA ) && + lcl_HasPrecedingChar( rTxt, nInsPos, + bSttQuote ? aStopDoubleAngleQuoteStart[0] : aStopDoubleAngleQuoteEndRo[0], + bSttQuote ? aStopDoubleAngleQuoteStart + 1 : aStopDoubleAngleQuoteEndRo + 1 ) ) ) ) + { + LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); + // only if the opening double quotation mark is the default one + if ( rLcl.getDoubleQuotationMarkStart() == OUStringChar(aStopDoubleAngleQuoteStart[0]) ) + eType = ACQuotes::DoubleAngleQuote; + } + else if ( bSingle && nInsPos && !bSttQuote && + // tdf#128860 use apostrophe outside of second level quotation in Czech, German, Icelandic, + // Slovak and Slovenian instead of the – in this case, bad – closing quotation mark U+2018. + // tdf#123786 the same for Russian and Ukrainian + ( ( eLang.anyOf ( + LANGUAGE_CZECH, + LANGUAGE_GERMAN, + LANGUAGE_GERMAN_SWISS, + LANGUAGE_GERMAN_AUSTRIAN, + LANGUAGE_GERMAN_LUXEMBOURG, + LANGUAGE_GERMAN_LIECHTENSTEIN, + LANGUAGE_ICELANDIC, + LANGUAGE_SLOVAK, + LANGUAGE_SLOVENIAN ) && + !lcl_HasPrecedingChar( rTxt, nInsPos, aStopSingleQuoteEnd[0], aStopSingleQuoteEnd + 1 ) ) || + ( eLang.anyOf ( + LANGUAGE_RUSSIAN, + LANGUAGE_UKRAINIAN ) && + !lcl_HasPrecedingChar( rTxt, nInsPos, aStopSingleQuoteEndRuUa[0], aStopSingleQuoteEndRuUa + 1 ) ) ) ) + { + LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); + CharClass& rCC = GetCharClass( eLang ); + if ( ( rLcl.getQuotationMarkStart() == OUStringChar(aStopSingleQuoteEnd[0]) || + rLcl.getQuotationMarkStart() == OUStringChar(aStopSingleQuoteEndRuUa[0]) ) && + // use apostrophe only after letters, not after digits or punctuation + rCC.isLetter(rTxt, nInsPos-1) ) + { + eType = ACQuotes::UseApostrophe; + } + } + } + + if ( eType == ACQuotes::NONE && !bSingle && + ( primary(eLang) == primary(LANGUAGE_FRENCH) && eLang != LANGUAGE_FRENCH_SWISS ) ) + eType = ACQuotes::NonBreakingSpace; + + InsertQuote( rDoc, nInsPos, cChar, bSttQuote, bInsert, eLang, eType ); + break; + } + // tdf#133524 change "<<" and ">>" to double angle quotation marks + else if ( IsAutoCorrFlag( ACFlags::ChgQuotes ) && + IsAutoCorrFlag( ACFlags::ChgAngleQuotes ) && + ('<' == cChar || '>' == cChar) && + nInsPos > 0 && cChar == rTxt[ nInsPos-1 ] ) + { + const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); + if ( eLang.anyOf( + LANGUAGE_CATALAN, // primary level + LANGUAGE_CATALAN_VALENCIAN, // primary level + LANGUAGE_FINNISH, // alternative primary level + LANGUAGE_FRENCH_SWISS, // second level + LANGUAGE_GALICIAN, // primary level + LANGUAGE_HUNGARIAN, // second level + LANGUAGE_POLISH, // second level + LANGUAGE_PORTUGUESE, // primary level + LANGUAGE_PORTUGUESE_BRAZILIAN, // primary level + LANGUAGE_ROMANIAN, // second level + LANGUAGE_ROMANIAN_MOLDOVA, // second level + LANGUAGE_SWEDISH, // alternative primary level + LANGUAGE_SWEDISH_FINLAND, // alternative primary level + LANGUAGE_UKRAINIAN, // primary level + LANGUAGE_USER_ARAGONESE, // primary level + LANGUAGE_USER_ASTURIAN ) || // primary level + primary(eLang) == primary(LANGUAGE_GERMAN) || // alternative primary level + primary(eLang) == primary(LANGUAGE_SPANISH) ) // primary level + { + InsertQuote( rDoc, nInsPos, cChar, false, bInsert, eLang, ACQuotes::DoubleAngleQuote ); + break; + } + } + + if( bInsert ) + rDoc.Insert( nInsPos, OUString(cChar) ); + else + rDoc.Replace( nInsPos, OUString(cChar) ); + + // Hardspaces autocorrection + if ( IsAutoCorrFlag( ACFlags::AddNonBrkSpace ) ) + { + if ( NeedsHardspaceAutocorr( cChar ) && + FnAddNonBrkSpace( rDoc, rTxt, nInsPos, GetDocLanguage( rDoc, nInsPos ), io_bNbspRunNext ) ) + { + ; + } + else if ( bIsNextRun && !IsAutoCorrectChar( cChar ) ) + { + // Remove the NBSP if it wasn't an autocorrection + if ( nInsPos != 0 && NeedsHardspaceAutocorr( rTxt[ nInsPos - 1 ] ) && + cChar != ' ' && cChar != '\t' && cChar != cNonBreakingSpace ) + { + // Look for the last HARD_SPACE + sal_Int32 nPos = nInsPos - 1; + bool bContinue = true; + while ( bContinue ) + { + const sal_Unicode cTmpChar = rTxt[ nPos ]; + if ( cTmpChar == cNonBreakingSpace ) + { + rDoc.Delete( nPos, nPos + 1 ); + bContinue = false; + } + else if ( !NeedsHardspaceAutocorr( cTmpChar ) || nPos == 0 ) + bContinue = false; + nPos--; + } + } + } + } + } + + if( !nInsPos ) + break; + + sal_Int32 nPos = nInsPos - 1; + + if( IsWordDelim( rTxt[ nPos ])) + break; + + // Set bold or underline automatically? + if (('*' == cChar || '_' == cChar || '/' == cChar || '-' == cChar) && (nPos+1 < rTxt.getLength())) + { + if( IsAutoCorrFlag( ACFlags::ChgWeightUnderl ) ) + { + FnChgWeightUnderl( rDoc, rTxt, nPos+1 ); + } + break; + } + + while( nPos && !IsWordDelim( rTxt[ --nPos ])) + ; + + // Found a Paragraph-start or a Blank, search for the word shortcut in + // auto. + sal_Int32 nCapLttrPos = nPos+1; // on the 1st Character + if( !nPos && !IsWordDelim( rTxt[ 0 ])) + --nCapLttrPos; // begin of paragraph and no blank + + const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos ); + CharClass& rCC = GetCharClass( eLang ); + + // no symbol characters + if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nInsPos )) + break; + + if( IsAutoCorrFlag( ACFlags::Autocorrect ) && + // tdf#134940 fix regression of arrow "-->" resulted by premature + // replacement of "--" since '>' was added to IsAutoCorrectChar() + '>' != cChar ) + { + // WARNING ATTENTION: rTxt is an alias of the text node's OUString + // and becomes INVALID if ChgAutoCorrWord returns true! + // => use aPara/pPara to create a valid copy of the string! + OUString aPara; + OUString* pPara = IsAutoCorrFlag(ACFlags::CapitalStartSentence) ? &aPara : nullptr; + + bool bChgWord = rDoc.ChgAutoCorrWord( nCapLttrPos, nInsPos, + *this, pPara ); + if( !bChgWord ) + { + sal_Int32 nCapLttrPos1 = nCapLttrPos, nInsPos1 = nInsPos; + while( nCapLttrPos1 < nInsPos && + lcl_IsInAsciiArr( sImplSttSkipChars, rTxt[ nCapLttrPos1 ] ) + ) + ++nCapLttrPos1; + while( nCapLttrPos1 < nInsPos1 && nInsPos1 && + lcl_IsInAsciiArr( sImplEndSkipChars, rTxt[ nInsPos1-1 ] ) + ) + --nInsPos1; + + if( (nCapLttrPos1 != nCapLttrPos || nInsPos1 != nInsPos ) && + nCapLttrPos1 < nInsPos1 && + rDoc.ChgAutoCorrWord( nCapLttrPos1, nInsPos1, *this, pPara )) + { + bChgWord = true; + nCapLttrPos = nCapLttrPos1; + } + } + + if( bChgWord ) + { + if( !aPara.isEmpty() ) + { + sal_Int32 nEnd = nCapLttrPos; + while( nEnd < aPara.getLength() && + !IsWordDelim( aPara[ nEnd ])) + ++nEnd; + + // Capital letter at beginning of paragraph? + if( IsAutoCorrFlag( ACFlags::CapitalStartSentence ) ) + { + FnCapitalStartSentence( rDoc, aPara, false, + nCapLttrPos, nEnd, eLang ); + } + + if( IsAutoCorrFlag( ACFlags::ChgToEnEmDash ) ) + { + FnChgToEnEmDash( rDoc, aPara, nCapLttrPos, nEnd, eLang ); + } + } + break; + } + } + + if( IsAutoCorrFlag( ACFlags::TransliterateRTL ) && GetDocLanguage( rDoc, nInsPos ) == LANGUAGE_HUNGARIAN ) + { + // WARNING ATTENTION: rTxt is an alias of the text node's OUString + // and becomes INVALID if TransliterateRTLWord returns true! + if ( rDoc.TransliterateRTLWord( nCapLttrPos, nInsPos ) ) + break; + } + + if( ( IsAutoCorrFlag( ACFlags::ChgOrdinalNumber ) && + (nInsPos >= 2 ) && // fdo#69762 avoid autocorrect for 2e-3 + ( '-' != cChar || 'E' != rtl::toAsciiUpperCase(rTxt[nInsPos-1]) || '0' > rTxt[nInsPos-2] || '9' < rTxt[nInsPos-2] ) && + FnChgOrdinalNumber( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) || + ( IsAutoCorrFlag( ACFlags::SetINetAttr ) && + ( ' ' == cChar || '\t' == cChar || 0x0a == cChar || !cChar ) && + FnSetINetAttr( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) ) + ; + else + { + bool bLockKeyOn = pFrameWin && (pFrameWin->GetIndicatorState() & KeyIndicatorState::CAPSLOCK); + bool bUnsupported = lcl_IsUnsupportedUnicodeChar( rCC, rTxt, nCapLttrPos, nInsPos ); + + if ( bLockKeyOn && IsAutoCorrFlag( ACFlags::CorrectCapsLock ) && + FnCorrectCapsLock( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) + { + // Correct accidental use of cAPS LOCK key (do this only when + // the caps or shift lock key is pressed). Turn off the caps + // lock afterwards. + pFrameWin->SimulateKeyPress( KEY_CAPSLOCK ); + } + + // Capital letter at beginning of paragraph ? + if( !bUnsupported && + IsAutoCorrFlag( ACFlags::CapitalStartSentence ) ) + { + FnCapitalStartSentence( rDoc, rTxt, true, nCapLttrPos, nInsPos, eLang ); + } + + // Two capital letters at beginning of word ?? + if( !bUnsupported && + IsAutoCorrFlag( ACFlags::CapitalStartWord ) ) + { + FnCapitalStartWord( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ); + } + + if( IsAutoCorrFlag( ACFlags::ChgToEnEmDash ) ) + { + FnChgToEnEmDash( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ); + } + } + + } while( false ); +} + +SvxAutoCorrectLanguageLists& SvxAutoCorrect::GetLanguageList_( + LanguageType eLang ) +{ + LanguageTag aLanguageTag( eLang); + if (m_aLangTable.find(aLanguageTag) == m_aLangTable.end()) + (void)CreateLanguageFile(aLanguageTag); + return *(m_aLangTable.find(aLanguageTag)->second); +} + +void SvxAutoCorrect::SaveCplSttExceptList( LanguageType eLang ) +{ + auto const iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end() && iter->second) + iter->second->SaveCplSttExceptList(); + else + { + SAL_WARN("editeng", "Save an empty list? "); + } +} + +void SvxAutoCorrect::SaveWordStartExceptList(LanguageType eLang) +{ + auto const iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end() && iter->second) + iter->second->SaveWordStartExceptList(); + else + { + SAL_WARN("editeng", "Save an empty list? "); + } +} + +// Adds a single word. The list will immediately be written to the file! +bool SvxAutoCorrect::AddCplSttException( const OUString& rNew, + LanguageType eLang ) +{ + SvxAutoCorrectLanguageLists* pLists = nullptr; + // either the right language is present or it will be this in the general list + auto iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end()) + pLists = iter->second.get(); + else + { + LanguageTag aLangTagUndetermined( LANGUAGE_UNDETERMINED); + iter = m_aLangTable.find(aLangTagUndetermined); + if (iter != m_aLangTable.end()) + pLists = iter->second.get(); + else if(CreateLanguageFile(aLangTagUndetermined)) + pLists = m_aLangTable.find(aLangTagUndetermined)->second.get(); + } + OSL_ENSURE(pLists, "No auto correction data"); + return pLists && pLists->AddToCplSttExceptList(rNew); +} + +// Adds a single word. The list will immediately be written to the file! +bool SvxAutoCorrect::AddWordStartException( const OUString& rNew, + LanguageType eLang ) +{ + SvxAutoCorrectLanguageLists* pLists = nullptr; + //either the right language is present or it is set in the general list + auto iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end()) + pLists = iter->second.get(); + else + { + LanguageTag aLangTagUndetermined( LANGUAGE_UNDETERMINED); + iter = m_aLangTable.find(aLangTagUndetermined); + if (iter != m_aLangTable.end()) + pLists = iter->second.get(); + else if(CreateLanguageFile(aLangTagUndetermined)) + pLists = m_aLangTable.find(aLangTagUndetermined)->second.get(); + } + OSL_ENSURE(pLists, "No auto correction file!"); + return pLists && pLists->AddToWordStartExceptList(rNew); +} + +OUString SvxAutoCorrect::GetPrevAutoCorrWord(SvxAutoCorrDoc const& rDoc, const OUString& rTxt, + sal_Int32 nPos) +{ + OUString sRet; + if( !nPos ) + return sRet; + + sal_Int32 nEnd = nPos; + + // it must be followed by a blank or tab! + if( ( nPos < rTxt.getLength() && + !IsWordDelim( rTxt[ nPos ])) || + IsWordDelim( rTxt[ --nPos ])) + return sRet; + + while( nPos && !IsWordDelim( rTxt[ --nPos ])) + ; + + // Found a Paragraph-start or a Blank, search for the word shortcut in + // auto. + sal_Int32 nCapLttrPos = nPos+1; // on the 1st Character + if( !nPos && !IsWordDelim( rTxt[ 0 ])) + --nCapLttrPos; // Beginning of paragraph and no Blank! + + while( lcl_IsInAsciiArr( sImplSttSkipChars, rTxt[ nCapLttrPos ]) ) + if( ++nCapLttrPos >= nEnd ) + return sRet; + + if( 3 > nEnd - nCapLttrPos ) + return sRet; + + const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos ); + + CharClass& rCC = GetCharClass(eLang); + + if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nEnd )) + return sRet; + + sRet = rTxt.copy( nCapLttrPos, nEnd - nCapLttrPos ); + return sRet; +} + +// static +std::vector<OUString> SvxAutoCorrect::GetChunkForAutoText(std::u16string_view rTxt, + const sal_Int32 nPos) +{ + constexpr sal_Int32 nMinLen = 3; + constexpr sal_Int32 nMaxLen = 9; + std::vector<OUString> aRes; + if (nPos >= nMinLen) + { + sal_Int32 nBegin = std::max<sal_Int32>(nPos - nMaxLen, 0); + // TODO: better detect word boundaries (not only whitespaces, but also e.g. punctuation) + if (nBegin > 0 && !IsWordDelim(rTxt[nBegin-1])) + { + while (nBegin + nMinLen <= nPos && !IsWordDelim(rTxt[nBegin])) + ++nBegin; + } + if (nBegin + nMinLen <= nPos) + { + OUString sRes( rTxt.substr(nBegin, nPos - nBegin) ); + aRes.push_back(sRes); + bool bLastStartedWithDelim = IsWordDelim(sRes[0]); + for (sal_Int32 i = 1; i <= sRes.getLength() - nMinLen; ++i) + { + bool bAdd = bLastStartedWithDelim; + bLastStartedWithDelim = IsWordDelim(sRes[i]); + bAdd = bAdd || bLastStartedWithDelim; + if (bAdd) + aRes.push_back(sRes.copy(i)); + } + } + } + return aRes; +} + +bool SvxAutoCorrect::CreateLanguageFile( const LanguageTag& rLanguageTag, bool bNewFile ) +{ + OSL_ENSURE(m_aLangTable.find(rLanguageTag) == m_aLangTable.end(), "Language already exists "); + + OUString sUserDirFile( GetAutoCorrFileName( rLanguageTag, true )); + OUString sShareDirFile( sUserDirFile ); + + SvxAutoCorrectLanguageLists* pLists = nullptr; + + tools::Time nMinTime( 0, 2 ), nAktTime( tools::Time::SYSTEM ), nLastCheckTime( tools::Time::EMPTY ); + + auto nFndPos = aLastFileTable.find(rLanguageTag); + if(nFndPos != aLastFileTable.end() && + (nLastCheckTime.SetTime(nFndPos->second), nLastCheckTime < nAktTime) && + nAktTime - nLastCheckTime < nMinTime) + { + // no need to test the file, because the last check is not older then + // 2 minutes. + if( bNewFile ) + { + sShareDirFile = sUserDirFile; + pLists = new SvxAutoCorrectLanguageLists( *this, sShareDirFile, sUserDirFile ); + LanguageTag aTmp(rLanguageTag); // this insert() needs a non-const reference + m_aLangTable.insert(std::make_pair(aTmp, std::unique_ptr<SvxAutoCorrectLanguageLists>(pLists))); + aLastFileTable.erase(nFndPos); + } + } + else if( + ( FStatHelper::IsDocument( sUserDirFile ) || + FStatHelper::IsDocument( sShareDirFile = + GetAutoCorrFileName( rLanguageTag ) ) || + FStatHelper::IsDocument( sShareDirFile = + GetAutoCorrFileName( rLanguageTag, false, false, true) ) + ) || + ( sShareDirFile = sUserDirFile, bNewFile ) + ) + { + pLists = new SvxAutoCorrectLanguageLists( *this, sShareDirFile, sUserDirFile ); + LanguageTag aTmp(rLanguageTag); // this insert() needs a non-const reference + m_aLangTable.insert(std::make_pair(aTmp, std::unique_ptr<SvxAutoCorrectLanguageLists>(pLists))); + if (nFndPos != aLastFileTable.end()) + aLastFileTable.erase(nFndPos); + } + else if( !bNewFile ) + { + aLastFileTable[rLanguageTag] = nAktTime.GetTime(); + } + return pLists != nullptr; +} + +bool SvxAutoCorrect::PutText( const OUString& rShort, const OUString& rLong, + LanguageType eLang ) +{ + LanguageTag aLanguageTag( eLang); + auto const iter = m_aLangTable.find(aLanguageTag); + if (iter != m_aLangTable.end()) + return iter->second->PutText(rShort, rLong); + if(CreateLanguageFile(aLanguageTag)) + return m_aLangTable.find(aLanguageTag)->second->PutText(rShort, rLong); + return false; +} + +void SvxAutoCorrect::MakeCombinedChanges( std::vector<SvxAutocorrWord>& aNewEntries, + std::vector<SvxAutocorrWord>& aDeleteEntries, + LanguageType eLang ) +{ + LanguageTag aLanguageTag( eLang); + auto const iter = m_aLangTable.find(aLanguageTag); + if (iter != m_aLangTable.end()) + { + iter->second->MakeCombinedChanges( aNewEntries, aDeleteEntries ); + } + else if(CreateLanguageFile( aLanguageTag )) + { + m_aLangTable.find( aLanguageTag )->second->MakeCombinedChanges( aNewEntries, aDeleteEntries ); + } +} + +// - return the replacement text (only for SWG-Format, all other +// can be taken from the word list!) +bool SvxAutoCorrect::GetLongText( const OUString&, OUString& ) +{ + return false; +} + +void SvxAutoCorrect::refreshBlockList( const uno::Reference< embed::XStorage >& ) +{ +} + +// Text with attribution (only the SWG - SWG format!) +bool SvxAutoCorrect::PutText( const css::uno::Reference < css::embed::XStorage >&, + const OUString&, const OUString&, SfxObjectShell&, OUString& ) +{ + return false; +} + +OUString EncryptBlockName_Imp(const OUString& rName) +{ + OUStringBuffer aName; + aName.append('#').append(rName); + for (sal_Int32 nLen = rName.getLength(), nPos = 1; nPos < nLen; ++nPos) + { + if (lcl_IsInAsciiArr( "!/:.\\", aName[nPos])) + aName[nPos] &= 0x0f; + } + return aName.makeStringAndClear(); +} + +/* This code is copied from SwXMLTextBlocks::GeneratePackageName */ +static void GeneratePackageName ( std::u16string_view rShort, OUString& rPackageName ) +{ + OString sByte(OUStringToOString(rShort, RTL_TEXTENCODING_UTF7)); + OUStringBuffer aBuf(OStringToOUString(sByte, RTL_TEXTENCODING_ASCII_US)); + + for (sal_Int32 nPos = 0; nPos < aBuf.getLength(); ++nPos) + { + switch (aBuf[nPos]) + { + case '!': + case '/': + case ':': + case '.': + case '\\': + aBuf[nPos] = '_'; + break; + default: + break; + } + } + + rPackageName = aBuf.makeStringAndClear(); +} + +static const SvxAutocorrWord* lcl_SearchWordsInList( + SvxAutoCorrectLanguageLists* pList, const OUString& rTxt, + sal_Int32& rStt, sal_Int32 nEndPos) +{ + const SvxAutocorrWordList* pAutoCorrWordList = pList->GetAutocorrWordList(); + return pAutoCorrWordList->SearchWordsInList( rTxt, rStt, nEndPos ); +} + +// the search for the words in the substitution table +const SvxAutocorrWord* SvxAutoCorrect::SearchWordsInList( + const OUString& rTxt, sal_Int32& rStt, sal_Int32 nEndPos, + SvxAutoCorrDoc&, LanguageTag& rLang ) +{ + const SvxAutocorrWord* pRet = nullptr; + LanguageTag aLanguageTag( rLang); + if( aLanguageTag.isSystemLocale() ) + aLanguageTag.reset( MsLangId::getConfiguredSystemLanguage()); + + /* TODO-BCP47: this is so ugly, should all maybe be a proper fallback + * list instead? */ + + // First search for eLang, then US-English -> English + // and last in LANGUAGE_UNDETERMINED + if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + std::unique_ptr<SvxAutoCorrectLanguageLists> const& pList = m_aLangTable.find(aLanguageTag)->second; + pRet = lcl_SearchWordsInList( pList.get(), rTxt, rStt, nEndPos ); + if( pRet ) + { + rLang = aLanguageTag; + return pRet; + } + else + return nullptr; + } + + // If it still could not be found here, then keep on searching + LanguageType eLang = aLanguageTag.getLanguageType(); + // the primary language for example EN + aLanguageTag.reset(aLanguageTag.getLanguage()); + LanguageType nTmpKey = aLanguageTag.getLanguageType(false); + if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED && + (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false))) + { + //the language is available - so bring it on + std::unique_ptr<SvxAutoCorrectLanguageLists> const& pList = m_aLangTable.find(aLanguageTag)->second; + pRet = lcl_SearchWordsInList( pList.get(), rTxt, rStt, nEndPos ); + if( pRet ) + { + rLang = aLanguageTag; + return pRet; + } + } + + if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + std::unique_ptr<SvxAutoCorrectLanguageLists> const& pList = m_aLangTable.find(aLanguageTag)->second; + pRet = lcl_SearchWordsInList( pList.get(), rTxt, rStt, nEndPos ); + if( pRet ) + { + rLang = aLanguageTag; + return pRet; + } + } + return nullptr; +} + +bool SvxAutoCorrect::FindInWordStartExceptList( LanguageType eLang, + const OUString& sWord ) +{ + LanguageTag aLanguageTag( eLang); + + /* TODO-BCP47: again horrible ugliness */ + + // First search for eLang, then primary language of eLang + // and last in LANGUAGE_UNDETERMINED + + if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + auto const& pList = m_aLangTable.find(aLanguageTag)->second; + if(pList->GetWordStartExceptList()->find(sWord) != pList->GetWordStartExceptList()->end() ) + return true; + } + + // If it still could not be found here, then keep on searching + // the primary language for example EN + aLanguageTag.reset(aLanguageTag.getLanguage()); + LanguageType nTmpKey = aLanguageTag.getLanguageType(false); + if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED && + (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false))) + { + //the language is available - so bring it on + auto const& pList = m_aLangTable.find(aLanguageTag)->second; + if(pList->GetWordStartExceptList()->find(sWord) != pList->GetWordStartExceptList()->end() ) + return true; + } + + if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + auto const& pList = m_aLangTable.find(aLanguageTag)->second; + if(pList->GetWordStartExceptList()->find(sWord) != pList->GetWordStartExceptList()->end() ) + return true; + } + return false; +} + +static bool lcl_FindAbbreviation(const SvStringsISortDtor* pList, const OUString& sWord) +{ + SvStringsISortDtor::const_iterator it = pList->find( "~" ); + SvStringsISortDtor::size_type nPos = it - pList->begin(); + if( nPos < pList->size() ) + { + OUString sLowerWord(sWord.toAsciiLowerCase()); + OUString sAbr; + for( SvStringsISortDtor::size_type n = nPos; n < pList->size(); ++n ) + { + sAbr = (*pList)[ n ]; + if (sAbr[0] != '~') + break; + // ~ and ~. are not allowed! + if( 2 < sAbr.getLength() && sAbr.getLength() - 1 <= sWord.getLength() ) + { + OUString sLowerAbk(sAbr.toAsciiLowerCase()); + for (sal_Int32 i = sLowerAbk.getLength(), ii = sLowerWord.getLength(); i;) + { + if( !--i ) // agrees + return true; + + if( sLowerAbk[i] != sLowerWord[--ii]) + break; + } + } + } + } + OSL_ENSURE( !(nPos && '~' == (*pList)[ --nPos ][ 0 ] ), + "Wrongly sorted exception list?" ); + return false; +} + +bool SvxAutoCorrect::FindInCplSttExceptList(LanguageType eLang, + const OUString& sWord, bool bAbbreviation) +{ + LanguageTag aLanguageTag( eLang); + + /* TODO-BCP47: did I mention terrible horrible ugliness? */ + + // First search for eLang, then primary language of eLang + // and last in LANGUAGE_UNDETERMINED + + if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const SvStringsISortDtor* pList = m_aLangTable.find(aLanguageTag)->second->GetCplSttExceptList(); + if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) + return true; + } + + // If it still could not be found here, then keep on searching + // the primary language for example EN + aLanguageTag.reset(aLanguageTag.getLanguage()); + LanguageType nTmpKey = aLanguageTag.getLanguageType(false); + if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED && + (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false))) + { + //the language is available - so bring it on + const SvStringsISortDtor* pList = m_aLangTable.find(aLanguageTag)->second->GetCplSttExceptList(); + if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) + return true; + } + + if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const SvStringsISortDtor* pList = m_aLangTable.find(aLanguageTag)->second->GetCplSttExceptList(); + if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) + return true; + } + return false; +} + +OUString SvxAutoCorrect::GetAutoCorrFileName( const LanguageTag& rLanguageTag, + bool bNewFile, bool bTst, bool bUnlocalized ) const +{ + OUString sRet, sExt( rLanguageTag.getBcp47() ); + if (bUnlocalized) + { + // we don't want variant, so we'll take "fr" instead of "fr-CA" for example + std::vector< OUString > vecFallBackStrings = rLanguageTag.getFallbackStrings(false); + if (!vecFallBackStrings.empty()) + sExt = vecFallBackStrings[0]; + } + + sExt = "_" + sExt + ".dat"; + if( bNewFile ) + sRet = sUserAutoCorrFile + sExt; + else if( !bTst ) + sRet = sShareAutoCorrFile + sExt; + else + { + // test first in the user directory - if not exist, then + sRet = sUserAutoCorrFile + sExt; + if( !FStatHelper::IsDocument( sRet )) + sRet = sShareAutoCorrFile + sExt; + } + return sRet; +} + +SvxAutoCorrectLanguageLists::SvxAutoCorrectLanguageLists( + SvxAutoCorrect& rParent, + OUString aShareAutoCorrectFile, + OUString aUserAutoCorrectFile) +: sShareAutoCorrFile(std::move( aShareAutoCorrectFile )), + sUserAutoCorrFile(std::move( aUserAutoCorrectFile )), + aModifiedDate( Date::EMPTY ), + aModifiedTime( tools::Time::EMPTY ), + aLastCheckTime( tools::Time::EMPTY ), + rAutoCorrect(rParent), + nFlags(ACFlags::NONE) +{ +} + +SvxAutoCorrectLanguageLists::~SvxAutoCorrectLanguageLists() +{ +} + +bool SvxAutoCorrectLanguageLists::IsFileChanged_Imp() +{ + // Access the file system only every 2 minutes to check the date stamp + bool bRet = false; + + tools::Time nMinTime( 0, 2 ); + tools::Time nAktTime( tools::Time::SYSTEM ); + if( aLastCheckTime <= nAktTime) // overflow? + return false; + nAktTime -= aLastCheckTime; + if( nAktTime > nMinTime ) // min time past + { + Date aTstDate( Date::EMPTY ); tools::Time aTstTime( tools::Time::EMPTY ); + if( FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, + &aTstDate, &aTstTime ) && + ( aModifiedDate != aTstDate || aModifiedTime != aTstTime )) + { + bRet = true; + // then remove all the lists fast! + if( (ACFlags::CplSttLstLoad & nFlags) && pCplStt_ExcptLst ) + { + pCplStt_ExcptLst.reset(); + } + if( (ACFlags::WordStartLstLoad & nFlags) && pWordStart_ExcptLst ) + { + pWordStart_ExcptLst.reset(); + } + if( (ACFlags::ChgWordLstLoad & nFlags) && pAutocorr_List ) + { + pAutocorr_List.reset(); + } + nFlags &= ~ACFlags(ACFlags::CplSttLstLoad | ACFlags::WordStartLstLoad | ACFlags::ChgWordLstLoad ); + } + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + } + return bRet; +} + +void SvxAutoCorrectLanguageLists::LoadXMLExceptList_Imp( + std::unique_ptr<SvStringsISortDtor>& rpLst, + const OUString& sStrmName, + tools::SvRef<SotStorage>& rStg) +{ + if( rpLst ) + rpLst->clear(); + else + rpLst.reset( new SvStringsISortDtor ); + + { + if( rStg.is() && rStg->IsStream( sStrmName ) ) + { + tools::SvRef<SotStorageStream> xStrm = rStg->OpenSotStream( sStrmName, + ( StreamMode::READ | StreamMode::SHARE_DENYWRITE | StreamMode::NOCREATE ) ); + if( ERRCODE_NONE != xStrm->GetError()) + { + xStrm.clear(); + rStg.clear(); + RemoveStream_Imp( sStrmName ); + } + else + { + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + xml::sax::InputSource aParserInput; + aParserInput.sSystemId = sStrmName; + + xStrm->Seek( 0 ); + xStrm->SetBufferSize( 8 * 1024 ); + aParserInput.aInputStream = new utl::OInputStreamWrapper( *xStrm ); + + // get filter + uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SvXMLExceptionListImport ( xContext, *rpLst ); + + // connect parser and filter + uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create( xContext ); + uno::Reference<xml::sax::XFastTokenHandler> xTokenHandler = new SvXMLAutoCorrectTokenHandler; + xParser->setFastDocumentHandler( xFilter ); + xParser->registerNamespace( "http://openoffice.org/2001/block-list", SvXMLAutoCorrectToken::NAMESPACE ); + xParser->setTokenHandler( xTokenHandler ); + + // parse + try + { + xParser->parseStream( aParserInput ); + } + catch( const xml::sax::SAXParseException& ) + { + // re throw ? + } + catch( const xml::sax::SAXException& ) + { + // re throw ? + } + catch( const io::IOException& ) + { + // re throw ? + } + } + } + + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + } + +} + +void SvxAutoCorrectLanguageLists::SaveExceptList_Imp( + const SvStringsISortDtor& rLst, + const OUString& sStrmName, + tools::SvRef<SotStorage> const &rStg, + bool bConvert ) +{ + if( !rStg.is() ) + return; + + if( rLst.empty() ) + { + rStg->Remove( sStrmName ); + rStg->Commit(); + } + else + { + tools::SvRef<SotStorageStream> xStrm = rStg->OpenSotStream( sStrmName, + ( StreamMode::READ | StreamMode::WRITE | StreamMode::SHARE_DENYWRITE ) ); + if( xStrm.is() ) + { + xStrm->SetSize( 0 ); + xStrm->SetBufferSize( 8192 ); + xStrm->SetProperty( "MediaType", Any(OUString( "text/xml" )) ); + + + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext); + uno::Reference < io::XOutputStream> xOut = new utl::OOutputStreamWrapper( *xStrm ); + xWriter->setOutputStream(xOut); + + uno::Reference < xml::sax::XDocumentHandler > xHandler(xWriter, UNO_QUERY_THROW); + rtl::Reference< SvXMLExceptionListExport > xExp( new SvXMLExceptionListExport( xContext, rLst, sStrmName, xHandler ) ); + + xExp->exportDoc( XML_BLOCK_LIST ); + + xStrm->Commit(); + if( xStrm->GetError() == ERRCODE_NONE ) + { + xStrm.clear(); + if (!bConvert) + { + rStg->Commit(); + if( ERRCODE_NONE != rStg->GetError() ) + { + rStg->Remove( sStrmName ); + rStg->Commit(); + } + } + } + } + } +} + +SvxAutocorrWordList* SvxAutoCorrectLanguageLists::LoadAutocorrWordList() +{ + if( pAutocorr_List ) + pAutocorr_List->DeleteAndDestroyAll(); + else + pAutocorr_List.reset( new SvxAutocorrWordList() ); + + try + { + uno::Reference < embed::XStorage > xStg = comphelper::OStorageHelper::GetStorageFromURL( sShareAutoCorrFile, embed::ElementModes::READ ); + uno::Reference < io::XStream > xStrm = xStg->openStreamElement( pXMLImplAutocorr_ListStr, embed::ElementModes::READ ); + uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + + xml::sax::InputSource aParserInput; + aParserInput.sSystemId = pXMLImplAutocorr_ListStr; + aParserInput.aInputStream = xStrm->getInputStream(); + + // get parser + uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create(xContext); + SAL_INFO("editeng", "AutoCorrect Import" ); + uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SvXMLAutoCorrectImport( xContext, pAutocorr_List.get(), rAutoCorrect, xStg ); + uno::Reference<xml::sax::XFastTokenHandler> xTokenHandler = new SvXMLAutoCorrectTokenHandler; + + // connect parser and filter + xParser->setFastDocumentHandler( xFilter ); + xParser->registerNamespace( "http://openoffice.org/2001/block-list", SvXMLAutoCorrectToken::NAMESPACE ); + xParser->setTokenHandler(xTokenHandler); + + // parse + xParser->parseStream( aParserInput ); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("editeng", "when loading " << sShareAutoCorrFile); + } + + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + + return pAutocorr_List.get(); +} + +const SvxAutocorrWordList* SvxAutoCorrectLanguageLists::GetAutocorrWordList() +{ + if( !( ACFlags::ChgWordLstLoad & nFlags ) || IsFileChanged_Imp() ) + { + LoadAutocorrWordList(); + if( !pAutocorr_List ) + { + OSL_ENSURE( false, "No valid list" ); + pAutocorr_List.reset( new SvxAutocorrWordList() ); + } + nFlags |= ACFlags::ChgWordLstLoad; + } + return pAutocorr_List.get(); +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::GetCplSttExceptList() +{ + if( !( ACFlags::CplSttLstLoad & nFlags ) || IsFileChanged_Imp() ) + { + LoadCplSttExceptList(); + if( !pCplStt_ExcptLst ) + { + OSL_ENSURE( false, "No valid list" ); + pCplStt_ExcptLst.reset( new SvStringsISortDtor ); + } + nFlags |= ACFlags::CplSttLstLoad; + } + return pCplStt_ExcptLst.get(); +} + +bool SvxAutoCorrectLanguageLists::AddToCplSttExceptList(const OUString& rNew) +{ + bool bRet = false; + if( !rNew.isEmpty() && GetCplSttExceptList()->insert( rNew ).second ) + { + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); + + xStg = nullptr; + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + bRet = true; + } + return bRet; +} + +bool SvxAutoCorrectLanguageLists::AddToWordStartExceptList(const OUString& rNew) +{ + bool bRet = false; + if( !rNew.isEmpty() && GetWordStartExceptList()->insert( rNew ).second ) + { + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pWordStart_ExcptLst, pXMLImplWordStart_ExcptLstStr, xStg ); + + xStg = nullptr; + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + bRet = true; + } + return bRet; +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::LoadCplSttExceptList() +{ + try + { + tools::SvRef<SotStorage> xStg = new SotStorage( sShareAutoCorrFile, StreamMode::READ | StreamMode::SHARE_DENYNONE ); + if( xStg.is() && xStg->IsContained( pXMLImplCplStt_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); + } + catch (const css::ucb::ContentCreationException&) + { + } + return pCplStt_ExcptLst.get(); +} + +void SvxAutoCorrectLanguageLists::SaveCplSttExceptList() +{ + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); + + xStg = nullptr; + + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::LoadWordStartExceptList() +{ + try + { + tools::SvRef<SotStorage> xStg = new SotStorage( sShareAutoCorrFile, StreamMode::READ | StreamMode::SHARE_DENYNONE ); + if( xStg.is() && xStg->IsContained( pXMLImplWordStart_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pWordStart_ExcptLst, pXMLImplWordStart_ExcptLstStr, xStg ); + } + catch (const css::ucb::ContentCreationException &) + { + TOOLS_WARN_EXCEPTION("editeng", "SvxAutoCorrectLanguageLists::LoadWordStartExceptList"); + } + return pWordStart_ExcptLst.get(); +} + +void SvxAutoCorrectLanguageLists::SaveWordStartExceptList() +{ + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pWordStart_ExcptLst, pXMLImplWordStart_ExcptLstStr, xStg ); + + xStg = nullptr; + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::GetWordStartExceptList() +{ + if( !( ACFlags::WordStartLstLoad & nFlags ) || IsFileChanged_Imp() ) + { + LoadWordStartExceptList(); + if( !pWordStart_ExcptLst ) + { + OSL_ENSURE( false, "No valid list" ); + pWordStart_ExcptLst.reset( new SvStringsISortDtor ); + } + nFlags |= ACFlags::WordStartLstLoad; + } + return pWordStart_ExcptLst.get(); +} + +void SvxAutoCorrectLanguageLists::RemoveStream_Imp( const OUString& rName ) +{ + if( sShareAutoCorrFile != sUserAutoCorrFile ) + { + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + if( xStg.is() && ERRCODE_NONE == xStg->GetError() && + xStg->IsStream( rName ) ) + { + xStg->Remove( rName ); + xStg->Commit(); + + xStg = nullptr; + } + } +} + +void SvxAutoCorrectLanguageLists::MakeUserStorage_Impl() +{ + // The conversion needs to happen if the file is already in the user + // directory and is in the old format. Additionally it needs to + // happen when the file is being copied from share to user. + + bool bError = false, bConvert = false, bCopy = false; + INetURLObject aDest; + INetURLObject aSource; + + if (sUserAutoCorrFile != sShareAutoCorrFile ) + { + aSource = INetURLObject ( sShareAutoCorrFile ); + aDest = INetURLObject ( sUserAutoCorrFile ); + if ( SotStorage::IsOLEStorage ( sShareAutoCorrFile ) ) + { + aDest.SetExtension ( u"bak" ); + bConvert = true; + } + bCopy = true; + } + else if ( SotStorage::IsOLEStorage ( sUserAutoCorrFile ) ) + { + aSource = INetURLObject ( sUserAutoCorrFile ); + aDest = INetURLObject ( sUserAutoCorrFile ); + aDest.SetExtension ( u"bak" ); + bCopy = bConvert = true; + } + if (bCopy) + { + try + { + OUString sMain(aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri )); + sal_Int32 nSlashPos = sMain.lastIndexOf('/'); + sMain = sMain.copy(0, nSlashPos); + ::ucbhelper::Content aNewContent( sMain, uno::Reference< XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + TransferInfo aInfo; + aInfo.NameClash = NameClash::OVERWRITE; + aInfo.NewTitle = aDest.GetLastName(); + aInfo.SourceURL = aSource.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); + aInfo.MoveData = false; + aNewContent.executeCommand( "transfer", Any(aInfo)); + } + catch (...) + { + bError = true; + } + } + if (bConvert && !bError) + { + tools::SvRef<SotStorage> xSrcStg = new SotStorage( aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ), StreamMode::READ ); + tools::SvRef<SotStorage> xDstStg = new SotStorage( sUserAutoCorrFile, StreamMode::WRITE ); + + if( xSrcStg.is() && xDstStg.is() ) + { + std::unique_ptr<SvStringsISortDtor> pTmpWordList; + + if (xSrcStg->IsContained( pXMLImplWordStart_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pTmpWordList, pXMLImplWordStart_ExcptLstStr, xSrcStg ); + + if (pTmpWordList) + { + SaveExceptList_Imp( *pTmpWordList, pXMLImplWordStart_ExcptLstStr, xDstStg, true ); + pTmpWordList.reset(); + } + + + if (xSrcStg->IsContained( pXMLImplCplStt_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pTmpWordList, pXMLImplCplStt_ExcptLstStr, xSrcStg ); + + if (pTmpWordList) + { + SaveExceptList_Imp( *pTmpWordList, pXMLImplCplStt_ExcptLstStr, xDstStg, true ); + pTmpWordList->clear(); + } + + GetAutocorrWordList(); + MakeBlocklist_Imp( *xDstStg ); + sShareAutoCorrFile = sUserAutoCorrFile; + xDstStg = nullptr; + try + { + ::ucbhelper::Content aContent ( aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ), uno::Reference < XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + aContent.executeCommand ( "delete", Any ( true ) ); + } + catch (...) + { + } + } + } + else if( bCopy && !bError ) + sShareAutoCorrFile = sUserAutoCorrFile; +} + +bool SvxAutoCorrectLanguageLists::MakeBlocklist_Imp( SotStorage& rStg ) +{ + bool bRet = true, bRemove = !pAutocorr_List || pAutocorr_List->empty(); + if( !bRemove ) + { + tools::SvRef<SotStorageStream> refList = rStg.OpenSotStream( pXMLImplAutocorr_ListStr, + ( StreamMode::READ | StreamMode::WRITE | StreamMode::SHARE_DENYWRITE ) ); + if( refList.is() ) + { + refList->SetSize( 0 ); + refList->SetBufferSize( 8192 ); + refList->SetProperty( "MediaType", Any(OUString( "text/xml" )) ); + + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext); + uno::Reference < io::XOutputStream> xOut = new utl::OOutputStreamWrapper( *refList ); + xWriter->setOutputStream(xOut); + + rtl::Reference< SvXMLAutoCorrectExport > xExp( new SvXMLAutoCorrectExport( xContext, pAutocorr_List.get(), pXMLImplAutocorr_ListStr, xWriter ) ); + + xExp->exportDoc( XML_BLOCK_LIST ); + + refList->Commit(); + bRet = ERRCODE_NONE == refList->GetError(); + if( bRet ) + { + refList.clear(); + rStg.Commit(); + if( ERRCODE_NONE != rStg.GetError() ) + { + bRemove = true; + bRet = false; + } + } + } + else + bRet = false; + } + + if( bRemove ) + { + rStg.Remove( pXMLImplAutocorr_ListStr ); + rStg.Commit(); + } + + return bRet; +} + +bool SvxAutoCorrectLanguageLists::MakeCombinedChanges( std::vector<SvxAutocorrWord>& aNewEntries, std::vector<SvxAutocorrWord>& aDeleteEntries ) +{ + // First get the current list! + GetAutocorrWordList(); + + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStorage = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + bool bRet = xStorage.is() && ERRCODE_NONE == xStorage->GetError(); + + if( bRet ) + { + for (SvxAutocorrWord & aWordToDelete : aDeleteEntries) + { + std::optional<SvxAutocorrWord> xFoundEntry = pAutocorr_List->FindAndRemove( &aWordToDelete ); + if( xFoundEntry ) + { + if( !xFoundEntry->IsTextOnly() ) + { + OUString aName( aWordToDelete.GetShort() ); + if (xStorage->IsOLEStorage()) + aName = EncryptBlockName_Imp(aName); + else + GeneratePackageName ( aWordToDelete.GetShort(), aName ); + + if( xStorage->IsContained( aName ) ) + { + xStorage->Remove( aName ); + bRet = xStorage->Commit(); + } + } + } + } + + for (const SvxAutocorrWord & aNewEntrie : aNewEntries) + { + SvxAutocorrWord aWordToAdd(aNewEntrie.GetShort(), aNewEntrie.GetLong(), true ); + std::optional<SvxAutocorrWord> xRemoved = pAutocorr_List->FindAndRemove( &aWordToAdd ); + if( xRemoved ) + { + if( !xRemoved->IsTextOnly() ) + { + // Still have to remove the Storage + OUString sStorageName( aWordToAdd.GetShort() ); + if (xStorage->IsOLEStorage()) + sStorageName = EncryptBlockName_Imp(sStorageName); + else + GeneratePackageName ( aWordToAdd.GetShort(), sStorageName); + + if( xStorage->IsContained( sStorageName ) ) + xStorage->Remove( sStorageName ); + } + } + bRet = pAutocorr_List->Insert( std::move(aWordToAdd) ); + + if ( !bRet ) + { + break; + } + } + + if ( bRet ) + { + bRet = MakeBlocklist_Imp( *xStorage ); + } + } + return bRet; +} + +bool SvxAutoCorrectLanguageLists::PutText( const OUString& rShort, const OUString& rLong ) +{ + // First get the current list! + GetAutocorrWordList(); + + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + bool bRet = xStg.is() && ERRCODE_NONE == xStg->GetError(); + + // Update the word list + if( bRet ) + { + SvxAutocorrWord aNew(rShort, rLong, true ); + std::optional<SvxAutocorrWord> xRemove = pAutocorr_List->FindAndRemove( &aNew ); + if( xRemove ) + { + if( !xRemove->IsTextOnly() ) + { + // Still have to remove the Storage + OUString sStgNm( rShort ); + if (xStg->IsOLEStorage()) + sStgNm = EncryptBlockName_Imp(sStgNm); + else + GeneratePackageName ( rShort, sStgNm); + + if( xStg->IsContained( sStgNm ) ) + xStg->Remove( sStgNm ); + } + } + + if( pAutocorr_List->Insert( std::move(aNew) ) ) + { + bRet = MakeBlocklist_Imp( *xStg ); + xStg = nullptr; + } + else + { + bRet = false; + } + } + return bRet; +} + +void SvxAutoCorrectLanguageLists::PutText( const OUString& rShort, + SfxObjectShell& rShell ) +{ + // First get the current list! + GetAutocorrWordList(); + + MakeUserStorage_Impl(); + + try + { + uno::Reference < embed::XStorage > xStg = comphelper::OStorageHelper::GetStorageFromURL( sUserAutoCorrFile, embed::ElementModes::READWRITE ); + OUString sLong; + bool bRet = rAutoCorrect.PutText( xStg, sUserAutoCorrFile, rShort, rShell, sLong ); + xStg = nullptr; + + // Update the word list + if( bRet ) + { + if( pAutocorr_List->Insert( SvxAutocorrWord(rShort, sLong, false) ) ) + { + tools::SvRef<SotStorage> xStor = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + MakeBlocklist_Imp( *xStor ); + } + } + } + catch ( const uno::Exception& ) + { + } +} + +// Keep the list sorted ... +struct SvxAutocorrWordList::CompareSvxAutocorrWordList +{ + bool operator()( SvxAutocorrWord const & lhs, SvxAutocorrWord const & rhs ) const + { + CollatorWrapper& rCmp = ::GetCollatorWrapper(); + return rCmp.compareString( lhs.GetShort(), rhs.GetShort() ) < 0; + } +}; + +namespace { + +typedef std::unordered_map<OUString, SvxAutocorrWord> AutocorrWordHashType; + +} + +struct SvxAutocorrWordList::Impl +{ + + // only one of these contains the data + // maSortedVector is manually sorted so we can optimise data movement + mutable AutocorrWordSetType maSortedVector; + mutable AutocorrWordHashType maHash; // key is 'Short' + + void DeleteAndDestroyAll() + { + maHash.clear(); + maSortedVector.clear(); + } +}; + +SvxAutocorrWordList::SvxAutocorrWordList() : mpImpl(new Impl) {} + +SvxAutocorrWordList::~SvxAutocorrWordList() +{ +} + +void SvxAutocorrWordList::DeleteAndDestroyAll() +{ + mpImpl->DeleteAndDestroyAll(); +} + +// returns true if inserted +const SvxAutocorrWord* SvxAutocorrWordList::Insert(SvxAutocorrWord aWord) const +{ + if ( mpImpl->maSortedVector.empty() ) // use the hash + { + OUString aShort = aWord.GetShort(); + auto [it,inserted] = mpImpl->maHash.emplace( std::move(aShort), std::move(aWord) ); + if (inserted) + return &(it->second); + return nullptr; + } + else + { + auto it = std::lower_bound(mpImpl->maSortedVector.begin(), mpImpl->maSortedVector.end(), aWord, CompareSvxAutocorrWordList()); + CollatorWrapper& rCmp = ::GetCollatorWrapper(); + if (it == mpImpl->maSortedVector.end() || rCmp.compareString( aWord.GetShort(), it->GetShort() ) != 0) + { + it = mpImpl->maSortedVector.insert(it, std::move(aWord)); + return &*it; + } + return nullptr; + } +} + +void SvxAutocorrWordList::LoadEntry(const OUString& sWrong, const OUString& sRight, bool bOnlyTxt) +{ + (void)Insert(SvxAutocorrWord( sWrong, sRight, bOnlyTxt )); +} + +bool SvxAutocorrWordList::empty() const +{ + return mpImpl->maHash.empty() && mpImpl->maSortedVector.empty(); +} + +std::optional<SvxAutocorrWord> SvxAutocorrWordList::FindAndRemove(const SvxAutocorrWord *pWord) +{ + + if ( mpImpl->maSortedVector.empty() ) // use the hash + { + AutocorrWordHashType::iterator it = mpImpl->maHash.find( pWord->GetShort() ); + if( it != mpImpl->maHash.end() ) + { + SvxAutocorrWord pMatch = std::move(it->second); + mpImpl->maHash.erase (it); + return pMatch; + } + } + else + { + auto it = std::lower_bound(mpImpl->maSortedVector.begin(), mpImpl->maSortedVector.end(), *pWord, CompareSvxAutocorrWordList()); + if (it != mpImpl->maSortedVector.end() && !CompareSvxAutocorrWordList()(*pWord, *it)) + { + SvxAutocorrWord pMatch = std::move(*it); + mpImpl->maSortedVector.erase (it); + return pMatch; + } + } + return std::optional<SvxAutocorrWord>(); +} + +// return the sorted contents - defer sorting until we have to. +const SvxAutocorrWordList::AutocorrWordSetType& SvxAutocorrWordList::getSortedContent() const +{ + // convert from hash to set permanently + if ( mpImpl->maSortedVector.empty() ) + { + std::vector<SvxAutocorrWord> tmp; + tmp.reserve(mpImpl->maHash.size()); + for (auto & rPair : mpImpl->maHash) + tmp.emplace_back(std::move(rPair.second)); + mpImpl->maHash.clear(); + // sort twice - this gets the list into mostly-sorted order, which + // reduces the number of times we need to invoke the expensive ICU collate fn. + std::sort(tmp.begin(), tmp.end(), + [] ( SvxAutocorrWord const & lhs, SvxAutocorrWord const & rhs ) + { + return lhs.GetShort() < rhs.GetShort(); + }); + // This beast has some O(N log(N)) in a terribly slow ICU collate fn. + // stable_sort is twice as fast as sort in this situation because it does + // fewer comparison operations. + std::stable_sort(tmp.begin(), tmp.end(), CompareSvxAutocorrWordList()); + mpImpl->maSortedVector = std::move(tmp); + } + return mpImpl->maSortedVector; +} + +const SvxAutocorrWord* SvxAutocorrWordList::WordMatches(const SvxAutocorrWord *pFnd, + const OUString &rTxt, + sal_Int32 &rStt, + sal_Int32 nEndPos) const +{ + const OUString& rChk = pFnd->GetShort(); + + sal_Int32 left_wildcard = rChk.startsWith( ".*" ) ? 2 : 0; // ".*word" pattern? + sal_Int32 right_wildcard = rChk.endsWith( ".*" ) ? 2 : 0; // "word.*" pattern? + sal_Int32 nSttWdPos = nEndPos; + + // direct replacement of keywords surrounded by colons (for example, ":name:") + bool bColonNameColon = rTxt.getLength() > nEndPos && + rTxt[nEndPos] == ':' && rChk[0] == ':' && rChk.endsWith(":"); + if ( nEndPos + (bColonNameColon ? 1 : 0) < rChk.getLength() - left_wildcard - right_wildcard ) + return nullptr; + + bool bWasWordDelim = false; + sal_Int32 nCalcStt = nEndPos - rChk.getLength() + left_wildcard; + if (bColonNameColon) + nCalcStt++; + if( !right_wildcard && ( !nCalcStt || nCalcStt == rStt || left_wildcard || bColonNameColon || + ( nCalcStt < rStt && + IsWordDelim( rTxt[ nCalcStt - 1 ] ))) ) + { + TransliterationWrapper& rCmp = GetIgnoreTranslWrapper(); + OUString sWord = rTxt.copy(nCalcStt, rChk.getLength() - left_wildcard); + if( (!left_wildcard && rCmp.isEqual( rChk, sWord )) || (left_wildcard && rCmp.isEqual( rChk.copy(left_wildcard), sWord) )) + { + rStt = nCalcStt; + if (!left_wildcard) + { + // fdo#33899 avoid "1/2", "1/3".. to be replaced by fractions in dates, eg. 1/2/14 + if (rTxt.getLength() > nEndPos && rTxt[nEndPos] == '/' && rChk.indexOf('/') != -1) + return nullptr; + return pFnd; + } + // get the first word delimiter position before the matching ".*word" pattern + while( rStt && !(bWasWordDelim = IsWordDelim( rTxt[ --rStt ]))) + ; + if (bWasWordDelim) rStt++; + OUString left_pattern = rTxt.copy(rStt, nEndPos - rStt - rChk.getLength() + left_wildcard); + // avoid double spaces before simple "word" replacement + left_pattern += (left_pattern.getLength() == 0 && pFnd->GetLong()[0] == 0x20) ? pFnd->GetLong().subView(1) : pFnd->GetLong(); + if( const SvxAutocorrWord* pNew = Insert( SvxAutocorrWord(rTxt.copy(rStt, nEndPos - rStt), left_pattern) ) ) + return pNew; + } + } else + // match "word.*" or ".*word.*" patterns, eg. "i18n.*", ".*---.*", TODO: add transliteration support + if ( right_wildcard ) + { + + OUString sTmp( rChk.copy( left_wildcard, rChk.getLength() - left_wildcard - right_wildcard ) ); + // Get the last word delimiter position + bool not_suffix; + + while( nSttWdPos && !(bWasWordDelim = IsWordDelim( rTxt[ --nSttWdPos ]))) + ; + // search the first occurrence (with a left word delimitation, if needed) + sal_Int32 nFndPos = -1; + do { + nFndPos = rTxt.indexOf( sTmp, nFndPos + 1); + if (nFndPos == -1) + break; + not_suffix = bWasWordDelim && (nSttWdPos >= (nFndPos + sTmp.getLength())); + } while ( (!left_wildcard && nFndPos && !IsWordDelim( rTxt[ nFndPos - 1 ])) || not_suffix ); + + if ( nFndPos != -1 ) + { + sal_Int32 extra_repl = nFndPos + sTmp.getLength() > nEndPos ? 1: 0; // for patterns with terminating characters, eg. "a:" + + if ( left_wildcard ) + { + // get the first word delimiter position before the matching ".*word.*" pattern + while( nFndPos && !(bWasWordDelim = IsWordDelim( rTxt[ --nFndPos ]))) + ; + if (bWasWordDelim) nFndPos++; + } + if (nEndPos + extra_repl <= nFndPos) + { + return nullptr; + } + // store matching pattern and its replacement as a new list item, eg. "i18ns" -> "internationalizations" + OUString aShort = rTxt.copy(nFndPos, nEndPos - nFndPos + extra_repl); + + OUString aLong; + rStt = nFndPos; + if ( !left_wildcard ) + { + sal_Int32 siz = nEndPos - nFndPos - sTmp.getLength(); + aLong = pFnd->GetLong() + (siz > 0 ? rTxt.subView(nFndPos + sTmp.getLength(), siz) : u""); + } else { + OUStringBuffer buf; + do { + nSttWdPos = rTxt.indexOf( sTmp, nFndPos); + if (nSttWdPos != -1) + { + sal_Int32 nTmp(nFndPos); + while (nTmp < nSttWdPos && !IsWordDelim(rTxt[nTmp])) + nTmp++; + if (nTmp < nSttWdPos) + break; // word delimiter found + buf.append(rTxt.subView(nFndPos, nSttWdPos - nFndPos)).append(pFnd->GetLong()); + nFndPos = nSttWdPos + sTmp.getLength(); + } + } while (nSttWdPos != -1); + if (nEndPos - nFndPos > extra_repl) + buf.append(rTxt.subView(nFndPos, nEndPos - nFndPos)); + aLong = buf.makeStringAndClear(); + } + if ( const SvxAutocorrWord* pNew = Insert( SvxAutocorrWord(aShort, aLong) ) ) + { + if ( (rTxt.getLength() > nEndPos && IsWordDelim(rTxt[nEndPos])) || rTxt.getLength() == nEndPos ) + return pNew; + } + } + } + return nullptr; +} + +const SvxAutocorrWord* SvxAutocorrWordList::SearchWordsInList(const OUString& rTxt, sal_Int32& rStt, + sal_Int32 nEndPos) const +{ + for (auto const& elem : mpImpl->maHash) + { + if( const SvxAutocorrWord *pTmp = WordMatches( &elem.second, rTxt, rStt, nEndPos ) ) + return pTmp; + } + + for (auto const& elem : mpImpl->maSortedVector) + { + if( const SvxAutocorrWord *pTmp = WordMatches( &elem, rTxt, rStt, nEndPos ) ) + return pTmp; + } + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/swafopt.cxx b/editeng/source/misc/swafopt.cxx new file mode 100644 index 000000000..126a54bb1 --- /dev/null +++ b/editeng/source/misc/swafopt.cxx @@ -0,0 +1,79 @@ +/* -*- 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 <editeng/swafopt.hxx> +#include <tools/gen.hxx> +#include <vcl/keycodes.hxx> + +SvxSwAutoFormatFlags::SvxSwAutoFormatFlags() + : aBulletFont( "StarSymbol", Size( 0, 14 ) ) +{ + bAutoCorrect = + bCapitalStartSentence = + bCapitalStartWord = + bChgUserColl = + bChgEnumNum = + bAddNonBrkSpace = + bChgOrdinalNumber = + bTransliterateRTL = + bChgAngleQuotes = + bChgToEnEmDash = + bChgWeightUnderl = + bSetINetAttr = + bAFormatDelSpacesAtSttEnd = + bAFormatDelSpacesBetweenLines = + bAFormatByInpDelSpacesAtSttEnd = + bAFormatByInpDelSpacesBetweenLines = true; + + bReplaceStyles = + bDelEmptyNode = + bWithRedlining = + bAutoCmpltEndless = + bAutoCmpltAppendBlank = false; + + bAutoCmpltShowAsTip = + bSetBorder = + bCreateTable = + bSetNumRule = + bAFormatByInput = + bRightMargin = + bAutoCompleteWords = + bAutoCmpltCollectWords = + bAutoCmpltKeepList = true; + + nRightMargin = 50; // default 50% + nAutoCmpltExpandKey = KEY_RETURN; + + aBulletFont.SetCharSet( RTL_TEXTENCODING_SYMBOL ); + aBulletFont.SetFamily( FAMILY_DONTKNOW ); + aBulletFont.SetPitch( PITCH_DONTKNOW ); + aBulletFont.SetWeight( WEIGHT_DONTKNOW ); + aBulletFont.SetTransparent( true ); + + cBullet = 0x2022; + cByInputBullet = cBullet; + aByInputBulletFont = aBulletFont; + + nAutoCmpltWordLen = 8; + nAutoCmpltListLen = 1000; + m_pAutoCompleteList = nullptr; + pSmartTagMgr = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/tokens.txt b/editeng/source/misc/tokens.txt new file mode 100644 index 000000000..0b5a64607 --- /dev/null +++ b/editeng/source/misc/tokens.txt @@ -0,0 +1,7 @@ +abbreviated-name +block +block-list +list-name +name +package-name +unformatted-text diff --git a/editeng/source/misc/txtrange.cxx b/editeng/source/misc/txtrange.cxx new file mode 100644 index 000000000..6374747be --- /dev/null +++ b/editeng/source/misc/txtrange.cxx @@ -0,0 +1,666 @@ +/* -*- 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 <editeng/txtrange.hxx> +#include <math.h> +#include <tools/poly.hxx> +#include <tools/debug.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include <vector> + +TextRanger::TextRanger( const basegfx::B2DPolyPolygon& rPolyPolygon, + const basegfx::B2DPolyPolygon* pLinePolyPolygon, + sal_uInt16 nCacheSz, sal_uInt16 nLft, sal_uInt16 nRght, + bool bSimpl, bool bInnr, bool bVert ) : + maPolyPolygon( rPolyPolygon.count() ), + nCacheSize( nCacheSz ), + nRight( nRght ), + nLeft( nLft ), + nUpper( 0 ), + nLower( 0 ), + nPointCount( 0 ), + bSimple( bSimpl ), + bInner( bInnr ), + bVertical( bVert ) +{ + sal_uInt32 nCount(rPolyPolygon.count()); + + for(sal_uInt32 i(0); i < nCount; i++) + { + const basegfx::B2DPolygon aCandidate(rPolyPolygon.getB2DPolygon(i).getDefaultAdaptiveSubdivision()); + nPointCount += aCandidate.count(); + maPolyPolygon.Insert( tools::Polygon(aCandidate), static_cast<sal_uInt16>(i) ); + } + + if( pLinePolyPolygon ) + { + nCount = pLinePolyPolygon->count(); + mpLinePolyPolygon = tools::PolyPolygon(nCount); + + for(sal_uInt32 i(0); i < nCount; i++) + { + const basegfx::B2DPolygon aCandidate(pLinePolyPolygon->getB2DPolygon(i).getDefaultAdaptiveSubdivision()); + nPointCount += aCandidate.count(); + mpLinePolyPolygon->Insert( tools::Polygon(aCandidate), static_cast<sal_uInt16>(i) ); + } + } + else + mpLinePolyPolygon.reset(); +} + + +TextRanger::~TextRanger() +{ + mRangeCache.clear(); +} + +/* TextRanger::SetVertical(..) + If there's is a change in the writing direction, + the cache has to be cleared. +*/ +void TextRanger::SetVertical( bool bNew ) +{ + if( IsVertical() != bNew ) + { + bVertical = bNew; + mRangeCache.clear(); + } +} + +namespace { + +//! SvxBoundArgs is used to perform temporary calculations on a range array. +//! Temporary instances are created in TextRanger::GetTextRanges() +class SvxBoundArgs +{ + std::vector<bool> aBoolArr; + std::deque<tools::Long>* pLongArr; + TextRanger *pTextRanger; + tools::Long nMin; + tools::Long nMax; + tools::Long nTop; + tools::Long nBottom; + tools::Long nUpDiff; + tools::Long nLowDiff; + tools::Long nUpper; + tools::Long nLower; + tools::Long nStart; + tools::Long nEnd; + sal_uInt16 nCut; + sal_uInt16 nLast; + sal_uInt16 nNext; + sal_uInt8 nAct; + sal_uInt8 nFirst; + bool bClosed : 1; + bool bInner : 1; + bool bMultiple : 1; + bool bConcat : 1; + bool bRotate : 1; + void NoteRange( bool bToggle ); + tools::Long Cut( tools::Long nY, const Point& rPt1, const Point& rPt2 ); + void Add(); + void NoteFarPoint_( tools::Long nPx, tools::Long nPyDiff, tools::Long nDiff ); + void NoteFarPoint( tools::Long nPx, tools::Long nPyDiff, tools::Long nDiff ) + { if( nDiff ) NoteFarPoint_( nPx, nPyDiff, nDiff ); } + tools::Long CalcMax( const Point& rPt1, const Point& rPt2, tools::Long nRange, tools::Long nFar ); + void CheckCut( const Point& rLst, const Point& rNxt ); + tools::Long A( const Point& rP ) const { return bRotate ? rP.Y() : rP.X(); } + tools::Long B( const Point& rP ) const { return bRotate ? rP.X() : rP.Y(); } +public: + SvxBoundArgs( TextRanger* pRanger, std::deque<tools::Long>* pLong, const Range& rRange ); + void NotePoint( const tools::Long nA ) { NoteMargin( nA - nStart, nA + nEnd ); } + void NoteMargin( const tools::Long nL, const tools::Long nR ) + { if( nMin > nL ) nMin = nL; if( nMax < nR ) nMax = nR; } + sal_uInt16 Area( const Point& rPt ); + void NoteUpLow( tools::Long nA, const sal_uInt8 nArea ); + void Calc( const tools::PolyPolygon& rPoly ); + void Concat( const tools::PolyPolygon* pPoly ); + // inlines + void NoteLast() { if( bMultiple ) NoteRange( nAct == nFirst ); } + void SetConcat( const bool bNew ){ bConcat = bNew; } + bool IsConcat() const { return bConcat; } +}; + +} + +SvxBoundArgs::SvxBoundArgs( TextRanger* pRanger, std::deque<tools::Long>* pLong, + const Range& rRange ) + : pLongArr(pLong) + , pTextRanger(pRanger) + , nMin(0) + , nMax(0) + , nTop(rRange.Min()) + , nBottom(rRange.Max()) + , nCut(0) + , nLast(0) + , nNext(0) + , nAct(0) + , nFirst(0) + , bClosed(false) + , bInner(pRanger->IsInner()) + , bMultiple(bInner || !pRanger->IsSimple()) + , bConcat(false) + , bRotate(pRanger->IsVertical()) +{ + if( bRotate ) + { + nStart = pRanger->GetUpper(); + nEnd = pRanger->GetLower(); + nLowDiff = pRanger->GetLeft(); + nUpDiff = pRanger->GetRight(); + } + else + { + nStart = pRanger->GetLeft(); + nEnd = pRanger->GetRight(); + nLowDiff = pRanger->GetUpper(); + nUpDiff = pRanger->GetLower(); + } + nUpper = nTop - nUpDiff; + nLower = nBottom + nLowDiff; + pLongArr->clear(); +} + +tools::Long SvxBoundArgs::CalcMax( const Point& rPt1, const Point& rPt2, + tools::Long nRange, tools::Long nFarRange ) +{ + double nDa = Cut( nRange, rPt1, rPt2 ) - Cut( nFarRange, rPt1, rPt2 ); + double nB; + if( nDa < 0 ) + { + nDa = -nDa; + nB = nEnd; + } + else + nB = nStart; + nB *= nB; + nB += nDa * nDa; + nB = sqrt( nB ); + if (nB == 0) // avoid div / 0 + return 0; + nB = nRange + nDa * ( nFarRange - nRange ) / nB; + + bool bNote; + if( nB < B(rPt2) ) + bNote = nB > B(rPt1); + else + bNote = nB < B(rPt1); + if( bNote ) + return( tools::Long( nB ) ); + return 0; +} + +void SvxBoundArgs::CheckCut( const Point& rLst, const Point& rNxt ) +{ + if( nCut & 1 ) + NotePoint( Cut( nBottom, rLst, rNxt ) ); + if( nCut & 2 ) + NotePoint( Cut( nTop, rLst, rNxt ) ); + if( rLst.X() == rNxt.X() || rLst.Y() == rNxt.Y() ) + return; + + tools::Long nYps; + if( nLowDiff && ( ( nCut & 1 ) || nLast == 1 || nNext == 1 ) ) + { + nYps = CalcMax( rLst, rNxt, nBottom, nLower ); + if( nYps ) + NoteFarPoint_( Cut( nYps, rLst, rNxt ), nLower-nYps, nLowDiff ); + } + if( nUpDiff && ( ( nCut & 2 ) || nLast == 2 || nNext == 2 ) ) + { + nYps = CalcMax( rLst, rNxt, nTop, nUpper ); + if( nYps ) + NoteFarPoint_( Cut( nYps, rLst, rNxt ), nYps-nUpper, nUpDiff ); + } +} + +void SvxBoundArgs::NoteFarPoint_( tools::Long nPa, tools::Long nPbDiff, tools::Long nDiff ) +{ + tools::Long nTmpA; + double nQuot = 2 * nDiff - nPbDiff; + nQuot *= nPbDiff; + nQuot = sqrt( nQuot ); + nQuot /= nDiff; + nTmpA = nPa - tools::Long( nStart * nQuot ); + nPbDiff = nPa + tools::Long( nEnd * nQuot ); + NoteMargin( nTmpA, nPbDiff ); +} + +void SvxBoundArgs::NoteRange( bool bToggle ) +{ + DBG_ASSERT( nMax >= nMin || bInner, "NoteRange: Min > Max?"); + if( nMax < nMin ) + return; + if( !bClosed ) + bToggle = false; + sal_uInt16 nIdx = 0; + sal_uInt16 nCount = pLongArr->size(); + DBG_ASSERT( nCount == 2 * aBoolArr.size(), "NoteRange: Incompatible Sizes" ); + while( nIdx < nCount && (*pLongArr)[ nIdx ] < nMin ) + ++nIdx; + bool bOdd = (nIdx % 2) != 0; + // No overlap with existing intervals? + if( nIdx == nCount || ( !bOdd && nMax < (*pLongArr)[ nIdx ] ) ) + { // Then a new one is inserted ... + pLongArr->insert( pLongArr->begin() + nIdx, nMin ); + pLongArr->insert( pLongArr->begin() + nIdx + 1, nMax ); + aBoolArr.insert( aBoolArr.begin() + (nIdx/2), bToggle ); + } + else + { // expand an existing interval ... + sal_uInt16 nMaxIdx = nIdx; + // If we end up on a left interval boundary, it must be reduced to nMin. + if( bOdd ) + --nIdx; + else + (*pLongArr)[ nIdx ] = nMin; + while( nMaxIdx < nCount && (*pLongArr)[ nMaxIdx ] < nMax ) + ++nMaxIdx; + DBG_ASSERT( nMaxIdx > nIdx || nMin == nMax, "NoteRange: Funny Situation." ); + if( nMaxIdx ) + --nMaxIdx; + if( nMaxIdx < nIdx ) + nMaxIdx = nIdx; + // If we end up on a right interval boundary, it must be raised to nMax. + if( nMaxIdx % 2 ) + (*pLongArr)[ nMaxIdx-- ] = nMax; + // Possible merge of intervals. + sal_uInt16 nDiff = nMaxIdx - nIdx; + nMaxIdx = nIdx / 2; // From here on is nMaxIdx the Index in BoolArray. + if( nDiff ) + { + pLongArr->erase( pLongArr->begin() + nIdx + 1, pLongArr->begin() + nIdx + 1 + nDiff ); + nDiff /= 2; + sal_uInt16 nStop = nMaxIdx + nDiff; + for( sal_uInt16 i = nMaxIdx; i < nStop; ++i ) + bToggle ^= aBoolArr[ i ]; + aBoolArr.erase( aBoolArr.begin() + nMaxIdx, aBoolArr.begin() + (nMaxIdx + nDiff) ); + } + DBG_ASSERT( nMaxIdx < aBoolArr.size(), "NoteRange: Too much deleted" ); + aBoolArr[ nMaxIdx ] = aBoolArr[ nMaxIdx ] != bToggle; + } +} + +void SvxBoundArgs::Calc( const tools::PolyPolygon& rPoly ) +{ + sal_uInt16 nCount; + nAct = 0; + for( sal_uInt16 i = 0; i < rPoly.Count(); ++i ) + { + const tools::Polygon& rPol = rPoly[ i ]; + nCount = rPol.GetSize(); + if( nCount ) + { + const Point& rNull = rPol[ 0 ]; + bClosed = IsConcat() || ( rNull == rPol[ nCount - 1 ] ); + nLast = Area( rNull ); + if( nLast & 12 ) + { + nFirst = 3; + if( bMultiple ) + nAct = 0; + } + else + { + // The first point of the polygon is within the line. + if( nLast ) + { + if( bMultiple || !nAct ) + { + nMin = USHRT_MAX; + nMax = 0; + } + if( nLast & 1 ) + NoteFarPoint( A(rNull), nLower - B(rNull), nLowDiff ); + else + NoteFarPoint( A(rNull), B(rNull) - nUpper, nUpDiff ); + } + else + { + if( bMultiple || !nAct ) + { + nMin = A(rNull); + nMax = nMin + nEnd; + nMin -= nStart; + } + else + NotePoint( A(rNull) ); + } + nFirst = 0; // leaving the line in which direction? + nAct = 3; // we are within the line at the moment. + } + if( nCount > 1 ) + { + sal_uInt16 nIdx = 1; + while( true ) + { + const Point& rLast = rPol[ nIdx - 1 ]; + if( nIdx == nCount ) + nIdx = 0; + const Point& rNext = rPol[ nIdx ]; + nNext = Area( rNext ); + nCut = nNext ^ nLast; + sal_uInt16 nOldAct = nAct; + if( nAct ) + CheckCut( rLast, rNext ); + if( nCut & 4 ) + { + NoteUpLow( Cut( nLower, rLast, rNext ), 2 ); + if( nAct && nAct != nOldAct ) + { + nOldAct = nAct; + CheckCut( rLast, rNext ); + } + } + if( nCut & 8 ) + { + NoteUpLow( Cut( nUpper, rLast, rNext ), 1 ); + if( nAct && nAct != nOldAct ) + CheckCut( rLast, rNext ); + } + if( !nIdx ) + { + if( !( nNext & 12 ) ) + NoteLast(); + break; + } + if( !( nNext & 12 ) ) + { + if( !nNext ) + NotePoint( A(rNext) ); + else if( nNext & 1 ) + NoteFarPoint( A(rNext), nLower-B(rNext), nLowDiff ); + else + NoteFarPoint( A(rNext), B(rNext)-nUpper, nUpDiff ); + } + nLast = nNext; + if( ++nIdx == nCount && !bClosed ) + { + if( !( nNext & 12 ) ) + NoteLast(); + break; + } + } + } + if( bMultiple && IsConcat() ) + { + Add(); + nAct = 0; + } + } + } + if( !bMultiple ) + { + DBG_ASSERT( pLongArr->empty(), "I said: Simple!" ); + if( nAct ) + { + if( bInner ) + { + tools::Long nTmpMin = nMin + 2 * nStart; + tools::Long nTmpMax = nMax - 2 * nEnd; + if( nTmpMin <= nTmpMax ) + { + pLongArr->push_front(nTmpMax); + pLongArr->push_front(nTmpMin); + } + } + else + { + pLongArr->push_front(nMax); + pLongArr->push_front(nMin); + } + } + } + else if( !IsConcat() ) + Add(); +} + +void SvxBoundArgs::Add() +{ + size_t nCount = aBoolArr.size(); + if( nCount && ( !bInner || !pTextRanger->IsSimple() ) ) + { + bool bDelete = aBoolArr.front(); + if( bInner ) + bDelete = !bDelete; + sal_uInt16 nLongIdx = 1; + for( size_t nBoolIdx = 1; nBoolIdx < nCount; ++nBoolIdx ) + { + if( bDelete ) + { + sal_uInt16 next = 2; + while( nBoolIdx < nCount && !aBoolArr[ nBoolIdx++ ] && + (!bInner || nBoolIdx < nCount ) ) + next += 2; + pLongArr->erase( pLongArr->begin() + nLongIdx, pLongArr->begin() + nLongIdx + next ); + next /= 2; + nBoolIdx = nBoolIdx - next; + nCount = nCount - next; + aBoolArr.erase( aBoolArr.begin() + nBoolIdx, aBoolArr.begin() + (nBoolIdx + next) ); + if( nBoolIdx ) + aBoolArr[ nBoolIdx - 1 ] = false; +#if OSL_DEBUG_LEVEL > 1 + else + ++next; +#endif + } + bDelete = nBoolIdx < nCount && aBoolArr[ nBoolIdx ]; + nLongIdx += 2; + DBG_ASSERT( nLongIdx == 2*nBoolIdx+1, "BoundArgs: Array-Idx Confusion" ); + DBG_ASSERT( aBoolArr.size()*2 == pLongArr->size(), + "BoundArgs: Array-Count: Confusion" ); + } + } + if( pLongArr->empty() ) + return; + + if( !bInner ) + return; + + pLongArr->pop_front(); + pLongArr->pop_back(); + + // Here the line is held inside a large rectangle for "simple" + // contour wrap. Currently (April 1999) the EditEngine evaluates + // only the first rectangle. If it one day is able to output a line + // in several parts, it may be advisable to delete the following lines. + if( pTextRanger->IsSimple() && pLongArr->size() > 2 ) + pLongArr->erase( pLongArr->begin() + 1, pLongArr->end() - 1 ); +} + +void SvxBoundArgs::Concat( const tools::PolyPolygon* pPoly ) +{ + SetConcat( true ); + DBG_ASSERT( pPoly, "Nothing to do?" ); + std::deque<tools::Long>* pOld = pLongArr; + pLongArr = new std::deque<tools::Long>; + aBoolArr.clear(); + bInner = false; + Calc( *pPoly ); // Note that this updates pLongArr, which is why we swapped it out earlier. + std::deque<tools::Long>::size_type nCount = pLongArr->size(); + std::deque<tools::Long>::size_type nIdx = 0; + std::deque<tools::Long>::size_type i = 0; + bool bSubtract = pTextRanger->IsInner(); + while( i < nCount ) + { + std::deque<tools::Long>::size_type nOldCount = pOld->size(); + if( nIdx == nOldCount ) + { // Reached the end of the old Array... + if( !bSubtract ) + pOld->insert( pOld->begin() + nIdx, pLongArr->begin() + i, pLongArr->end() ); + break; + } + tools::Long nLeft = (*pLongArr)[ i++ ]; + tools::Long nRight = (*pLongArr)[ i++ ]; + std::deque<tools::Long>::size_type nLeftPos = nIdx + 1; + while( nLeftPos < nOldCount && nLeft > (*pOld)[ nLeftPos ] ) + nLeftPos += 2; + if( nLeftPos >= nOldCount ) + { // The current interval belongs to the end of the old array ... + if( !bSubtract ) + pOld->insert( pOld->begin() + nOldCount, pLongArr->begin() + i - 2, pLongArr->end() ); + break; + } + std::deque<tools::Long>::size_type nRightPos = nLeftPos - 1; + while( nRightPos < nOldCount && nRight >= (*pOld)[ nRightPos ] ) + nRightPos += 2; + if( nRightPos < nLeftPos ) + { // The current interval belongs between two old intervals + if( !bSubtract ) + pOld->insert( pOld->begin() + nRightPos, pLongArr->begin() + i - 2, pLongArr->begin() + i ); + } + else if( bSubtract ) // Subtract, if necessary separate + { + const tools::Long nOld = (*pOld)[nLeftPos - 1]; + if (nLeft > nOld) + { // Now we split the left part... + if( nLeft - 1 > nOld ) + { + pOld->insert( pOld->begin() + nLeftPos - 1, nOld ); + pOld->insert( pOld->begin() + nLeftPos, nLeft - 1 ); + nLeftPos += 2; + nRightPos += 2; + } + } + if( nRightPos - nLeftPos > 1 ) + pOld->erase( pOld->begin() + nLeftPos, pOld->begin() + nRightPos - 1 ); + if (++nRight >= (*pOld)[nLeftPos]) + pOld->erase( pOld->begin() + nLeftPos - 1, pOld->begin() + nLeftPos + 1 ); + else + (*pOld)[ nLeftPos - 1 ] = nRight; + } + else // Merge + { + if( nLeft < (*pOld)[ nLeftPos - 1 ] ) + (*pOld)[ nLeftPos - 1 ] = nLeft; + if( nRight > (*pOld)[ nRightPos - 1 ] ) + (*pOld)[ nRightPos - 1 ] = nRight; + if( nRightPos - nLeftPos > 1 ) + pOld->erase( pOld->begin() + nLeftPos, pOld->begin() + nRightPos - 1 ); + + } + nIdx = nLeftPos - 1; + } + delete pLongArr; +} + +/************************************************************************* + * SvxBoundArgs::Area returns the area in which the point is located. + * 0 = within the line + * 1 = below, but within the upper edge + * 2 = above, but within the lower edge + * 5 = below the upper edge + *10 = above the lower edge + *************************************************************************/ + +sal_uInt16 SvxBoundArgs::Area( const Point& rPt ) +{ + tools::Long nB = B( rPt ); + if( nB >= nBottom ) + { + if( nB >= nLower ) + return 5; + return 1; + } + if( nB <= nTop ) + { + if( nB <= nUpper ) + return 10; + return 2; + } + return 0; +} + +/************************************************************************* + * lcl_Cut calculates the X-Coordinate of the distance (Pt1-Pt2) at the + * Y-Coordinate nY. + * It is assumed that the one of the points are located above and the other + * one below the Y-Coordinate. + *************************************************************************/ + +tools::Long SvxBoundArgs::Cut( tools::Long nB, const Point& rPt1, const Point& rPt2 ) +{ + if( pTextRanger->IsVertical() ) + { + double nQuot = nB - rPt1.X(); + nQuot /= ( rPt2.X() - rPt1.X() ); + nQuot *= ( rPt2.Y() - rPt1.Y() ); + return tools::Long( rPt1.Y() + nQuot ); + } + double nQuot = nB - rPt1.Y(); + nQuot /= ( rPt2.Y() - rPt1.Y() ); + nQuot *= ( rPt2.X() - rPt1.X() ); + return tools::Long( rPt1.X() + nQuot ); +} + +void SvxBoundArgs::NoteUpLow( tools::Long nA, const sal_uInt8 nArea ) +{ + if( nAct ) + { + NoteMargin( nA, nA ); + if( bMultiple ) + { + NoteRange( nArea != nAct ); + nAct = 0; + } + if( !nFirst ) + nFirst = nArea; + } + else + { + nAct = nArea; + nMin = nA; + nMax = nA; + } +} + +std::deque<tools::Long>* TextRanger::GetTextRanges( const Range& rRange ) +{ + DBG_ASSERT( rRange.Min() || rRange.Max(), "Zero-Range not allowed, Bye Bye" ); + //Can we find the result we need in the cache? + for (auto & elem : mRangeCache) + { + if (elem.range == rRange) + return &(elem.results); + } + //Calculate a new result + RangeCacheItem rngCache(rRange); + SvxBoundArgs aArg( this, &(rngCache.results), rRange ); + aArg.Calc( maPolyPolygon ); + if( mpLinePolyPolygon ) + aArg.Concat( &*mpLinePolyPolygon ); + //Add new result to the cache + mRangeCache.push_back(std::move(rngCache)); + if (mRangeCache.size() > nCacheSize) + mRangeCache.pop_front(); + return &(mRangeCache.back().results); +} + +const tools::Rectangle& TextRanger::GetBoundRect_() const +{ + DBG_ASSERT( !mxBound, "Don't call twice." ); + mxBound = maPolyPolygon.GetBoundRect(); + return *mxBound; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/unolingu.cxx b/editeng/source/misc/unolingu.cxx new file mode 100644 index 000000000..c0935283f --- /dev/null +++ b/editeng/source/misc/unolingu.cxx @@ -0,0 +1,754 @@ +/* -*- 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 <editeng/unolingu.hxx> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/linguistic2/XHyphenatedWord.hpp> +#include <com/sun/star/linguistic2/DictionaryList.hpp> +#include <com/sun/star/linguistic2/LinguServiceManager.hpp> +#include <com/sun/star/linguistic2/LinguProperties.hpp> +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> + +#include <comphelper/processfactory.hxx> +#include <cppuhelper/implbase.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <unotools/lingucfg.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <linguistic/misc.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/editrids.hrc> +#include <svtools/strings.hrc> +#include <unotools/resmgr.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +using namespace ::comphelper; +using namespace ::linguistic; +using namespace ::com::sun::star; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::linguistic2; + +static uno::Reference< XLinguServiceManager2 > GetLngSvcMgr_Impl() +{ + uno::Reference< XComponentContext > xContext = comphelper::getProcessComponentContext(); + uno::Reference< XLinguServiceManager2 > xRes = LinguServiceManager::create(xContext); + return xRes; +} + +namespace { + +//! Dummy implementation in order to avoid loading of lingu DLL +//! when only the XSupportedLocales interface is used. +//! The dummy accesses the real implementation (and thus loading the DLL) +//! when "real" work needs to be done only. +class ThesDummy_Impl : + public cppu::WeakImplHelper< XThesaurus > +{ + uno::Reference< XThesaurus > xThes; // the real one... + std::unique_ptr<Sequence< lang::Locale >> pLocaleSeq; + + void GetCfgLocales(); + + void GetThes_Impl(); + +public: + ThesDummy_Impl() {} + + // XSupportedLocales + virtual css::uno::Sequence< css::lang::Locale > SAL_CALL + getLocales() override; + virtual sal_Bool SAL_CALL + hasLocale( const css::lang::Locale& rLocale ) override; + + // XThesaurus + virtual css::uno::Sequence< + css::uno::Reference< css::linguistic2::XMeaning > > SAL_CALL + queryMeanings( const OUString& rTerm, + const css::lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; +}; + +} + +void ThesDummy_Impl::GetCfgLocales() +{ + if (pLocaleSeq) + return; + + SvtLinguConfig aCfg; + Sequence < OUString > aNodeNames( aCfg.GetNodeNames( "ServiceManager/ThesaurusList" ) ); + const OUString *pNodeNames = aNodeNames.getConstArray(); + sal_Int32 nLen = aNodeNames.getLength(); + pLocaleSeq.reset( new Sequence< lang::Locale >( nLen ) ); + lang::Locale *pLocale = pLocaleSeq->getArray(); + for (sal_Int32 i = 0; i < nLen; ++i) + { + pLocale[i] = LanguageTag::convertToLocaleWithFallback( pNodeNames[i] ); + } +} + + +void ThesDummy_Impl::GetThes_Impl() +{ + if (!xThes.is()) + { + uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() ); + xThes = xLngSvcMgr->getThesaurus(); + + if (xThes.is()) + { + // no longer needed... + pLocaleSeq.reset(); + } + } +} + + +uno::Sequence< lang::Locale > SAL_CALL + ThesDummy_Impl::getLocales() +{ + GetThes_Impl(); + if (xThes.is()) + return xThes->getLocales(); + else if (!pLocaleSeq) // if not already loaded save startup time by avoiding loading them now + GetCfgLocales(); + return *pLocaleSeq; +} + + +sal_Bool SAL_CALL + ThesDummy_Impl::hasLocale( const lang::Locale& rLocale ) +{ + GetThes_Impl(); + if (xThes.is()) + return xThes->hasLocale( rLocale ); + else if (!pLocaleSeq) // if not already loaded save startup time by avoiding loading them now + GetCfgLocales(); + bool bFound = false; + sal_Int32 nLen = pLocaleSeq->getLength(); + const lang::Locale *pLocale = pLocaleSeq->getConstArray(); + const lang::Locale *pEnd = pLocale + nLen; + for ( ; pLocale < pEnd && !bFound; ++pLocale) + { + bFound = pLocale->Language == rLocale.Language && + pLocale->Country == rLocale.Country && + pLocale->Variant == rLocale.Variant; + } + return bFound; +} + + +uno::Sequence< uno::Reference< linguistic2::XMeaning > > SAL_CALL + ThesDummy_Impl::queryMeanings( + const OUString& rTerm, + const lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetThes_Impl(); + uno::Sequence< uno::Reference< linguistic2::XMeaning > > aRes; + OSL_ENSURE( xThes.is(), "Thesaurus missing" ); + if (xThes.is()) + aRes = xThes->queryMeanings( rTerm, rLocale, rProperties ); + return aRes; +} + +namespace { + +//! Dummy implementation in order to avoid loading of lingu DLL. +//! The dummy accesses the real implementation (and thus loading the DLL) +//! when it needs to be done only. +class SpellDummy_Impl : + public cppu::WeakImplHelper< XSpellChecker1 > +{ + uno::Reference< XSpellChecker1 > xSpell; // the real one... + + void GetSpell_Impl(); + +public: + + // XSupportedLanguages (for XSpellChecker1) + virtual css::uno::Sequence< sal_Int16 > SAL_CALL + getLanguages() override; + virtual sal_Bool SAL_CALL + hasLanguage( sal_Int16 nLanguage ) override; + + // XSpellChecker1 (same as XSpellChecker but sal_Int16 for language) + virtual sal_Bool SAL_CALL + isValid( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; + virtual css::uno::Reference< css::linguistic2::XSpellAlternatives > SAL_CALL + spell( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; +}; + +} + +void SpellDummy_Impl::GetSpell_Impl() +{ + if (!xSpell.is()) + { + uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() ); + xSpell.set( xLngSvcMgr->getSpellChecker(), UNO_QUERY ); + } +} + + +uno::Sequence< sal_Int16 > SAL_CALL + SpellDummy_Impl::getLanguages() +{ + GetSpell_Impl(); + if (xSpell.is()) + return xSpell->getLanguages(); + else + return uno::Sequence< sal_Int16 >(); +} + + +sal_Bool SAL_CALL + SpellDummy_Impl::hasLanguage( sal_Int16 nLanguage ) +{ + GetSpell_Impl(); + bool bRes = false; + if (xSpell.is()) + bRes = xSpell->hasLanguage( nLanguage ); + return bRes; +} + + +sal_Bool SAL_CALL + SpellDummy_Impl::isValid( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetSpell_Impl(); + bool bRes = true; + if (xSpell.is()) + bRes = xSpell->isValid( rWord, nLanguage, rProperties ); + return bRes; +} + + +uno::Reference< linguistic2::XSpellAlternatives > SAL_CALL + SpellDummy_Impl::spell( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetSpell_Impl(); + uno::Reference< linguistic2::XSpellAlternatives > xRes; + if (xSpell.is()) + xRes = xSpell->spell( rWord, nLanguage, rProperties ); + return xRes; +} + +namespace { + +//! Dummy implementation in order to avoid loading of lingu DLL. +//! The dummy accesses the real implementation (and thus loading the DLL) +//! when it needs to be done only. +class HyphDummy_Impl : + public cppu::WeakImplHelper< XHyphenator > +{ + uno::Reference< XHyphenator > xHyph; // the real one... + + void GetHyph_Impl(); + +public: + + // XSupportedLocales + virtual css::uno::Sequence< + css::lang::Locale > SAL_CALL + getLocales() override; + virtual sal_Bool SAL_CALL + hasLocale( const css::lang::Locale& rLocale ) override; + + // XHyphenator + virtual css::uno::Reference< + css::linguistic2::XHyphenatedWord > SAL_CALL + hyphenate( const OUString& rWord, + const css::lang::Locale& rLocale, + sal_Int16 nMaxLeading, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; + virtual css::uno::Reference< + css::linguistic2::XHyphenatedWord > SAL_CALL + queryAlternativeSpelling( const OUString& rWord, + const css::lang::Locale& rLocale, + sal_Int16 nIndex, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; + virtual css::uno::Reference< + css::linguistic2::XPossibleHyphens > SAL_CALL + createPossibleHyphens( + const OUString& rWord, + const css::lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; +}; + +} + +void HyphDummy_Impl::GetHyph_Impl() +{ + if (!xHyph.is()) + { + uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() ); + xHyph = xLngSvcMgr->getHyphenator(); + } +} + + +uno::Sequence< lang::Locale > SAL_CALL + HyphDummy_Impl::getLocales() +{ + GetHyph_Impl(); + if (xHyph.is()) + return xHyph->getLocales(); + else + return uno::Sequence< lang::Locale >(); +} + + +sal_Bool SAL_CALL + HyphDummy_Impl::hasLocale( const lang::Locale& rLocale ) +{ + GetHyph_Impl(); + bool bRes = false; + if (xHyph.is()) + bRes = xHyph->hasLocale( rLocale ); + return bRes; +} + + +uno::Reference< linguistic2::XHyphenatedWord > SAL_CALL + HyphDummy_Impl::hyphenate( + const OUString& rWord, + const lang::Locale& rLocale, + sal_Int16 nMaxLeading, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetHyph_Impl(); + uno::Reference< linguistic2::XHyphenatedWord > xRes; + if (xHyph.is()) + xRes = xHyph->hyphenate( rWord, rLocale, nMaxLeading, rProperties ); + return xRes; +} + + +uno::Reference< linguistic2::XHyphenatedWord > SAL_CALL + HyphDummy_Impl::queryAlternativeSpelling( + const OUString& rWord, + const lang::Locale& rLocale, + sal_Int16 nIndex, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetHyph_Impl(); + uno::Reference< linguistic2::XHyphenatedWord > xRes; + if (xHyph.is()) + xRes = xHyph->queryAlternativeSpelling( rWord, rLocale, nIndex, rProperties ); + return xRes; +} + + +uno::Reference< linguistic2::XPossibleHyphens > SAL_CALL + HyphDummy_Impl::createPossibleHyphens( + const OUString& rWord, + const lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetHyph_Impl(); + uno::Reference< linguistic2::XPossibleHyphens > xRes; + if (xHyph.is()) + xRes = xHyph->createPossibleHyphens( rWord, rLocale, rProperties ); + return xRes; +} + +class LinguMgrExitLstnr : public cppu::WeakImplHelper<XEventListener> +{ + uno::Reference< XDesktop2 > xDesktop; + + static void AtExit(); + +public: + LinguMgrExitLstnr(); + virtual ~LinguMgrExitLstnr() override; + + // lang::XEventListener + virtual void SAL_CALL disposing(const EventObject& rSource) override; +}; + +LinguMgrExitLstnr::LinguMgrExitLstnr() +{ + // add object to frame::Desktop EventListeners in order to properly call + // the AtExit function at application exit. + + uno::Reference< XComponentContext > xContext = getProcessComponentContext(); + xDesktop = Desktop::create( xContext ); + xDesktop->addEventListener( this ); +} + +LinguMgrExitLstnr::~LinguMgrExitLstnr() +{ + if (xDesktop.is()) + { + xDesktop->removeEventListener( this ); + xDesktop = nullptr; //! release reference to desktop + } + OSL_ENSURE(!xDesktop.is(), "reference to desktop should be released"); +} + +void LinguMgrExitLstnr::disposing(const EventObject& rSource) +{ + if (xDesktop.is() && rSource.Source == xDesktop) + { + xDesktop->removeEventListener( this ); + xDesktop = nullptr; //! release reference to desktop + + AtExit(); + } +} + +void LinguMgrExitLstnr::AtExit() +{ + SolarMutexGuard g; + + // release references + LinguMgr::xLngSvcMgr = nullptr; + LinguMgr::xSpell = nullptr; + LinguMgr::xHyph = nullptr; + LinguMgr::xThes = nullptr; + LinguMgr::xDicList = nullptr; + LinguMgr::xProp = nullptr; + LinguMgr::xIgnoreAll = nullptr; + LinguMgr::xChangeAll = nullptr; + + LinguMgr::bExiting = true; + + LinguMgr::pExitLstnr = nullptr; +} + + +rtl::Reference<LinguMgrExitLstnr> LinguMgr::pExitLstnr; +bool LinguMgr::bExiting = false; +uno::Reference< XLinguServiceManager2 > LinguMgr::xLngSvcMgr; +uno::Reference< XSpellChecker1 > LinguMgr::xSpell; +uno::Reference< XHyphenator > LinguMgr::xHyph; +uno::Reference< XThesaurus > LinguMgr::xThes; +uno::Reference< XSearchableDictionaryList > LinguMgr::xDicList; +uno::Reference< XLinguProperties > LinguMgr::xProp; +uno::Reference< XDictionary > LinguMgr::xIgnoreAll; +uno::Reference< XDictionary > LinguMgr::xChangeAll; + + +uno::Reference< XLinguServiceManager2 > LinguMgr::GetLngSvcMgr() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + if (!xLngSvcMgr.is()) + xLngSvcMgr = GetLngSvcMgr_Impl(); + + return xLngSvcMgr; +} + + +uno::Reference< XSpellChecker1 > LinguMgr::GetSpellChecker() +{ + return xSpell.is() ? xSpell : GetSpell(); +} + +uno::Reference< XHyphenator > LinguMgr::GetHyphenator() +{ + return xHyph.is() ? xHyph : GetHyph(); +} + +uno::Reference< XThesaurus > LinguMgr::GetThesaurus() +{ + return xThes.is() ? xThes : GetThes(); +} + +uno::Reference< XSearchableDictionaryList > LinguMgr::GetDictionaryList() +{ + return xDicList.is() ? xDicList : GetDicList(); +} + +uno::Reference< linguistic2::XLinguProperties > LinguMgr::GetLinguPropertySet() +{ + return xProp.is() ? xProp : GetProp(); +} + +uno::Reference< XDictionary > LinguMgr::GetStandardDic() +{ + //! don't hold reference to this + //! (it may be removed from dictionary list and needs to be + //! created empty if accessed again) + return GetStandard(); +} + +uno::Reference< XDictionary > LinguMgr::GetIgnoreAllList() +{ + return xIgnoreAll.is() ? xIgnoreAll : GetIgnoreAll(); +} + +uno::Reference< XDictionary > LinguMgr::GetChangeAllList() +{ + return xChangeAll.is() ? xChangeAll : GetChangeAll(); +} + +uno::Reference< XSpellChecker1 > LinguMgr::GetSpell() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + //! use dummy implementation in order to avoid loading of lingu DLL + xSpell = new SpellDummy_Impl; + return xSpell; +} + +uno::Reference< XHyphenator > LinguMgr::GetHyph() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + //! use dummy implementation in order to avoid loading of lingu DLL + xHyph = new HyphDummy_Impl; + return xHyph; +} + +uno::Reference< XThesaurus > LinguMgr::GetThes() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + //! use dummy implementation in order to avoid loading of lingu DLL + //! when only the XSupportedLocales interface is used. + //! The dummy accesses the real implementation (and thus loading the DLL) + //! when "real" work needs to be done only. + xThes = new ThesDummy_Impl; + return xThes; +} + +uno::Reference< XSearchableDictionaryList > LinguMgr::GetDicList() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + xDicList = linguistic2::DictionaryList::create( getProcessComponentContext() ); + return xDicList; +} + +uno::Reference< linguistic2::XLinguProperties > LinguMgr::GetProp() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + xProp = linguistic2::LinguProperties::create( getProcessComponentContext() ); + return xProp; +} + +uno::Reference< XDictionary > LinguMgr::GetIgnoreAll() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + uno::Reference< XSearchableDictionaryList > xTmpDicList( GetDictionaryList() ); + if (xTmpDicList.is()) + { + std::locale loc(Translate::Create("svt")); + xIgnoreAll = xTmpDicList->getDictionaryByName( + Translate::get(STR_DESCRIPTION_IGNOREALLLIST, loc) ); + } + return xIgnoreAll; +} + +uno::Reference< XDictionary > LinguMgr::GetChangeAll() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + uno::Reference< XSearchableDictionaryList > _xDicList = GetDictionaryList(); + if (_xDicList.is()) + { + xChangeAll = _xDicList->createDictionary( + "ChangeAllList", + LanguageTag::convertToLocale( LANGUAGE_NONE ), + DictionaryType_NEGATIVE, OUString() ); + } + return xChangeAll; +} + +uno::Reference< XDictionary > LinguMgr::GetStandard() +{ + // Tries to return a dictionary which may hold positive entries is + // persistent and not read-only. + + if (bExiting) + return nullptr; + + uno::Reference< XSearchableDictionaryList > xTmpDicList( GetDictionaryList() ); + if (!xTmpDicList.is()) + return nullptr; + + static const OUStringLiteral aDicName( u"standard.dic" ); + uno::Reference< XDictionary > xDic = xTmpDicList->getDictionaryByName( aDicName ); + if (!xDic.is()) + { + // try to create standard dictionary + uno::Reference< XDictionary > xTmp; + try + { + xTmp = xTmpDicList->createDictionary( aDicName, + LanguageTag::convertToLocale( LANGUAGE_NONE ), + DictionaryType_POSITIVE, + linguistic::GetWritableDictionaryURL( aDicName ) ); + } + catch(const css::uno::Exception &) + { + } + + // add new dictionary to list + if (xTmp.is()) + { + xTmpDicList->addDictionary( xTmp ); + xTmp->setActive( true ); + } + xDic = xTmp; + } +#if OSL_DEBUG_LEVEL > 1 + uno::Reference< XStorable > xStor( xDic, UNO_QUERY ); + OSL_ENSURE( xDic.is() && xDic->getDictionaryType() == DictionaryType_POSITIVE, + "wrong dictionary type"); + OSL_ENSURE( xDic.is() && LanguageTag( xDic->getLocale() ).getLanguageType() == LANGUAGE_NONE, + "wrong dictionary language"); + OSL_ENSURE( !xStor.is() || (xStor->hasLocation() && !xStor->isReadonly()), + "dictionary not editable" ); +#endif + + return xDic; +} + +SvxAlternativeSpelling SvxGetAltSpelling( + const css::uno::Reference< css::linguistic2::XHyphenatedWord > & rHyphWord ) +{ + SvxAlternativeSpelling aRes; + if (rHyphWord.is() && rHyphWord->isAlternativeSpelling()) + { + OUString aWord( rHyphWord->getWord() ), + aAltWord( rHyphWord->getHyphenatedWord() ); + sal_Int16 nHyphenationPos = rHyphWord->getHyphenationPos(), + nHyphenPos = rHyphWord->getHyphenPos(); + sal_Int16 nLen = static_cast<sal_Int16>(aWord.getLength()); + sal_Int16 nAltLen = static_cast<sal_Int16>(aAltWord.getLength()); + const sal_Unicode *pWord = aWord.getStr(), + *pAltWord = aAltWord.getStr(); + + // count number of chars from the left to the + // hyphenation pos / hyphen pos that are equal + sal_Int16 nL = 0; + while (nL <= nHyphenationPos && nL <= nHyphenPos + && pWord[ nL ] == pAltWord[ nL ]) + ++nL; + // count number of chars from the right to the + // hyphenation pos / hyphen pos that are equal + sal_Int16 nR = 0; + sal_Int32 nIdx = nLen - 1; + sal_Int32 nAltIdx = nAltLen - 1; + while (nIdx > nHyphenationPos && nAltIdx > nHyphenPos + && pWord[ nIdx-- ] == pAltWord[ nAltIdx-- ]) + ++nR; + + aRes.aReplacement = aAltWord.copy( nL, nAltLen - nL - nR ); + aRes.nChangedPos = nL; + aRes.nChangedLength = nLen - nL - nR; + aRes.bIsAltSpelling = true; + } + return aRes; +} + + +SvxDicListChgClamp::SvxDicListChgClamp( uno::Reference< XSearchableDictionaryList > _xDicList ) : + xDicList (std::move( _xDicList )) +{ + if (xDicList.is()) + { + xDicList->beginCollectEvents(); + } +} + +SvxDicListChgClamp::~SvxDicListChgClamp() +{ + if (xDicList.is()) + { + xDicList->endCollectEvents(); + } +} + +short SvxDicError(weld::Window *pParent, linguistic::DictionaryError nError) +{ + short nRes = 0; + if (linguistic::DictionaryError::NONE != nError) + { + TranslateId pRid; + switch (nError) + { + case linguistic::DictionaryError::FULL : pRid = RID_SVXSTR_DIC_ERR_FULL; break; + case linguistic::DictionaryError::READONLY : pRid = RID_SVXSTR_DIC_ERR_READONLY; break; + default: + pRid = RID_SVXSTR_DIC_ERR_UNKNOWN; + SAL_WARN("editeng", "unexpected case"); + } + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pParent, + VclMessageType::Info, VclButtonsType::Ok, + EditResId(pRid))); + nRes = xInfoBox->run(); + + } + return nRes; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/urlfieldhelper.cxx b/editeng/source/misc/urlfieldhelper.cxx new file mode 100644 index 000000000..564bc54e7 --- /dev/null +++ b/editeng/source/misc/urlfieldhelper.cxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <editeng/urlfieldhelper.hxx> + +#include <editeng/flditem.hxx> +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> + +void URLFieldHelper::RemoveURLField(EditView& pEditView) +{ + pEditView.SelectFieldAtCursor(); + const SvxFieldData* pField = pEditView.GetFieldAtCursor(); + if (auto pUrlField = dynamic_cast<const SvxURLField*>(pField)) + { + ESelection aSel = pEditView.GetSelection(); + pEditView.GetEditEngine()->QuickInsertText(pUrlField->GetRepresentation(), aSel); + pEditView.Invalidate(); + } +} + +bool URLFieldHelper::IsCursorAtURLField(const EditView& pEditView) +{ + // tdf#128666 Make sure only URL field (or nothing) is selected + ESelection aSel = pEditView.GetSelection(); + auto nSelectedParas = aSel.nEndPara - aSel.nStartPara; + auto nSelectedChars = aSel.nEndPos - aSel.nStartPos; + bool bIsValidSelection + = nSelectedParas == 0 + && (nSelectedChars == 0 || nSelectedChars == 1 || nSelectedChars == -1); + if (!bIsValidSelection) + return false; + + const SvxFieldData* pField = pEditView.GetFieldAtCursor(); + if (dynamic_cast<const SvxURLField*>(pField)) + return true; + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/editeng/source/outliner/outleeng.cxx b/editeng/source/outliner/outleeng.cxx new file mode 100644 index 000000000..e4fc414ad --- /dev/null +++ b/editeng/source/outliner/outleeng.cxx @@ -0,0 +1,192 @@ +/* -*- 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 <editeng/editeng.hxx> +#include <editeng/eerdll.hxx> + +#include <editeng/outliner.hxx> +#include "outleeng.hxx" +#include "paralist.hxx" +#include <editeng/editrids.hrc> +#include <svl/itemset.hxx> +#include <editeng/editstat.hxx> +#include "outlundo.hxx" + +OutlinerEditEng::OutlinerEditEng( Outliner* pEngOwner, SfxItemPool* pPool ) + : EditEngine( pPool ) +{ + pOwner = pEngOwner; +} + +OutlinerEditEng::~OutlinerEditEng() +{ +} + +void OutlinerEditEng::PaintingFirstLine(sal_Int32 nPara, const Point& rStartPos, const Point& rOrigin, Degree10 nOrientation, OutputDevice& rOutDev) +{ + if( GetControlWord() & EEControlBits::OUTLINER ) + { + PaintFirstLineInfo aInfo(nPara, rStartPos, &rOutDev); + pOwner->maPaintFirstLineHdl.Call( &aInfo ); + } + + pOwner->PaintBullet(nPara, rStartPos, rOrigin, nOrientation, rOutDev); +} + +const SvxNumberFormat* OutlinerEditEng::GetNumberFormat( sal_Int32 nPara ) const +{ + const SvxNumberFormat* pFmt = nullptr; + if (pOwner) + pFmt = pOwner->GetNumberFormat( nPara ); + return pFmt; +} + + +tools::Rectangle OutlinerEditEng::GetBulletArea( sal_Int32 nPara ) +{ + tools::Rectangle aBulletArea { Point(), Point() }; + if ( nPara < pOwner->pParaList->GetParagraphCount() ) + { + if ( pOwner->ImplHasNumberFormat( nPara ) ) + aBulletArea = pOwner->ImpCalcBulletArea( nPara, false, false ); + } + return aBulletArea; +} + +void OutlinerEditEng::ParagraphInserted( sal_Int32 nNewParagraph ) +{ + pOwner->ParagraphInserted( nNewParagraph ); + + EditEngine::ParagraphInserted( nNewParagraph ); +} + +void OutlinerEditEng::ParagraphDeleted( sal_Int32 nDeletedParagraph ) +{ + pOwner->ParagraphDeleted( nDeletedParagraph ); + + EditEngine::ParagraphDeleted( nDeletedParagraph ); +} + +void OutlinerEditEng::ParagraphConnected( sal_Int32 /*nLeftParagraph*/, sal_Int32 nRightParagraph ) +{ + if( pOwner && pOwner->IsUndoEnabled() && !pOwner->GetEditEngine().IsInUndo() ) + { + Paragraph* pPara = pOwner->GetParagraph( nRightParagraph ); + if( pPara && Outliner::HasParaFlag( pPara, ParaFlag::ISPAGE ) ) + { + pOwner->InsertUndo( std::make_unique<OutlinerUndoChangeParaFlags>( pOwner, nRightParagraph, ParaFlag::ISPAGE, ParaFlag::NONE ) ); + } + } +} + + +void OutlinerEditEng::StyleSheetChanged( SfxStyleSheet* pStyle ) +{ + pOwner->StyleSheetChanged( pStyle ); +} + +void OutlinerEditEng::ParaAttribsChanged( sal_Int32 nPara ) +{ + pOwner->ParaAttribsChanged( nPara ); +} + +bool OutlinerEditEng::SpellNextDocument() +{ + return pOwner->SpellNextDocument(); +} + +bool OutlinerEditEng::ConvertNextDocument() +{ + return pOwner->ConvertNextDocument(); +} + +OUString OutlinerEditEng::GetUndoComment( sal_uInt16 nUndoId ) const +{ + switch( nUndoId ) + { + case OLUNDO_DEPTH: + return EditResId(RID_OUTLUNDO_DEPTH); + + case OLUNDO_EXPAND: + return EditResId(RID_OUTLUNDO_EXPAND); + + case OLUNDO_COLLAPSE: + return EditResId(RID_OUTLUNDO_COLLAPSE); + + case OLUNDO_ATTR: + return EditResId(RID_OUTLUNDO_ATTR); + + case OLUNDO_INSERT: + return EditResId(RID_OUTLUNDO_INSERT); + + default: + return EditEngine::GetUndoComment( nUndoId ); + } +} + +void OutlinerEditEng::DrawingText( const Point& rStartPos, const OUString& rText, sal_Int32 nTextStart, sal_Int32 nTextLen, + o3tl::span<const sal_Int32> pDXArray, const SvxFont& rFont, sal_Int32 nPara, sal_uInt8 nRightToLeft, + const EEngineData::WrongSpellVector* pWrongSpellVector, + const SvxFieldData* pFieldData, + bool bEndOfLine, + bool bEndOfParagraph, + const css::lang::Locale* pLocale, + const Color& rOverlineColor, + const Color& rTextLineColor) +{ + pOwner->DrawingText(rStartPos,rText,nTextStart,nTextLen,pDXArray,rFont,nPara,nRightToLeft, + pWrongSpellVector, pFieldData, bEndOfLine, bEndOfParagraph, false/*bEndOfBullet*/, pLocale, rOverlineColor, rTextLineColor); +} + +void OutlinerEditEng::DrawingTab( const Point& rStartPos, tools::Long nWidth, const OUString& rChar, + const SvxFont& rFont, sal_Int32 nPara, sal_uInt8 nRightToLeft, + bool bEndOfLine, bool bEndOfParagraph, + const Color& rOverlineColor, const Color& rTextLineColor) +{ + pOwner->DrawingTab(rStartPos, nWidth, rChar, rFont, nPara, nRightToLeft, + bEndOfLine, bEndOfParagraph, rOverlineColor, rTextLineColor ); +} + +OUString OutlinerEditEng::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor ) +{ + return pOwner->CalcFieldValue( rField, nPara, nPos, rpTxtColor, rpFldColor ); +} + +void OutlinerEditEng::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + if( !pPara ) + return; + + if ( !IsInUndo() && IsUndoEnabled() ) + pOwner->UndoActionStart( OLUNDO_ATTR ); + + EditEngine::SetParaAttribs( nPara, rSet ); + + pOwner->ImplCheckNumBulletItem( nPara ); + // #i100014# + // It is not a good idea to subtract 1 from a count and cast the result + // to sal_uInt16 without check, if the count is 0. + pOwner->ImplCheckParagraphs( nPara, pOwner->pParaList->GetParagraphCount() ); + + if ( !IsInUndo() && IsUndoEnabled() ) + pOwner->UndoActionEnd(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outleeng.hxx b/editeng/source/outliner/outleeng.hxx new file mode 100644 index 000000000..963e74582 --- /dev/null +++ b/editeng/source/outliner/outleeng.hxx @@ -0,0 +1,84 @@ +/* -*- 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 <editeng/outliner.hxx> +#include <editeng/editeng.hxx> + +typedef std::vector<EENotify> NotifyList; + +class OutlinerEditEng : public EditEngine +{ + Outliner* pOwner; + +protected: + + // derived from EditEngine. Allows Outliner objects to provide + // bullet access to the EditEngine. + virtual const SvxNumberFormat* GetNumberFormat( sal_Int32 nPara ) const override; + +public: + OutlinerEditEng( Outliner* pOwner, SfxItemPool* pPool ); + virtual ~OutlinerEditEng() override; + + virtual void PaintingFirstLine(sal_Int32 nPara, const Point& rStartPos, const Point& rOrigin, Degree10 nOrientation, OutputDevice& rOutDev) override; + + virtual void ParagraphInserted( sal_Int32 nNewParagraph ) override; + virtual void ParagraphDeleted( sal_Int32 nDeletedParagraph ) override; + virtual void ParagraphConnected( sal_Int32 nLeftParagraph, sal_Int32 nRightParagraph ) override; + + virtual void DrawingText( const Point& rStartPos, const OUString& rText, sal_Int32 nTextStart, + sal_Int32 nTextLen, o3tl::span<const sal_Int32> pDXArray, const SvxFont& rFont, + sal_Int32 nPara, sal_uInt8 nRightToLeft, + const EEngineData::WrongSpellVector* pWrongSpellVector, + const SvxFieldData* pFieldData, + bool bEndOfLine, + bool bEndOfParagraph, + const css::lang::Locale* pLocale, + const Color& rOverlineColor, + const Color& rTextLineColor) override; + + virtual void DrawingTab( + const Point& rStartPos, tools::Long nWidth, const OUString& rChar, + const SvxFont& rFont, sal_Int32 nPara, sal_uInt8 nRightToLeft, + bool bEndOfLine, + bool bEndOfParagraph, + const Color& rOverlineColor, + const Color& rTextLineColor) override; + + virtual void StyleSheetChanged( SfxStyleSheet* pStyle ) override; + virtual void ParaAttribsChanged( sal_Int32 nPara ) override; + virtual bool SpellNextDocument() override; + virtual OUString GetUndoComment( sal_uInt16 nUndoId ) const override; + + // for text conversion + virtual bool ConvertNextDocument() override; + + virtual OUString CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rTxtColor, std::optional<Color>& rFldColor ) override; + + virtual tools::Rectangle GetBulletArea( sal_Int32 nPara ) override; + + virtual void SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) override; + + // belongs into class Outliner, move there before incompatible update! + Link<EENotify&,void> aOutlinerNotifyHdl; + NotifyList aNotifyCache; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outlin2.cxx b/editeng/source/outliner/outlin2.cxx new file mode 100644 index 000000000..951f69235 --- /dev/null +++ b/editeng/source/outliner/outlin2.cxx @@ -0,0 +1,600 @@ +/* -*- 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 <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editund2.hxx> + +#include <svl/style.hxx> +#include <vcl/mapmod.hxx> + +#include <editeng/forbiddencharacterstable.hxx> + +#include <editeng/outliner.hxx> +#include "paralist.hxx" +#include "outleeng.hxx" +#include <editeng/editstat.hxx> + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::linguistic2; + + +// ====================== Simple pass-through ======================= + + +bool Outliner::SetUpdateLayout( bool bUpdate ) +{ + return pEditEngine->SetUpdateLayout( bUpdate ); +} + + +bool Outliner::IsUpdateLayout() const +{ + return pEditEngine->IsUpdateLayout(); +} + +const SfxItemSet& Outliner::GetEmptyItemSet() const +{ + return pEditEngine->GetEmptyItemSet(); +} + +void Outliner::EnableUndo( bool bEnable ) +{ + pEditEngine->EnableUndo( bEnable ); +} + +bool Outliner::IsUndoEnabled() const +{ + return pEditEngine->IsUndoEnabled(); +} + +MapMode const & Outliner::GetRefMapMode() const +{ + return pEditEngine->GetRefMapMode(); +} + +void Outliner::SetRefMapMode( const MapMode& rMMode ) +{ + pEditEngine->SetRefMapMode( rMMode ); +} + +void Outliner::SetBackgroundColor( const Color& rColor ) +{ + pEditEngine->SetBackgroundColor( rColor ); +} + +Color const & Outliner::GetBackgroundColor() const +{ + return pEditEngine->GetBackgroundColor(); +} + + +void Outliner::ClearModifyFlag() +{ + pEditEngine->ClearModifyFlag(); +} + +bool Outliner::IsModified() const +{ + return pEditEngine->IsModified(); +} + +sal_uInt32 Outliner::GetTextHeight() const +{ + return pEditEngine->GetTextHeight(); +} + +void Outliner::SetModifyHdl( const Link<LinkParamNone*,void>& rLink ) +{ + pEditEngine->SetModifyHdl( rLink ); +} + +Link<LinkParamNone*,void> const & Outliner::GetModifyHdl() const +{ + return pEditEngine->GetModifyHdl(); +} + +void Outliner::SetNotifyHdl( const Link<EENotify&,void>& rLink ) +{ + pEditEngine->aOutlinerNotifyHdl = rLink; + + if ( rLink.IsSet() ) + pEditEngine->SetNotifyHdl( LINK( this, Outliner, EditEngineNotifyHdl ) ); + else + pEditEngine->SetNotifyHdl( Link<EENotify&,void>() ); +} + +void Outliner::SetStatusEventHdl( const Link<EditStatus&, void>& rLink ) +{ + pEditEngine->SetStatusEventHdl( rLink ); +} + +Link<EditStatus&, void> const & Outliner::GetStatusEventHdl() const +{ + return pEditEngine->GetStatusEventHdl(); +} + +void Outliner::SetDefTab( sal_uInt16 nTab ) +{ + pEditEngine->SetDefTab( nTab ); +} + +bool Outliner::IsFlatMode() const +{ + return pEditEngine->IsFlatMode(); +} + +bool Outliner::UpdateFields() +{ + return pEditEngine->UpdateFields(); +} + +void Outliner::RemoveFields( const std::function<bool ( const SvxFieldData* )>& isFieldData ) +{ + pEditEngine->RemoveFields( isFieldData ); +} + +void Outliner::SetWordDelimiters( const OUString& rDelimiters ) +{ + pEditEngine->SetWordDelimiters( rDelimiters ); +} + +OUString const & Outliner::GetWordDelimiters() const +{ + return pEditEngine->GetWordDelimiters(); +} + +OUString Outliner::GetWord( sal_Int32 nPara, sal_Int32 nIndex ) +{ + return pEditEngine->GetWord( nPara, nIndex ); +} + +void Outliner::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect ) +{ + pEditEngine->Draw( rOutDev, rOutRect ); +} + +void Outliner::Draw( OutputDevice& rOutDev, const Point& rStartPos ) +{ + pEditEngine->Draw( rOutDev, rStartPos ); +} + +void Outliner::SetPaperSize( const Size& rSize ) +{ + pEditEngine->SetPaperSize( rSize ); +} + +const Size& Outliner::GetPaperSize() const +{ + return pEditEngine->GetPaperSize(); +} + +void Outliner::SetPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon ) +{ + pEditEngine->SetPolygon( rPolyPolygon ); +} + +void Outliner::SetPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DPolyPolygon* pLinePolyPolygon) +{ + pEditEngine->SetPolygon( rPolyPolygon, pLinePolyPolygon); +} + +void Outliner::ClearPolygon() +{ + pEditEngine->ClearPolygon(); +} + +const Size& Outliner::GetMinAutoPaperSize() const +{ + return pEditEngine->GetMinAutoPaperSize(); +} + +void Outliner::SetMinAutoPaperSize( const Size& rSz ) +{ + pEditEngine->SetMinAutoPaperSize( rSz ); +} + +const Size& Outliner::GetMaxAutoPaperSize() const +{ + return pEditEngine->GetMaxAutoPaperSize(); +} + +void Outliner::SetMaxAutoPaperSize( const Size& rSz ) +{ + pEditEngine->SetMaxAutoPaperSize( rSz ); +} + +void Outliner::SetMinColumnWrapHeight(tools::Long nVal) +{ + pEditEngine->SetMinColumnWrapHeight(nVal); +} + +bool Outliner::IsExpanded( Paragraph const * pPara ) const +{ + return pParaList->HasVisibleChildren( pPara ); +} + +Paragraph* Outliner::GetParent( Paragraph const * pParagraph ) const +{ + return pParaList->GetParent( pParagraph ); +} + +sal_Int32 Outliner::GetChildCount( Paragraph const * pParent ) const +{ + return pParaList->GetChildCount( pParent ); +} + +Size Outliner::CalcTextSize() +{ + return Size(pEditEngine->CalcTextWidth(),pEditEngine->GetTextHeight()); +} + +Size Outliner::CalcTextSizeNTP() +{ + return Size(pEditEngine->CalcTextWidth(),pEditEngine->GetTextHeightNTP()); +} + +void Outliner::SetStyleSheetPool( SfxStyleSheetPool* pSPool ) +{ + pEditEngine->SetStyleSheetPool( pSPool ); +} + +SfxStyleSheetPool* Outliner::GetStyleSheetPool() +{ + return pEditEngine->GetStyleSheetPool(); +} + +SfxStyleSheet* Outliner::GetStyleSheet( sal_Int32 nPara ) +{ + return pEditEngine->GetStyleSheet( nPara ); +} + +bool Outliner::IsInSelectionMode() const +{ + return pEditEngine->IsInSelectionMode(); +} + +void Outliner::SetControlWord( EEControlBits nWord ) +{ + pEditEngine->SetControlWord( nWord ); +} + +EEControlBits Outliner::GetControlWord() const +{ + return pEditEngine->GetControlWord(); +} + +void Outliner::SetAsianCompressionMode( CharCompressType n ) +{ + pEditEngine->SetAsianCompressionMode( n ); +} + +void Outliner::SetKernAsianPunctuation( bool b ) +{ + pEditEngine->SetKernAsianPunctuation( b ); +} + +void Outliner::SetAddExtLeading( bool bExtLeading ) +{ + pEditEngine->SetAddExtLeading( bExtLeading ); +} + +void Outliner::UndoActionStart( sal_uInt16 nId ) +{ + pEditEngine->UndoActionStart( nId ); +} + +void Outliner::UndoActionEnd() +{ + pEditEngine->UndoActionEnd(); +} + +void Outliner::InsertUndo( std::unique_ptr<EditUndo> pUndo ) +{ + pEditEngine->GetUndoManager().AddUndoAction( std::move(pUndo) ); +} + +bool Outliner::IsInUndo() const +{ + return pEditEngine->IsInUndo(); +} + +sal_uInt32 Outliner::GetLineCount( sal_Int32 nParagraph ) const +{ + return pEditEngine->GetLineCount( nParagraph ); +} + +sal_Int32 Outliner::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + return pEditEngine->GetLineLen( nParagraph, nLine ); +} + +sal_uInt32 Outliner::GetLineHeight( sal_Int32 nParagraph ) +{ + return pEditEngine->GetLineHeight( nParagraph ); +} + +void Outliner::RemoveCharAttribs( sal_Int32 nPara, sal_uInt16 nWhich ) +{ + pEditEngine->RemoveCharAttribs( nPara, nWhich ); +} + +EESpellState Outliner::HasSpellErrors() +{ + return pEditEngine->HasSpellErrors(); +} + +bool Outliner::HasConvertibleTextPortion( LanguageType nLang ) +{ + return pEditEngine->HasConvertibleTextPortion( nLang ); +} + +bool Outliner::ConvertNextDocument() +{ + return false; +} + +void Outliner::SetDefaultLanguage( LanguageType eLang ) +{ + pEditEngine->SetDefaultLanguage( eLang ); +} + +void Outliner::CompleteOnlineSpelling() +{ + pEditEngine->CompleteOnlineSpelling(); +} + +bool Outliner::HasText( const SvxSearchItem& rSearchItem ) +{ + return pEditEngine->HasText( rSearchItem ); +} + +void Outliner::SetEditTextObjectPool( SfxItemPool* pPool ) +{ + pEditEngine->SetEditTextObjectPool( pPool ); +} + +SfxItemPool* Outliner::GetEditTextObjectPool() const +{ + return pEditEngine->GetEditTextObjectPool(); +} + +bool Outliner::SpellNextDocument() +{ + return false; +} + + +void Outliner::SetSpeller( Reference< XSpellChecker1 > const &xSpeller ) +{ + pEditEngine->SetSpeller( xSpeller ); +} + +Reference< XSpellChecker1 > const & Outliner::GetSpeller() +{ + return pEditEngine->GetSpeller(); +} + +void Outliner::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars) +{ + EditEngine::SetForbiddenCharsTable(xForbiddenChars); +} + +void Outliner::SetHyphenator( Reference< XHyphenator > const & xHyph ) +{ + pEditEngine->SetHyphenator( xHyph ); +} + +OutputDevice* Outliner::GetRefDevice() const +{ + return pEditEngine->GetRefDevice(); +} + +tools::Rectangle Outliner::GetParaBounds( sal_Int32 nParagraph ) const +{ + return pEditEngine->GetParaBounds(nParagraph ); +} + +Point Outliner::GetDocPos( const Point& rPaperPos ) const +{ + return pEditEngine->GetDocPos( rPaperPos ); +} + +bool Outliner::IsTextPos( const Point& rPaperPos, sal_uInt16 nBorder ) +{ + return IsTextPos( rPaperPos, nBorder, nullptr ); +} + +bool Outliner::IsTextPos( const Point& rPaperPos, sal_uInt16 nBorder, bool* pbBullet ) +{ + if ( pbBullet) + *pbBullet = false; + bool bTextPos = pEditEngine->IsTextPos( rPaperPos, nBorder ); + if ( !bTextPos ) + { + Point aDocPos = GetDocPos( rPaperPos ); + sal_Int32 nPara = pEditEngine->FindParagraph( aDocPos.Y() ); + if ( ( nPara != EE_PARA_NOT_FOUND ) && ImplHasNumberFormat( nPara ) ) + { + tools::Rectangle aBulArea = ImpCalcBulletArea( nPara, true, true ); + if ( aBulArea.Contains( rPaperPos ) ) + { + bTextPos = true; + if ( pbBullet) + *pbBullet = true; + } + } + } + + return bTextPos; +} + +void Outliner::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) +{ + pEditEngine->QuickSetAttribs( rSet, rSel ); +} + +void Outliner::QuickInsertText( const OUString& rText, const ESelection& rSel ) +{ + bFirstParaIsEmpty = false; + pEditEngine->QuickInsertText( rText, rSel ); +} + +void Outliner::QuickDelete( const ESelection& rSel ) +{ + bFirstParaIsEmpty = false; + pEditEngine->QuickDelete( rSel ); +} + +void Outliner::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) +{ + bFirstParaIsEmpty = false; + pEditEngine->QuickInsertField( rFld, rSel ); +} + +void Outliner::QuickInsertLineBreak( const ESelection& rSel ) +{ + bFirstParaIsEmpty = false; + pEditEngine->QuickInsertLineBreak( rSel ); +} + +void Outliner::QuickFormatDoc() +{ + pEditEngine->QuickFormatDoc(); +} + +void Outliner::SetGlobalCharStretching( sal_uInt16 nX, sal_uInt16 nY ) +{ + + // reset bullet size + sal_Int32 nParagraphs = pParaList->GetParagraphCount(); + for ( sal_Int32 nPara = 0; nPara < nParagraphs; nPara++ ) + { + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if ( pPara ) + pPara->aBulSize.setWidth( -1 ); + } + + pEditEngine->SetGlobalCharStretching( nX, nY ); +} + +void Outliner::GetGlobalCharStretching( sal_uInt16& rX, sal_uInt16& rY ) const +{ + pEditEngine->GetGlobalCharStretching( rX, rY ); +} + +void Outliner::EraseVirtualDevice() +{ + pEditEngine->EraseVirtualDevice(); +} + +bool Outliner::ShouldCreateBigTextObject() const +{ + return pEditEngine->ShouldCreateBigTextObject(); +} + +const EditEngine& Outliner::GetEditEngine() const +{ + return *pEditEngine; +} + +void Outliner::SetVertical(bool bVertical) +{ + pEditEngine->SetVertical(bVertical); +} + +void Outliner::SetRotation(TextRotation nRotation) +{ + pEditEngine->SetRotation(nRotation); +} + +bool Outliner::IsVertical() const +{ + return pEditEngine->IsEffectivelyVertical(); +} + +bool Outliner::IsTopToBottom() const +{ + return pEditEngine->IsTopToBottom(); +} + +void Outliner::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing) +{ + pEditEngine->SetTextColumns(nColumns, nSpacing); +} + +void Outliner::SetFixedCellHeight( bool bUseFixedCellHeight ) +{ + pEditEngine->SetFixedCellHeight( bUseFixedCellHeight ); +} + +void Outliner::SetDefaultHorizontalTextDirection( EEHorizontalTextDirection eHTextDir ) +{ + pEditEngine->SetDefaultHorizontalTextDirection( eHTextDir ); +} + +EEHorizontalTextDirection Outliner::GetDefaultHorizontalTextDirection() const +{ + return pEditEngine->GetDefaultHorizontalTextDirection(); +} + +LanguageType Outliner::GetLanguage( sal_Int32 nPara, sal_Int32 nPos ) const +{ + return pEditEngine->GetLanguage( nPara, nPos ).nLang; +} + +void Outliner::RemoveAttribs( const ESelection& rSelection, bool bRemoveParaAttribs, sal_uInt16 nWhich ) +{ + pEditEngine->RemoveAttribs( rSelection, bRemoveParaAttribs, nWhich ); +} + +void Outliner::EnableAutoColor( bool b ) +{ + pEditEngine->EnableAutoColor( b ); +} + +void Outliner::ForceAutoColor( bool b ) +{ + pEditEngine->ForceAutoColor( b ); +} + +bool Outliner::IsForceAutoColor() const +{ + return pEditEngine->IsForceAutoColor(); +} + +bool Outliner::SpellSentence(EditView const & rEditView, svx::SpellPortions& rToFill ) +{ + return pEditEngine->SpellSentence(rEditView, rToFill ); +} + +void Outliner::PutSpellingToSentenceStart( EditView const & rEditView ) +{ + pEditEngine->PutSpellingToSentenceStart( rEditView ); +} + +void Outliner::ApplyChangedSentence(EditView const & rEditView, const svx::SpellPortions& rNewPortions, bool bRecheck ) +{ + pEditEngine->ApplyChangedSentence( rEditView, rNewPortions, bRecheck ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outliner.cxx b/editeng/source/outliner/outliner.cxx new file mode 100644 index 000000000..13e490f87 --- /dev/null +++ b/editeng/source/outliner/outliner.cxx @@ -0,0 +1,2170 @@ +/* -*- 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 <comphelper/string.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/editdata.hxx> +#include <editeng/lrspitem.hxx> + +#include <math.h> +#include <svl/style.hxx> +#include <editeng/outliner.hxx> +#include "paralist.hxx" +#include <editeng/outlobj.hxx> +#include "outleeng.hxx" +#include "outlundo.hxx" +#include <editeng/eeitem.hxx> +#include <editeng/editstat.hxx> +#include <editeng/overflowingtxt.hxx> +#include <editeng/editobj.hxx> +#include <svl/itemset.hxx> +#include <vcl/metric.hxx> +#include <editeng/numitem.hxx> +#include <editeng/adjustitem.hxx> +#include <vcl/GraphicObject.hxx> +#include <editeng/svxfont.hxx> +#include <editeng/brushitem.hxx> +#include <svl/itempool.hxx> +#include <libxml/xmlwriter.h> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> + +#include <memory> +using std::advance; + + +// Outliner + + +void Outliner::ImplCheckDepth( sal_Int16& rnDepth ) const +{ + if( rnDepth < gnMinDepth ) + rnDepth = gnMinDepth; + else if( rnDepth > nMaxDepth ) + rnDepth = nMaxDepth; +} + +Paragraph* Outliner::Insert(const OUString& rText, sal_Int32 nAbsPos, sal_Int16 nDepth) +{ + DBG_ASSERT(pParaList->GetParagraphCount(),"Insert:No Paras"); + + Paragraph* pPara; + + ImplCheckDepth( nDepth ); + + sal_Int32 nParagraphCount = pParaList->GetParagraphCount(); + if( nAbsPos > nParagraphCount ) + nAbsPos = nParagraphCount; + + if( bFirstParaIsEmpty ) + { + pPara = pParaList->GetParagraph( 0 ); + if( pPara->GetDepth() != nDepth ) + { + nDepthChangedHdlPrevDepth = pPara->GetDepth(); + ParaFlag nPrevFlags = pPara->nFlags; + pPara->SetDepth( nDepth ); + DepthChangedHdl(pPara, nPrevFlags); + } + pPara->nFlags |= ParaFlag::HOLDDEPTH; + SetText( rText, pPara ); + } + else + { + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + ImplBlockInsertionCallbacks( true ); + pPara = new Paragraph( nDepth ); + pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nAbsPos ); + pEditEngine->InsertParagraph( nAbsPos, OUString() ); + DBG_ASSERT(pPara==pParaList->GetParagraph(nAbsPos),"Insert:Failed"); + ImplInitDepth( nAbsPos, nDepth, false ); + ParagraphInsertedHdl(pPara); + pPara->nFlags |= ParaFlag::HOLDDEPTH; + SetText( rText, pPara ); + ImplBlockInsertionCallbacks( false ); + pEditEngine->SetUpdateLayout( bUpdate ); + } + bFirstParaIsEmpty = false; + DBG_ASSERT(pEditEngine->GetParagraphCount()==pParaList->GetParagraphCount(),"SetText failed"); + return pPara; +} + + +void Outliner::ParagraphInserted( sal_Int32 nPara ) +{ + + if ( nBlockInsCallback ) + return; + + if( bPasting || pEditEngine->IsInUndo() ) + { + Paragraph* pPara = new Paragraph( -1 ); + pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nPara ); + if( pEditEngine->IsInUndo() ) + { + pPara->nFlags = ParaFlag::SETBULLETTEXT; + pPara->bVisible = true; + const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); + pPara->SetDepth( rLevel.GetValue() ); + } + } + else + { + sal_Int16 nDepth = -1; + Paragraph* pParaBefore = pParaList->GetParagraph( nPara-1 ); + if ( pParaBefore ) + nDepth = pParaBefore->GetDepth(); + + Paragraph* pPara = new Paragraph( nDepth ); + pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nPara ); + + if( !pEditEngine->IsInUndo() ) + { + ImplCalcBulletText( nPara, true, false ); + ParagraphInsertedHdl(pPara); + } + } +} + +void Outliner::ParagraphDeleted( sal_Int32 nPara ) +{ + + if ( nBlockInsCallback || ( nPara == EE_PARA_ALL ) ) + return; + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (!pPara) + return; + + sal_Int16 nDepth = pPara->GetDepth(); + + if( !pEditEngine->IsInUndo() ) + { + aParaRemovingHdl.Call( { this, pPara } ); + } + + pParaList->Remove( nPara ); + + if( pEditEngine->IsInUndo() || bPasting ) + return; + + pPara = pParaList->GetParagraph( nPara ); + if ( pPara && ( pPara->GetDepth() > nDepth ) ) + { + ImplCalcBulletText( nPara, true, false ); + // Search for next on the this level ... + while ( pPara && pPara->GetDepth() > nDepth ) + pPara = pParaList->GetParagraph( ++nPara ); + } + + if ( pPara && ( pPara->GetDepth() == nDepth ) ) + ImplCalcBulletText( nPara, true, false ); +} + +void Outliner::Init( OutlinerMode nMode ) +{ + nOutlinerMode = nMode; + + Clear(); + + EEControlBits nCtrl = pEditEngine->GetControlWord(); + nCtrl &= ~EEControlBits(EEControlBits::OUTLINER|EEControlBits::OUTLINER2); + + SetMaxDepth( 9 ); + + switch ( GetOutlinerMode() ) + { + case OutlinerMode::TextObject: + case OutlinerMode::TitleObject: + break; + + case OutlinerMode::OutlineObject: + nCtrl |= EEControlBits::OUTLINER2; + break; + case OutlinerMode::OutlineView: + nCtrl |= EEControlBits::OUTLINER; + break; + + default: OSL_FAIL( "Outliner::Init - Invalid Mode!" ); + } + + pEditEngine->SetControlWord( nCtrl ); + + const bool bWasUndoEnabled(IsUndoEnabled()); + EnableUndo(false); + ImplInitDepth( 0, -1, false ); + GetUndoManager().Clear(); + EnableUndo(bWasUndoEnabled); +} + +void Outliner::SetMaxDepth( sal_Int16 nDepth ) +{ + if( nMaxDepth != nDepth ) + { + nMaxDepth = std::min( nDepth, sal_Int16(SVX_MAX_NUM-1) ); + } +} + +sal_Int16 Outliner::GetDepth( sal_Int32 nPara ) const +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + DBG_ASSERT( pPara, "Outliner::GetDepth - Paragraph not found!" ); + return pPara ? pPara->GetDepth() : -1; +} + +void Outliner::SetDepth( Paragraph* pPara, sal_Int16 nNewDepth ) +{ + + ImplCheckDepth( nNewDepth ); + + if ( nNewDepth == pPara->GetDepth() ) + return; + + nDepthChangedHdlPrevDepth = pPara->GetDepth(); + ParaFlag nPrevFlags = pPara->nFlags; + + sal_Int32 nPara = GetAbsPos( pPara ); + ImplInitDepth( nPara, nNewDepth, true ); + ImplCalcBulletText( nPara, false, false ); + + if ( GetOutlinerMode() == OutlinerMode::OutlineObject ) + ImplSetLevelDependentStyleSheet( nPara ); + + DepthChangedHdl(pPara, nPrevFlags); +} + +sal_Int16 Outliner::GetNumberingStartValue( sal_Int32 nPara ) const +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + DBG_ASSERT( pPara, "Outliner::GetNumberingStartValue - Paragraph not found!" ); + return pPara ? pPara->GetNumberingStartValue() : -1; +} + +void Outliner::SetNumberingStartValue( sal_Int32 nPara, sal_Int16 nNumberingStartValue ) +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + DBG_ASSERT( pPara, "Outliner::GetNumberingStartValue - Paragraph not found!" ); + if( pPara && pPara->GetNumberingStartValue() != nNumberingStartValue ) + { + if( IsUndoEnabled() && !IsInUndo() ) + InsertUndo( std::make_unique<OutlinerUndoChangeParaNumberingRestart>( this, nPara, + pPara->GetNumberingStartValue(), nNumberingStartValue, + pPara->IsParaIsNumberingRestart(), pPara->IsParaIsNumberingRestart() ) ); + + pPara->SetNumberingStartValue( nNumberingStartValue ); + ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() ); + pEditEngine->SetModified(); + } +} + +bool Outliner::IsParaIsNumberingRestart( sal_Int32 nPara ) const +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + DBG_ASSERT( pPara, "Outliner::IsParaIsNumberingRestart - Paragraph not found!" ); + return pPara && pPara->IsParaIsNumberingRestart(); +} + +void Outliner::SetParaIsNumberingRestart( sal_Int32 nPara, bool bParaIsNumberingRestart ) +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + DBG_ASSERT( pPara, "Outliner::SetParaIsNumberingRestart - Paragraph not found!" ); + if( pPara && (pPara->IsParaIsNumberingRestart() != bParaIsNumberingRestart) ) + { + if( IsUndoEnabled() && !IsInUndo() ) + InsertUndo( std::make_unique<OutlinerUndoChangeParaNumberingRestart>( this, nPara, + pPara->GetNumberingStartValue(), pPara->GetNumberingStartValue(), + pPara->IsParaIsNumberingRestart(), bParaIsNumberingRestart ) ); + + pPara->SetParaIsNumberingRestart( bParaIsNumberingRestart ); + ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() ); + pEditEngine->SetModified(); + } +} + +sal_Int32 Outliner::GetBulletsNumberingStatus( + const sal_Int32 nParaStart, + const sal_Int32 nParaEnd ) const +{ + if ( nParaStart > nParaEnd + || nParaEnd >= pParaList->GetParagraphCount() ) + { + SAL_WARN("editeng", "<Outliner::GetBulletsNumberingStatus> - unexpected parameter values" ); + return 2; + } + + sal_Int32 nBulletsCount = 0; + sal_Int32 nNumberingCount = 0; + for (sal_Int32 nPara = nParaStart; nPara <= nParaEnd; ++nPara) + { + if ( !pParaList->GetParagraph(nPara) ) + { + break; + } + const SvxNumberFormat* pFmt = GetNumberFormat(nPara); + if (!pFmt) + { + // At least, exists one paragraph that has no Bullets/Numbering. + break; + } + else if ((pFmt->GetNumberingType() == SVX_NUM_BITMAP) || (pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL)) + { + // Having Bullets in this paragraph. + nBulletsCount++; + } + else + { + // Having Numbering in this paragraph. + nNumberingCount++; + } + } + + const sal_Int32 nParaCount = nParaEnd - nParaStart + 1; + if ( nBulletsCount == nParaCount ) + { + return 0; + } + else if ( nNumberingCount == nParaCount ) + { + return 1; + } + return 2; +} + +sal_Int32 Outliner::GetBulletsNumberingStatus() const +{ + return pParaList->GetParagraphCount() > 0 + ? GetBulletsNumberingStatus( 0, pParaList->GetParagraphCount()-1 ) + : 2; +} + +std::optional<OutlinerParaObject> Outliner::CreateParaObject( sal_Int32 nStartPara, sal_Int32 nCount ) const +{ + if ( static_cast<sal_uLong>(nStartPara) + nCount > + o3tl::make_unsigned(pParaList->GetParagraphCount()) ) + nCount = pParaList->GetParagraphCount() - nStartPara; + + // When a new OutlinerParaObject is created because a paragraph is just being deleted, + // it can happen that the ParaList is not updated yet... + if ( ( nStartPara + nCount ) > pEditEngine->GetParagraphCount() ) + nCount = pEditEngine->GetParagraphCount() - nStartPara; + + if (nCount <= 0) + return std::nullopt; + + std::unique_ptr<EditTextObject> xText = pEditEngine->CreateTextObject( nStartPara, nCount ); + const bool bIsEditDoc(OutlinerMode::TextObject == GetOutlinerMode()); + ParagraphDataVector aParagraphDataVector(nCount); + const sal_Int32 nLastPara(nStartPara + nCount - 1); + + for(sal_Int32 nPara(nStartPara); nPara <= nLastPara; nPara++) + { + aParagraphDataVector[nPara-nStartPara] = *GetParagraph(nPara); + } + + xText->ClearPortionInfo(); // tdf#147166 the PortionInfo is unwanted here + OutlinerParaObject aPObj(std::move(xText), std::move(aParagraphDataVector), bIsEditDoc); + aPObj.SetOutlinerMode(GetOutlinerMode()); + + return aPObj; +} + +void Outliner::SetToEmptyText() +{ + SetText(GetEmptyParaObject()); +} + +void Outliner::SetText( const OUString& rText, Paragraph* pPara ) +{ + DBG_ASSERT(pPara,"SetText:No Para"); + + const sal_Int32 nPara = pParaList->GetAbsPos( pPara ); + + if (pEditEngine->GetText( nPara ) == rText) + { + // short-circuit logic to improve performance + bFirstParaIsEmpty = false; + return; + } + + const bool bUpdate = pEditEngine->SetUpdateLayout( false ); + ImplBlockInsertionCallbacks( true ); + + if (rText.isEmpty()) + { + pEditEngine->SetText( nPara, rText ); + ImplInitDepth( nPara, pPara->GetDepth(), false ); + } + else + { + const OUString aText(convertLineEnd(rText, LINEEND_LF)); + + sal_Int32 nPos = 0; + sal_Int32 nInsPos = nPara+1; + sal_Int32 nIdx {0}; + // Loop over all tokens, but ignore the last one if empty + // (i.e. if strings ends with the delimiter, detected by + // checking nIdx against string length). This check also + // handle empty strings. + while( nIdx>=0 && nIdx<aText.getLength() ) + { + std::u16string_view aStr = o3tl::getToken(aText, 0, '\x0A', nIdx ); + + sal_Int16 nCurDepth; + if( nPos ) + { + pPara = new Paragraph( -1 ); + nCurDepth = -1; + } + else + nCurDepth = pPara->GetDepth(); + + // In the outliner mode, filter the tabs and set the indentation + // about a LRSpaceItem. In EditEngine mode intend over old tabs + if( ( GetOutlinerMode() == OutlinerMode::OutlineObject ) || + ( GetOutlinerMode() == OutlinerMode::OutlineView ) ) + { + // Extract Tabs + size_t nTabs = 0; + while ( ( nTabs < aStr.size() ) && ( aStr[nTabs] == '\t' ) ) + nTabs++; + if ( nTabs ) + aStr = aStr.substr(nTabs); + + // Keep depth? (see Outliner::Insert) + if( !(pPara->nFlags & ParaFlag::HOLDDEPTH) ) + { + nCurDepth = nTabs-1; //TODO: sal_Int32 -> sal_Int16! + ImplCheckDepth( nCurDepth ); + pPara->SetDepth( nCurDepth ); + pPara->nFlags &= ~ParaFlag::HOLDDEPTH; + } + } + if( nPos ) // not with the first paragraph + { + pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nInsPos ); + pEditEngine->InsertParagraph( nInsPos, OUString(aStr) ); + ParagraphInsertedHdl(pPara); + } + else + { + nInsPos--; + pEditEngine->SetText( nInsPos, OUString(aStr) ); + } + ImplInitDepth( nInsPos, nCurDepth, false ); + nInsPos++; + nPos++; + } + } + + DBG_ASSERT(pParaList->GetParagraphCount()==pEditEngine->GetParagraphCount(),"SetText failed!"); + bFirstParaIsEmpty = false; + ImplBlockInsertionCallbacks( false ); + // Restore the update mode. + pEditEngine->SetUpdateLayout(bUpdate, /*bRestoring=*/true); +} + +// pView == 0 -> Ignore tabs + +bool Outliner::ImpConvertEdtToOut( sal_Int32 nPara ) +{ + + bool bConverted = false; + sal_Int32 nTabs = 0; + ESelection aDelSel; + + OUString aName; + + OUString aStr( pEditEngine->GetText( nPara ) ); + const sal_Unicode* pPtr = aStr.getStr(); + + sal_Int32 nHeadingNumberStart = 0; + sal_Int32 nNumberingNumberStart = 0; + SfxStyleSheet* pStyle= pEditEngine->GetStyleSheet( nPara ); + if( pStyle ) + { + OUString aHeading_US( "heading" ); + OUString aNumber_US( "Numbering" ); + aName = pStyle->GetName(); + sal_Int32 nSearch; + if ( ( nSearch = aName.indexOf( aHeading_US ) ) != -1 ) + nHeadingNumberStart = nSearch + aHeading_US.getLength(); + else if ( ( nSearch = aName.indexOf( aNumber_US ) ) != -1 ) + nNumberingNumberStart = nSearch + aNumber_US.getLength(); + } + + if ( nHeadingNumberStart || nNumberingNumberStart ) + { + // PowerPoint import ? + if( nHeadingNumberStart && ( aStr.getLength() >= 2 ) && + ( pPtr[0] != '\t' ) && ( pPtr[1] == '\t' ) ) + { + // Extract Bullet and Tab + aDelSel = ESelection( nPara, 0, nPara, 2 ); + } + + sal_Int32 nPos = nHeadingNumberStart ? nHeadingNumberStart : nNumberingNumberStart; + std::u16string_view aLevel = comphelper::string::stripStart(aName.subView(nPos), ' '); + nTabs = o3tl::toInt32(aLevel); + if( nTabs ) + nTabs--; // Level 0 = "heading 1" + bConverted = true; + } + else + { + // filter leading tabs + while( *pPtr == '\t' ) + { + pPtr++; + nTabs++; + } + // Remove tabs from the text + if( nTabs ) + aDelSel = ESelection( nPara, 0, nPara, nTabs ); + } + + if ( aDelSel.HasRange() ) + { + pEditEngine->QuickDelete( aDelSel ); + } + + const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); + sal_Int16 nOutlLevel = rLevel.GetValue(); + + ImplCheckDepth( nOutlLevel ); + ImplInitDepth( nPara, nOutlLevel, false ); + + return bConverted; +} + +void Outliner::SetText( const OutlinerParaObject& rPObj ) +{ + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + bool bUndo = pEditEngine->IsUndoEnabled(); + EnableUndo( false ); + + Init( rPObj.GetOutlinerMode() ); + + ImplBlockInsertionCallbacks( true ); + pEditEngine->SetText(rPObj.GetTextObject()); + + bFirstParaIsEmpty = false; + + pParaList->Clear(); + for( sal_Int32 nCurPara = 0; nCurPara < rPObj.Count(); nCurPara++ ) + { + std::unique_ptr<Paragraph> pPara(new Paragraph( rPObj.GetParagraphData(nCurPara))); + ImplCheckDepth( pPara->nDepth ); + + pParaList->Append(std::move(pPara)); + ImplCheckNumBulletItem( nCurPara ); + } + + ImplCheckParagraphs( 0, pParaList->GetParagraphCount() ); + + EnableUndo( bUndo ); + ImplBlockInsertionCallbacks( false ); + pEditEngine->SetUpdateLayout( bUpdate ); + + DBG_ASSERT( pParaList->GetParagraphCount()==rPObj.Count(),"SetText failed"); + DBG_ASSERT( pEditEngine->GetParagraphCount()==rPObj.Count(),"SetText failed"); +} + +void Outliner::AddText( const OutlinerParaObject& rPObj, bool bAppend ) +{ + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + ImplBlockInsertionCallbacks( true ); + sal_Int32 nPara; + if( bFirstParaIsEmpty ) + { + pParaList->Clear(); + pEditEngine->SetText(rPObj.GetTextObject()); + nPara = 0; + bAppend = false; + } + else + { + nPara = pParaList->GetParagraphCount(); + pEditEngine->InsertParagraph( EE_PARA_APPEND, rPObj.GetTextObject(), bAppend ); + } + bFirstParaIsEmpty = false; + + for( sal_Int32 n = 0; n < rPObj.Count(); n++ ) + { + if ( n == 0 && bAppend ) + { + // This first "paragraph" was just appended to an existing (incomplete) paragraph. + // Since no new paragraph will be added, the assumed increase-by-1 also won't happen. + --nPara; + continue; + } + + Paragraph* pPara = new Paragraph( rPObj.GetParagraphData(n) ); + pParaList->Append(std::unique_ptr<Paragraph>(pPara)); + sal_Int32 nP = nPara+n; + DBG_ASSERT(pParaList->GetAbsPos(pPara)==nP,"AddText:Out of sync"); + ImplInitDepth( nP, pPara->GetDepth(), false ); + } + DBG_ASSERT( pEditEngine->GetParagraphCount()==pParaList->GetParagraphCount(), "SetText: OutOfSync" ); + + ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() ); + + ImplBlockInsertionCallbacks( false ); + pEditEngine->SetUpdateLayout( bUpdate ); +} + +OUString Outliner::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor ) +{ + if ( !aCalcFieldValueHdl.IsSet() ) + return OUString( ' ' ); + + EditFieldInfo aFldInfo( this, rField, nPara, nPos ); + // The FldColor is preset with COL_LIGHTGRAY. + if ( rpFldColor ) + aFldInfo.SetFieldColor( *rpFldColor ); + + aCalcFieldValueHdl.Call( &aFldInfo ); + if ( aFldInfo.GetTextColor() ) + { + rpTxtColor = *aFldInfo.GetTextColor(); + } + + if (aFldInfo.GetFieldColor()) + rpFldColor = *aFldInfo.GetFieldColor(); + else + rpFldColor.reset(); + + return aFldInfo.GetRepresentation(); +} + +void Outliner::SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle ) +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (pPara) + { + pEditEngine->SetStyleSheet( nPara, pStyle ); + pPara->nFlags |= ParaFlag::SETBULLETTEXT; + ImplCheckNumBulletItem( nPara ); + } +} + +void Outliner::ImplCheckNumBulletItem( sal_Int32 nPara ) +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (pPara) + pPara->aBulSize.setWidth( -1 ); +} + +void Outliner::ImplSetLevelDependentStyleSheet( sal_Int32 nPara ) +{ + + DBG_ASSERT( ( GetOutlinerMode() == OutlinerMode::OutlineObject ) || ( GetOutlinerMode() == OutlinerMode::OutlineView ), "SetLevelDependentStyleSheet: Wrong Mode!" ); + + SfxStyleSheet* pStyle = GetStyleSheet( nPara ); + + if ( !pStyle ) + return; + + sal_Int16 nDepth = GetDepth( nPara ); + if( nDepth < 0 ) + nDepth = 0; + + OUString aNewStyleSheetName( pStyle->GetName() ); + aNewStyleSheetName = aNewStyleSheetName.subView( 0, aNewStyleSheetName.getLength()-1 ) + + OUString::number( nDepth+1 ); + SfxStyleSheet* pNewStyle = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( aNewStyleSheetName, pStyle->GetFamily() )); + DBG_ASSERT( pNewStyle, "AutoStyleSheetName - Style not found!" ); + if ( pNewStyle && ( pNewStyle != GetStyleSheet( nPara ) ) ) + { + SfxItemSet aOldAttrs( GetParaAttribs( nPara ) ); + SetStyleSheet( nPara, pNewStyle ); + if ( aOldAttrs.GetItemState( EE_PARA_NUMBULLET ) == SfxItemState::SET ) + { + SfxItemSet aAttrs( GetParaAttribs( nPara ) ); + aAttrs.Put( aOldAttrs.Get( EE_PARA_NUMBULLET ) ); + SetParaAttribs( nPara, aAttrs ); + } + } +} + +void Outliner::ImplInitDepth( sal_Int32 nPara, sal_Int16 nDepth, bool bCreateUndo ) +{ + + DBG_ASSERT( ( nDepth >= gnMinDepth ) && ( nDepth <= nMaxDepth ), "ImplInitDepth - Depth is invalid!" ); + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (!pPara) + return; + sal_Int16 nOldDepth = pPara->GetDepth(); + pPara->SetDepth( nDepth ); + + // For IsInUndo attributes and style do not have to be set, there + // the old values are restored by the EditEngine. + if( IsInUndo() ) + return; + + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + bool bUndo = bCreateUndo && IsUndoEnabled(); + + SfxItemSet aAttrs( pEditEngine->GetParaAttribs( nPara ) ); + aAttrs.Put( SfxInt16Item( EE_PARA_OUTLLEVEL, nDepth ) ); + pEditEngine->SetParaAttribs( nPara, aAttrs ); + ImplCheckNumBulletItem( nPara ); + ImplCalcBulletText( nPara, false, false ); + + if ( bUndo ) + { + InsertUndo( std::make_unique<OutlinerUndoChangeDepth>( this, nPara, nOldDepth, nDepth ) ); + } + + pEditEngine->SetUpdateLayout( bUpdate ); +} + +void Outliner::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + + pEditEngine->SetParaAttribs( nPara, rSet ); +} + +void Outliner::SetCharAttribs(sal_Int32 nPara, const SfxItemSet& rSet) +{ + pEditEngine->SetCharAttribs(nPara, rSet); +} + +bool Outliner::Expand( Paragraph const * pPara ) +{ + if ( !pParaList->HasHiddenChildren( pPara ) ) + return false; + + std::unique_ptr<OLUndoExpand> pUndo; + bool bUndo = IsUndoEnabled() && !IsInUndo(); + if( bUndo ) + { + UndoActionStart( OLUNDO_EXPAND ); + pUndo.reset( new OLUndoExpand( this, OLUNDO_EXPAND ) ); + pUndo->nCount = pParaList->GetAbsPos( pPara ); + } + pParaList->Expand( pPara ); + InvalidateBullet(pParaList->GetAbsPos(pPara)); + if( bUndo ) + { + InsertUndo( std::move(pUndo) ); + UndoActionEnd(); + } + return true; +} + +bool Outliner::Collapse( Paragraph const * pPara ) +{ + if ( !pParaList->HasVisibleChildren( pPara ) ) // collapsed + return false; + + std::unique_ptr<OLUndoExpand> pUndo; + bool bUndo = false; + + if( !IsInUndo() && IsUndoEnabled() ) + bUndo = true; + if( bUndo ) + { + UndoActionStart( OLUNDO_COLLAPSE ); + pUndo.reset( new OLUndoExpand( this, OLUNDO_COLLAPSE ) ); + pUndo->nCount = pParaList->GetAbsPos( pPara ); + } + + pParaList->Collapse( pPara ); + InvalidateBullet(pParaList->GetAbsPos(pPara)); + if( bUndo ) + { + InsertUndo( std::move(pUndo) ); + UndoActionEnd(); + } + return true; +} + + +vcl::Font Outliner::ImpCalcBulletFont( sal_Int32 nPara ) const +{ + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + DBG_ASSERT( pFmt && ( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) && ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ), "ImpCalcBulletFont: Missing or BitmapBullet!" ); + + vcl::Font aStdFont; + if ( !pEditEngine->IsFlatMode() ) + { + ESelection aSel( nPara, 0, nPara, 0 ); + aStdFont = EditEngine::CreateFontFromItemSet( pEditEngine->GetAttribs( aSel ), pEditEngine->GetScriptType( aSel ) ); + } + else + { + aStdFont = pEditEngine->GetStandardFont( nPara ); + } + + vcl::Font aBulletFont; + std::optional<vcl::Font> pSourceFont; + if ( pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL ) + { + pSourceFont = pFmt->GetBulletFont(); + } + + if (pSourceFont) + { + aBulletFont = *pSourceFont; + } + else + { + aBulletFont = aStdFont; + aBulletFont.SetUnderline( LINESTYLE_NONE ); + aBulletFont.SetOverline( LINESTYLE_NONE ); + aBulletFont.SetStrikeout( STRIKEOUT_NONE ); + aBulletFont.SetEmphasisMark( FontEmphasisMark::NONE ); + aBulletFont.SetRelief( FontRelief::NONE ); + } + + // Use original scale... + sal_uInt16 nStretchX, nStretchY; + GetGlobalCharStretching(nStretchX, nStretchY); + + sal_uInt16 nScale = pFmt->GetBulletRelSize() * nStretchY / 100; + tools::Long nScaledLineHeight = aStdFont.GetFontSize().Height(); + nScaledLineHeight *= nScale*10; + nScaledLineHeight /= 1000; + + aBulletFont.SetAlignment( ALIGN_BOTTOM ); + aBulletFont.SetFontSize( Size( 0, nScaledLineHeight ) ); + bool bVertical = IsVertical(); + aBulletFont.SetVertical( bVertical ); + aBulletFont.SetOrientation( Degree10(bVertical ? (IsTopToBottom() ? 2700 : 900) : 0) ); + + Color aColor( COL_AUTO ); + if( !pEditEngine->IsFlatMode() && !( pEditEngine->GetControlWord() & EEControlBits::NOCOLORS ) ) + { + aColor = pFmt->GetBulletColor(); + } + + if ( ( aColor == COL_AUTO ) || ( IsForceAutoColor() ) ) + aColor = pEditEngine->GetAutoColor(); + + aBulletFont.SetColor( aColor ); + return aBulletFont; +} + +void Outliner::PaintBullet(sal_Int32 nPara, const Point& rStartPos, const Point& rOrigin, + Degree10 nOrientation, OutputDevice& rOutDev) +{ + + bool bDrawBullet = false; + if (pEditEngine) + { + const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib( nPara, EE_PARA_BULLETSTATE ); + bDrawBullet = rBulletState.GetValue(); + } + + if (!(bDrawBullet && ImplHasNumberFormat(nPara))) + return; + + bool bVertical = IsVertical(); + bool bTopToBottom = IsTopToBottom(); + + bool bRightToLeftPara = pEditEngine->IsRightToLeft( nPara ); + + tools::Rectangle aBulletArea( ImpCalcBulletArea( nPara, true, false ) ); + sal_uInt16 nStretchX, nStretchY; + GetGlobalCharStretching(nStretchX, nStretchY); + aBulletArea = tools::Rectangle( Point(aBulletArea.Left()*nStretchX/100, + aBulletArea.Top()), + Size(aBulletArea.GetWidth()*nStretchX/100, + aBulletArea.GetHeight()) ); + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + if ( pFmt && ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ) ) + { + if( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) + { + vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) ); + // Use baseline + bool bSymbol = pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL; + aBulletFont.SetAlignment( bSymbol ? ALIGN_BOTTOM : ALIGN_BASELINE ); + vcl::Font aOldFont = rOutDev.GetFont(); + rOutDev.SetFont( aBulletFont ); + + ParagraphInfos aParaInfos = pEditEngine->GetParagraphInfos( nPara ); + Point aTextPos; + if ( !bVertical ) + { +// aTextPos.Y() = rStartPos.Y() + aBulletArea.Bottom(); + aTextPos.setY( rStartPos.Y() + ( bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent ) ); + if ( !bRightToLeftPara ) + aTextPos.setX( rStartPos.X() + aBulletArea.Left() ); + else + aTextPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Right() ); + } + else + { + if (bTopToBottom) + { +// aTextPos.X() = rStartPos.X() - aBulletArea.Bottom(); + aTextPos.setX( rStartPos.X() - (bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent) ); + aTextPos.setY( rStartPos.Y() + aBulletArea.Left() ); + } + else + { + aTextPos.setX( rStartPos.X() + (bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent) ); + aTextPos.setY( rStartPos.Y() + aBulletArea.Left() ); + } + } + + if ( nOrientation ) + { + // Both TopLeft and bottom left is not quite correct, + // since in EditEngine baseline ... + double nRealOrientation = toRadians(nOrientation); + double nCos = cos( nRealOrientation ); + double nSin = sin( nRealOrientation ); + Point aRotatedPos; + // Translation... + aTextPos -= rOrigin; + // Rotation... + aRotatedPos.setX(static_cast<tools::Long>(nCos*aTextPos.X() + nSin*aTextPos.Y()) ); + aRotatedPos.setY(static_cast<tools::Long>(- (nSin*aTextPos.X() - nCos*aTextPos.Y())) ); + aTextPos = aRotatedPos; + // Translation... + aTextPos += rOrigin; + vcl::Font aRotatedFont( aBulletFont ); + aRotatedFont.SetOrientation( nOrientation ); + rOutDev.SetFont( aRotatedFont ); + } + + // VCL will take care of brackets and so on... + vcl::text::ComplexTextLayoutFlags nLayoutMode = rOutDev.GetLayoutMode(); + nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags(vcl::text::ComplexTextLayoutFlags::BiDiRtl|vcl::text::ComplexTextLayoutFlags::BiDiStrong); + if ( bRightToLeftPara ) + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft | vcl::text::ComplexTextLayoutFlags::BiDiStrong; + rOutDev.SetLayoutMode( nLayoutMode ); + + if(bStrippingPortions) + { + const vcl::Font& aSvxFont(rOutDev.GetFont()); + std::vector<sal_Int32> aBuf; + rOutDev.GetTextArray( pPara->GetText(), &aBuf ); + + if(bSymbol) + { + // aTextPos is Bottom, go to Baseline + FontMetric aMetric(rOutDev.GetFontMetric()); + aTextPos.AdjustY( -(aMetric.GetDescent()) ); + } + + DrawingText(aTextPos, pPara->GetText(), 0, pPara->GetText().getLength(), aBuf, + aSvxFont, nPara, bRightToLeftPara ? 1 : 0, nullptr, nullptr, false, false, true, nullptr, Color(), Color()); + } + else + { + rOutDev.DrawText( aTextPos, pPara->GetText() ); + } + + rOutDev.SetFont( aOldFont ); + } + else + { + if ( pFmt->GetBrush()->GetGraphicObject() ) + { + Point aBulletPos; + if ( !bVertical ) + { + aBulletPos.setY( rStartPos.Y() + aBulletArea.Top() ); + if ( !bRightToLeftPara ) + aBulletPos.setX( rStartPos.X() + aBulletArea.Left() ); + else + aBulletPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Right() ); + } + else + { + if (bTopToBottom) + { + aBulletPos.setX( rStartPos.X() - aBulletArea.Bottom() ); + aBulletPos.setY( rStartPos.Y() + aBulletArea.Left() ); + } + else + { + aBulletPos.setX( rStartPos.X() + aBulletArea.Top() ); + aBulletPos.setY( rStartPos.Y() - aBulletArea.Right() ); + } + } + + if(bStrippingPortions) + { + if(aDrawBulletHdl.IsSet()) + { + // call something analog to aDrawPortionHdl (if set) and feed it something + // analog to DrawPortionInfo... + // created aDrawBulletHdl, Set/GetDrawBulletHdl. + // created DrawBulletInfo and added handling to sdrtextdecomposition.cxx + DrawBulletInfo aDrawBulletInfo( + *pFmt->GetBrush()->GetGraphicObject(), + aBulletPos, + pPara->aBulSize); + + aDrawBulletHdl.Call(&aDrawBulletInfo); + } + } + else + { + pFmt->GetBrush()->GetGraphicObject()->Draw(rOutDev, aBulletPos, pPara->aBulSize); + } + } + } + } + + // In case of collapsed subparagraphs paint a line before the text. + if( !pParaList->HasChildren(pPara) || pParaList->HasVisibleChildren(pPara) || + bStrippingPortions || nOrientation ) + return; + + tools::Long nWidth = rOutDev.PixelToLogic( Size( 10, 0 ) ).Width(); + + Point aStartPos, aEndPos; + if ( !bVertical ) + { + aStartPos.setY( rStartPos.Y() + aBulletArea.Bottom() ); + if ( !bRightToLeftPara ) + aStartPos.setX( rStartPos.X() + aBulletArea.Right() ); + else + aStartPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Left() ); + aEndPos = aStartPos; + aEndPos.AdjustX(nWidth ); + } + else + { + aStartPos.setX( rStartPos.X() - aBulletArea.Bottom() ); + aStartPos.setY( rStartPos.Y() + aBulletArea.Right() ); + aEndPos = aStartPos; + aEndPos.AdjustY(nWidth ); + } + + const Color& rOldLineColor = rOutDev.GetLineColor(); + rOutDev.SetLineColor( COL_BLACK ); + rOutDev.DrawLine( aStartPos, aEndPos ); + rOutDev.SetLineColor( rOldLineColor ); +} + +void Outliner::InvalidateBullet(sal_Int32 nPara) +{ + tools::Long nLineHeight = static_cast<tools::Long>(pEditEngine->GetLineHeight(nPara )); + for (OutlinerView* pView : aViewList) + { + Point aPos( pView->pEditView->GetWindowPosTopLeft(nPara ) ); + tools::Rectangle aRect( pView->GetOutputArea() ); + aRect.SetRight( aPos.X() ); + aRect.SetTop( aPos.Y() ); + aRect.SetBottom( aPos.Y() ); + aRect.AdjustBottom(nLineHeight ); + + pView->pEditView->InvalidateWindow(aRect); + } +} + +ErrCode Outliner::Read( SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs ) +{ + + bool bOldUndo = pEditEngine->IsUndoEnabled(); + EnableUndo( false ); + + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + Clear(); + + ImplBlockInsertionCallbacks( true ); + ErrCode nRet = pEditEngine->Read( rInput, rBaseURL, eFormat, pHTTPHeaderAttrs ); + + bFirstParaIsEmpty = false; + + sal_Int32 nParas = pEditEngine->GetParagraphCount(); + pParaList->Clear(); + for ( sal_Int32 n = 0; n < nParas; n++ ) + { + std::unique_ptr<Paragraph> pPara(new Paragraph( 0 )); + pParaList->Append(std::move(pPara)); + } + + ImpFilterIndents( 0, nParas-1 ); + + ImplBlockInsertionCallbacks( false ); + pEditEngine->SetUpdateLayout( bUpdate ); + EnableUndo( bOldUndo ); + + return nRet; +} + + +void Outliner::ImpFilterIndents( sal_Int32 nFirstPara, sal_Int32 nLastPara ) +{ + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + Paragraph* pLastConverted = nullptr; + for( sal_Int32 nPara = nFirstPara; nPara <= nLastPara; nPara++ ) + { + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (pPara) + { + if( ImpConvertEdtToOut( nPara ) ) + { + pLastConverted = pPara; + } + else if ( pLastConverted ) + { + // Arrange normal paragraphs below the heading ... + pPara->SetDepth( pLastConverted->GetDepth() ); + } + + ImplInitDepth( nPara, pPara->GetDepth(), false ); + } + } + + pEditEngine->SetUpdateLayout( bUpdate ); +} + +SfxUndoManager& Outliner::GetUndoManager() +{ + return pEditEngine->GetUndoManager(); +} + +SfxUndoManager* Outliner::SetUndoManager(SfxUndoManager* pNew) +{ + return pEditEngine->SetUndoManager(pNew); +} + +void Outliner::ImpTextPasted( sal_Int32 nStartPara, sal_Int32 nCount ) +{ + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + const sal_Int32 nStart = nStartPara; + + Paragraph* pPara = pParaList->GetParagraph( nStartPara ); + + while( nCount && pPara ) + { + if( GetOutlinerMode() != OutlinerMode::TextObject ) + { + nDepthChangedHdlPrevDepth = pPara->GetDepth(); + ParaFlag nPrevFlags = pPara->nFlags; + + ImpConvertEdtToOut( nStartPara ); + + if( nStartPara == nStart ) + { + // the existing paragraph has changed depth or flags + if( (pPara->GetDepth() != nDepthChangedHdlPrevDepth) || (pPara->nFlags != nPrevFlags) ) + DepthChangedHdl(pPara, nPrevFlags); + } + } + else // EditEngine mode + { + sal_Int16 nDepth = -1; + const SfxItemSet& rAttrs = pEditEngine->GetParaAttribs( nStartPara ); + if ( rAttrs.GetItemState( EE_PARA_OUTLLEVEL ) == SfxItemState::SET ) + { + const SfxInt16Item& rLevel = rAttrs.Get( EE_PARA_OUTLLEVEL ); + nDepth = rLevel.GetValue(); + } + if ( nDepth != GetDepth( nStartPara ) ) + ImplInitDepth( nStartPara, nDepth, false ); + } + + nCount--; + nStartPara++; + pPara = pParaList->GetParagraph( nStartPara ); + } + + pEditEngine->SetUpdateLayout( bUpdate ); + + DBG_ASSERT(pParaList->GetParagraphCount()==pEditEngine->GetParagraphCount(),"ImpTextPasted failed"); +} + +bool Outliner::IndentingPagesHdl( OutlinerView* pView ) +{ + if( !aIndentingPagesHdl.IsSet() ) + return true; + return aIndentingPagesHdl.Call( pView ); +} + +bool Outliner::ImpCanIndentSelectedPages( OutlinerView* pCurView ) +{ + // The selected pages must already be set in advance through + // ImpCalcSelectedPages + + // If the first paragraph is on level 0 it can not indented in any case, + // possible there might be indentations in the following on the 0 level. + if ( ( mnFirstSelPage == 0 ) && ( GetOutlinerMode() != OutlinerMode::TextObject ) ) + { + if ( nDepthChangedHdlPrevDepth == 1 ) // is the only page + return false; + else + (void)pCurView->ImpCalcSelectedPages( false ); // without the first + } + return IndentingPagesHdl( pCurView ); +} + + +bool Outliner::ImpCanDeleteSelectedPages( OutlinerView* pCurView ) +{ + // The selected pages must already be set in advance through + // ImpCalcSelectedPages + return RemovingPagesHdl( pCurView ); +} + +Outliner::Outliner(SfxItemPool* pPool, OutlinerMode nMode) + : mnFirstSelPage(0) + , nDepthChangedHdlPrevDepth(0) + , nMaxDepth(9) + , bFirstParaIsEmpty(true) + , nBlockInsCallback(0) + , bStrippingPortions(false) + , bPasting(false) +{ + + pParaList.reset( new ParagraphList ); + pParaList->SetVisibleStateChangedHdl( LINK( this, Outliner, ParaVisibleStateChangedHdl ) ); + std::unique_ptr<Paragraph> pPara(new Paragraph( 0 )); + pParaList->Append(std::move(pPara)); + + pEditEngine.reset( new OutlinerEditEng( this, pPool ) ); + pEditEngine->SetBeginMovingParagraphsHdl( LINK( this, Outliner, BeginMovingParagraphsHdl ) ); + pEditEngine->SetEndMovingParagraphsHdl( LINK( this, Outliner, EndMovingParagraphsHdl ) ); + pEditEngine->SetBeginPasteOrDropHdl( LINK( this, Outliner, BeginPasteOrDropHdl ) ); + pEditEngine->SetEndPasteOrDropHdl( LINK( this, Outliner, EndPasteOrDropHdl ) ); + + Init( nMode ); +} + +Outliner::~Outliner() +{ + pParaList->Clear(); + pParaList.reset(); + pEditEngine.reset(); +} + +size_t Outliner::InsertView( OutlinerView* pView, size_t nIndex ) +{ + size_t ActualIndex; + + if ( nIndex >= aViewList.size() ) + { + aViewList.push_back( pView ); + ActualIndex = aViewList.size() - 1; + } + else + { + ViewList::iterator it = aViewList.begin(); + advance( it, nIndex ); + ActualIndex = nIndex; + } + pEditEngine->InsertView( pView->pEditView.get(), nIndex ); + return ActualIndex; +} + +void Outliner::RemoveView( OutlinerView const * pView ) +{ + ViewList::iterator it = std::find(aViewList.begin(), aViewList.end(), pView); + if (it != aViewList.end()) + { + pView->pEditView->HideCursor(); // HACK + pEditEngine->RemoveView( pView->pEditView.get() ); + aViewList.erase( it ); + } +} + +void Outliner::RemoveView( size_t nIndex ) +{ + EditView* pEditView = pEditEngine->GetView( nIndex ); + pEditView->HideCursor(); // HACK + + pEditEngine->RemoveView( nIndex ); + + { + ViewList::iterator it = aViewList.begin(); + advance( it, nIndex ); + aViewList.erase( it ); + } +} + + +OutlinerView* Outliner::GetView( size_t nIndex ) const +{ + return ( nIndex >= aViewList.size() ) ? nullptr : aViewList[ nIndex ]; +} + +size_t Outliner::GetViewCount() const +{ + return aViewList.size(); +} + +void Outliner::ParagraphInsertedHdl(Paragraph* pPara) +{ + if( !IsInUndo() ) + aParaInsertedHdl.Call( { this, pPara } ); +} + + +void Outliner::DepthChangedHdl(Paragraph* pPara, ParaFlag nPrevFlags) +{ + if( !IsInUndo() ) + aDepthChangedHdl.Call( { this, pPara, nPrevFlags } ); +} + + +sal_Int32 Outliner::GetAbsPos( Paragraph const * pPara ) const +{ + DBG_ASSERT(pPara,"GetAbsPos:No Para"); + return pParaList->GetAbsPos( pPara ); +} + +sal_Int32 Outliner::GetParagraphCount() const +{ + return pParaList->GetParagraphCount(); +} + +Paragraph* Outliner::GetParagraph( sal_Int32 nAbsPos ) const +{ + return pParaList->GetParagraph( nAbsPos ); +} + +bool Outliner::HasChildren( Paragraph const * pParagraph ) const +{ + return pParaList->HasChildren( pParagraph ); +} + +bool Outliner::ImplHasNumberFormat( sal_Int32 nPara ) const +{ + return GetNumberFormat(nPara) != nullptr; +} + +const SvxNumberFormat* Outliner::GetNumberFormat( sal_Int32 nPara ) const +{ + const SvxNumberFormat* pFmt = nullptr; + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (!pPara) + return nullptr; + + sal_Int16 nDepth = pPara->GetDepth(); + + if( nDepth >= 0 ) + { + const SvxNumBulletItem& rNumBullet = pEditEngine->GetParaAttrib( nPara, EE_PARA_NUMBULLET ); + if ( rNumBullet.GetNumRule().GetLevelCount() > nDepth ) + pFmt = rNumBullet.GetNumRule().Get( nDepth ); + } + + return pFmt; +} + +Size Outliner::ImplGetBulletSize( sal_Int32 nPara ) +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (!pPara) + return Size(); + + if( pPara->aBulSize.Width() == -1 ) + { + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + DBG_ASSERT( pFmt, "ImplGetBulletSize - no Bullet!" ); + + if ( pFmt->GetNumberingType() == SVX_NUM_NUMBER_NONE ) + { + pPara->aBulSize = Size( 0, 0 ); + } + else if( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) + { + OUString aBulletText = ImplGetBulletText( nPara ); + OutputDevice* pRefDev = pEditEngine->GetRefDevice(); + vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) ); + vcl::Font aRefFont( pRefDev->GetFont()); + pRefDev->SetFont( aBulletFont ); + pPara->aBulSize.setWidth( pRefDev->GetTextWidth( aBulletText ) ); + pPara->aBulSize.setHeight( pRefDev->GetTextHeight() ); + pRefDev->SetFont( aRefFont ); + } + else + { + pPara->aBulSize = OutputDevice::LogicToLogic(pFmt->GetGraphicSize(), + MapMode(MapUnit::Map100thMM), + pEditEngine->GetRefDevice()->GetMapMode()); + } + } + + return pPara->aBulSize; +} + +void Outliner::ImplCheckParagraphs( sal_Int32 nStart, sal_Int32 nEnd ) +{ + + for ( sal_Int32 n = nStart; n < nEnd; n++ ) + { + Paragraph* pPara = pParaList->GetParagraph( n ); + if (pPara) + { + pPara->Invalidate(); + ImplCalcBulletText( n, false, false ); + } + } +} + +void Outliner::SetRefDevice( OutputDevice* pRefDev ) +{ + pEditEngine->SetRefDevice( pRefDev ); + for ( sal_Int32 n = pParaList->GetParagraphCount(); n; ) + { + Paragraph* pPara = pParaList->GetParagraph( --n ); + pPara->Invalidate(); + } +} + +void Outliner::ParaAttribsChanged( sal_Int32 nPara ) +{ + // The Outliner does not have an undo of its own, when paragraphs are + // separated/merged. When ParagraphInserted the attribute EE_PARA_OUTLLEVEL + // may not be set, this is however needed when the depth of the paragraph + // is to be determined. + if (!pEditEngine->IsInUndo()) + return; + if (pParaList->GetParagraphCount() != pEditEngine->GetParagraphCount()) + return; + Paragraph* pPara = pParaList->GetParagraph(nPara); + if (!pPara) + return; + // tdf#100734: force update of bullet + pPara->Invalidate(); + const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); + if (pPara->GetDepth() == rLevel.GetValue()) + return; + pPara->SetDepth(rLevel.GetValue()); + ImplCalcBulletText(nPara, true, true); +} + +void Outliner::StyleSheetChanged( SfxStyleSheet const * pStyle ) +{ + + // The EditEngine calls StyleSheetChanged also for derived styles. + // Here all the paragraphs, which had the said template, used to be + // hunted by an ImpRecalcParaAttribs, why? + // => only the Bullet-representation can really change... + sal_Int32 nParas = pParaList->GetParagraphCount(); + for( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) + { + if ( pEditEngine->GetStyleSheet( nPara ) == pStyle ) + { + ImplCheckNumBulletItem( nPara ); + ImplCalcBulletText( nPara, false, false ); + // EditEngine formats changed paragraphs before calling this method, + // so they are not reformatted now and use wrong bullet indent + pEditEngine->QuickMarkInvalid( ESelection( nPara, 0, nPara, 0 ) ); + } + } +} + +tools::Rectangle Outliner::ImpCalcBulletArea( sal_Int32 nPara, bool bAdjust, bool bReturnPaperPos ) +{ + // Bullet area within the paragraph ... + tools::Rectangle aBulletArea; + + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + if ( pFmt ) + { + Point aTopLeft; + Size aBulletSize( ImplGetBulletSize( nPara ) ); + + bool bOutlineMode = bool( pEditEngine->GetControlWord() & EEControlBits::OUTLINER ); + + // the ODF attribute text:space-before which holds the spacing to add to the left of the label + const auto nSpaceBefore = pFmt->GetAbsLSpace() + pFmt->GetFirstLineOffset(); + + const SvxLRSpaceItem& rLR = pEditEngine->GetParaAttrib( nPara, bOutlineMode ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE ); + aTopLeft.setX( rLR.GetTextLeft() + rLR.GetTextFirstLineOffset() + nSpaceBefore ); + + tools::Long nBulletWidth = std::max( static_cast<tools::Long>(-rLR.GetTextFirstLineOffset()), static_cast<tools::Long>((-pFmt->GetFirstLineOffset()) + pFmt->GetCharTextDistance()) ); + if ( nBulletWidth < aBulletSize.Width() ) // The Bullet creates its space + nBulletWidth = aBulletSize.Width(); + + if ( bAdjust && !bOutlineMode ) + { + // Adjust when centered or align right + const SvxAdjustItem& rItem = pEditEngine->GetParaAttrib( nPara, EE_PARA_JUST ); + if ( ( !pEditEngine->IsRightToLeft( nPara ) && ( rItem.GetAdjust() != SvxAdjust::Left ) ) || + ( pEditEngine->IsRightToLeft( nPara ) && ( rItem.GetAdjust() != SvxAdjust::Right ) ) ) + { + aTopLeft.setX( pEditEngine->GetFirstLineStartX( nPara ) - nBulletWidth ); + } + } + + // Vertical: + ParagraphInfos aInfos = pEditEngine->GetParagraphInfos( nPara ); + if ( aInfos.bValid ) + { + aTopLeft.setY( /* aInfos.nFirstLineOffset + */ // nFirstLineOffset is already added to the StartPos (PaintBullet) from the EditEngine + aInfos.nFirstLineHeight - aInfos.nFirstLineTextHeight + + aInfos.nFirstLineTextHeight / 2 + - aBulletSize.Height() / 2 ); + // may prefer to print out on the baseline ... + if( ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ) && ( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) && ( pFmt->GetNumberingType() != SVX_NUM_CHAR_SPECIAL ) ) + { + vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) ); + if ( aBulletFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL ) + { + OutputDevice* pRefDev = pEditEngine->GetRefDevice(); + vcl::Font aOldFont = pRefDev->GetFont(); + pRefDev->SetFont( aBulletFont ); + FontMetric aMetric( pRefDev->GetFontMetric() ); + // Leading on the first line ... + aTopLeft.setY( /* aInfos.nFirstLineOffset + */ aInfos.nFirstLineMaxAscent ); + aTopLeft.AdjustY( -(aMetric.GetAscent()) ); + pRefDev->SetFont( aOldFont ); + } + } + } + + // Horizontal: + if( pFmt->GetNumAdjust() == SvxAdjust::Right ) + { + aTopLeft.AdjustX(nBulletWidth - aBulletSize.Width() ); + } + else if( pFmt->GetNumAdjust() == SvxAdjust::Center ) + { + aTopLeft.AdjustX(( nBulletWidth - aBulletSize.Width() ) / 2 ); + } + + if ( aTopLeft.X() < 0 ) // then push + aTopLeft.setX( 0 ); + + aBulletArea = tools::Rectangle( aTopLeft, aBulletSize ); + } + if ( bReturnPaperPos ) + { + Size aBulletSize( aBulletArea.GetSize() ); + Point aBulletDocPos( aBulletArea.TopLeft() ); + aBulletDocPos.AdjustY(pEditEngine->GetDocPosTopLeft( nPara ).Y() ); + Point aBulletPos( aBulletDocPos ); + + if ( IsVertical() ) + { + aBulletPos.setY( aBulletDocPos.X() ); + aBulletPos.setX( GetPaperSize().Width() - aBulletDocPos.Y() ); + // Rotate: + aBulletPos.AdjustX( -(aBulletSize.Height()) ); + Size aSz( aBulletSize ); + aBulletSize.setWidth( aSz.Height() ); + aBulletSize.setHeight( aSz.Width() ); + } + else if ( pEditEngine->IsRightToLeft( nPara ) ) + { + aBulletPos.setX( GetPaperSize().Width() - aBulletDocPos.X() - aBulletSize.Width() ); + } + + aBulletArea = tools::Rectangle( aBulletPos, aBulletSize ); + } + return aBulletArea; +} + +EBulletInfo Outliner::GetBulletInfo( sal_Int32 nPara ) +{ + EBulletInfo aInfo; + + aInfo.nParagraph = nPara; + aInfo.bVisible = ImplHasNumberFormat( nPara ); + + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + aInfo.nType = pFmt ? pFmt->GetNumberingType() : 0; + + if( pFmt ) + { + if( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) + { + aInfo.aText = ImplGetBulletText( nPara ); + + if( pFmt->GetBulletFont() ) + aInfo.aFont = *pFmt->GetBulletFont(); + } + } + + if ( aInfo.bVisible ) + { + aInfo.aBounds = ImpCalcBulletArea( nPara, true, true ); + } + + return aInfo; +} + +OUString Outliner::GetText( Paragraph const * pParagraph, sal_Int32 nCount ) const +{ + + OUStringBuffer aText(128); + sal_Int32 nStartPara = pParaList->GetAbsPos( pParagraph ); + for ( sal_Int32 n = 0; n < nCount; n++ ) + { + aText.append(pEditEngine->GetText( nStartPara + n )); + if ( (n+1) < nCount ) + aText.append("\n"); + } + return aText.makeStringAndClear(); +} + +void Outliner::Remove( Paragraph const * pPara, sal_Int32 nParaCount ) +{ + + sal_Int32 nPos = pParaList->GetAbsPos( pPara ); + if( !nPos && ( nParaCount >= pParaList->GetParagraphCount() ) ) + { + Clear(); + } + else + { + for( sal_Int32 n = 0; n < nParaCount; n++ ) + pEditEngine->RemoveParagraph( nPos ); + } +} + +void Outliner::StripPortions() +{ + bStrippingPortions = true; + pEditEngine->StripPortions(); + bStrippingPortions = false; +} + +void Outliner::DrawingText( const Point& rStartPos, const OUString& rText, sal_Int32 nTextStart, + sal_Int32 nTextLen, o3tl::span<const sal_Int32> pDXArray,const SvxFont& rFont, + sal_Int32 nPara, sal_uInt8 nRightToLeft, + const EEngineData::WrongSpellVector* pWrongSpellVector, + const SvxFieldData* pFieldData, + bool bEndOfLine, + bool bEndOfParagraph, + bool bEndOfBullet, + const css::lang::Locale* pLocale, + const Color& rOverlineColor, + const Color& rTextLineColor) +{ + if(aDrawPortionHdl.IsSet()) + { + DrawPortionInfo aInfo( rStartPos, rText, nTextStart, nTextLen, rFont, nPara, pDXArray, pWrongSpellVector, + pFieldData, pLocale, rOverlineColor, rTextLineColor, nRightToLeft, false, 0, bEndOfLine, bEndOfParagraph, bEndOfBullet); + + aDrawPortionHdl.Call( &aInfo ); + } +} + +void Outliner::DrawingTab( const Point& rStartPos, tools::Long nWidth, const OUString& rChar, const SvxFont& rFont, + sal_Int32 nPara, sal_uInt8 nRightToLeft, bool bEndOfLine, bool bEndOfParagraph, + const Color& rOverlineColor, const Color& rTextLineColor) +{ + if(aDrawPortionHdl.IsSet()) + { + DrawPortionInfo aInfo( rStartPos, rChar, 0, rChar.getLength(), rFont, nPara, {}, nullptr, + nullptr, nullptr, rOverlineColor, rTextLineColor, nRightToLeft, true, nWidth, bEndOfLine, bEndOfParagraph, false); + + aDrawPortionHdl.Call( &aInfo ); + } +} + +bool Outliner::RemovingPagesHdl( OutlinerView* pView ) +{ + return !aRemovingPagesHdl.IsSet() || aRemovingPagesHdl.Call( pView ); +} + +bool Outliner::ImpCanDeleteSelectedPages( OutlinerView* pCurView, sal_Int32 _nFirstPage, sal_Int32 nPages ) +{ + + nDepthChangedHdlPrevDepth = nPages; + mnFirstSelPage = _nFirstPage; + return RemovingPagesHdl( pCurView ); +} + +SfxItemSet const & Outliner::GetParaAttribs( sal_Int32 nPara ) const +{ + return pEditEngine->GetParaAttribs( nPara ); +} + +IMPL_LINK( Outliner, ParaVisibleStateChangedHdl, Paragraph&, rPara, void ) +{ + sal_Int32 nPara = pParaList->GetAbsPos( &rPara ); + pEditEngine->ShowParagraph( nPara, rPara.IsVisible() ); +} + +IMPL_LINK_NOARG(Outliner, BeginMovingParagraphsHdl, MoveParagraphsInfo&, void) +{ + if( !IsInUndo() ) + aBeginMovingHdl.Call( this ); +} + +IMPL_LINK( Outliner, BeginPasteOrDropHdl, PasteOrDropInfos&, rInfos, void ) +{ + UndoActionStart( EDITUNDO_DRAGANDDROP ); + maBeginPasteOrDropHdl.Call(&rInfos); +} + +IMPL_LINK( Outliner, EndPasteOrDropHdl, PasteOrDropInfos&, rInfos, void ) +{ + bPasting = false; + ImpTextPasted( rInfos.nStartPara, rInfos.nEndPara - rInfos.nStartPara + 1 ); + maEndPasteOrDropHdl.Call( &rInfos ); + UndoActionEnd(); +} + +IMPL_LINK( Outliner, EndMovingParagraphsHdl, MoveParagraphsInfo&, rInfos, void ) +{ + pParaList->MoveParagraphs( rInfos.nStartPara, rInfos.nDestPara, rInfos.nEndPara - rInfos.nStartPara + 1 ); + sal_Int32 nChangesStart = std::min( rInfos.nStartPara, rInfos.nDestPara ); + sal_Int32 nParas = pParaList->GetParagraphCount(); + for ( sal_Int32 n = nChangesStart; n < nParas; n++ ) + ImplCalcBulletText( n, false, false ); + + if( !IsInUndo() ) + aEndMovingHdl.Call( this ); +} + +static bool isSameNumbering( const SvxNumberFormat& rN1, const SvxNumberFormat& rN2 ) +{ + if( rN1.GetNumberingType() != rN2.GetNumberingType() ) + return false; + + if( rN1.GetNumStr(1) != rN2.GetNumStr(1) ) + return false; + + if( (rN1.GetPrefix() != rN2.GetPrefix()) || (rN1.GetSuffix() != rN2.GetSuffix()) ) + return false; + + return true; +} + +sal_uInt16 Outliner::ImplGetNumbering( sal_Int32 nPara, const SvxNumberFormat* pParaFmt ) +{ + sal_uInt16 nNumber = pParaFmt->GetStart() - 1; + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + const sal_Int16 nParaDepth = pPara->GetDepth(); + + do + { + pPara = pParaList->GetParagraph( nPara ); + const sal_Int16 nDepth = pPara->GetDepth(); + + // ignore paragraphs that are below our paragraph or have no numbering + if( (nDepth > nParaDepth) || (nDepth == -1) ) + continue; + + // stop on paragraphs that are above our paragraph + if( nDepth < nParaDepth ) + break; + + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + + if( pFmt == nullptr ) + continue; // ignore paragraphs without bullets + + // check if numbering less than or equal to pParaFmt + if( !isSameNumbering( *pFmt, *pParaFmt ) || ( pFmt->GetStart() < pParaFmt->GetStart() ) ) + break; + + if ( pFmt->GetStart() > pParaFmt->GetStart() ) + { + nNumber += pFmt->GetStart() - pParaFmt->GetStart(); + pParaFmt = pFmt; + } + + const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib( nPara, EE_PARA_BULLETSTATE ); + + if( rBulletState.GetValue() ) + nNumber += 1; + + // same depth, same number format, check for restart + const sal_Int16 nNumberingStartValue = pPara->GetNumberingStartValue(); + if( (nNumberingStartValue != -1) || pPara->IsParaIsNumberingRestart() ) + { + if( nNumberingStartValue != -1 ) + nNumber += nNumberingStartValue - 1; + break; + } + } + while( nPara-- ); + + return nNumber; +} + +void Outliner::ImplCalcBulletText( sal_Int32 nPara, bool bRecalcLevel, bool bRecalcChildren ) +{ + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + + while ( pPara ) + { + OUString aBulletText; + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + if( pFmt && ( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) ) + { + aBulletText += pFmt->GetPrefix(); + if( pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL ) + { + sal_UCS4 cChar = pFmt->GetBulletChar(); + aBulletText += OUString(&cChar, 1); + } + else if( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ) + { + aBulletText += pFmt->GetNumStr( ImplGetNumbering( nPara, pFmt ) ); + } + aBulletText += pFmt->GetSuffix(); + } + + if (pPara->GetText() != aBulletText) + pPara->SetText( aBulletText ); + + pPara->nFlags &= ~ParaFlag::SETBULLETTEXT; + + if ( bRecalcLevel ) + { + sal_Int16 nDepth = pPara->GetDepth(); + pPara = pParaList->GetParagraph( ++nPara ); + if ( !bRecalcChildren ) + { + while ( pPara && ( pPara->GetDepth() > nDepth ) ) + pPara = pParaList->GetParagraph( ++nPara ); + } + + if ( pPara && ( pPara->GetDepth() < nDepth ) ) + pPara = nullptr; + } + else + { + pPara = nullptr; + } + } +} + +void Outliner::Clear() +{ + + if( !bFirstParaIsEmpty ) + { + ImplBlockInsertionCallbacks( true ); + pEditEngine->Clear(); + pParaList->Clear(); + pParaList->Append( std::unique_ptr<Paragraph>(new Paragraph( gnMinDepth ))); + bFirstParaIsEmpty = true; + ImplBlockInsertionCallbacks( false ); + } + else + { + Paragraph* pPara = pParaList->GetParagraph( 0 ); + if(pPara) + pPara->SetDepth( gnMinDepth ); + } +} + +void Outliner::SetFlatMode( bool bFlat ) +{ + + if( bFlat != pEditEngine->IsFlatMode() ) + { + for ( sal_Int32 nPara = pParaList->GetParagraphCount(); nPara; ) + pParaList->GetParagraph( --nPara )->aBulSize.setWidth( -1 ); + + pEditEngine->SetFlatMode( bFlat ); + } +} + +OUString Outliner::ImplGetBulletText( sal_Int32 nPara ) +{ + OUString aRes; + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (pPara) + { + // Enable optimization again ... +// if( pPara->nFlags & ParaFlag::SETBULLETTEXT ) + ImplCalcBulletText( nPara, false, false ); + aRes = pPara->GetText(); + } + return aRes; +} + +// this is needed for StarOffice Api +void Outliner::SetLevelDependentStyleSheet( sal_Int32 nPara ) +{ + SfxItemSet aOldAttrs( pEditEngine->GetParaAttribs( nPara ) ); + ImplSetLevelDependentStyleSheet( nPara ); + pEditEngine->SetParaAttribs( nPara, aOldAttrs ); +} + +void Outliner::ImplBlockInsertionCallbacks( bool b ) +{ + if ( b ) + { + nBlockInsCallback++; + } + else + { + DBG_ASSERT( nBlockInsCallback, "ImplBlockInsertionCallbacks ?!" ); + nBlockInsCallback--; + if ( !nBlockInsCallback ) + { + // Call blocked notify events... + while(!pEditEngine->aNotifyCache.empty()) + { + EENotify aNotify(pEditEngine->aNotifyCache.front()); + // Remove from list before calling, maybe we enter LeaveBlockNotifications while calling the handler... + pEditEngine->aNotifyCache.erase(pEditEngine->aNotifyCache.begin()); + pEditEngine->aOutlinerNotifyHdl.Call( aNotify ); + } + } + } +} + +IMPL_LINK( Outliner, EditEngineNotifyHdl, EENotify&, rNotify, void ) +{ + if ( !nBlockInsCallback ) + pEditEngine->aOutlinerNotifyHdl.Call( rNotify ); + else + pEditEngine->aNotifyCache.push_back(rNotify); +} + +/** sets a link that is called at the beginning of a drag operation at an edit view */ +void Outliner::SetBeginDropHdl( const Link<EditView*,void>& rLink ) +{ + pEditEngine->SetBeginDropHdl( rLink ); +} + +/** sets a link that is called at the end of a drag operation at an edit view */ +void Outliner::SetEndDropHdl( const Link<EditView*,void>& rLink ) +{ + pEditEngine->SetEndDropHdl( rLink ); +} + +/** sets a link that is called before a drop or paste operation. */ +void Outliner::SetBeginPasteOrDropHdl( const Link<PasteOrDropInfos*,void>& rLink ) +{ + maBeginPasteOrDropHdl = rLink; +} + +/** sets a link that is called after a drop or paste operation. */ +void Outliner::SetEndPasteOrDropHdl( const Link<PasteOrDropInfos*,void>& rLink ) +{ + maEndPasteOrDropHdl = rLink; +} + +void Outliner::SetParaFlag( Paragraph* pPara, ParaFlag nFlag ) +{ + if( pPara && !pPara->HasFlag( nFlag ) ) + { + if( IsUndoEnabled() && !IsInUndo() ) + InsertUndo( std::make_unique<OutlinerUndoChangeParaFlags>( this, GetAbsPos( pPara ), pPara->nFlags, pPara->nFlags|nFlag ) ); + + pPara->SetFlag( nFlag ); + } +} + +bool Outliner::HasParaFlag( const Paragraph* pPara, ParaFlag nFlag ) +{ + return pPara && pPara->HasFlag( nFlag ); +} + + +bool Outliner::IsPageOverflow() +{ + return pEditEngine->IsPageOverflow(); +} + +std::optional<NonOverflowingText> Outliner::GetNonOverflowingText() const +{ + /* XXX: + * nCount should be the number of paragraphs of the non overflowing text + * nStart should be the starting paragraph of the non overflowing text (XXX: Always 0?) + */ + + if ( GetParagraphCount() < 1 ) + return {}; + + // last non-overflowing paragraph is before the first overflowing one + sal_Int32 nCount = pEditEngine->GetOverflowingParaNum(); + sal_Int32 nOverflowLine = pEditEngine->GetOverflowingLineNum(); // XXX: Unused for now + + // Defensive check: overflowing para index beyond actual # of paragraphs? + if ( nCount > GetParagraphCount()-1) { + SAL_INFO("editeng.chaining", + "[Overflowing] Ops, trying to retrieve para " + << nCount << " when max index is " << GetParagraphCount()-1 ); + return {}; + } + + if (nCount < 0) + { + SAL_INFO("editeng.chaining", + "[Overflowing] No Overflowing text but GetNonOverflowinText called?!"); + return {}; + } + + // NOTE: We want the selection of the overflowing text from here + // At the same time we may want to consider the beginning of such text + // in a more fine grained way (i.e. as GetNonOverflowingText did) + +/* + sal_Int32 nHeadPara = pEditEngine->GetOverflowingParaNum(); + sal_uInt32 nParaCount = GetParagraphCount(); + + sal_uInt32 nLen = 0; + for ( sal_Int32 nLine = 0; + nLine < pEditEngine->GetOverflowingLineNum(); + nLine++) { + nLen += GetLineLen(nHeadPara, nLine); + } + + sal_uInt32 nOverflowingPara = pEditEngine->GetOverflowingParaNum(); + ESelection aOverflowingTextSel; + sal_Int32 nLastPara = nParaCount-1; + sal_Int32 nLastParaLen = GetText(GetParagraph(nLastPara)).getLength(); + aOverflowingTextSel = ESelection(nOverflowingPara, nLen, + nLastPara, nLastParaLen); + bool bLastParaInterrupted = + pEditEngine->GetOverflowingLineNum() > 0; + + return new NonOverflowingText(aOverflowingTextSel, bLastParaInterrupted); + **/ + + + // Only overflowing text, i.e. 1st line of 1st paragraph overflowing + bool bItAllOverflew = nCount == 0 && nOverflowLine == 0; + if ( bItAllOverflew ) + { + ESelection aEmptySel(0,0,0,0); + //EditTextObject *pTObj = pEditEngine->CreateTextObject(aEmptySel); + bool const bLastParaInterrupted = true; // Last Para was interrupted since everything overflew + return NonOverflowingText(aEmptySel, bLastParaInterrupted); + } else { // Get the lines that of the overflowing para fit in the box + + sal_Int32 nOverflowingPara = nCount; + sal_uInt32 nLen = 0; + + for ( sal_Int32 nLine = 0; + nLine < pEditEngine->GetOverflowingLineNum(); + nLine++) + { + nLen += GetLineLen(nOverflowingPara, nLine); + } + + //sal_Int32 nStartPara = 0; + //sal_Int32 nStartPos = 0; + ESelection aOverflowingTextSelection; + + const sal_Int32 nEndPara = GetParagraphCount()-1; + const sal_Int32 nEndPos = pEditEngine->GetTextLen(nEndPara); + + if (nLen == 0) { + // XXX: What happens inside this case might be dependent on the joining paragraph or not-thingy + // Overflowing paragraph is empty or first line overflowing: it's not "Non-Overflowing" text then + sal_Int32 nParaLen = GetText(GetParagraph(nOverflowingPara-1)).getLength(); + aOverflowingTextSelection = + ESelection(nOverflowingPara-1, nParaLen, nEndPara, nEndPos); + } else { + // We take until we have to from the overflowing paragraph + aOverflowingTextSelection = + ESelection(nOverflowingPara, nLen, nEndPara, nEndPos); + } + //EditTextObject *pTObj = pEditEngine->CreateTextObject(aNonOverflowingTextSelection); + + //sal_Int32 nLastLine = GetLineCount(nOverflowingPara)-1; + bool bLastParaInterrupted = + pEditEngine->GetOverflowingLineNum() > 0; + + return NonOverflowingText(aOverflowingTextSelection, bLastParaInterrupted); + } +} + +OutlinerParaObject Outliner::GetEmptyParaObject() const +{ + std::unique_ptr<EditTextObject> pEmptyText = pEditEngine->GetEmptyTextObject(); + OutlinerParaObject aPObj( std::move(pEmptyText) ); + aPObj.SetOutlinerMode(GetOutlinerMode()); + return aPObj; +} + +std::optional<OverflowingText> Outliner::GetOverflowingText() const +{ + if ( pEditEngine->GetOverflowingParaNum() < 0) + return {}; + + + // Defensive check: overflowing para index beyond actual # of paragraphs? + if ( pEditEngine->GetOverflowingParaNum() > GetParagraphCount()-1) { + SAL_INFO("editeng.chaining", + "[Overflowing] Ops, trying to retrieve para " + << pEditEngine->GetOverflowingParaNum() << " when max index is " + << GetParagraphCount()-1 ); + return {}; + } + + sal_Int32 nHeadPara = pEditEngine->GetOverflowingParaNum(); + sal_uInt32 nParaCount = GetParagraphCount(); + + sal_uInt32 nLen = 0; + for ( sal_Int32 nLine = 0; + nLine < pEditEngine->GetOverflowingLineNum(); + nLine++) { + nLen += GetLineLen(nHeadPara, nLine); + } + + sal_uInt32 nOverflowingPara = pEditEngine->GetOverflowingParaNum(); + ESelection aOverflowingTextSel; + sal_Int32 nLastPara = nParaCount-1; + sal_Int32 nLastParaLen = GetText(GetParagraph(nLastPara)).getLength(); + aOverflowingTextSel = ESelection(nOverflowingPara, nLen, + nLastPara, nLastParaLen); + return OverflowingText(pEditEngine->CreateTransferable(aOverflowingTextSel)); + +} + +void Outliner::ClearOverflowingParaNum() +{ + pEditEngine->ClearOverflowingParaNum(); +} + +void Outliner::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + bool bOwns = false; + if (!pWriter) + { + pWriter = xmlNewTextWriterFilename("outliner.xml", 0); + xmlTextWriterSetIndent(pWriter,1); + (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); + (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr); + bOwns = true; + } + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("Outliner")); + pParaList->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + + if (bOwns) + { + (void)xmlTextWriterEndDocument(pWriter); + xmlFreeTextWriter(pWriter); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outlobj.cxx b/editeng/source/outliner/outlobj.cxx new file mode 100644 index 000000000..e6dc6e691 --- /dev/null +++ b/editeng/source/outliner/outlobj.cxx @@ -0,0 +1,254 @@ +/* -*- 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 <editeng/editdata.hxx> + +#include <editeng/outliner.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/editobj.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <o3tl/cow_wrapper.hxx> +#include <o3tl/safeint.hxx> +#include <libxml/xmlwriter.h> + +OutlinerParaObjData::OutlinerParaObjData( std::unique_ptr<EditTextObject> pEditTextObject, ParagraphDataVector&& rParagraphDataVector, bool bIsEditDoc ) : + mpEditTextObject(std::move(pEditTextObject)), + maParagraphDataVector(std::move(rParagraphDataVector)), + mbIsEditDoc(bIsEditDoc) +{ + if( maParagraphDataVector.empty() && (mpEditTextObject->GetParagraphCount() != 0) ) + maParagraphDataVector.resize(mpEditTextObject->GetParagraphCount()); +} + +OutlinerParaObjData::OutlinerParaObjData( const OutlinerParaObjData& r ): + mpEditTextObject(r.mpEditTextObject->Clone()), + maParagraphDataVector(r.maParagraphDataVector), + mbIsEditDoc(r.mbIsEditDoc) +{ +} + +OutlinerParaObjData::~OutlinerParaObjData() +{ +} + +bool OutlinerParaObjData::operator==(const OutlinerParaObjData& rCandidate) const +{ + return (*mpEditTextObject == *rCandidate.mpEditTextObject + && maParagraphDataVector == rCandidate.maParagraphDataVector + && mbIsEditDoc == rCandidate.mbIsEditDoc); +} + +bool OutlinerParaObjData::isWrongListEqual(const OutlinerParaObjData& rCompare) const +{ + return mpEditTextObject->isWrongListEqual(*rCompare.mpEditTextObject); +} + +OutlinerParaObject::OutlinerParaObject( + std::unique_ptr<EditTextObject> xTextObj, ParagraphDataVector&& rParagraphDataVector, bool bIsEditDoc ) : + mpImpl(OutlinerParaObjData(std::move(xTextObj), std::move(rParagraphDataVector), bIsEditDoc)) +{ +} + +OutlinerParaObject::OutlinerParaObject( std::unique_ptr<EditTextObject> pTextObj ) : + mpImpl(OutlinerParaObjData(std::move(pTextObj), ParagraphDataVector(), true)) +{ +} + +OutlinerParaObject::OutlinerParaObject( const OutlinerParaObject& r ) : + mpImpl(r.mpImpl) +{ +} + +OutlinerParaObject::OutlinerParaObject( OutlinerParaObject&& r ) noexcept : + mpImpl(std::move(r.mpImpl)) +{ +} + +OutlinerParaObject::~OutlinerParaObject() +{ +} + +OutlinerParaObject& OutlinerParaObject::operator=( const OutlinerParaObject& r ) +{ + mpImpl = r.mpImpl; + return *this; +} + +OutlinerParaObject& OutlinerParaObject::operator=( OutlinerParaObject&& r ) noexcept +{ + mpImpl = std::move(r.mpImpl); + return *this; +} + +bool OutlinerParaObject::operator==( const OutlinerParaObject& r ) const +{ + return r.mpImpl == mpImpl; +} + +// #i102062# +bool OutlinerParaObject::isWrongListEqual( const OutlinerParaObject& r ) const +{ + if (r.mpImpl.same_object(mpImpl)) + { + return true; + } + + return mpImpl->isWrongListEqual(*r.mpImpl); +} + +OutlinerMode OutlinerParaObject::GetOutlinerMode() const +{ + return mpImpl->mpEditTextObject->GetUserType(); +} + +void OutlinerParaObject::SetOutlinerMode(OutlinerMode nNew) +{ + // create a const pointer to avoid an early call to + // make_unique() in the dereference of mpImpl + const ::o3tl::cow_wrapper< OutlinerParaObjData >* pImpl = &mpImpl; + if ( ( *pImpl )->mpEditTextObject->GetUserType() != nNew ) + { + mpImpl->mpEditTextObject->SetUserType(nNew); + } +} + +bool OutlinerParaObject::IsEffectivelyVertical() const +{ + return mpImpl->mpEditTextObject->IsEffectivelyVertical(); +} + +bool OutlinerParaObject::GetVertical() const +{ + return mpImpl->mpEditTextObject->GetVertical(); +} + +bool OutlinerParaObject::IsTopToBottom() const +{ + return mpImpl->mpEditTextObject->IsTopToBottom(); +} + +void OutlinerParaObject::SetVertical(bool bNew) +{ + const ::o3tl::cow_wrapper< OutlinerParaObjData >* pImpl = &mpImpl; + if ( ( *pImpl )->mpEditTextObject->IsEffectivelyVertical() != bNew) + { + mpImpl->mpEditTextObject->SetVertical(bNew); + } +} +void OutlinerParaObject::SetRotation(TextRotation nRotation) +{ + mpImpl->mpEditTextObject->SetRotation(nRotation); +} + +TextRotation OutlinerParaObject::GetRotation() const +{ + return mpImpl->mpEditTextObject->GetRotation(); +} + +sal_Int32 OutlinerParaObject::Count() const +{ + size_t nSize = mpImpl->maParagraphDataVector.size(); + if (nSize > EE_PARA_MAX_COUNT) + { + SAL_WARN( "editeng", "OutlinerParaObject::Count - overflow " << nSize); + return EE_PARA_MAX_COUNT; + } + return static_cast<sal_Int32>(nSize); +} + +sal_Int16 OutlinerParaObject::GetDepth(sal_Int32 nPara) const +{ + if(0 <= nPara && o3tl::make_unsigned(nPara) < mpImpl->maParagraphDataVector.size()) + { + return mpImpl->maParagraphDataVector[nPara].getDepth(); + } + else + { + return -1; + } +} + +const EditTextObject& OutlinerParaObject::GetTextObject() const +{ + return *mpImpl->mpEditTextObject; +} + +const ParagraphData& OutlinerParaObject::GetParagraphData(sal_Int32 nIndex) const +{ + if(0 <= nIndex && o3tl::make_unsigned(nIndex) < mpImpl->maParagraphDataVector.size()) + { + return mpImpl->maParagraphDataVector[nIndex]; + } + else + { + OSL_FAIL("OutlinerParaObject::GetParagraphData: Access out of range (!)"); + static ParagraphData aEmptyParagraphData; + return aEmptyParagraphData; + } +} + +void OutlinerParaObject::ClearPortionInfo() +{ + mpImpl->mpEditTextObject->ClearPortionInfo(); +} + +bool OutlinerParaObject::ChangeStyleSheets(std::u16string_view rOldName, + SfxStyleFamily eOldFamily, const OUString& rNewName, SfxStyleFamily eNewFamily) +{ + return mpImpl->mpEditTextObject->ChangeStyleSheets(rOldName, eOldFamily, rNewName, eNewFamily); +} + +void OutlinerParaObject::ChangeStyleSheetName(SfxStyleFamily eFamily, + std::u16string_view rOldName, const OUString& rNewName) +{ + mpImpl->mpEditTextObject->ChangeStyleSheetName(eFamily, rOldName, rNewName); +} + +void OutlinerParaObject::SetStyleSheets(sal_uInt16 nLevel, const OUString& rNewName, + const SfxStyleFamily& rNewFamily) +{ + const sal_Int32 nCount(Count()); + + if(nCount) + { + sal_Int32 nDecrementer(nCount); + + while(nDecrementer > 0) + { + if(GetDepth(--nDecrementer) == nLevel) + { + mpImpl->mpEditTextObject->SetStyleSheet(nDecrementer, rNewName, rNewFamily); + } + } + } +} + +void OutlinerParaObject::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("OutlinerParaObject")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + mpImpl->mpEditTextObject->dumpAsXml(pWriter); + for (ParagraphData const & p : mpImpl->maParagraphDataVector) + Paragraph(p).dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outlundo.cxx b/editeng/source/outliner/outlundo.cxx new file mode 100644 index 000000000..c2db1a77f --- /dev/null +++ b/editeng/source/outliner/outlundo.cxx @@ -0,0 +1,164 @@ +/* -*- 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 <editeng/outliner.hxx> +#include <tools/debug.hxx> +#include "outlundo.hxx" + + +OutlinerUndoBase::OutlinerUndoBase( sal_uInt16 _nId, Outliner* pOutliner ) + : EditUndo( _nId, nullptr ) +{ + DBG_ASSERT( pOutliner, "Undo: Outliner?!" ); + mpOutliner = pOutliner; +} + +OutlinerUndoChangeParaFlags::OutlinerUndoChangeParaFlags( Outliner* pOutliner, sal_Int32 nPara, ParaFlag nOldFlags, ParaFlag nNewFlags ) +: OutlinerUndoBase( OLUNDO_DEPTH, pOutliner ), mnPara(nPara), mnOldFlags(nOldFlags), mnNewFlags(nNewFlags) +{ +} + +void OutlinerUndoChangeParaFlags::Undo() +{ + ImplChangeFlags( mnOldFlags ); +} + +void OutlinerUndoChangeParaFlags::Redo() +{ + ImplChangeFlags( mnNewFlags ); +} + +void OutlinerUndoChangeParaFlags::ImplChangeFlags( ParaFlag nFlags ) +{ + Outliner* pOutliner = GetOutliner(); + Paragraph* pPara = pOutliner->GetParagraph( mnPara ); + if( pPara ) + { + pOutliner->nDepthChangedHdlPrevDepth = pPara->GetDepth(); + ParaFlag nPrevFlags = pPara->nFlags; + + pPara->nFlags = nFlags; + pOutliner->DepthChangedHdl(pPara, nPrevFlags); + } +} + +OutlinerUndoChangeParaNumberingRestart::OutlinerUndoChangeParaNumberingRestart( Outliner* pOutliner, sal_Int32 nPara, + sal_Int16 nOldNumberingStartValue, sal_Int16 nNewNumberingStartValue, + bool bOldParaIsNumberingRestart, bool bNewParaIsNumberingRestart ) +: OutlinerUndoBase( OLUNDO_DEPTH, pOutliner ), mnPara(nPara) +{ + maUndoData.mnNumberingStartValue = nOldNumberingStartValue; + maUndoData.mbParaIsNumberingRestart = bOldParaIsNumberingRestart; + maRedoData.mnNumberingStartValue = nNewNumberingStartValue; + maRedoData.mbParaIsNumberingRestart = bNewParaIsNumberingRestart; +} + +void OutlinerUndoChangeParaNumberingRestart::Undo() +{ + ImplApplyData( maUndoData ); +} + +void OutlinerUndoChangeParaNumberingRestart::Redo() +{ + ImplApplyData( maRedoData ); +} + +void OutlinerUndoChangeParaNumberingRestart::ImplApplyData( const ParaRestartData& rData ) +{ + Outliner* pOutliner = GetOutliner(); + pOutliner->SetNumberingStartValue( mnPara, rData.mnNumberingStartValue ); + pOutliner->SetParaIsNumberingRestart( mnPara, rData.mbParaIsNumberingRestart ); +} + +OutlinerUndoChangeDepth::OutlinerUndoChangeDepth( Outliner* pOutliner, sal_Int32 nPara, sal_Int16 nOldDepth, sal_Int16 nNewDepth ) + : OutlinerUndoBase( OLUNDO_DEPTH, pOutliner ), mnPara(nPara), mnOldDepth(nOldDepth), mnNewDepth(nNewDepth) +{ +} + +void OutlinerUndoChangeDepth::Undo() +{ + GetOutliner()->ImplInitDepth( mnPara, mnOldDepth, false ); +} + +void OutlinerUndoChangeDepth::Redo() +{ + GetOutliner()->ImplInitDepth( mnPara, mnNewDepth, false ); +} + +OutlinerUndoCheckPara::OutlinerUndoCheckPara( Outliner* pOutliner, sal_Int32 nPara ) + : OutlinerUndoBase( OLUNDO_DEPTH, pOutliner ), mnPara(nPara) +{ +} + +void OutlinerUndoCheckPara::Undo() +{ + Paragraph* pPara = GetOutliner()->GetParagraph( mnPara ); + pPara->Invalidate(); + GetOutliner()->ImplCalcBulletText( mnPara, false, false ); +} + +void OutlinerUndoCheckPara::Redo() +{ + Paragraph* pPara = GetOutliner()->GetParagraph( mnPara ); + pPara->Invalidate(); + GetOutliner()->ImplCalcBulletText( mnPara, false, false ); +} + +OLUndoExpand::OLUndoExpand(Outliner* pOut, sal_uInt16 _nId ) + : EditUndo( _nId, nullptr ), pOutliner(pOut), nCount(0) +{ + DBG_ASSERT(pOut,"Undo:No Outliner"); +} + + +OLUndoExpand::~OLUndoExpand() +{ +} + + +void OLUndoExpand::Restore( bool bUndo ) +{ + DBG_ASSERT(pOutliner,"Undo:No Outliner"); + DBG_ASSERT(pOutliner->pEditEngine,"Outliner already deleted"); + Paragraph* pPara; + + bool bExpand = false; + sal_uInt16 _nId = GetId(); + if((_nId == OLUNDO_EXPAND && !bUndo) || (_nId == OLUNDO_COLLAPSE && bUndo)) + bExpand = true; + + pPara = pOutliner->GetParagraph( nCount ); + if( bExpand ) + pOutliner->Expand( pPara ); + else + pOutliner->Collapse( pPara ); +} + +void OLUndoExpand::Undo() +{ + Restore( true ); +} + +void OLUndoExpand::Redo() +{ + Restore( false ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outlundo.hxx b/editeng/source/outliner/outlundo.hxx new file mode 100644 index 000000000..066b415e1 --- /dev/null +++ b/editeng/source/outliner/outlundo.hxx @@ -0,0 +1,118 @@ +/* -*- 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 <editeng/outliner.hxx> +#include <editeng/editund2.hxx> + +class OutlinerUndoBase : public EditUndo +{ +private: + Outliner* mpOutliner; + +public: + OutlinerUndoBase( sal_uInt16 nId, Outliner* pOutliner ); + + Outliner* GetOutliner() const { return mpOutliner; } +}; + +class OutlinerUndoChangeParaFlags : public OutlinerUndoBase +{ +private: + sal_Int32 mnPara; + ParaFlag mnOldFlags; + ParaFlag mnNewFlags; + + void ImplChangeFlags( ParaFlag nFlags ); + +public: + OutlinerUndoChangeParaFlags( Outliner* pOutliner, sal_Int32 nPara, ParaFlag nOldFlags, ParaFlag nNewFlags ); + + virtual void Undo() override; + virtual void Redo() override; +}; + +class OutlinerUndoChangeParaNumberingRestart : public OutlinerUndoBase +{ +private: + sal_Int32 mnPara; + + struct ParaRestartData + { + sal_Int16 mnNumberingStartValue; + bool mbParaIsNumberingRestart; + }; + + ParaRestartData maUndoData; + ParaRestartData maRedoData; + + void ImplApplyData( const ParaRestartData& rData ); +public: + OutlinerUndoChangeParaNumberingRestart( Outliner* pOutliner, sal_Int32 nPara, + sal_Int16 nOldNumberingStartValue, sal_Int16 mnNewNumberingStartValue, + bool bOldbParaIsNumberingRestart, bool bNewParaIsNumberingRestart ); + + virtual void Undo() override; + virtual void Redo() override; +}; + +class OutlinerUndoChangeDepth : public OutlinerUndoBase +{ +private: + sal_Int32 mnPara; + sal_Int16 mnOldDepth; + sal_Int16 mnNewDepth; + +public: + OutlinerUndoChangeDepth( Outliner* pOutliner, sal_Int32 nPara, sal_Int16 nOldDepth, sal_Int16 nNewDepth ); + + virtual void Undo() override; + virtual void Redo() override; +}; + +// Help-Undo: If it does not exist an OutlinerUndoAction for a certain action +// because this is handled by the EditEngine, but for example the bullet has +// to be recalculated. +class OutlinerUndoCheckPara : public OutlinerUndoBase +{ +private: + sal_Int32 mnPara; + +public: + OutlinerUndoCheckPara( Outliner* pOutliner, sal_Int32 nPara ); + + virtual void Undo() override; + virtual void Redo() override; +}; + +class OLUndoExpand : public EditUndo +{ + void Restore( bool bUndo ); +public: + OLUndoExpand( Outliner* pOut, sal_uInt16 nId ); + virtual ~OLUndoExpand() override; + virtual void Undo() override; + virtual void Redo() override; + + Outliner* pOutliner; + sal_Int32 nCount; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outlvw.cxx b/editeng/source/outliner/outlvw.cxx new file mode 100644 index 000000000..d5f426abb --- /dev/null +++ b/editeng/source/outliner/outlvw.cxx @@ -0,0 +1,1503 @@ +/* -*- 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 <com/sun/star/i18n/WordType.hpp> + +#include <svl/itempool.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/editdata.hxx> + +#include <svl/style.hxx> +#include <svl/languageoptions.hxx> +#include <i18nlangtag/languagetag.hxx> + +#include <editeng/outliner.hxx> +#include "outleeng.hxx" +#include "paralist.hxx" +#include "outlundo.hxx" +#include <editeng/outlobj.hxx> +#include <editeng/flditem.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/numitem.hxx> +#include <vcl/window.hxx> +#include <vcl/event.hxx> +#include <vcl/ptrstyle.hxx> +#include <svl/itemset.hxx> +#include <svl/eitem.hxx> +#include <editeng/editstat.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/debug.hxx> + +using namespace ::com::sun::star; + + +OutlinerView::OutlinerView( Outliner* pOut, vcl::Window* pWin ) +{ + pOwner = pOut; + pEditView.reset( new EditView( pOut->pEditEngine.get(), pWin ) ); +} + +OutlinerView::~OutlinerView() +{ +} + +void OutlinerView::Paint( const tools::Rectangle& rRect, OutputDevice* pTargetDevice ) +{ + // For the first Paint/KeyInput/Drop an empty Outliner is turned into + // an Outliner with exactly one paragraph. + if( pOwner->bFirstParaIsEmpty ) + pOwner->Insert( OUString() ); + + pEditView->Paint( rRect, pTargetDevice ); +} + +bool OutlinerView::PostKeyEvent( const KeyEvent& rKEvt, vcl::Window const * pFrameWin ) +{ + // For the first Paint/KeyInput/Drop an empty Outliner is turned into + // an Outliner with exactly one paragraph. + if( pOwner->bFirstParaIsEmpty ) + pOwner->Insert( OUString() ); + + bool bKeyProcessed = false; + ESelection aSel( pEditView->GetSelection() ); + bool bSelection = aSel.HasRange(); + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + KeyFuncType eFunc = aKeyCode.GetFunction(); + sal_uInt16 nCode = aKeyCode.GetCode(); + bool bReadOnly = IsReadOnly(); + + if( bSelection && ( nCode != KEY_TAB ) && EditEngine::DoesKeyChangeText( rKEvt ) ) + { + if ( ImpCalcSelectedPages( false ) && !pOwner->ImpCanDeleteSelectedPages( this ) ) + return true; + } + + if ( eFunc != KeyFuncType::DONTKNOW ) + { + switch ( eFunc ) + { + case KeyFuncType::CUT: + { + if ( !bReadOnly ) + { + Cut(); + bKeyProcessed = true; + } + } + break; + case KeyFuncType::COPY: + { + Copy(); + bKeyProcessed = true; + } + break; + case KeyFuncType::PASTE: + { + if ( !bReadOnly ) + { + PasteSpecial(); + bKeyProcessed = true; + } + } + break; + case KeyFuncType::DELETE: + { + if( !bReadOnly && !bSelection && ( pOwner->GetOutlinerMode() != OutlinerMode::TextObject ) ) + { + if( aSel.nEndPos == pOwner->pEditEngine->GetTextLen( aSel.nEndPara ) ) + { + Paragraph* pNext = pOwner->pParaList->GetParagraph( aSel.nEndPara+1 ); + if( pNext && pNext->HasFlag(ParaFlag::ISPAGE) ) + { + if( !pOwner->ImpCanDeleteSelectedPages( this, aSel.nEndPara, 1 ) ) + return false; + } + } + } + } + break; + default: // is then possibly edited below. + eFunc = KeyFuncType::DONTKNOW; + } + } + if ( eFunc == KeyFuncType::DONTKNOW ) + { + switch ( nCode ) + { + case KEY_TAB: + { + if ( !bReadOnly && !aKeyCode.IsMod1() && !aKeyCode.IsMod2() ) + { + if ( ( pOwner->GetOutlinerMode() != OutlinerMode::TextObject ) && + ( pOwner->GetOutlinerMode() != OutlinerMode::TitleObject ) && + ( bSelection || !aSel.nStartPos ) ) + { + Indent( aKeyCode.IsShift() ? -1 : +1 ); + bKeyProcessed = true; + } + else if ( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) && + !bSelection && !aSel.nEndPos && pOwner->ImplHasNumberFormat( aSel.nEndPara ) ) + { + Indent( aKeyCode.IsShift() ? -1 : +1 ); + bKeyProcessed = true; + } + } + } + break; + case KEY_BACKSPACE: + { + if( !bReadOnly && !bSelection && aSel.nEndPara && !aSel.nEndPos ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( aSel.nEndPara ); + Paragraph* pPrev = pOwner->pParaList->GetParagraph( aSel.nEndPara-1 ); + if( !pPrev->IsVisible() ) + return true; + if( !pPara->GetDepth() ) + { + if(!pOwner->ImpCanDeleteSelectedPages(this, aSel.nEndPara , 1 ) ) + return true; + } + } + } + break; + case KEY_RETURN: + { + if ( !bReadOnly ) + { + // Special treatment: hard return at the end of a paragraph, + // which has collapsed subparagraphs. + Paragraph* pPara = pOwner->pParaList->GetParagraph( aSel.nEndPara ); + + if( !aKeyCode.IsShift() ) + { + // ImpGetCursor again??? + if( !bSelection && + aSel.nEndPos == pOwner->pEditEngine->GetTextLen( aSel.nEndPara ) ) + { + sal_Int32 nChildren = pOwner->pParaList->GetChildCount(pPara); + if( nChildren && !pOwner->pParaList->HasVisibleChildren(pPara)) + { + pOwner->UndoActionStart( OLUNDO_INSERT ); + sal_Int32 nTemp = aSel.nEndPara; + nTemp += nChildren; + nTemp++; // insert above next Non-Child + SAL_WARN_IF( nTemp < 0, "editeng", "OutlinerView::PostKeyEvent - overflow"); + if (nTemp >= 0) + { + pOwner->Insert( OUString(),nTemp,pPara->GetDepth()); + // Position the cursor + ESelection aTmpSel(nTemp,0,nTemp,0); + pEditView->SetSelection( aTmpSel ); + } + pEditView->ShowCursor(); + pOwner->UndoActionEnd(); + bKeyProcessed = true; + } + } + } + if( !bKeyProcessed && !bSelection && + !aKeyCode.IsShift() && aKeyCode.IsMod1() && + ( aSel.nEndPos == pOwner->pEditEngine->GetTextLen(aSel.nEndPara) ) ) + { + pOwner->UndoActionStart( OLUNDO_INSERT ); + sal_Int32 nTemp = aSel.nEndPara; + nTemp++; + pOwner->Insert( OUString(), nTemp, pPara->GetDepth()+1 ); + + // Position the cursor + ESelection aTmpSel(nTemp,0,nTemp,0); + pEditView->SetSelection( aTmpSel ); + pEditView->ShowCursor(); + pOwner->UndoActionEnd(); + bKeyProcessed = true; + } + } + } + break; + } + } + + return bKeyProcessed || pEditView->PostKeyEvent( rKEvt, pFrameWin ); +} + +sal_Int32 OutlinerView::ImpCheckMousePos(const Point& rPosPix, MouseTarget& reTarget) +{ + sal_Int32 nPara = EE_PARA_NOT_FOUND; + + Point aMousePosWin = pEditView->GetOutputDevice().PixelToLogic( rPosPix ); + if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) + { + reTarget = MouseTarget::Outside; + } + else + { + reTarget = MouseTarget::Text; + + Point aPaperPos( aMousePosWin ); + tools::Rectangle aOutArea = pEditView->GetOutputArea(); + tools::Rectangle aVisArea = pEditView->GetVisArea(); + aPaperPos.AdjustX( -(aOutArea.Left()) ); + aPaperPos.AdjustX(aVisArea.Left() ); + aPaperPos.AdjustY( -(aOutArea.Top()) ); + aPaperPos.AdjustY(aVisArea.Top() ); + + bool bBullet; + if ( pOwner->IsTextPos( aPaperPos, 0, &bBullet ) ) + { + Point aDocPos = pOwner->GetDocPos( aPaperPos ); + nPara = pOwner->pEditEngine->FindParagraph( aDocPos.Y() ); + + if ( bBullet ) + { + reTarget = MouseTarget::Bullet; + } + else + { + // Check for hyperlink + const SvxFieldItem* pFieldItem = pEditView->GetField( aMousePosWin ); + if ( pFieldItem && pFieldItem->GetField() && dynamic_cast< const SvxURLField* >(pFieldItem->GetField()) != nullptr ) + reTarget = MouseTarget::Hypertext; + } + } + } + return nPara; +} + +bool OutlinerView::MouseMove( const MouseEvent& rMEvt ) +{ + if( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) || pEditView->GetEditEngine()->IsInSelectionMode()) + return pEditView->MouseMove( rMEvt ); + + Point aMousePosWin( pEditView->GetOutputDevice().PixelToLogic( rMEvt.GetPosPixel() ) ); + if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) + return false; + + PointerStyle aPointer = GetPointer( rMEvt.GetPosPixel() ); + pEditView->GetWindow()->SetPointer( aPointer ); + return pEditView->MouseMove( rMEvt ); +} + + +bool OutlinerView::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) || pEditView->GetEditEngine()->IsInSelectionMode() ) + return pEditView->MouseButtonDown( rMEvt ); + + Point aMousePosWin( pEditView->GetOutputDevice().PixelToLogic( rMEvt.GetPosPixel() ) ); + if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) + return false; + + PointerStyle aPointer = GetPointer( rMEvt.GetPosPixel() ); + pEditView->GetWindow()->SetPointer( aPointer ); + + MouseTarget eTarget; + sal_Int32 nPara = ImpCheckMousePos( rMEvt.GetPosPixel(), eTarget ); + if ( eTarget == MouseTarget::Bullet ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + bool bHasChildren = (pPara && pOwner->pParaList->HasChildren(pPara)); + if( rMEvt.GetClicks() == 1 ) + { + sal_Int32 nEndPara = nPara; + if ( bHasChildren && pOwner->pParaList->HasVisibleChildren(pPara) ) + nEndPara += pOwner->pParaList->GetChildCount( pPara ); + // The selection is inverted, so that EditEngine does not scroll + ESelection aSel(nEndPara, EE_TEXTPOS_ALL, nPara, 0 ); + pEditView->SetSelection( aSel ); + } + else if( rMEvt.GetClicks() == 2 && bHasChildren ) + ImpToggleExpand( pPara ); + + return true; + } + + // special case for outliner view in impress, check if double click hits the page icon for toggle + if( (nPara == EE_PARA_NOT_FOUND) && (pOwner->GetOutlinerMode() == OutlinerMode::OutlineView) && (eTarget == MouseTarget::Text) && (rMEvt.GetClicks() == 2) ) + { + ESelection aSel( pEditView->GetSelection() ); + nPara = aSel.nStartPara; + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + if( (pPara && pOwner->pParaList->HasChildren(pPara)) && pPara->HasFlag(ParaFlag::ISPAGE) ) + { + ImpToggleExpand( pPara ); + } + } + return pEditView->MouseButtonDown( rMEvt ); +} + + +bool OutlinerView::MouseButtonUp( const MouseEvent& rMEvt ) +{ + if ( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) || pEditView->GetEditEngine()->IsInSelectionMode() ) + return pEditView->MouseButtonUp( rMEvt ); + + Point aMousePosWin( pEditView->GetOutputDevice().PixelToLogic( rMEvt.GetPosPixel() ) ); + if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) + return false; + + PointerStyle aPointer = GetPointer( rMEvt.GetPosPixel() ); + pEditView->GetWindow()->SetPointer( aPointer ); + + return pEditView->MouseButtonUp( rMEvt ); +} + +void OutlinerView::ReleaseMouse() +{ + pEditView->ReleaseMouse(); +} + +void OutlinerView::ImpToggleExpand( Paragraph const * pPara ) +{ + sal_Int32 nPara = pOwner->pParaList->GetAbsPos( pPara ); + pEditView->SetSelection( ESelection( nPara, 0, nPara, 0 ) ); + ImplExpandOrCollaps( nPara, nPara, !pOwner->pParaList->HasVisibleChildren( pPara ) ); + pEditView->ShowCursor(); +} + +void OutlinerView::Select( Paragraph const * pParagraph, bool bSelect ) +{ + sal_Int32 nPara = pOwner->pParaList->GetAbsPos( pParagraph ); + sal_Int32 nEnd = 0; + if ( bSelect ) + nEnd = SAL_MAX_INT32; + + ESelection aSel( nPara, 0, nPara, nEnd ); + pEditView->SetSelection( aSel ); +} + + +void OutlinerView::SetAttribs( const SfxItemSet& rAttrs ) +{ + bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); + + if( !pOwner->IsInUndo() && pOwner->IsUndoEnabled() ) + pOwner->UndoActionStart( OLUNDO_ATTR ); + + ParaRange aSel = ImpGetSelectedParagraphs( false ); + + pEditView->SetAttribs( rAttrs ); + + // Update Bullet text + for( sal_Int32 nPara= aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + pOwner->ImplCheckNumBulletItem( nPara ); + pOwner->ImplCalcBulletText( nPara, false, false ); + + if( !pOwner->IsInUndo() && pOwner->IsUndoEnabled() ) + pOwner->InsertUndo( std::make_unique<OutlinerUndoCheckPara>( pOwner, nPara ) ); + } + + if( !pOwner->IsInUndo() && pOwner->IsUndoEnabled() ) + pOwner->UndoActionEnd(); + + pEditView->SetEditEngineUpdateLayout( bUpdate ); +} + +ParaRange OutlinerView::ImpGetSelectedParagraphs( bool bIncludeHiddenChildren ) +{ + ESelection aSel = pEditView->GetSelection(); + ParaRange aParas( aSel.nStartPara, aSel.nEndPara ); + aParas.Adjust(); + + // Record the invisible Children of the last Parents in the selection + if ( bIncludeHiddenChildren ) + { + Paragraph* pLast = pOwner->pParaList->GetParagraph( aParas.nEndPara ); + if ( pOwner->pParaList->HasHiddenChildren( pLast ) ) + aParas.nEndPara = aParas.nEndPara + pOwner->pParaList->GetChildCount( pLast ); + } + return aParas; +} + +// TODO: Name should be changed! +void OutlinerView::AdjustDepth( short nDX ) +{ + Indent( nDX ); +} + +void OutlinerView::Indent( short nDiff ) +{ + if( !nDiff || ( ( nDiff > 0 ) && ImpCalcSelectedPages( true ) && !pOwner->ImpCanIndentSelectedPages( this ) ) ) + return; + + const bool bOutlinerView = bool(pOwner->pEditEngine->GetControlWord() & EEControlBits::OUTLINER); + bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); + + bool bUndo = !pOwner->IsInUndo() && pOwner->IsUndoEnabled(); + + if( bUndo ) + pOwner->UndoActionStart( OLUNDO_DEPTH ); + + sal_Int16 nMinDepth = -1; // Optimization: avoid recalculate too many paragraphs if not really needed. + + ParaRange aSel = ImpGetSelectedParagraphs( true ); + for ( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + + sal_Int16 nOldDepth = pPara->GetDepth(); + sal_Int16 nNewDepth = nOldDepth + nDiff; + + if( bOutlinerView && nPara ) + { + const bool bPage = pPara->HasFlag(ParaFlag::ISPAGE); + if( (bPage && (nDiff == +1)) || (!bPage && (nDiff == -1) && (nOldDepth <= 0)) ) + { + // Notify App + pOwner->nDepthChangedHdlPrevDepth = nOldDepth; + ParaFlag nPrevFlags = pPara->nFlags; + + if( bPage ) + pPara->RemoveFlag( ParaFlag::ISPAGE ); + else + pPara->SetFlag( ParaFlag::ISPAGE ); + + pOwner->DepthChangedHdl(pPara, nPrevFlags); + pOwner->pEditEngine->QuickMarkInvalid( ESelection( nPara, 0, nPara, 0 ) ); + + if( bUndo ) + pOwner->InsertUndo( std::make_unique<OutlinerUndoChangeParaFlags>( pOwner, nPara, nPrevFlags, pPara->nFlags ) ); + + continue; + } + } + + // do not switch off numeration with tab + if( (nOldDepth == 0) && (nNewDepth == -1) ) + continue; + + // do not indent if there is no numeration enabled + if( nOldDepth == -1 ) + continue; + + if ( nNewDepth < Outliner::gnMinDepth ) + nNewDepth = Outliner::gnMinDepth; + if ( nNewDepth > pOwner->nMaxDepth ) + nNewDepth = pOwner->nMaxDepth; + + if( nOldDepth < nMinDepth ) + nMinDepth = nOldDepth; + if( nNewDepth < nMinDepth ) + nMinDepth = nNewDepth; + + if( nOldDepth != nNewDepth ) + { + if ( ( nPara == aSel.nStartPara ) && aSel.nStartPara && ( pOwner->GetOutlinerMode() != OutlinerMode::TextObject )) + { + // Special case: the predecessor of an indented paragraph is + // invisible and is now on the same level as the visible + // paragraph. In this case, the next visible paragraph is + // searched for and fluffed. +#ifdef DBG_UTIL + Paragraph* _pPara = pOwner->pParaList->GetParagraph( aSel.nStartPara ); + DBG_ASSERT(_pPara->IsVisible(),"Selected Paragraph invisible ?!"); +#endif + Paragraph* pPrev= pOwner->pParaList->GetParagraph( aSel.nStartPara-1 ); + + if( !pPrev->IsVisible() && ( pPrev->GetDepth() == nNewDepth ) ) + { + // Predecessor is collapsed and is on the same level + // => find next visible paragraph and expand it + pPrev = pOwner->pParaList->GetParent( pPrev ); + while( !pPrev->IsVisible() ) + pPrev = pOwner->pParaList->GetParent( pPrev ); + + pOwner->Expand( pPrev ); + pOwner->InvalidateBullet(pOwner->pParaList->GetAbsPos(pPrev)); + } + } + + pOwner->nDepthChangedHdlPrevDepth = nOldDepth; + ParaFlag nPrevFlags = pPara->nFlags; + + pOwner->ImplInitDepth( nPara, nNewDepth, true ); + pOwner->ImplCalcBulletText( nPara, false, false ); + + if ( pOwner->GetOutlinerMode() == OutlinerMode::OutlineObject ) + pOwner->ImplSetLevelDependentStyleSheet( nPara ); + + // Notify App + pOwner->DepthChangedHdl(pPara, nPrevFlags); + } + else + { + // Needs at least a repaint... + pOwner->pEditEngine->QuickMarkInvalid( ESelection( nPara, 0, nPara, 0 ) ); + } + } + + sal_Int32 nParas = pOwner->pParaList->GetParagraphCount(); + for ( sal_Int32 n = aSel.nEndPara+1; n < nParas; n++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( n ); + if ( pPara->GetDepth() < nMinDepth ) + break; + pOwner->ImplCalcBulletText( n, false, false ); + } + + if ( bUpdate ) + { + pEditView->SetEditEngineUpdateLayout( true ); + pEditView->ShowCursor(); + } + + if( bUndo ) + pOwner->UndoActionEnd(); +} + +void OutlinerView::AdjustHeight( tools::Long nDY ) +{ + pEditView->MoveParagraphs( nDY ); +} + +tools::Rectangle OutlinerView::GetVisArea() const +{ + return pEditView->GetVisArea(); +} + +void OutlinerView::Expand() +{ + ParaRange aParas = ImpGetSelectedParagraphs( false ); + ImplExpandOrCollaps( aParas.nStartPara, aParas.nEndPara, true ); +} + + +void OutlinerView::Collapse() +{ + ParaRange aParas = ImpGetSelectedParagraphs( false ); + ImplExpandOrCollaps( aParas.nStartPara, aParas.nEndPara, false ); +} + + +void OutlinerView::ExpandAll() +{ + ImplExpandOrCollaps( 0, pOwner->pParaList->GetParagraphCount()-1, true ); +} + + +void OutlinerView::CollapseAll() +{ + ImplExpandOrCollaps( 0, pOwner->pParaList->GetParagraphCount()-1, false ); +} + +void OutlinerView::ImplExpandOrCollaps( sal_Int32 nStartPara, sal_Int32 nEndPara, bool bExpand ) +{ + bool bUpdate = pOwner->SetUpdateLayout( false ); + + bool bUndo = !pOwner->IsInUndo() && pOwner->IsUndoEnabled(); + if( bUndo ) + pOwner->UndoActionStart( bExpand ? OLUNDO_EXPAND : OLUNDO_COLLAPSE ); + + for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + bool bDone = bExpand ? pOwner->Expand( pPara ) : pOwner->Collapse( pPara ); + if( bDone ) + { + // The line under the paragraph should disappear ... + pOwner->pEditEngine->QuickMarkToBeRepainted( nPara ); + } + } + + if( bUndo ) + pOwner->UndoActionEnd(); + + if ( bUpdate ) + { + pOwner->SetUpdateLayout( true ); + pEditView->ShowCursor(); + } +} + +void OutlinerView::InsertText( const OutlinerParaObject& rParaObj ) +{ + // Like Paste, only EditView::Insert, instead of EditView::Paste. + // Actually not quite true that possible indentations must be corrected, + // but that comes later by a universal import. The indentation level is + // then determined right in the Inserted method. + // Possible structure: + // pImportInfo with DestPara, DestPos, nFormat, pParaObj... + // Possibly problematic: + // EditEngine, RTF => Splitting the area, later join together. + + if ( ImpCalcSelectedPages( false ) && !pOwner->ImpCanDeleteSelectedPages( this ) ) + return; + + pOwner->UndoActionStart( OLUNDO_INSERT ); + + const bool bPrevUpdateLayout = pOwner->pEditEngine->SetUpdateLayout( false ); + sal_Int32 nStart, nParaCount; + nParaCount = pOwner->pEditEngine->GetParagraphCount(); + sal_uInt16 nSize = ImpInitPaste( nStart ); + pEditView->InsertText( rParaObj.GetTextObject() ); + ImpPasted( nStart, nParaCount, nSize); + pEditView->SetEditEngineUpdateLayout( bPrevUpdateLayout ); + + pOwner->UndoActionEnd(); + + pEditView->ShowCursor(); +} + + +void OutlinerView::Cut() +{ + if ( !ImpCalcSelectedPages( false ) || pOwner->ImpCanDeleteSelectedPages( this ) ) { + pEditView->Cut(); + // Chaining handling + aEndCutPasteLink.Call(nullptr); + } +} + +void OutlinerView::PasteSpecial() +{ + Paste( true ); +} + +void OutlinerView::Paste( bool bUseSpecial ) +{ + if ( ImpCalcSelectedPages( false ) && !pOwner->ImpCanDeleteSelectedPages( this ) ) + return; + + pOwner->UndoActionStart( OLUNDO_INSERT ); + + const bool bPrevUpdateLayout = pOwner->pEditEngine->SetUpdateLayout( false ); + pOwner->bPasting = true; + + if ( bUseSpecial ) + pEditView->PasteSpecial(); + else + pEditView->Paste(); + + if ( pOwner->GetOutlinerMode() == OutlinerMode::OutlineObject ) + { + const sal_Int32 nParaCount = pOwner->pEditEngine->GetParagraphCount(); + + for( sal_Int32 nPara = 0; nPara < nParaCount; nPara++ ) + pOwner->ImplSetLevelDependentStyleSheet( nPara ); + } + + pEditView->SetEditEngineUpdateLayout( bPrevUpdateLayout ); + pOwner->UndoActionEnd(); + pEditView->ShowCursor(); + + // Chaining handling + // NOTE: We need to do this last because it pEditView may be deleted if a switch of box occurs + aEndCutPasteLink.Call(nullptr); +} + +void OutlinerView::CreateSelectionList (std::vector<Paragraph*> &aSelList) +{ + ParaRange aParas = ImpGetSelectedParagraphs( true ); + + for ( sal_Int32 nPara = aParas.nStartPara; nPara <= aParas.nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + aSelList.push_back(pPara); + } +} + +const SfxStyleSheet* OutlinerView::GetStyleSheet() const +{ + return pEditView->GetStyleSheet(); +} + +SfxStyleSheet* OutlinerView::GetStyleSheet() +{ + return pEditView->GetStyleSheet(); +} + +PointerStyle OutlinerView::GetPointer( const Point& rPosPixel ) +{ + MouseTarget eTarget; + ImpCheckMousePos( rPosPixel, eTarget ); + + PointerStyle ePointerStyle = PointerStyle::Arrow; + if ( eTarget == MouseTarget::Text ) + { + ePointerStyle = GetOutliner()->IsVertical() ? PointerStyle::TextVertical : PointerStyle::Text; + } + else if ( eTarget == MouseTarget::Hypertext ) + { + ePointerStyle = PointerStyle::RefHand; + } + else if ( eTarget == MouseTarget::Bullet ) + { + ePointerStyle = PointerStyle::Move; + } + + return ePointerStyle; +} + + +sal_Int32 OutlinerView::ImpInitPaste( sal_Int32& rStart ) +{ + pOwner->bPasting = true; + ESelection aSelection( pEditView->GetSelection() ); + aSelection.Adjust(); + rStart = aSelection.nStartPara; + sal_Int32 nSize = aSelection.nEndPara - aSelection.nStartPara + 1; + return nSize; +} + + +void OutlinerView::ImpPasted( sal_Int32 nStart, sal_Int32 nPrevParaCount, sal_Int32 nSize) +{ + pOwner->bPasting = false; + sal_Int32 nCurParaCount = pOwner->pEditEngine->GetParagraphCount(); + if( nCurParaCount < nPrevParaCount ) + nSize = nSize - ( nPrevParaCount - nCurParaCount ); + else + nSize = nSize + ( nCurParaCount - nPrevParaCount ); + pOwner->ImpTextPasted( nStart, nSize ); +} + +bool OutlinerView::Command(const CommandEvent& rCEvt) +{ + return pEditView->Command(rCEvt); +} + +void OutlinerView::SelectRange( sal_Int32 nFirst, sal_Int32 nCount ) +{ + sal_Int32 nLast = nFirst+nCount; + nCount = pOwner->pParaList->GetParagraphCount(); + if( nLast <= nCount ) + nLast = nCount - 1; + ESelection aSel( nFirst, 0, nLast, EE_TEXTPOS_ALL ); + pEditView->SetSelection( aSel ); +} + + +sal_Int32 OutlinerView::ImpCalcSelectedPages( bool bIncludeFirstSelected ) +{ + ESelection aSel( pEditView->GetSelection() ); + aSel.Adjust(); + + sal_Int32 nPages = 0; + sal_Int32 nFirstPage = EE_PARA_MAX_COUNT; + sal_Int32 nStartPara = aSel.nStartPara; + if ( !bIncludeFirstSelected ) + nStartPara++; // All paragraphs after StartPara will be deleted + for ( sal_Int32 nPara = nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + DBG_ASSERT(pPara, "ImpCalcSelectedPages: invalid Selection? "); + if( pPara->HasFlag(ParaFlag::ISPAGE) ) + { + nPages++; + if( nFirstPage == EE_PARA_MAX_COUNT ) + nFirstPage = nPara; + } + } + + if( nPages ) + { + pOwner->nDepthChangedHdlPrevDepth = nPages; + pOwner->mnFirstSelPage = nFirstPage; + } + + return nPages; +} + + +void OutlinerView::ToggleBullets() +{ + pOwner->UndoActionStart( OLUNDO_DEPTH ); + + ESelection aSel( pEditView->GetSelection() ); + aSel.Adjust(); + + const bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); + + sal_Int16 nNewDepth = -2; + const SvxNumRule* pDefaultBulletNumRule = nullptr; + + for ( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + DBG_ASSERT(pPara, "OutlinerView::ToggleBullets(), illegal selection?"); + + if( pPara ) + { + if( nNewDepth == -2 ) + { + nNewDepth = (pOwner->GetDepth(nPara) == -1) ? 0 : -1; + if ( nNewDepth == 0 ) + { + // determine default numbering rule for bullets + const ESelection aSelection(nPara, 0); + const SfxItemSet aTmpSet(pOwner->pEditEngine->GetAttribs(aSelection)); + const SfxPoolItem& rPoolItem = aTmpSet.GetPool()->GetDefaultItem( EE_PARA_NUMBULLET ); + const SvxNumBulletItem* pNumBulletItem = dynamic_cast< const SvxNumBulletItem* >(&rPoolItem); + pDefaultBulletNumRule = pNumBulletItem ? &pNumBulletItem->GetNumRule() : nullptr; + } + } + + pOwner->SetDepth( pPara, nNewDepth ); + + if( nNewDepth == -1 ) + { + const SfxItemSet& rAttrs = pOwner->GetParaAttribs( nPara ); + if ( rAttrs.GetItemState( EE_PARA_BULLETSTATE ) == SfxItemState::SET ) + { + SfxItemSet aAttrs(rAttrs); + aAttrs.ClearItem( EE_PARA_BULLETSTATE ); + pOwner->SetParaAttribs( nPara, aAttrs ); + } + } + else + { + if ( pDefaultBulletNumRule ) + { + const SvxNumberFormat* pFmt = pOwner ->GetNumberFormat( nPara ); + if ( !pFmt + || ( pFmt->GetNumberingType() != SVX_NUM_BITMAP + && pFmt->GetNumberingType() != SVX_NUM_CHAR_SPECIAL ) ) + { + SfxItemSet aAttrs( pOwner->GetParaAttribs( nPara ) ); + SvxNumRule aNewNumRule( *pDefaultBulletNumRule ); + aAttrs.Put( SvxNumBulletItem( std::move(aNewNumRule), EE_PARA_NUMBULLET ) ); + pOwner->SetParaAttribs( nPara, aAttrs ); + } + } + } + } + } + + const sal_Int32 nParaCount = pOwner->pParaList->GetParagraphCount(); + pOwner->ImplCheckParagraphs( aSel.nStartPara, nParaCount ); + + sal_Int32 nEndPara = (nParaCount > 0) ? nParaCount-1 : nParaCount; + pOwner->pEditEngine->QuickMarkInvalid( ESelection( aSel.nStartPara, 0, nEndPara, 0 ) ); + + pOwner->pEditEngine->SetUpdateLayout( bUpdate ); + + pOwner->UndoActionEnd(); +} + + +void OutlinerView::ToggleBulletsNumbering( + const bool bToggle, + const bool bHandleBullets, + const SvxNumRule* pNumRule ) +{ + ESelection aSel( pEditView->GetSelection() ); + aSel.Adjust(); + + bool bToggleOn = true; + if ( bToggle ) + { + bToggleOn = false; + const sal_Int16 nBulletNumberingStatus( pOwner->GetBulletsNumberingStatus( aSel.nStartPara, aSel.nEndPara ) ); + if ( nBulletNumberingStatus != 0 && bHandleBullets ) + { + // not all paragraphs have bullets and method called to toggle bullets --> bullets on + bToggleOn = true; + } + else if ( nBulletNumberingStatus != 1 && !bHandleBullets ) + { + // not all paragraphs have numbering and method called to toggle numberings --> numberings on + bToggleOn = true; + } + } + if ( bToggleOn ) + { + // apply bullets/numbering for selected paragraphs + ApplyBulletsNumbering( bHandleBullets, pNumRule, bToggle, true ); + } + else + { + // switch off bullets/numbering for selected paragraphs + SwitchOffBulletsNumbering( true ); + } +} + +void OutlinerView::EnsureNumberingIsOn() +{ + pOwner->UndoActionStart(OLUNDO_DEPTH); + + ESelection aSel(pEditView->GetSelection()); + aSel.Adjust(); + + const bool bUpdate = pOwner->pEditEngine->IsUpdateLayout(); + pOwner->pEditEngine->SetUpdateLayout(false); + + for (sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph(nPara); + DBG_ASSERT(pPara, "OutlinerView::EnableBullets(), illegal selection?"); + + if (pPara && pOwner->GetDepth(nPara) == -1) + pOwner->SetDepth(pPara, 0); + } + + sal_Int32 nParaCount = pOwner->pParaList->GetParagraphCount(); + pOwner->ImplCheckParagraphs(aSel.nStartPara, nParaCount); + + const sal_Int32 nEndPara = (nParaCount > 0) ? nParaCount-1 : nParaCount; + pOwner->pEditEngine->QuickMarkInvalid(ESelection(aSel.nStartPara, 0, nEndPara, 0)); + + pOwner->pEditEngine->SetUpdateLayout(bUpdate); + + pOwner->UndoActionEnd(); +} + +void OutlinerView::ApplyBulletsNumbering( + const bool bHandleBullets, + const SvxNumRule* pNewNumRule, + const bool bCheckCurrentNumRuleBeforeApplyingNewNumRule, + const bool bAtSelection ) +{ + if (!pOwner || !pOwner->pEditEngine || !pOwner->pParaList) + { + return; + } + + pOwner->UndoActionStart(OLUNDO_DEPTH); + const bool bUpdate = pOwner->pEditEngine->SetUpdateLayout(false); + + sal_Int32 nStartPara = 0; + sal_Int32 nEndPara = 0; + if ( bAtSelection ) + { + ESelection aSel( pEditView->GetSelection() ); + aSel.Adjust(); + nStartPara = aSel.nStartPara; + nEndPara = aSel.nEndPara; + } + else + { + nStartPara = 0; + nEndPara = pOwner->pParaList->GetParagraphCount() - 1; + } + + for (sal_Int32 nPara = nStartPara; nPara <= nEndPara; ++nPara) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph(nPara); + DBG_ASSERT(pPara, "OutlinerView::ApplyBulletsNumbering(..), illegal selection?"); + + if (pPara) + { + const sal_Int16 nDepth = pOwner->GetDepth(nPara); + if ( nDepth == -1 ) + { + pOwner->SetDepth( pPara, 0 ); + } + + const SfxItemSet& rAttrs = pOwner->GetParaAttribs(nPara); + SfxItemSet aAttrs(rAttrs); + aAttrs.Put(SfxBoolItem(EE_PARA_BULLETSTATE, true)); + + // apply new numbering rule + if ( pNewNumRule ) + { + bool bApplyNumRule = false; + if ( !bCheckCurrentNumRuleBeforeApplyingNewNumRule ) + { + bApplyNumRule = true; + } + else + { + const SvxNumberFormat* pFmt = pOwner ->GetNumberFormat(nPara); + if (!pFmt) + { + bApplyNumRule = true; + } + else + { + sal_Int16 nNumType = pFmt->GetNumberingType(); + if ( bHandleBullets + && nNumType != SVX_NUM_BITMAP && nNumType != SVX_NUM_CHAR_SPECIAL) + { + // Set to Normal bullet, old bullet type is Numbering bullet. + bApplyNumRule = true; + } + else if ( !bHandleBullets + && (nNumType == SVX_NUM_BITMAP || nNumType == SVX_NUM_CHAR_SPECIAL)) + { + // Set to Numbering bullet, old bullet type is Normal bullet. + bApplyNumRule = true; + } + } + } + + if ( bApplyNumRule ) + { + SvxNumRule aNewRule(*pNewNumRule); + + // Get old bullet space. + { + const SvxNumBulletItem* pNumBulletItem = rAttrs.GetItemIfSet(EE_PARA_NUMBULLET, false); + if (pNumBulletItem) + { + // Use default value when has not contain bullet item. + ESelection aSelection(nPara, 0); + SfxItemSet aTmpSet(pOwner->pEditEngine->GetAttribs(aSelection)); + pNumBulletItem = aTmpSet.GetItem(EE_PARA_NUMBULLET); + } + + if (pNumBulletItem) + { + const sal_uInt16 nLevelCnt = std::min(pNumBulletItem->GetNumRule().GetLevelCount(), aNewRule.GetLevelCount()); + for ( sal_uInt16 nLevel = 0; nLevel < nLevelCnt; ++nLevel ) + { + const SvxNumberFormat* pOldFmt = pNumBulletItem->GetNumRule().Get(nLevel); + const SvxNumberFormat* pNewFmt = aNewRule.Get(nLevel); + if (pOldFmt && pNewFmt && (pOldFmt->GetFirstLineOffset() != pNewFmt->GetFirstLineOffset() || pOldFmt->GetAbsLSpace() != pNewFmt->GetAbsLSpace())) + { + SvxNumberFormat aNewFmtClone(*pNewFmt); + aNewFmtClone.SetFirstLineOffset(pOldFmt->GetFirstLineOffset()); + aNewFmtClone.SetAbsLSpace(pOldFmt->GetAbsLSpace()); + aNewRule.SetLevel(nLevel, &aNewFmtClone); + } + } + } + } + + aAttrs.Put(SvxNumBulletItem(std::move(aNewRule), EE_PARA_NUMBULLET)); + } + } + pOwner->SetParaAttribs(nPara, aAttrs); + } + } + + const sal_uInt16 nParaCount = static_cast<sal_uInt16>(pOwner->pParaList->GetParagraphCount()); + pOwner->ImplCheckParagraphs( nStartPara, nParaCount ); + pOwner->pEditEngine->QuickMarkInvalid( ESelection( nStartPara, 0, nParaCount, 0 ) ); + + pOwner->pEditEngine->SetUpdateLayout( bUpdate ); + + pOwner->UndoActionEnd(); +} + + +void OutlinerView::SwitchOffBulletsNumbering( + const bool bAtSelection ) +{ + sal_Int32 nStartPara = 0; + sal_Int32 nEndPara = 0; + if ( bAtSelection ) + { + ESelection aSel( pEditView->GetSelection() ); + aSel.Adjust(); + nStartPara = aSel.nStartPara; + nEndPara = aSel.nEndPara; + } + else + { + nStartPara = 0; + nEndPara = pOwner->pParaList->GetParagraphCount() - 1; + } + + pOwner->UndoActionStart( OLUNDO_DEPTH ); + const bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); + + for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; ++nPara ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + DBG_ASSERT(pPara, "OutlinerView::SwitchOffBulletsNumbering(...), illegal paragraph index?"); + + if( pPara ) + { + pOwner->SetDepth( pPara, -1 ); + + const SfxItemSet& rAttrs = pOwner->GetParaAttribs( nPara ); + if (rAttrs.GetItemState( EE_PARA_BULLETSTATE ) == SfxItemState::SET) + { + SfxItemSet aAttrs(rAttrs); + aAttrs.ClearItem( EE_PARA_BULLETSTATE ); + pOwner->SetParaAttribs( nPara, aAttrs ); + } + } + } + + const sal_uInt16 nParaCount = static_cast<sal_uInt16>(pOwner->pParaList->GetParagraphCount()); + pOwner->ImplCheckParagraphs( nStartPara, nParaCount ); + pOwner->pEditEngine->QuickMarkInvalid( ESelection( nStartPara, 0, nParaCount, 0 ) ); + + pOwner->pEditEngine->SetUpdateLayout( bUpdate ); + pOwner->UndoActionEnd(); +} + + +void OutlinerView::RemoveAttribsKeepLanguages( bool bRemoveParaAttribs ) +{ + RemoveAttribs( bRemoveParaAttribs, true /*keep language attribs*/ ); +} + +void OutlinerView::RemoveAttribs( bool bRemoveParaAttribs, bool bKeepLanguages ) +{ + bool bUpdate = pOwner->SetUpdateLayout( false ); + pOwner->UndoActionStart( OLUNDO_ATTR ); + if (bKeepLanguages) + pEditView->RemoveAttribsKeepLanguages( bRemoveParaAttribs ); + else + pEditView->RemoveAttribs( bRemoveParaAttribs ); + if ( bRemoveParaAttribs ) + { + // Loop through all paragraphs and set indentation and level + ESelection aSel = pEditView->GetSelection(); + aSel.Adjust(); + for ( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + pOwner->ImplInitDepth( nPara, pPara->GetDepth(), false ); + } + } + pOwner->UndoActionEnd(); + pOwner->SetUpdateLayout( bUpdate ); +} + + +// ====================== Simple pass-through ======================= + + +void OutlinerView::InsertText( const OUString& rNew, bool bSelect ) +{ + if( pOwner->bFirstParaIsEmpty ) + pOwner->Insert( OUString() ); + pEditView->InsertText( rNew, bSelect ); +} + +void OutlinerView::SetVisArea( const tools::Rectangle& rRect ) +{ + pEditView->SetVisArea( rRect ); +} + + +void OutlinerView::SetSelection( const ESelection& rSel ) +{ + pEditView->SetSelection( rSel ); +} + +void OutlinerView::GetSelectionRectangles(std::vector<tools::Rectangle>& rLogicRects) const +{ + pEditView->GetSelectionRectangles(rLogicRects); +} + +void OutlinerView::SetReadOnly( bool bReadOnly ) +{ + pEditView->SetReadOnly( bReadOnly ); +} + +bool OutlinerView::IsReadOnly() const +{ + return pEditView->IsReadOnly(); +} + +bool OutlinerView::HasSelection() const +{ + return pEditView->HasSelection(); +} + +void OutlinerView::ShowCursor( bool bGotoCursor, bool bActivate ) +{ + pEditView->ShowCursor( bGotoCursor, /*bForceVisCursor=*/true, bActivate ); +} + +void OutlinerView::HideCursor(bool bDeactivate) +{ + pEditView->HideCursor(bDeactivate); +} + +void OutlinerView::SetWindow( vcl::Window* pWin ) +{ + pEditView->SetWindow( pWin ); +} + +vcl::Window* OutlinerView::GetWindow() const +{ + return pEditView->GetWindow(); +} + +void OutlinerView::SetOutputArea( const tools::Rectangle& rRect ) +{ + pEditView->SetOutputArea( rRect ); +} + +tools::Rectangle const & OutlinerView::GetOutputArea() const +{ + return pEditView->GetOutputArea(); +} + +OUString OutlinerView::GetSelected() const +{ + return pEditView->GetSelected(); +} + +void OutlinerView::StartSpeller(weld::Widget* pDialogParent) +{ + pEditView->StartSpeller(pDialogParent); +} + +EESpellState OutlinerView::StartThesaurus(weld::Widget* pDialogParent) +{ + return pEditView->StartThesaurus(pDialogParent); +} + +void OutlinerView::StartTextConversion(weld::Widget* pDialogParent, + LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont, + sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc ) +{ + if ( + (LANGUAGE_KOREAN == nSrcLang && LANGUAGE_KOREAN == nDestLang) || + (LANGUAGE_CHINESE_SIMPLIFIED == nSrcLang && LANGUAGE_CHINESE_TRADITIONAL == nDestLang) || + (LANGUAGE_CHINESE_TRADITIONAL == nSrcLang && LANGUAGE_CHINESE_SIMPLIFIED == nDestLang) + ) + { + pEditView->StartTextConversion(pDialogParent, nSrcLang, nDestLang, pDestFont, nOptions, bIsInteractive, bMultipleDoc); + } + else + { + OSL_FAIL( "unexpected language" ); + } +} + + +sal_Int32 OutlinerView::StartSearchAndReplace( const SvxSearchItem& rSearchItem ) +{ + return pEditView->StartSearchAndReplace( rSearchItem ); +} + +void OutlinerView::TransliterateText( TransliterationFlags nTransliterationMode ) +{ + pEditView->TransliterateText( nTransliterationMode ); +} + +ESelection OutlinerView::GetSelection() const +{ + return pEditView->GetSelection(); +} + + +void OutlinerView::Scroll( tools::Long nHorzScroll, tools::Long nVertScroll ) +{ + pEditView->Scroll( nHorzScroll, nVertScroll ); +} + +void OutlinerView::SetControlWord( EVControlBits nWord ) +{ + pEditView->SetControlWord( nWord ); +} + +EVControlBits OutlinerView::GetControlWord() const +{ + return pEditView->GetControlWord(); +} + +void OutlinerView::SetAnchorMode( EEAnchorMode eMode ) +{ + pEditView->SetAnchorMode( eMode ); +} + +EEAnchorMode OutlinerView::GetAnchorMode() const +{ + return pEditView->GetAnchorMode(); +} + +void OutlinerView::Copy() +{ + pEditView->Copy(); +} + +void OutlinerView::InsertField( const SvxFieldItem& rFld ) +{ + pEditView->InsertField( rFld ); +} + +const SvxFieldItem* OutlinerView::GetFieldUnderMousePointer() const +{ + return pEditView->GetFieldUnderMousePointer(); +} + +const SvxFieldItem* OutlinerView::GetFieldAtSelection() const +{ + return pEditView->GetFieldAtSelection(); +} + +const SvxFieldData* OutlinerView::GetFieldAtCursor() const +{ + return pEditView->GetFieldAtCursor(); +} + +void OutlinerView::SelectFieldAtCursor() +{ + pEditView->SelectFieldAtCursor(); +} + +void OutlinerView::SetInvalidateMore( sal_uInt16 nPixel ) +{ + pEditView->SetInvalidateMore( nPixel ); +} + + +sal_uInt16 OutlinerView::GetInvalidateMore() const +{ + return pEditView->GetInvalidateMore(); +} + + +bool OutlinerView::IsCursorAtWrongSpelledWord() +{ + return pEditView->IsCursorAtWrongSpelledWord(); +} + + +bool OutlinerView::IsWrongSpelledWordAtPos( const Point& rPosPixel ) +{ + return pEditView->IsWrongSpelledWordAtPos( rPosPixel, /*bMarkIfWrong*/false ); +} + +void OutlinerView::ExecuteSpellPopup(const Point& rPosPixel, const Link<SpellCallbackInfo&,void>& rStartDlg) +{ + pEditView->ExecuteSpellPopup(rPosPixel, rStartDlg); +} + +void OutlinerView::Read( SvStream& rInput, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs ) +{ + sal_Int32 nOldParaCount = pEditView->GetEditEngine()->GetParagraphCount(); + ESelection aOldSel = pEditView->GetSelection(); + aOldSel.Adjust(); + + pEditView->Read( rInput, eFormat, pHTTPHeaderAttrs ); + + tools::Long nParaDiff = pEditView->GetEditEngine()->GetParagraphCount() - nOldParaCount; + sal_Int32 nChangesStart = aOldSel.nStartPara; + sal_Int32 nChangesEnd = nChangesStart + nParaDiff + (aOldSel.nEndPara-aOldSel.nStartPara); + + for ( sal_Int32 n = nChangesStart; n <= nChangesEnd; n++ ) + { + if ( pOwner->GetOutlinerMode() == OutlinerMode::OutlineObject ) + pOwner->ImplSetLevelDependentStyleSheet( n ); + } + + pOwner->ImpFilterIndents( nChangesStart, nChangesEnd ); +} + +void OutlinerView::SetBackgroundColor( const Color& rColor ) +{ + pEditView->SetBackgroundColor( rColor ); +} + +void OutlinerView::RegisterViewShell(OutlinerViewShell* pViewShell) +{ + pEditView->RegisterViewShell(pViewShell); +} + +Color const & OutlinerView::GetBackgroundColor() const +{ + return pEditView->GetBackgroundColor(); +} + +SfxItemSet OutlinerView::GetAttribs() +{ + return pEditView->GetAttribs(); +} + +SvtScriptType OutlinerView::GetSelectedScriptType() const +{ + return pEditView->GetSelectedScriptType(); +} + +OUString OutlinerView::GetSurroundingText() const +{ + return pEditView->GetSurroundingText(); +} + +Selection OutlinerView::GetSurroundingTextSelection() const +{ + return pEditView->GetSurroundingTextSelection(); +} + +bool OutlinerView::DeleteSurroundingText(const Selection& rSelection) +{ + return pEditView->DeleteSurroundingText(rSelection); +} + +// ===== some code for thesaurus sub menu within context menu + +namespace { + +bool isSingleScriptType( SvtScriptType nScriptType ) +{ + sal_uInt8 nScriptCount = 0; + + if (nScriptType & SvtScriptType::LATIN) + ++nScriptCount; + if (nScriptType & SvtScriptType::ASIAN) + ++nScriptCount; + if (nScriptType & SvtScriptType::COMPLEX) + ++nScriptCount; + + return nScriptCount == 1; +} + +} + +// returns: true if a word for thesaurus look-up was found at the current cursor position. +// The status string will be word + iso language string (e.g. "light#en-US") +bool GetStatusValueForThesaurusFromContext( + OUString &rStatusVal, + LanguageType &rLang, + const EditView &rEditView ) +{ + // get text and locale for thesaurus look up + OUString aText; + EditEngine *pEditEngine = rEditView.GetEditEngine(); + ESelection aTextSel( rEditView.GetSelection() ); + if (!aTextSel.HasRange()) + aTextSel = pEditEngine->GetWord( aTextSel, i18n::WordType::DICTIONARY_WORD ); + aText = pEditEngine->GetText( aTextSel ); + aTextSel.Adjust(); + + if (!isSingleScriptType(pEditEngine->GetScriptType(aTextSel))) + return false; + + LanguageType nLang = pEditEngine->GetLanguage( aTextSel.nStartPara, aTextSel.nStartPos ).nLang; + OUString aLangText( LanguageTag::convertToBcp47( nLang ) ); + + // set word and locale to look up as status value + rStatusVal = aText + "#" + aLangText; + rLang = nLang; + + return aText.getLength() > 0; +} + + +void ReplaceTextWithSynonym( EditView &rEditView, const OUString &rSynonmText ) +{ + // get selection to use + ESelection aCurSel( rEditView.GetSelection() ); + if (!rEditView.HasSelection()) + { + // select the same word that was used in GetStatusValueForThesaurusFromContext by calling GetWord. + // (In the end both functions will call ImpEditEngine::SelectWord) + rEditView.SelectCurrentWord( i18n::WordType::DICTIONARY_WORD ); + aCurSel = rEditView.GetSelection(); + } + + // replace word ... + rEditView.InsertText( rSynonmText ); + rEditView.ShowCursor( true, false ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/overflowingtxt.cxx b/editeng/source/outliner/overflowingtxt.cxx new file mode 100644 index 000000000..42316fa1f --- /dev/null +++ b/editeng/source/outliner/overflowingtxt.cxx @@ -0,0 +1,227 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <sal/log.hxx> + +#include <editeng/overflowingtxt.hxx> +#include <editeng/outliner.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editobj.hxx> +#include <editeng/editdata.hxx> + +#include <editdoc.hxx> +#include <utility> + + +std::optional<OutlinerParaObject> TextChainingUtils::JuxtaposeParaObject( + css::uno::Reference< css::datatransfer::XTransferable > const & xOverflowingContent, + Outliner *pOutl, + OutlinerParaObject const *pNextPObj) +{ + if (!pNextPObj) { + pOutl->SetToEmptyText(); + } else { + pOutl->SetText(*pNextPObj); + } + + // Special case: if only empty text remove it at the end + bool bOnlyOneEmptyPara = !pNextPObj || + (pOutl->GetParagraphCount() == 1 && + pNextPObj->GetTextObject().GetText(0).isEmpty()); + + EditEngine &rEditEngine = const_cast<EditEngine &>(pOutl->GetEditEngine()); + + // XXX: this code should be moved in Outliner directly + // creating Outliner::InsertText(...transferable...) + EditSelection aStartSel(rEditEngine.CreateSelection(ESelection(0,0))); + EditSelection aNewSel = rEditEngine.InsertText(xOverflowingContent, + OUString(), + aStartSel.Min(), + true); + + if (!bOnlyOneEmptyPara) { + // Separate Paragraphs + rEditEngine.InsertParaBreak(aNewSel); + } + + + return pOutl->CreateParaObject(); +} + +std::optional<OutlinerParaObject> TextChainingUtils::DeeplyMergeParaObject( + css::uno::Reference< css::datatransfer::XTransferable > const & xOverflowingContent, + Outliner *pOutl, + OutlinerParaObject const *pNextPObj) +{ + if (!pNextPObj) { + pOutl->SetToEmptyText(); + } else { + pOutl->SetText(*pNextPObj); + } + + EditEngine &rEditEngine = const_cast<EditEngine &>(pOutl->GetEditEngine()); + + // XXX: this code should be moved in Outliner directly + // creating Outliner::InsertText(...transferable...) + EditSelection aStartSel(rEditEngine.CreateSelection(ESelection(0,0))); + // We don't need to mark the selection + // EditSelection aNewSel = + rEditEngine.InsertText(xOverflowingContent, + OUString(), + aStartSel.Min(), + true); + + return pOutl->CreateParaObject(); +} + +css::uno::Reference< css::datatransfer::XTransferable > TextChainingUtils::CreateTransferableFromText(Outliner const *pOutl) +{ + const EditEngine &rEditEngine = pOutl->GetEditEngine(); + sal_Int32 nLastPara = pOutl->GetParagraphCount()-1; + ESelection aWholeTextSel(0, 0, nLastPara, rEditEngine.GetTextLen(nLastPara)); + + return rEditEngine.CreateTransferable(aWholeTextSel); +} + + + +OverflowingText::OverflowingText(css::uno::Reference< css::datatransfer::XTransferable > xOverflowingContent) : + mxOverflowingContent(std::move(xOverflowingContent)) +{ + +} + + + +NonOverflowingText::NonOverflowingText(const ESelection &aSel, bool bLastParaInterrupted) + : maContentSel(aSel) + , mbLastParaInterrupted(bLastParaInterrupted) +{ +} + +bool NonOverflowingText::IsLastParaInterrupted() const +{ + return mbLastParaInterrupted; +} + + +std::optional<OutlinerParaObject> NonOverflowingText::RemoveOverflowingText(Outliner *pOutliner) const +{ + pOutliner->QuickDelete(maContentSel); + SAL_INFO("editeng.chaining", "Deleting selection from (Para: " << maContentSel.nStartPara + << ", Pos: " << maContentSel.nStartPos << ") to (Para: " << maContentSel.nEndPara + << ", Pos: " << maContentSel.nEndPos << ")"); + return pOutliner->CreateParaObject(); +} + +ESelection NonOverflowingText::GetOverflowPointSel() const +{ + //return getLastPositionSel(mpContentTextObj); + + // return the starting point of the selection we are removing + return ESelection(maContentSel.nStartPara, maContentSel.nStartPos); //XXX +} + +// The equivalent of ToParaObject for OverflowingText. Here we are prepending the overflowing text to the old dest box's text +// XXX: In a sense a better name for OverflowingText and NonOverflowingText are respectively DestLinkText and SourceLinkText +std::optional<OutlinerParaObject> OverflowingText::JuxtaposeParaObject(Outliner *pOutl, OutlinerParaObject const *pNextPObj) +{ + return TextChainingUtils::JuxtaposeParaObject(mxOverflowingContent, pOutl, pNextPObj); +} + +std::optional<OutlinerParaObject> OverflowingText::DeeplyMergeParaObject(Outliner *pOutl, OutlinerParaObject const *pNextPObj) +{ + return TextChainingUtils::DeeplyMergeParaObject(mxOverflowingContent, pOutl, pNextPObj); +} + + +OFlowChainedText::OFlowChainedText(Outliner const *pOutl, bool bIsDeepMerge) +{ + mpOverflowingTxt = pOutl->GetOverflowingText(); + mpNonOverflowingTxt = pOutl->GetNonOverflowingText(); + + mbIsDeepMerge = bIsDeepMerge; +} + +OFlowChainedText::~OFlowChainedText() +{ +} + + +ESelection OFlowChainedText::GetOverflowPointSel() const +{ + return mpNonOverflowingTxt->GetOverflowPointSel(); +} + +std::optional<OutlinerParaObject> OFlowChainedText::InsertOverflowingText(Outliner *pOutliner, OutlinerParaObject const *pTextToBeMerged) +{ + // Just return the roughly merged paras for now + if (!mpOverflowingTxt) + return std::nullopt; + + if (mbIsDeepMerge) { + SAL_INFO("editeng.chaining", "[TEXTCHAINFLOW - OF] Deep merging paras" ); + return mpOverflowingTxt->DeeplyMergeParaObject(pOutliner, pTextToBeMerged ); + } else { + SAL_INFO("editeng.chaining", "[TEXTCHAINFLOW - OF] Juxtaposing paras" ); + return mpOverflowingTxt->JuxtaposeParaObject(pOutliner, pTextToBeMerged ); + } +} + + +std::optional<OutlinerParaObject> OFlowChainedText::RemoveOverflowingText(Outliner *pOutliner) +{ + if (!mpNonOverflowingTxt) + return std::nullopt; + + return mpNonOverflowingTxt->RemoveOverflowingText(pOutliner); +} + +bool OFlowChainedText::IsLastParaInterrupted() const +{ + return mpNonOverflowingTxt->IsLastParaInterrupted(); +} + + + +UFlowChainedText::UFlowChainedText(Outliner const *pOutl, bool bIsDeepMerge) +{ + mxUnderflowingTxt = TextChainingUtils::CreateTransferableFromText(pOutl); + mbIsDeepMerge = bIsDeepMerge; +} + +std::optional<OutlinerParaObject> UFlowChainedText::CreateMergedUnderflowParaObject(Outliner *pOutl, OutlinerParaObject const *pNextLinkWholeText) +{ + std::optional<OutlinerParaObject> pNewText; + + if (mbIsDeepMerge) { + SAL_INFO("editeng.chaining", "[TEXTCHAINFLOW - UF] Deep merging paras" ); + pNewText = TextChainingUtils::DeeplyMergeParaObject(mxUnderflowingTxt, pOutl, pNextLinkWholeText); + } else { + // NewTextForCurBox = Txt(CurBox) ++ Txt(NextBox) + SAL_INFO("editeng.chaining", "[TEXTCHAINFLOW - UF] Juxtaposing paras" ); + pNewText = TextChainingUtils::JuxtaposeParaObject(mxUnderflowingTxt, pOutl, pNextLinkWholeText); + } + + return pNewText; + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/paralist.cxx b/editeng/source/outliner/paralist.cxx new file mode 100644 index 000000000..5b9f21449 --- /dev/null +++ b/editeng/source/outliner/paralist.cxx @@ -0,0 +1,256 @@ +/* -*- 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 "paralist.hxx" + +#include <editeng/outliner.hxx> +#include <editeng/numdef.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <libxml/xmlwriter.h> + +ParagraphData::ParagraphData() +: nDepth( -1 ) +, mnNumberingStartValue( -1 ) +, mbParaIsNumberingRestart( false ) +{ +} + +bool ParagraphData::operator==(const ParagraphData& rCandidate) const +{ + return (nDepth == rCandidate.nDepth + && mnNumberingStartValue == rCandidate.mnNumberingStartValue + && mbParaIsNumberingRestart == rCandidate.mbParaIsNumberingRestart); +} + +Paragraph::Paragraph( sal_Int16 nDDepth ) +: aBulSize( -1, -1) +{ + + DBG_ASSERT( ( nDDepth >= -1 ) && ( nDDepth < SVX_MAX_NUM ), "Paragraph-CTOR: nDepth invalid!" ); + + nDepth = nDDepth; + nFlags = ParaFlag::NONE; + bVisible = true; +} + +Paragraph::Paragraph( const ParagraphData& rData ) +: aBulSize( -1, -1) +, nFlags( ParaFlag::NONE ) +, bVisible( true ) +{ + nDepth = rData.nDepth; + mnNumberingStartValue = rData.mnNumberingStartValue; + mbParaIsNumberingRestart = rData.mbParaIsNumberingRestart; +} + +Paragraph::~Paragraph() +{ +} + +void Paragraph::SetNumberingStartValue( sal_Int16 nNumberingStartValue ) +{ + mnNumberingStartValue = nNumberingStartValue; + if( mnNumberingStartValue != -1 ) + mbParaIsNumberingRestart = true; +} + +void Paragraph::SetParaIsNumberingRestart( bool bParaIsNumberingRestart ) +{ + mbParaIsNumberingRestart = bParaIsNumberingRestart; + if( !mbParaIsNumberingRestart ) + mnNumberingStartValue = -1; +} + +void Paragraph::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("Paragraph")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("nDepth"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(nDepth)); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("mnNumberingStartValue"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(mnNumberingStartValue)); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("mbParaIsNumberingRestart"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(mbParaIsNumberingRestart)); + (void)xmlTextWriterEndElement(pWriter); +} + +void ParagraphList::Clear() +{ + maEntries.clear(); +} + +void ParagraphList::Append( std::unique_ptr<Paragraph> pPara) +{ + SAL_WARN_IF( maEntries.size() >= EE_PARA_MAX_COUNT, "editeng", "ParagraphList::Append - overflow"); + maEntries.push_back(std::move(pPara)); +} + +void ParagraphList::Insert( std::unique_ptr<Paragraph> pPara, sal_Int32 nAbsPos) +{ + SAL_WARN_IF( nAbsPos < 0 || (maEntries.size() < o3tl::make_unsigned(nAbsPos) && nAbsPos != EE_PARA_APPEND), + "editeng", "ParagraphList::Insert - bad insert position " << nAbsPos); + SAL_WARN_IF( maEntries.size() >= EE_PARA_MAX_COUNT, "editeng", "ParagraphList::Insert - overflow"); + + if (nAbsPos < 0 || maEntries.size() <= o3tl::make_unsigned(nAbsPos)) + Append( std::move(pPara) ); + else + maEntries.insert(maEntries.begin()+nAbsPos, std::move(pPara)); +} + +void ParagraphList::Remove( sal_Int32 nPara ) +{ + if (nPara < 0 || maEntries.size() <= o3tl::make_unsigned(nPara)) + { + SAL_WARN( "editeng", "ParagraphList::Remove - out of bounds " << nPara); + return; + } + + maEntries.erase(maEntries.begin() + nPara ); +} + +void ParagraphList::MoveParagraphs( sal_Int32 nStart, sal_Int32 nDest, sal_Int32 _nCount ) +{ + OSL_ASSERT(o3tl::make_unsigned(nStart) < maEntries.size() && o3tl::make_unsigned(nDest) < maEntries.size()); + + if ( (( nDest < nStart ) || ( nDest >= ( nStart + _nCount ) )) && nStart >= 0 && nDest >= 0 && _nCount >= 0 ) + { + std::vector<std::unique_ptr<Paragraph>> aParas; + auto iterBeg = maEntries.begin() + nStart; + auto iterEnd = iterBeg + _nCount; + + for (auto it = iterBeg; it != iterEnd; ++it) + aParas.push_back(std::move(*it)); + + maEntries.erase(iterBeg,iterEnd); + + if ( nDest > nStart ) + nDest -= _nCount; + + for (auto & i : aParas) + { + maEntries.insert(maEntries.begin() + nDest, std::move(i)); + ++nDest; + } + } + else + { + OSL_FAIL( "MoveParagraphs: Invalid Parameters" ); + } +} + +bool ParagraphList::HasChildren( Paragraph const * pParagraph ) const +{ + sal_Int32 n = GetAbsPos( pParagraph ); + Paragraph* pNext = GetParagraph( ++n ); + return pNext && ( pNext->GetDepth() > pParagraph->GetDepth() ); +} + +bool ParagraphList::HasHiddenChildren( Paragraph const * pParagraph ) const +{ + sal_Int32 n = GetAbsPos( pParagraph ); + Paragraph* pNext = GetParagraph( ++n ); + return pNext && ( pNext->GetDepth() > pParagraph->GetDepth() ) && !pNext->IsVisible(); +} + +bool ParagraphList::HasVisibleChildren( Paragraph const * pParagraph ) const +{ + sal_Int32 n = GetAbsPos( pParagraph ); + Paragraph* pNext = GetParagraph( ++n ); + return pNext && ( pNext->GetDepth() > pParagraph->GetDepth() ) && pNext->IsVisible(); +} + +sal_Int32 ParagraphList::GetChildCount( Paragraph const * pParent ) const +{ + sal_Int32 nChildCount = 0; + sal_Int32 n = GetAbsPos( pParent ); + Paragraph* pPara = GetParagraph( ++n ); + while ( pPara && ( pPara->GetDepth() > pParent->GetDepth() ) ) + { + nChildCount++; + pPara = GetParagraph( ++n ); + } + return nChildCount; +} + +Paragraph* ParagraphList::GetParent( Paragraph const * pParagraph ) const +{ + sal_Int32 n = GetAbsPos( pParagraph ); + Paragraph* pPrev = GetParagraph( --n ); + while ( pPrev && ( pPrev->GetDepth() >= pParagraph->GetDepth() ) ) + { + pPrev = GetParagraph( --n ); + } + + return pPrev; +} + +void ParagraphList::Expand( Paragraph const * pParent ) +{ + sal_Int32 nChildCount = GetChildCount( pParent ); + sal_Int32 nPos = GetAbsPos( pParent ); + + for ( sal_Int32 n = 1; n <= nChildCount; n++ ) + { + Paragraph* pPara = GetParagraph( nPos+n ); + if ( !( pPara->IsVisible() ) ) + { + pPara->bVisible = true; + aVisibleStateChangedHdl.Call( *pPara ); + } + } +} + +void ParagraphList::Collapse( Paragraph const * pParent ) +{ + sal_Int32 nChildCount = GetChildCount( pParent ); + sal_Int32 nPos = GetAbsPos( pParent ); + + for ( sal_Int32 n = 1; n <= nChildCount; n++ ) + { + Paragraph* pPara = GetParagraph( nPos+n ); + if ( pPara->IsVisible() ) + { + pPara->bVisible = false; + aVisibleStateChangedHdl.Call( *pPara ); + } + } +} + +sal_Int32 ParagraphList::GetAbsPos( Paragraph const * pParent ) const +{ + sal_Int32 pos = 0; + for (auto const& entry : maEntries) + { + if (entry.get() == pParent) + return pos; + ++pos; + } + + return EE_PARA_NOT_FOUND; +} + +void ParagraphList::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ParagraphList")); + for (auto const & pParagraph : maEntries) + pParagraph->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/paralist.hxx b/editeng/source/outliner/paralist.hxx new file mode 100644 index 000000000..47413ff5f --- /dev/null +++ b/editeng/source/outliner/paralist.hxx @@ -0,0 +1,82 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> + +#include <memory> +#include <vector> + +#include <editeng/outliner.hxx> +#include <o3tl/safeint.hxx> +#include <tools/link.hxx> + +class Paragraph; +typedef struct _xmlTextWriter* xmlTextWriterPtr; + +class ParagraphList +{ +public: + void Clear(); + + sal_Int32 GetParagraphCount() const + { + size_t nSize = maEntries.size(); + if (nSize > SAL_MAX_INT32) + { + SAL_WARN( "editeng", "ParagraphList::GetParagraphCount - overflow " << nSize); + return SAL_MAX_INT32; + } + return nSize; + } + + Paragraph* GetParagraph( sal_Int32 nPos ) const + { + return 0 <= nPos && o3tl::make_unsigned(nPos) < maEntries.size() ? maEntries[nPos].get() : nullptr; + } + + sal_Int32 GetAbsPos( Paragraph const * pParent ) const; + + void Append( std::unique_ptr<Paragraph> pPara); + void Insert( std::unique_ptr<Paragraph> pPara, sal_Int32 nAbsPos); + void Remove( sal_Int32 nPara ); + void MoveParagraphs( sal_Int32 nStart, sal_Int32 nDest, sal_Int32 nCount ); + + Paragraph* GetParent( Paragraph const * pParagraph ) const; + bool HasChildren( Paragraph const * pParagraph ) const; + bool HasHiddenChildren( Paragraph const * pParagraph ) const; + bool HasVisibleChildren( Paragraph const * pParagraph ) const; + sal_Int32 GetChildCount( Paragraph const * pParagraph ) const; + + void Expand( Paragraph const * pParent ); + void Collapse( Paragraph const * pParent ); + + void SetVisibleStateChangedHdl( const Link<Paragraph&,void>& rLink ) { aVisibleStateChangedHdl = rLink; } + + void dumpAsXml(xmlTextWriterPtr pWriter) const; + +private: + + Link<Paragraph&,void> aVisibleStateChangedHdl; + std::vector<std::unique_ptr<Paragraph>> maEntries; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/rtf/rtfitem.cxx b/editeng/source/rtf/rtfitem.cxx new file mode 100644 index 000000000..b74b11879 --- /dev/null +++ b/editeng/source/rtf/rtfitem.cxx @@ -0,0 +1,1872 @@ +/* -*- 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 <editeng/fontitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/twolinesitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/spltitem.hxx> +#include <editeng/hyphenzoneitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/paravertalignitem.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include <editeng/hngpnctitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/charhiddenitem.hxx> + +#include <svtools/rtftoken.h> +#include <svl/itempool.hxx> +#include <svl/itemiter.hxx> +#include <sal/log.hxx> +#include <vcl/font.hxx> + +#include <editeng/svxrtf.hxx> +#include <editeng/editids.hrc> + +#include <limits.h> + +#define BRACELEFT '{' +#define BRACERIGHT '}' + +using namespace ::com::sun::star; +using namespace editeng; + +void SvxRTFParser::SetScriptAttr( RTF_CharTypeDef eType, SfxItemSet& rSet, + SfxPoolItem& rItem ) +{ + const sal_uInt16 *pNormal = nullptr, *pCJK = nullptr, *pCTL = nullptr; + switch( rItem.Which() ) + { + case SID_ATTR_CHAR_FONT: + pNormal = &aPlainMap[SID_ATTR_CHAR_FONT]; + pCJK = &aPlainMap[SID_ATTR_CHAR_CJK_FONT]; + pCTL = &aPlainMap[SID_ATTR_CHAR_CTL_FONT]; + break; + + case SID_ATTR_CHAR_FONTHEIGHT: + pNormal = &aPlainMap[SID_ATTR_CHAR_FONTHEIGHT]; + pCJK = &aPlainMap[SID_ATTR_CHAR_CJK_FONTHEIGHT]; + pCTL = &aPlainMap[SID_ATTR_CHAR_CTL_FONTHEIGHT]; + break; + + case SID_ATTR_CHAR_POSTURE: + pNormal = &aPlainMap[SID_ATTR_CHAR_POSTURE]; + pCJK = &aPlainMap[SID_ATTR_CHAR_CJK_POSTURE]; + pCTL = &aPlainMap[SID_ATTR_CHAR_CTL_POSTURE]; + break; + + case SID_ATTR_CHAR_WEIGHT: + pNormal = &aPlainMap[SID_ATTR_CHAR_WEIGHT]; + pCJK = &aPlainMap[SID_ATTR_CHAR_CJK_WEIGHT]; + pCTL = &aPlainMap[SID_ATTR_CHAR_CTL_WEIGHT]; + break; + + case SID_ATTR_CHAR_LANGUAGE: + pNormal = &aPlainMap[SID_ATTR_CHAR_LANGUAGE]; + pCJK = &aPlainMap[SID_ATTR_CHAR_CJK_LANGUAGE]; + pCTL = &aPlainMap[SID_ATTR_CHAR_CTL_LANGUAGE]; + break; + + case 0: + // it exist no WhichId - don't set this item + break; + + default: + rSet.Put( rItem ); + break; + } + + if( DOUBLEBYTE_CHARTYPE == eType ) + { + if( bIsLeftToRightDef && pCJK ) + { + rItem.SetWhich( *pCJK ); + rSet.Put( rItem ); + } + } + else if( !bIsLeftToRightDef ) + { + if( pCTL ) + { + rItem.SetWhich( *pCTL ); + rSet.Put( rItem ); + } + } + else + { + if( LOW_CHARTYPE == eType ) + { + if( pNormal ) + { + rItem.SetWhich( *pNormal ); + rSet.Put( rItem ); + } + } + else if( HIGH_CHARTYPE == eType ) + { + if( pCTL ) + { + rItem.SetWhich( *pCTL ); + rSet.Put( rItem ); + } + } + else + { + if( pCJK ) + { + rItem.SetWhich( *pCJK ); + rSet.Put( rItem ); + } + if( pCTL ) + { + rItem.SetWhich( *pCTL ); + rSet.Put( rItem ); + } + if( pNormal ) + { + rItem.SetWhich( *pNormal ); + rSet.Put( rItem ); + } + } + } +} + + +void SvxRTFParser::ReadAttr( int nToken, SfxItemSet* pSet ) +{ + DBG_ASSERT( pSet, "A SfxItemSet has to be provided as argument!" ); + bool bFirstToken = true; + bool bContinue = true; + FontLineStyle eUnderline; + FontLineStyle eOverline; + FontEmphasisMark eEmphasis; + RTF_CharTypeDef eCharType = NOTDEF_CHARTYPE; + SvxParaVertAlignItem::Align nFontAlign; + + bool bChkStkPos = !bNewGroup && !aAttrStack.empty(); + + while( bContinue && IsParserWorking() ) // as long as known Attribute are recognized + { + switch( nToken ) + { + case RTF_PARD: + RTFPardPlain( true, &pSet ); + break; + + case RTF_PLAIN: + RTFPardPlain( false, &pSet ); + break; + + default: + do { // middle checked loop + if( !bChkStkPos ) + break; + + SvxRTFItemStackType* pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); + if( !pCurrent || (pCurrent->mxStartNodeIdx->GetIdx() == mxInsertPosition->GetNodeIdx() && + pCurrent->nSttCnt == mxInsertPosition->GetCntIdx() )) + break; + + int nLastToken = GetStackPtr(-1)->nTokenId; + if( RTF_PARD == nLastToken || RTF_PLAIN == nLastToken ) + break; + + if (pCurrent->aAttrSet.Count() || !pCurrent->maChildList.empty() || + pCurrent->nStyleNo ) + { + // Open a new Group + auto xNew(std::make_unique<SvxRTFItemStackType>(*pCurrent, *mxInsertPosition, true)); + xNew->SetRTFDefaults( GetRTFDefaults() ); + + // "Set" all valid attributes up until this point + AttrGroupEnd(); + pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); // can be changed after AttrGroupEnd! + xNew->aAttrSet.SetParent( pCurrent ? &pCurrent->aAttrSet : nullptr ); + + aAttrStack.push_back( std::move(xNew) ); + pCurrent = aAttrStack.back().get(); + } + else + // continue to use this entry as a new one + pCurrent->SetStartPos( *mxInsertPosition ); + + pSet = &pCurrent->aAttrSet; + } while( false ); + + switch( nToken ) + { + case RTF_INTBL: + case RTF_PAGEBB: + case RTF_SBYS: + case RTF_CS: + case RTF_LS: + case RTF_ILVL: + UnknownAttrToken( nToken ); + break; + + case RTF_S: + if( bIsInReadStyleTab ) + { + if( !bFirstToken ) + SkipToken(); + bContinue = false; + } + else + { + sal_uInt16 nStyleNo = -1 == nTokenValue ? 0 : sal_uInt16(nTokenValue); + // set StyleNo to the current style on the AttrStack + SvxRTFItemStackType* pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); + if( !pCurrent ) + break; + + pCurrent->nStyleNo = nStyleNo; + } + break; + + case RTF_KEEP: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_SPLIT]) + { + pSet->Put(SvxFormatSplitItem(false, wid)); + } + break; + + case RTF_KEEPN: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_KEEP]) + { + pSet->Put(SvxFormatKeepItem(true, wid)); + } + break; + + case RTF_LEVEL: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_OUTLLEVEL]) + { + pSet->Put(SfxInt16Item(wid, static_cast<sal_uInt16>(nTokenValue))); + } + break; + + case RTF_QL: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_ADJUST]) + { + pSet->Put(SvxAdjustItem(SvxAdjust::Left, wid)); + } + break; + case RTF_QR: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_ADJUST]) + { + pSet->Put(SvxAdjustItem(SvxAdjust::Right, wid)); + } + break; + case RTF_QJ: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_ADJUST]) + { + pSet->Put(SvxAdjustItem(SvxAdjust::Block, wid)); + } + break; + case RTF_QC: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_ADJUST]) + { + pSet->Put(SvxAdjustItem(SvxAdjust::Center, wid)); + } + break; + + case RTF_FI: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_LRSPACE]) + { + SvxLRSpaceItem aLR(static_cast<const SvxLRSpaceItem&>(pSet->Get(wid))); + sal_uInt16 nSz = 0; + if( -1 != nTokenValue ) + { + if( IsCalcValue() ) + CalcValue(); + nSz = sal_uInt16(nTokenValue); + } + aLR.SetTextFirstLineOffset( nSz ); + pSet->Put( aLR ); + } + break; + + case RTF_LI: + case RTF_LIN: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_LRSPACE]) + { + SvxLRSpaceItem aLR(static_cast<const SvxLRSpaceItem&>(pSet->Get(wid))); + sal_uInt16 nSz = 0; + if( 0 < nTokenValue ) + { + if( IsCalcValue() ) + CalcValue(); + nSz = sal_uInt16(nTokenValue); + } + aLR.SetTextLeft( nSz ); + pSet->Put( aLR ); + } + break; + + case RTF_RI: + case RTF_RIN: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_LRSPACE]) + { + SvxLRSpaceItem aLR(static_cast<const SvxLRSpaceItem&>(pSet->Get(wid))); + sal_uInt16 nSz = 0; + if( 0 < nTokenValue ) + { + if( IsCalcValue() ) + CalcValue(); + nSz = sal_uInt16(nTokenValue); + } + aLR.SetRight( nSz ); + pSet->Put( aLR ); + } + break; + + case RTF_SB: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_ULSPACE]) + { + SvxULSpaceItem aUL(static_cast<const SvxULSpaceItem&>(pSet->Get(wid))); + sal_uInt16 nSz = 0; + if( 0 < nTokenValue ) + { + if( IsCalcValue() ) + CalcValue(); + nSz = sal_uInt16(nTokenValue); + } + aUL.SetUpper( nSz ); + pSet->Put( aUL ); + } + break; + + case RTF_SA: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_ULSPACE]) + { + SvxULSpaceItem aUL(static_cast<const SvxULSpaceItem&>(pSet->Get(wid))); + sal_uInt16 nSz = 0; + if( 0 < nTokenValue ) + { + if( IsCalcValue() ) + CalcValue(); + nSz = sal_uInt16(nTokenValue); + } + aUL.SetLower( nSz ); + pSet->Put( aUL ); + } + break; + + case RTF_SLMULT: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_LINESPACE]; + wid && 1 == nTokenValue) + { + // then switches to multi-line! + SvxLineSpacingItem aLSpace( + static_cast<const SvxLineSpacingItem&>(pSet->Get(wid, false))); + + // how much do you get from the line height value? + + // Proportional-Size: + // Ie, the ratio is (n / 240) twips + + nTokenValue = 240; + if( IsCalcValue() ) + CalcValue(); + + nTokenValue = short( 100 * aLSpace.GetLineHeight() / nTokenValue ); + + aLSpace.SetPropLineSpace( static_cast<sal_uInt16>(nTokenValue) ); + aLSpace.SetLineSpaceRule( SvxLineSpaceRule::Auto ); + + pSet->Put( aLSpace ); + } + break; + + case RTF_SL: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_LINESPACE]) + { + // Calculate the ratio between the default font and the + // specified size. The distance consists of the line height + // (100%) and the space above the line (20%). + SvxLineSpacingItem aLSpace(0, wid); + + nTokenValue = !bTokenHasValue ? 0 : nTokenValue; + if (1000 == nTokenValue ) + nTokenValue = 240; + + SvxLineSpaceRule eLnSpc; + if (nTokenValue < 0) + { + eLnSpc = SvxLineSpaceRule::Fix; + nTokenValue = -nTokenValue; + } + else if (nTokenValue == 0) + { + //if \sl0 is used, the line spacing is automatically + //determined + eLnSpc = SvxLineSpaceRule::Auto; + } + else + eLnSpc = SvxLineSpaceRule::Min; + + if (IsCalcValue()) + CalcValue(); + + if (eLnSpc != SvxLineSpaceRule::Auto) + aLSpace.SetLineHeight( static_cast<sal_uInt16>(nTokenValue) ); + + aLSpace.SetLineSpaceRule(eLnSpc); + pSet->Put(aLSpace); + } + break; + + case RTF_NOCWRAP: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_FORBIDDEN_RULES]) + { + pSet->Put(SvxForbiddenRuleItem(false, wid)); + } + break; + case RTF_NOOVERFLOW: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_HANGPUNCTUATION]) + { + pSet->Put(SvxHangingPunctuationItem(false, wid)); + } + break; + + case RTF_ASPALPHA: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_PARA_SCRIPTSPACE]) + { + pSet->Put(SvxScriptSpaceItem(true, wid)); + } + break; + + case RTF_FAFIXED: + case RTF_FAAUTO: nFontAlign = SvxParaVertAlignItem::Align::Automatic; + goto SET_FONTALIGNMENT; + case RTF_FAHANG: nFontAlign = SvxParaVertAlignItem::Align::Top; + goto SET_FONTALIGNMENT; + case RTF_FAVAR: nFontAlign = SvxParaVertAlignItem::Align::Bottom; + goto SET_FONTALIGNMENT; + case RTF_FACENTER: nFontAlign = SvxParaVertAlignItem::Align::Center; + goto SET_FONTALIGNMENT; + case RTF_FAROMAN: nFontAlign = SvxParaVertAlignItem::Align::Baseline; + goto SET_FONTALIGNMENT; +SET_FONTALIGNMENT: + if (const sal_uInt16 wid = aPardMap[SID_PARA_VERTALIGN]) + { + pSet->Put(SvxParaVertAlignItem(nFontAlign, wid)); + } + break; + + case RTF_B: + case RTF_AB: + if( IsAttrSttPos() ) // not in the text flow? + { + + SvxWeightItem aTmpItem( + nTokenValue ? WEIGHT_BOLD : WEIGHT_NORMAL, + SID_ATTR_CHAR_WEIGHT ); + SetScriptAttr( eCharType, *pSet, aTmpItem); + } + break; + + case RTF_CAPS: + case RTF_SCAPS: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_CASEMAP]; + wid && IsAttrSttPos()) // not in the text flow? + { + SvxCaseMap eCaseMap; + if( !nTokenValue ) + eCaseMap = SvxCaseMap::NotMapped; + else if( RTF_CAPS == nToken ) + eCaseMap = SvxCaseMap::Uppercase; + else + eCaseMap = SvxCaseMap::SmallCaps; + + pSet->Put(SvxCaseMapItem(eCaseMap, wid)); + } + break; + + case RTF_DN: + case RTF_SUB: + if (const sal_uInt16 nEsc = aPlainMap[SID_ATTR_CHAR_ESCAPEMENT]) + { + if( -1 == nTokenValue ) + nTokenValue = 6; //RTF default \dn value in half-points + if( IsCalcValue() ) + CalcValue(); + const SvxEscapementItem& rOld = + static_cast<const SvxEscapementItem&>(pSet->Get( nEsc,false)); + sal_Int16 nEs; + sal_uInt8 nProp; + if( DFLT_ESC_AUTO_SUPER == rOld.GetEsc() ) + { + nEs = DFLT_ESC_AUTO_SUB; + nProp = rOld.GetProportionalHeight(); + } + else + { + nEs = (nToken == RTF_SUB) ? DFLT_ESC_AUTO_SUB : -nTokenValue; + nProp = (nToken == RTF_SUB) ? DFLT_ESC_PROP : 100; + } + pSet->Put( SvxEscapementItem( nEs, nProp, nEsc )); + } + break; + + case RTF_NOSUPERSUB: + if (const sal_uInt16 nEsc = aPlainMap[SID_ATTR_CHAR_ESCAPEMENT]) + { + pSet->Put( SvxEscapementItem( nEsc )); + } + break; + + case RTF_EXPND: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_KERNING]) + { + if( -1 == nTokenValue ) + nTokenValue = 0; + else + nTokenValue *= 5; + if( IsCalcValue() ) + CalcValue(); + pSet->Put(SvxKerningItem(static_cast<short>(nTokenValue), wid)); + } + break; + + case RTF_KERNING: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_AUTOKERN]) + { + if( -1 == nTokenValue ) + nTokenValue = 0; + else + nTokenValue *= 10; + if( IsCalcValue() ) + CalcValue(); + pSet->Put(SvxAutoKernItem(0 != nTokenValue, wid)); + } + break; + + case RTF_EXPNDTW: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_KERNING]) + { + if( -1 == nTokenValue ) + nTokenValue = 0; + if( IsCalcValue() ) + CalcValue(); + pSet->Put(SvxKerningItem(static_cast<short>(nTokenValue), wid)); + } + break; + + case RTF_F: + case RTF_AF: + { + const vcl::Font& rSVFont = GetFont( sal_uInt16(nTokenValue) ); + SvxFontItem aTmpItem( rSVFont.GetFamilyType(), + rSVFont.GetFamilyName(), rSVFont.GetStyleName(), + rSVFont.GetPitch(), rSVFont.GetCharSet(), + SID_ATTR_CHAR_FONT ); + SetScriptAttr( eCharType, *pSet, aTmpItem ); + if( RTF_F == nToken ) + { + SetEncoding( rSVFont.GetCharSet() ); + RereadLookahead(); + } + } + break; + + case RTF_FS: + case RTF_AFS: + { + if( -1 == nTokenValue ) + nTokenValue = 240; + else + nTokenValue *= 10; +// #i66167# +// for the SwRTFParser 'IsCalcValue' will be false and for the EditRTFParser +// the conversion takes now place in EditRTFParser since for other reasons +// the wrong MapUnit might still be use there +// if( IsCalcValue() ) +// CalcValue(); + SvxFontHeightItem aTmpItem( + static_cast<sal_uInt16>(nTokenValue), 100, + SID_ATTR_CHAR_FONTHEIGHT ); + SetScriptAttr( eCharType, *pSet, aTmpItem ); + } + break; + + case RTF_I: + case RTF_AI: + if( IsAttrSttPos() ) // not in the text flow? + { + SvxPostureItem aTmpItem( + nTokenValue ? ITALIC_NORMAL : ITALIC_NONE, + SID_ATTR_CHAR_POSTURE ); + SetScriptAttr( eCharType, *pSet, aTmpItem ); + } + break; + + case RTF_OUTL: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_CONTOUR]; + wid && IsAttrSttPos()) // not in the text flow? + { + pSet->Put(SvxContourItem(nTokenValue != 0, wid)); + } + break; + + case RTF_SHAD: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_SHADOWED]; + wid && IsAttrSttPos()) // not in the text flow? + { + pSet->Put(SvxShadowedItem(nTokenValue != 0, wid)); + } + break; + + case RTF_STRIKE: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_STRIKEOUT]; + wid && IsAttrSttPos()) // not in the text flow? + { + pSet->Put( SvxCrossedOutItem( + nTokenValue ? STRIKEOUT_SINGLE : STRIKEOUT_NONE, + wid )); + } + break; + + case RTF_STRIKED: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_STRIKEOUT]) // not in the text flow? + { + pSet->Put( SvxCrossedOutItem( + nTokenValue ? STRIKEOUT_DOUBLE : STRIKEOUT_NONE, + wid )); + } + break; + + case RTF_UL: + if( !IsAttrSttPos() ) + break; + eUnderline = nTokenValue ? LINESTYLE_SINGLE : LINESTYLE_NONE; + goto ATTR_SETUNDERLINE; + + case RTF_ULD: + eUnderline = LINESTYLE_DOTTED; + goto ATTR_SETUNDERLINE; + case RTF_ULDASH: + eUnderline = LINESTYLE_DASH; + goto ATTR_SETUNDERLINE; + case RTF_ULDASHD: + eUnderline = LINESTYLE_DASHDOT; + goto ATTR_SETUNDERLINE; + case RTF_ULDASHDD: + eUnderline = LINESTYLE_DASHDOTDOT; + goto ATTR_SETUNDERLINE; + case RTF_ULDB: + eUnderline = LINESTYLE_DOUBLE; + goto ATTR_SETUNDERLINE; + case RTF_ULNONE: + eUnderline = LINESTYLE_NONE; + goto ATTR_SETUNDERLINE; + case RTF_ULTH: + eUnderline = LINESTYLE_BOLD; + goto ATTR_SETUNDERLINE; + case RTF_ULWAVE: + eUnderline = LINESTYLE_WAVE; + goto ATTR_SETUNDERLINE; + case RTF_ULTHD: + eUnderline = LINESTYLE_BOLDDOTTED; + goto ATTR_SETUNDERLINE; + case RTF_ULTHDASH: + eUnderline = LINESTYLE_BOLDDASH; + goto ATTR_SETUNDERLINE; + case RTF_ULLDASH: + eUnderline = LINESTYLE_LONGDASH; + goto ATTR_SETUNDERLINE; + case RTF_ULTHLDASH: + eUnderline = LINESTYLE_BOLDLONGDASH; + goto ATTR_SETUNDERLINE; + case RTF_ULTHDASHD: + eUnderline = LINESTYLE_BOLDDASHDOT; + goto ATTR_SETUNDERLINE; + case RTF_ULTHDASHDD: + eUnderline = LINESTYLE_BOLDDASHDOTDOT; + goto ATTR_SETUNDERLINE; + case RTF_ULHWAVE: + eUnderline = LINESTYLE_BOLDWAVE; + goto ATTR_SETUNDERLINE; + case RTF_ULULDBWAVE: + eUnderline = LINESTYLE_DOUBLEWAVE; + goto ATTR_SETUNDERLINE; + + case RTF_ULW: + eUnderline = LINESTYLE_SINGLE; + + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_WORDLINEMODE]) + { + pSet->Put(SvxWordLineModeItem(true, wid)); + } + goto ATTR_SETUNDERLINE; + +ATTR_SETUNDERLINE: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_UNDERLINE]) + { + pSet->Put(SvxUnderlineItem(eUnderline, wid)); + } + break; + + case RTF_ULC: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_UNDERLINE]) + { + std::unique_ptr<SvxUnderlineItem> aUL(std::make_unique<SvxUnderlineItem>(LINESTYLE_SINGLE, wid)); + const SfxPoolItem* pItem(nullptr); + + if (SfxItemState::SET == pSet->GetItemState(wid, false, &pItem)) + { + // is switched off ? + if( LINESTYLE_NONE == static_cast<const SvxUnderlineItem*>(pItem)->GetLineStyle() ) + break; + + aUL.reset(static_cast<SvxUnderlineItem*>(pItem->Clone())); + } + else + { + aUL.reset(static_cast<SvxUnderlineItem*>(pSet->Get(wid, false).Clone())); + } + + if(LINESTYLE_NONE == aUL->GetLineStyle()) + { + aUL->SetLineStyle(LINESTYLE_SINGLE); + } + + aUL->SetColor(GetColor(sal_uInt16(nTokenValue))); + + pSet->Put(std::move(aUL)); + } + break; + + case RTF_OL: + if( !IsAttrSttPos() ) + break; + eOverline = nTokenValue ? LINESTYLE_SINGLE : LINESTYLE_NONE; + goto ATTR_SETOVERLINE; + + case RTF_OLD: + eOverline = LINESTYLE_DOTTED; + goto ATTR_SETOVERLINE; + case RTF_OLDASH: + eOverline = LINESTYLE_DASH; + goto ATTR_SETOVERLINE; + case RTF_OLDASHD: + eOverline = LINESTYLE_DASHDOT; + goto ATTR_SETOVERLINE; + case RTF_OLDASHDD: + eOverline = LINESTYLE_DASHDOTDOT; + goto ATTR_SETOVERLINE; + case RTF_OLDB: + eOverline = LINESTYLE_DOUBLE; + goto ATTR_SETOVERLINE; + case RTF_OLNONE: + eOverline = LINESTYLE_NONE; + goto ATTR_SETOVERLINE; + case RTF_OLTH: + eOverline = LINESTYLE_BOLD; + goto ATTR_SETOVERLINE; + case RTF_OLWAVE: + eOverline = LINESTYLE_WAVE; + goto ATTR_SETOVERLINE; + case RTF_OLTHD: + eOverline = LINESTYLE_BOLDDOTTED; + goto ATTR_SETOVERLINE; + case RTF_OLTHDASH: + eOverline = LINESTYLE_BOLDDASH; + goto ATTR_SETOVERLINE; + case RTF_OLLDASH: + eOverline = LINESTYLE_LONGDASH; + goto ATTR_SETOVERLINE; + case RTF_OLTHLDASH: + eOverline = LINESTYLE_BOLDLONGDASH; + goto ATTR_SETOVERLINE; + case RTF_OLTHDASHD: + eOverline = LINESTYLE_BOLDDASHDOT; + goto ATTR_SETOVERLINE; + case RTF_OLTHDASHDD: + eOverline = LINESTYLE_BOLDDASHDOTDOT; + goto ATTR_SETOVERLINE; + case RTF_OLHWAVE: + eOverline = LINESTYLE_BOLDWAVE; + goto ATTR_SETOVERLINE; + case RTF_OLOLDBWAVE: + eOverline = LINESTYLE_DOUBLEWAVE; + goto ATTR_SETOVERLINE; + + case RTF_OLW: + eOverline = LINESTYLE_SINGLE; + + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_WORDLINEMODE]) + { + pSet->Put(SvxWordLineModeItem(true, wid)); + } + goto ATTR_SETOVERLINE; + +ATTR_SETOVERLINE: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_OVERLINE]) + { + pSet->Put(SvxOverlineItem(eOverline, wid)); + } + break; + + case RTF_OLC: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_OVERLINE]) + { + std::unique_ptr<SvxOverlineItem> aOL(std::make_unique<SvxOverlineItem>(LINESTYLE_SINGLE, wid)); + const SfxPoolItem* pItem(nullptr); + + if (SfxItemState::SET == pSet->GetItemState(wid, false, &pItem)) + { + // is switched off ? + if( LINESTYLE_NONE == static_cast<const SvxOverlineItem*>(pItem)->GetLineStyle() ) + break; + + aOL.reset(static_cast<SvxOverlineItem*>(pItem->Clone())); + } + else + { + aOL.reset(static_cast<SvxOverlineItem*>(pSet->Get(wid, false).Clone())); + } + + if(LINESTYLE_NONE == aOL->GetLineStyle()) + { + aOL->SetLineStyle(LINESTYLE_SINGLE); + } + + aOL->SetColor(GetColor(sal_uInt16(nTokenValue))); + + pSet->Put(std::move(aOL)); + } + break; + + case RTF_UP: + case RTF_SUPER: + if (const sal_uInt16 nEsc = aPlainMap[SID_ATTR_CHAR_ESCAPEMENT]) + { + if( -1 == nTokenValue ) + nTokenValue = 6; //RTF default \up value in half-points + if( IsCalcValue() ) + CalcValue(); + const SvxEscapementItem& rOld = + static_cast<const SvxEscapementItem&>(pSet->Get( nEsc,false)); + sal_Int16 nEs; + sal_uInt8 nProp; + if( DFLT_ESC_AUTO_SUB == rOld.GetEsc() ) + { + nEs = DFLT_ESC_AUTO_SUPER; + nProp = rOld.GetProportionalHeight(); + } + else + { + nEs = (nToken == RTF_SUPER) ? DFLT_ESC_AUTO_SUPER : nTokenValue; + nProp = (nToken == RTF_SUPER) ? DFLT_ESC_PROP : 100; + } + pSet->Put( SvxEscapementItem( nEs, nProp, nEsc )); + } + break; + + case RTF_CF: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_COLOR]) + { + pSet->Put(SvxColorItem(GetColor(sal_uInt16(nTokenValue)), wid)); + } + break; + //#i12501# While cb is clearly documented in the rtf spec, word + //doesn't accept it at all +#if 0 + case RTF_CB: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_BRUSH_CHAR]) + { + pSet->Put(SvxBrushItem(GetColor(sal_uInt16(nTokenValue)), wid)); + } + break; +#endif + + case RTF_LANG: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_LANGUAGE]) + { + pSet->Put(SvxLanguageItem(LanguageType(nTokenValue), wid)); + } + break; + + case RTF_LANGFE: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_CJK_LANGUAGE]) + { + pSet->Put(SvxLanguageItem(LanguageType(nTokenValue), wid)); + } + break; + case RTF_ALANG: + { + SvxLanguageItem aTmpItem( LanguageType(nTokenValue), + SID_ATTR_CHAR_LANGUAGE ); + SetScriptAttr( eCharType, *pSet, aTmpItem ); + } + break; + + case RTF_RTLCH: + bIsLeftToRightDef = false; + break; + case RTF_LTRCH: + bIsLeftToRightDef = true; + break; + case RTF_RTLPAR: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_FRAMEDIRECTION]) + { + pSet->Put(SvxFrameDirectionItem(SvxFrameDirection::Horizontal_RL_TB, wid)); + } + break; + case RTF_LTRPAR: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_FRAMEDIRECTION]) + { + pSet->Put(SvxFrameDirectionItem(SvxFrameDirection::Horizontal_LR_TB, wid)); + } + break; + case RTF_LOCH: eCharType = LOW_CHARTYPE; break; + case RTF_HICH: eCharType = HIGH_CHARTYPE; break; + case RTF_DBCH: eCharType = DOUBLEBYTE_CHARTYPE; break; + + + case RTF_ACCNONE: + eEmphasis = FontEmphasisMark::NONE; + goto ATTR_SETEMPHASIS; + case RTF_ACCDOT: + eEmphasis = (FontEmphasisMark::Dot | FontEmphasisMark::PosAbove); + goto ATTR_SETEMPHASIS; + + case RTF_ACCCOMMA: + eEmphasis = (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove); +ATTR_SETEMPHASIS: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_EMPHASISMARK]) + { + pSet->Put(SvxEmphasisMarkItem(eEmphasis, wid)); + } + break; + + case RTF_TWOINONE: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_TWO_LINES]) + { + sal_Unicode cStt, cEnd; + switch ( nTokenValue ) + { + case 1: cStt = '('; cEnd = ')'; break; + case 2: cStt = '['; cEnd = ']'; break; + case 3: cStt = '<'; cEnd = '>'; break; + case 4: cStt = '{'; cEnd = '}'; break; + default: cStt = 0; cEnd = 0; break; + } + + pSet->Put(SvxTwoLinesItem(true, cStt, cEnd, wid)); + } + break; + + case RTF_CHARSCALEX : + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_SCALEWIDTH]) + { + //i21372 + if (nTokenValue < 1 || nTokenValue > 600) + nTokenValue = 100; + pSet->Put(SvxCharScaleWidthItem(sal_uInt16(nTokenValue), wid)); + } + break; + + case RTF_HORZVERT: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_ROTATED]) + { + // RTF knows only 90deg + pSet->Put(SvxCharRotateItem(900_deg10, 1 == nTokenValue, wid)); + } + break; + + case RTF_EMBO: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_RELIEF]) + { + pSet->Put(SvxCharReliefItem(FontRelief::Embossed, wid)); + } + break; + case RTF_IMPR: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_RELIEF]) + { + pSet->Put(SvxCharReliefItem(FontRelief::Engraved, wid)); + } + break; + case RTF_V: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_HIDDEN]) + { + pSet->Put(SvxCharHiddenItem(nTokenValue != 0, wid)); + } + break; + case RTF_CHBGFDIAG: + case RTF_CHBGDKVERT: + case RTF_CHBGDKHORIZ: + case RTF_CHBGVERT: + case RTF_CHBGHORIZ: + case RTF_CHBGDKFDIAG: + case RTF_CHBGDCROSS: + case RTF_CHBGCROSS: + case RTF_CHBGBDIAG: + case RTF_CHBGDKDCROSS: + case RTF_CHBGDKCROSS: + case RTF_CHBGDKBDIAG: + case RTF_CHCBPAT: + case RTF_CHCFPAT: + case RTF_CHSHDNG: + if (aPlainMap[SID_ATTR_BRUSH_CHAR] != 0) + ReadBackgroundAttr( nToken, *pSet ); + break; + + case BRACELEFT: + { + // tests on Swg internal tokens + bool bHandled = false; + short nSkip = 0; + if( RTF_IGNOREFLAG != GetNextToken()) + nSkip = -1; + else if( (nToken = GetNextToken() ) & RTF_SWGDEFS ) + { + bHandled = true; + switch( nToken ) + { + case RTF_PGDSCNO: + case RTF_PGBRK: + case RTF_SOUTLVL: + UnknownAttrToken( nToken ); + // overwrite the closing parenthesis + break; + + case RTF_SWG_ESCPROP: + { + // Store percentage change! + sal_uInt8 nProp = sal_uInt8( nTokenValue / 100 ); + short nEsc = 0; + if( 1 == ( nTokenValue % 100 )) + // Recognize own auto-flags! + nEsc = DFLT_ESC_AUTO_SUPER; + + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_ESCAPEMENT]) + pSet->Put(SvxEscapementItem(nEsc, nProp, wid)); + } + break; + + case RTF_HYPHEN: + { + SvxHyphenZoneItem aHypenZone( + (nTokenValue & 1) != 0, + aPardMap[SID_ATTR_PARA_HYPHENZONE]); + aHypenZone.SetPageEnd((nTokenValue & 2) != 0); + + if( aPardMap[SID_ATTR_PARA_HYPHENZONE] != 0 && + RTF_HYPHLEAD == GetNextToken() && + RTF_HYPHTRAIL == GetNextToken() && + RTF_HYPHMAX == GetNextToken() ) + { + aHypenZone.GetMinLead() = + sal_uInt8(GetStackPtr( -2 )->nTokenValue); + aHypenZone.GetMinTrail() = + sal_uInt8(GetStackPtr( -1 )->nTokenValue); + aHypenZone.GetMaxHyphens() = + sal_uInt8(nTokenValue); + + pSet->Put( aHypenZone ); + } + else + SkipGroup(); // at the end of the group + } + break; + + // We expect these to be preceded by a RTF_HYPHEN and + // so normally are handled by the RTF_HYPHEN case, but + // if they appear 'bare' in a document then safely skip + // them here + case RTF_HYPHLEAD: + case RTF_HYPHTRAIL: + case RTF_HYPHMAX: + SkipGroup(); + break; + + case RTF_SHADOW: + { + bool bSkip = true; + do { // middle check loop + SvxShadowLocation eSL = SvxShadowLocation( nTokenValue ); + if( RTF_SHDW_DIST != GetNextToken() ) + break; + sal_uInt16 nDist = sal_uInt16( nTokenValue ); + + if( RTF_SHDW_STYLE != GetNextToken() ) + break; + + if( RTF_SHDW_COL != GetNextToken() ) + break; + sal_uInt16 nCol = sal_uInt16( nTokenValue ); + + if( RTF_SHDW_FCOL != GetNextToken() ) + break; + + Color aColor = GetColor( nCol ); + + if (const sal_uInt16 wid = aPardMap[SID_ATTR_BORDER_SHADOW]) + pSet->Put(SvxShadowItem(wid, &aColor, nDist, eSL)); + + bSkip = false; + } while( false ); + + if( bSkip ) + SkipGroup(); // at the end of the group + } + break; + + default: + bHandled = false; + if( (nToken & ~(0xff | RTF_SWGDEFS)) == RTF_TABSTOPDEF ) + { + nToken = SkipToken( -2 ); + ReadTabAttr( nToken, *pSet ); + + /* + cmc: #i76140, he who consumed the { must consume the } + We rewound to a state of { being the current + token so it is our responsibility to consume the } + token if we consumed the {. We will not have consumed + the { if it belonged to our caller, i.e. if the { we + are handling is the "firsttoken" passed to us then + the *caller* must consume it, not us. Otherwise *we* + should consume it. + */ + if (nToken == BRACELEFT && !bFirstToken) + { + nToken = GetNextToken(); + SAL_WARN_IF( nToken != BRACERIGHT, + "editeng", + "} did not follow { as expected"); + } + } + else if( (nToken & ~(0xff| RTF_SWGDEFS)) == RTF_BRDRDEF) + { + nToken = SkipToken( -2 ); + ReadBorderAttr( nToken, *pSet ); + } + else // so no more attribute + nSkip = -2; + break; + } + +#if 1 + /* + cmc: #i4727# / #i12713# Who owns this closing bracket? + If we read the opening one, we must read this one, if + other is counting the brackets so as to push/pop off + the correct environment then we will have pushed a new + environment for the start { of this, but will not see + the } and so is out of sync for the rest of the + document. + */ + if (bHandled && !bFirstToken) + GetNextToken(); +#endif + } + else + nSkip = -2; + + if( nSkip ) // all completely unknown + { + if (!bFirstToken) + --nSkip; // BRACELEFT: is the next token + SkipToken( nSkip ); + bContinue = false; + } + } + break; + default: + if( (nToken & ~0xff ) == RTF_TABSTOPDEF ) + ReadTabAttr( nToken, *pSet ); + else if( (nToken & ~0xff ) == RTF_BRDRDEF ) + ReadBorderAttr( nToken, *pSet ); + else if( (nToken & ~0xff ) == RTF_SHADINGDEF ) + ReadBackgroundAttr( nToken, *pSet ); + else + { + // unknown token, so token "returned in Parser" + if( !bFirstToken ) + SkipToken(); + bContinue = false; + } + } + } + if( bContinue ) + { + nToken = GetNextToken(); + } + bFirstToken = false; + } +} + +void SvxRTFParser::ReadTabAttr( int nToken, SfxItemSet& rSet ) +{ + bool bMethodOwnsToken = false; // #i52542# patch from cmc. +// then read all the TabStops + SvxTabStop aTabStop; + SvxTabStopItem aAttr(0, 0, SvxTabAdjust::Default, aPardMap[SID_ATTR_TABSTOP]); + bool bContinue = true; + do { + switch( nToken ) + { + case RTF_TB: // BarTab ??? + case RTF_TX: + { + if( IsCalcValue() ) + CalcValue(); + aTabStop.GetTabPos() = nTokenValue; + aAttr.Insert( aTabStop ); + aTabStop = SvxTabStop(); // all values default + } + break; + + case RTF_TQL: + aTabStop.GetAdjustment() = SvxTabAdjust::Left; + break; + case RTF_TQR: + aTabStop.GetAdjustment() = SvxTabAdjust::Right; + break; + case RTF_TQC: + aTabStop.GetAdjustment() = SvxTabAdjust::Center; + break; + case RTF_TQDEC: + aTabStop.GetAdjustment() = SvxTabAdjust::Decimal; + break; + + case RTF_TLDOT: aTabStop.GetFill() = '.'; break; + case RTF_TLHYPH: aTabStop.GetFill() = ' '; break; + case RTF_TLUL: aTabStop.GetFill() = '_'; break; + case RTF_TLTH: aTabStop.GetFill() = '-'; break; + case RTF_TLEQ: aTabStop.GetFill() = '='; break; + + case BRACELEFT: + { + // Swg - control BRACELEFT RTF_IGNOREFLAG RTF_TLSWG BRACERIGHT + short nSkip = 0; + if( RTF_IGNOREFLAG != GetNextToken() ) + nSkip = -1; + else if( RTF_TLSWG != ( nToken = GetNextToken() )) + nSkip = -2; + else + { + aTabStop.GetDecimal() = sal_uInt8(nTokenValue & 0xff); + aTabStop.GetFill() = sal_uInt8((nTokenValue >> 8) & 0xff); + // overwrite the closing parenthesis + if (bMethodOwnsToken) + GetNextToken(); + } + if( nSkip ) + { + SkipToken( nSkip ); // Ignore back again + bContinue = false; + } + } + break; + + default: + bContinue = false; + } + if( bContinue ) + { + nToken = GetNextToken(); + bMethodOwnsToken = true; + } + } while( bContinue ); + + // Fill with defaults is still missing! + rSet.Put( aAttr ); + SkipToken(); +} + +static void SetBorderLine( int nBorderTyp, SvxBoxItem& rItem, + const SvxBorderLine& rBorder ) +{ + switch( nBorderTyp ) + { + case RTF_BOX: // run through all levels + case RTF_BRDRT: + rItem.SetLine( &rBorder, SvxBoxItemLine::TOP ); + if( RTF_BOX != nBorderTyp ) + return; + [[fallthrough]]; + case RTF_BRDRB: + rItem.SetLine( &rBorder, SvxBoxItemLine::BOTTOM ); + if( RTF_BOX != nBorderTyp ) + return; + [[fallthrough]]; + case RTF_BRDRL: + rItem.SetLine( &rBorder, SvxBoxItemLine::LEFT ); + if( RTF_BOX != nBorderTyp ) + return; + [[fallthrough]]; + case RTF_BRDRR: + rItem.SetLine( &rBorder, SvxBoxItemLine::RIGHT ); + if( RTF_BOX != nBorderTyp ) + return; + } +} + +void SvxRTFParser::ReadBorderAttr( int nToken, SfxItemSet& rSet, + bool bTableDef ) +{ + // then read the border attribute + std::unique_ptr<SvxBoxItem> aAttr(std::make_unique<SvxBoxItem>(aPardMap[SID_ATTR_BORDER_OUTER])); + const SfxPoolItem* pItem(nullptr); + + if (SfxItemState::SET == rSet.GetItemState(aPardMap[SID_ATTR_BORDER_OUTER], false, &pItem)) + { + aAttr.reset(static_cast<SvxBoxItem*>(pItem->Clone())); + } + + SvxBorderLine aBrd( nullptr, SvxBorderLineWidth::Hairline ); + bool bContinue = true; + int nBorderTyp = 0; + + tools::Long nWidth = 1; + bool bDoubleWidth = false; + + do { + switch( nToken ) + { + case RTF_BOX: + case RTF_BRDRT: + case RTF_BRDRB: + case RTF_BRDRL: + case RTF_BRDRR: + nBorderTyp = nToken; + break; + + case RTF_CLBRDRT: // Cell top border + { + if( bTableDef ) + { + if (nBorderTyp != 0) + SetBorderLine( nBorderTyp, *aAttr, aBrd ); + nBorderTyp = RTF_BRDRT; + } + break; + } + case RTF_CLBRDRB: // Cell bottom border + { + if( bTableDef ) + { + if (nBorderTyp != 0) + SetBorderLine( nBorderTyp, *aAttr, aBrd ); + nBorderTyp = RTF_BRDRB; + } + break; + } + case RTF_CLBRDRL: // Cell left border + { + if( bTableDef ) + { + if (nBorderTyp != 0) + SetBorderLine( nBorderTyp, *aAttr, aBrd ); + nBorderTyp = RTF_BRDRL; + } + break; + } + case RTF_CLBRDRR: // Cell right border + { + if( bTableDef ) + { + if (nBorderTyp != 0) + SetBorderLine( nBorderTyp, *aAttr, aBrd ); + nBorderTyp = RTF_BRDRR; + } + break; + } + + case RTF_BRDRDOT: // dotted border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::DOTTED); + break; + case RTF_BRDRDASH: // dashed border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::DASHED); + break; + case RTF_BRDRHAIR: // hairline border + { + aBrd.SetBorderLineStyle( SvxBorderLineStyle::SOLID); + aBrd.SetWidth( SvxBorderLineWidth::Hairline ); + } + break; + case RTF_BRDRDB: // Double border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + break; + case RTF_BRDRINSET: // inset border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::INSET); + break; + case RTF_BRDROUTSET: // outset border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::OUTSET); + break; + case RTF_BRDRTNTHSG: // ThinThick Small gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_SMALLGAP); + break; + case RTF_BRDRTNTHMG: // ThinThick Medium gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_MEDIUMGAP); + break; + case RTF_BRDRTNTHLG: // ThinThick Large gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_LARGEGAP); + break; + case RTF_BRDRTHTNSG: // ThickThin Small gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_SMALLGAP); + break; + case RTF_BRDRTHTNMG: // ThickThin Medium gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_MEDIUMGAP); + break; + case RTF_BRDRTHTNLG: // ThickThin Large gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_LARGEGAP); + break; + case RTF_BRDREMBOSS: // Embossed border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::EMBOSSED); + break; + case RTF_BRDRENGRAVE: // Engraved border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::ENGRAVED); + break; + + case RTF_BRDRS: // single thickness border + bDoubleWidth = false; + break; + case RTF_BRDRTH: // double thickness border width*2 + bDoubleWidth = true; + break; + case RTF_BRDRW: // border width <255 + nWidth = nTokenValue; + break; + + case RTF_BRDRCF: // Border color + aBrd.SetColor( GetColor( sal_uInt16(nTokenValue) ) ); + break; + + case RTF_BRDRSH: // Shadowed border + rSet.Put( SvxShadowItem( aPardMap[SID_ATTR_BORDER_SHADOW], nullptr, 60 /*3pt*/, + SvxShadowLocation::BottomRight ) ); + break; + + case RTF_BRSP: // Spacing to content in twip + { + switch( nBorderTyp ) + { + case RTF_BRDRB: + aAttr->SetDistance( static_cast<sal_uInt16>(nTokenValue), SvxBoxItemLine::BOTTOM ); + break; + + case RTF_BRDRT: + aAttr->SetDistance( static_cast<sal_uInt16>(nTokenValue), SvxBoxItemLine::TOP ); + break; + + case RTF_BRDRL: + aAttr->SetDistance( static_cast<sal_uInt16>(nTokenValue), SvxBoxItemLine::LEFT ); + break; + + case RTF_BRDRR: + aAttr->SetDistance( static_cast<sal_uInt16>(nTokenValue), SvxBoxItemLine::RIGHT ); + break; + + case RTF_BOX: + aAttr->SetAllDistances( static_cast<sal_uInt16>(nTokenValue) ); + break; + } + } + break; + + case RTF_BRDRBTW: // Border formatting group + case RTF_BRDRBAR: // Border outside + // TODO unhandled ATM + break; + + default: + bContinue = (nToken & ~(0xff| RTF_SWGDEFS)) == RTF_BRDRDEF; + } + if( bContinue ) + nToken = GetNextToken(); + } while( bContinue ); + + // Finally compute the border width + if ( bDoubleWidth ) nWidth *= 2; + aBrd.SetWidth( nWidth ); + + SetBorderLine( nBorderTyp, *aAttr, aBrd ); + + rSet.Put( std::move(aAttr) ); + SkipToken(); +} + +static sal_uInt32 CalcShading( sal_uInt32 nColor, sal_uInt32 nFillColor, sal_uInt8 nShading ) +{ + nColor = (nColor * nShading) / 100; + nFillColor = (nFillColor * ( 100 - nShading )) / 100; + return nColor + nFillColor; +} + +void SvxRTFParser::ReadBackgroundAttr( int nToken, SfxItemSet& rSet, + bool bTableDef ) +{ + // then read the border attribute + bool bContinue = true; + sal_uInt16 nColor = USHRT_MAX, nFillColor = USHRT_MAX; + sal_uInt8 nFillValue = 0; + + sal_uInt16 nWh = ( nToken & ~0xff ) == RTF_CHRFMT + ? aPlainMap[SID_ATTR_BRUSH_CHAR] + : aPardMap[SID_ATTR_BRUSH]; + + do { + switch( nToken ) + { + case RTF_CLCBPAT: + case RTF_CHCBPAT: + case RTF_CBPAT: + nFillColor = sal_uInt16( nTokenValue ); + break; + + case RTF_CLCFPAT: + case RTF_CHCFPAT: + case RTF_CFPAT: + nColor = sal_uInt16( nTokenValue ); + break; + + case RTF_CLSHDNG: + case RTF_CHSHDNG: + case RTF_SHADING: + nFillValue = static_cast<sal_uInt8>( nTokenValue / 100 ); + break; + + case RTF_CLBGDKHOR: + case RTF_CHBGDKHORIZ: + case RTF_BGDKHORIZ: + case RTF_CLBGDKVERT: + case RTF_CHBGDKVERT: + case RTF_BGDKVERT: + case RTF_CLBGDKBDIAG: + case RTF_CHBGDKBDIAG: + case RTF_BGDKBDIAG: + case RTF_CLBGDKFDIAG: + case RTF_CHBGDKFDIAG: + case RTF_BGDKFDIAG: + case RTF_CLBGDKCROSS: + case RTF_CHBGDKCROSS: + case RTF_BGDKCROSS: + case RTF_CLBGDKDCROSS: + case RTF_CHBGDKDCROSS: + case RTF_BGDKDCROSS: + // dark -> 60% + nFillValue = 60; + break; + + case RTF_CLBGHORIZ: + case RTF_CHBGHORIZ: + case RTF_BGHORIZ: + case RTF_CLBGVERT: + case RTF_CHBGVERT: + case RTF_BGVERT: + case RTF_CLBGBDIAG: + case RTF_CHBGBDIAG: + case RTF_BGBDIAG: + case RTF_CLBGFDIAG: + case RTF_CHBGFDIAG: + case RTF_BGFDIAG: + case RTF_CLBGCROSS: + case RTF_CHBGCROSS: + case RTF_BGCROSS: + case RTF_CLBGDCROSS: + case RTF_CHBGDCROSS: + case RTF_BGDCROSS: + // light -> 20% + nFillValue = 20; + break; + + default: + if( bTableDef ) + bContinue = (nToken & ~(0xff | RTF_TABLEDEF) ) == RTF_SHADINGDEF; + else + bContinue = (nToken & ~0xff) == RTF_SHADINGDEF; + } + if( bContinue ) + nToken = GetNextToken(); + } while( bContinue ); + + Color aCol( COL_WHITE ), aFCol; + if( !nFillValue ) + { + // there was only one of two colors specified or no BrushType + if( USHRT_MAX != nFillColor ) + { + nFillValue = 100; + aCol = GetColor( nFillColor ); + } + else if( USHRT_MAX != nColor ) + aFCol = GetColor( nColor ); + } + else + { + if( USHRT_MAX != nColor ) + aCol = GetColor( nColor ); + else + aCol = COL_BLACK; + + if( USHRT_MAX != nFillColor ) + aFCol = GetColor( nFillColor ); + else + aFCol = COL_WHITE; + } + + Color aColor; + if( 0 == nFillValue || 100 == nFillValue ) + aColor = aCol; + else + aColor = Color( + static_cast<sal_uInt8>(CalcShading( aCol.GetRed(), aFCol.GetRed(), nFillValue )), + static_cast<sal_uInt8>(CalcShading( aCol.GetGreen(), aFCol.GetGreen(), nFillValue )), + static_cast<sal_uInt8>(CalcShading( aCol.GetBlue(), aFCol.GetBlue(), nFillValue )) ); + + rSet.Put( SvxBrushItem( aColor, nWh ) ); + SkipToken(); +} + + +// pard / plain handling +void SvxRTFParser::RTFPardPlain( bool const bPard, SfxItemSet** ppSet ) +{ + if( bNewGroup || aAttrStack.empty() ) // not at the beginning of a new group + return; + + SvxRTFItemStackType* pCurrent = aAttrStack.back().get(); + + int nLastToken = GetStackPtr(-1)->nTokenId; + bool bNewStkEntry = true; + if( RTF_PARD != nLastToken && + RTF_PLAIN != nLastToken && + BRACELEFT != nLastToken ) + { + if (pCurrent->aAttrSet.Count() || !pCurrent->maChildList.empty() || pCurrent->nStyleNo) + { + // open a new group + auto xNew(std::make_unique<SvxRTFItemStackType>(*pCurrent, *mxInsertPosition, true)); + xNew->SetRTFDefaults( GetRTFDefaults() ); + + // Set all until here valid attributes + AttrGroupEnd(); + pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); // can be changed after AttrGroupEnd! + xNew->aAttrSet.SetParent( pCurrent ? &pCurrent->aAttrSet : nullptr ); + aAttrStack.push_back( std::move(xNew) ); + pCurrent = aAttrStack.back().get(); + } + else + { + // continue to use this entry as new + pCurrent->SetStartPos( *mxInsertPosition ); + bNewStkEntry = false; + } + } + + // now reset all to default + if( bNewStkEntry && + ( pCurrent->aAttrSet.GetParent() || pCurrent->aAttrSet.Count() )) + { + const SfxPoolItem *pItem, *pDef; + std::map<sal_uInt16, sal_uInt16>::const_iterator aIt; + std::map<sal_uInt16, sal_uInt16>::const_iterator aEnd; + const SfxItemSet* pDfltSet = &GetRTFDefaults(); + if( bPard ) + { + pCurrent->nStyleNo = 0; + aIt = aPardMap.begin(); + aEnd = aPardMap.end(); + } + else + { + aIt = aPlainMap.begin(); + aEnd = aPlainMap.end(); + } + + for (; aIt != aEnd; ++aIt) + { + const sal_uInt16 wid = aIt->second; + // Item set and different -> Set the Default Pool + if (!wid) + ; + else if (SfxItemPool::IsSlot(wid)) + pCurrent->aAttrSet.ClearItem(wid); + else if( IsChkStyleAttr() ) + pCurrent->aAttrSet.Put(pDfltSet->Get(wid)); + else if( !pCurrent->aAttrSet.GetParent() ) + { + if (SfxItemState::SET == pDfltSet->GetItemState(wid, false, &pDef)) + pCurrent->aAttrSet.Put( *pDef ); + else + pCurrent->aAttrSet.ClearItem(wid); + } + else if( SfxItemState::SET == pCurrent->aAttrSet.GetParent()-> + GetItemState(wid, true, &pItem) && + *( pDef = &pDfltSet->Get(wid)) != *pItem ) + pCurrent->aAttrSet.Put( *pDef ); + else + { + if (SfxItemState::SET == pDfltSet->GetItemState(wid, false, &pDef)) + pCurrent->aAttrSet.Put( *pDef ); + else + pCurrent->aAttrSet.ClearItem(wid); + } + } + } + else if( bPard ) + pCurrent->nStyleNo = 0; // reset Style number + + *ppSet = &pCurrent->aAttrSet; + + if (bPard) + return; + + //Once we have a default font, then any text without a font specifier is + //in the default font, and thus has the default font charset, otherwise + //we can fall back to the ansicpg set codeset + if (nDfltFont != -1) + { + const vcl::Font& rSVFont = GetFont(sal_uInt16(nDfltFont)); + SetEncoding(rSVFont.GetCharSet()); + } + else + SetEncoding(GetCodeSet()); +} + +void SvxRTFParser::SetDefault( int nToken, int nValue ) +{ + if( !bNewDoc ) + return; + + SfxItemSet aTmp(*pAttrPool, aWhichMap); + bool bOldFlag = bIsLeftToRightDef; + bIsLeftToRightDef = true; + switch( nToken ) + { + case RTF_ADEFF: + bIsLeftToRightDef = false; + [[fallthrough]]; + case RTF_DEFF: + { + if( -1 == nValue ) + nValue = 0; + const vcl::Font& rSVFont = GetFont( sal_uInt16(nValue) ); + SvxFontItem aTmpItem( + rSVFont.GetFamilyType(), rSVFont.GetFamilyName(), + rSVFont.GetStyleName(), rSVFont.GetPitch(), + rSVFont.GetCharSet(), SID_ATTR_CHAR_FONT ); + SetScriptAttr( NOTDEF_CHARTYPE, aTmp, aTmpItem ); + } + break; + + case RTF_ADEFLANG: + bIsLeftToRightDef = false; + [[fallthrough]]; + case RTF_DEFLANG: + // store default Language + if( -1 != nValue ) + { + SvxLanguageItem aTmpItem( LanguageType(nValue), SID_ATTR_CHAR_LANGUAGE ); + SetScriptAttr( NOTDEF_CHARTYPE, aTmp, aTmpItem ); + } + break; + + case RTF_DEFTAB: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_TABSTOP]) + { + // RTF defines 720 twips as default + bIsSetDfltTab = true; + if( -1 == nValue || !nValue ) + nValue = 720; + + // who would like to have no twips ... + if( IsCalcValue() ) + { + nTokenValue = nValue; + CalcValue(); + nValue = nTokenValue; + } + + // Calculate the ratio of default TabWidth / Tabs and + // calculate the corresponding new number. + // ?? how did one come up with 13 ?? + sal_uInt16 nTabCount = (SVX_TAB_DEFDIST * 13 ) / sal_uInt16(nValue); + /* + cmc, make sure we have at least one, or all hell breaks loose in + everybody exporters, #i8247# + */ + if (nTabCount < 1) + nTabCount = 1; + + // we want Defaulttabs + SvxTabStopItem aNewTab(nTabCount, sal_uInt16(nValue), SvxTabAdjust::Default, wid); + while( nTabCount ) + const_cast<SvxTabStop&>(aNewTab[ --nTabCount ]).GetAdjustment() = SvxTabAdjust::Default; + + pAttrPool->SetPoolDefaultItem( aNewTab ); + } + break; + } + bIsLeftToRightDef = bOldFlag; + + if( aTmp.Count() ) + { + SfxItemIter aIter( aTmp ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + pAttrPool->SetPoolDefaultItem( *pItem ); + pItem = aIter.NextItem(); + } while (pItem); + } +} + +// default: no conversion, leaving everything in twips. +void SvxRTFParser::CalcValue() +{ +} + +// for tokens that are not evaluated in ReadAttr +void SvxRTFParser::UnknownAttrToken( int ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/rtf/svxrtf.cxx b/editeng/source/rtf/svxrtf.cxx new file mode 100644 index 000000000..b06337a14 --- /dev/null +++ b/editeng/source/rtf/svxrtf.cxx @@ -0,0 +1,1162 @@ +/* -*- 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 <queue> +#include <tools/diagnose_ex.h> +#include <rtl/tencinfo.h> +#include <svl/itemiter.hxx> +#include <svl/whiter.hxx> +#include <svtools/rtftoken.h> +#include <svl/itempool.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <tools/debug.hxx> +#include <unotools/configmgr.hxx> + +#include <comphelper/string.hxx> + +#include <editeng/scriptspaceitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/svxrtf.hxx> +#include <editeng/editids.hrc> +#include <vcl/font.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + + +using namespace ::com::sun::star; + + +static rtl_TextEncoding lcl_GetDefaultTextEncodingForRTF() +{ + + OUString aLangString( Application::GetSettings().GetLanguageTag().getLanguage()); + + if ( aLangString == "ru" || aLangString == "uk" ) + return RTL_TEXTENCODING_MS_1251; + if ( aLangString == "tr" ) + return RTL_TEXTENCODING_MS_1254; + else + return RTL_TEXTENCODING_MS_1252; +} + +// -------------- Methods -------------------- + +SvxRTFParser::SvxRTFParser( SfxItemPool& rPool, SvStream& rIn ) + : SvRTFParser( rIn, 5 ) + , pAttrPool( &rPool ) + , nDfltFont( 0) + , bNewDoc( true ) + , bNewGroup( false) + , bIsSetDfltTab( false) + , bChkStyleAttr( false ) + , bCalcValue( false ) + , bIsLeftToRightDef( true) + , bIsInReadStyleTab( false) +{ + pDfltFont.emplace(); + mxDefaultColor = Color(); + + // generate the correct WhichId table from the set WhichIds. + BuildWhichTable(); +} + +SvxRTFParser::~SvxRTFParser() +{ + if( !aAttrStack.empty() ) + ClearAttrStack(); +} + +void SvxRTFParser::SetInsPos( const EditPosition& rNew ) +{ + mxInsertPosition = rNew; +} + +SvParserState SvxRTFParser::CallParser() +{ + DBG_ASSERT( mxInsertPosition, "no insertion position"); + + if( !mxInsertPosition ) + return SvParserState::Error; + + if( !maColorTable.empty() ) + ClearColorTbl(); + m_FontTable.clear(); + m_StyleTable.clear(); + if( !aAttrStack.empty() ) + ClearAttrStack(); + + bIsSetDfltTab = false; + bNewGroup = false; + nDfltFont = 0; + + return SvRTFParser::CallParser(); +} + +void SvxRTFParser::Continue( int nToken ) +{ + SvRTFParser::Continue( nToken ); + + SvParserState eStatus = GetStatus(); + if (eStatus != SvParserState::Pending && eStatus != SvParserState::Error) + { + SetAllAttrOfStk(); + //Regardless of what "color 0" is, word defaults to auto as the default colour. + //e.g. see #i7713# + } +} + + +// is called for each token that is recognized in CallParser +void SvxRTFParser::NextToken( int nToken ) +{ + sal_Unicode cCh; + switch( nToken ) + { + case RTF_COLORTBL: ReadColorTable(); break; + case RTF_FONTTBL: ReadFontTable(); break; + case RTF_STYLESHEET: ReadStyleTable(); break; + + case RTF_DEFF: + if( bNewDoc ) + { + if (!m_FontTable.empty()) + // Can immediately be set + SetDefault( nToken, nTokenValue ); + else + // is set after reading the font table + nDfltFont = int(nTokenValue); + } + break; + + case RTF_DEFTAB: + case RTF_DEFLANG: + if( bNewDoc ) + SetDefault( nToken, nTokenValue ); + break; + + + case RTF_PICT: ReadBitmapData(); break; + + case RTF_LINE: cCh = '\n'; goto INSINGLECHAR; + case RTF_TAB: cCh = '\t'; goto INSINGLECHAR; + case RTF_SUBENTRYINDEX: cCh = ':'; goto INSINGLECHAR; + + case RTF_EMDASH: cCh = 0x2014; goto INSINGLECHAR; + case RTF_ENDASH: cCh = 0x2013; goto INSINGLECHAR; + case RTF_BULLET: cCh = 0x2022; goto INSINGLECHAR; + case RTF_LQUOTE: cCh = 0x2018; goto INSINGLECHAR; + case RTF_RQUOTE: cCh = 0x2019; goto INSINGLECHAR; + case RTF_LDBLQUOTE: cCh = 0x201C; goto INSINGLECHAR; + case RTF_RDBLQUOTE: cCh = 0x201D; goto INSINGLECHAR; +INSINGLECHAR: + aToken = OUStringChar(cCh); + [[fallthrough]]; // aToken is set as Text + case RTF_TEXTTOKEN: + { + InsertText(); + // all collected Attributes are set + for (size_t n = m_AttrSetList.size(); n; ) + { + auto const& pStkSet = m_AttrSetList[--n]; + SetAttrSet( *pStkSet ); + m_AttrSetList.pop_back(); + } + } + break; + + + case RTF_PAR: + InsertPara(); + break; + case '{': + if (bNewGroup) // Nesting! + GetAttrSet_(); + bNewGroup = true; + break; + case '}': + if( !bNewGroup ) // Empty Group ?? + AttrGroupEnd(); + bNewGroup = false; + break; + case RTF_INFO: + SkipGroup(); + break; + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // First overwrite all (all have to be in one group!!) + // Could also appear in the RTF-file without the IGNORE-Flag; all Groups + // with the IGNORE-Flag are overwritten in the default branch. + + case RTF_SWG_PRTDATA: + case RTF_FIELD: + case RTF_ATNID: + case RTF_ANNOTATION: + + case RTF_BKMKSTART: + case RTF_BKMKEND: + case RTF_BKMK_KEY: + case RTF_XE: + case RTF_TC: + case RTF_NEXTFILE: + case RTF_TEMPLATE: + // RTF_SHPRSLT disabled for #i19718# + SkipGroup(); + break; + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + case RTF_PGDSCNO: + case RTF_PGBRK: + case RTF_SHADOW: + if( RTF_IGNOREFLAG != GetStackPtr( -1 )->nTokenId ) + break; + nToken = SkipToken(); + if( '{' == GetStackPtr( -1 )->nTokenId ) + nToken = SkipToken(); + + ReadAttr( nToken, &GetAttrSet() ); + break; + + default: + switch( nToken & ~(0xff | RTF_SWGDEFS) ) + { + case RTF_PARFMT: // here are no SWGDEFS + ReadAttr( nToken, &GetAttrSet() ); + break; + + case RTF_CHRFMT: + case RTF_BRDRDEF: + case RTF_TABSTOPDEF: + + if( RTF_SWGDEFS & nToken) + { + if( RTF_IGNOREFLAG != GetStackPtr( -1 )->nTokenId ) + break; + nToken = SkipToken(); + if( '{' == GetStackPtr( -1 )->nTokenId ) + { + nToken = SkipToken(); + } + } + ReadAttr( nToken, &GetAttrSet() ); + break; + default: + { + if( RTF_IGNOREFLAG == GetStackPtr( -1 )->nTokenId && + '{' == GetStackPtr( -2 )->nTokenId ) + SkipGroup(); + } + break; + } + break; + } +} + +void SvxRTFParser::ReadStyleTable() +{ + int bSaveChkStyleAttr = bChkStyleAttr ? 1 : 0; + sal_uInt16 nStyleNo = 0; + bool bHasStyleNo = false; + int _nOpenBrackets = 1; // the first was already detected earlier!! + std::optional<SvxRTFStyleType> xStyle(SvxRTFStyleType(*pAttrPool, aWhichMap)); + xStyle->aAttrSet.Put( GetRTFDefaults() ); + + bIsInReadStyleTab = true; + bChkStyleAttr = false; // Do not check Attribute against the Styles + + while( _nOpenBrackets && IsParserWorking() ) + { + int nToken = GetNextToken(); + switch( nToken ) + { + case '}': if( --_nOpenBrackets && IsParserWorking() ) + // Style has been completely read, + // so this is still a stable status + SaveState( RTF_STYLESHEET ); + break; + case '{': + { + if( RTF_IGNOREFLAG != GetNextToken() ) + SkipToken(); + else if( RTF_UNKNOWNCONTROL != ( nToken = GetNextToken() ) && + RTF_PN != nToken ) + SkipToken( -2 ); + else + { + // filter out at once + ReadUnknownData(); + nToken = GetNextToken(); + if( '}' != nToken ) + eState = SvParserState::Error; + break; + } + ++_nOpenBrackets; + } + break; + + case RTF_SBASEDON: xStyle->nBasedOn = sal_uInt16(nTokenValue); break; + case RTF_SNEXT: break; + case RTF_OUTLINELEVEL: + case RTF_SOUTLVL: xStyle->nOutlineNo = sal_uInt8(nTokenValue); break; + case RTF_S: nStyleNo = static_cast<short>(nTokenValue); + bHasStyleNo = true; + break; + case RTF_CS: nStyleNo = static_cast<short>(nTokenValue); + bHasStyleNo = true; + break; + + case RTF_TEXTTOKEN: + if (bHasStyleNo) + { + DelCharAtEnd( aToken, ';' ); + xStyle->sName = aToken.toString(); + + if (!m_StyleTable.empty()) + { + m_StyleTable.erase(nStyleNo); + } + // All data from the font is available, so off to the table + m_StyleTable.emplace(nStyleNo, std::move(*xStyle)); + xStyle.emplace(*pAttrPool, aWhichMap); + xStyle->aAttrSet.Put( GetRTFDefaults() ); + nStyleNo = 0; + bHasStyleNo = false; + } + break; + default: + switch( nToken & ~(0xff | RTF_SWGDEFS) ) + { + case RTF_PARFMT: // here are no SWGDEFS + ReadAttr( nToken, &xStyle->aAttrSet ); + break; + + case RTF_CHRFMT: + case RTF_BRDRDEF: + case RTF_TABSTOPDEF: +#ifndef NDEBUG + auto nEnteringToken = nToken; +#endif + auto nEnteringIndex = m_nTokenIndex; + int nSkippedTokens = 0; + if( RTF_SWGDEFS & nToken) + { + if( RTF_IGNOREFLAG != GetStackPtr( -1 )->nTokenId ) + break; + nToken = SkipToken(); + ++nSkippedTokens; + if( '{' == GetStackPtr( -1 )->nTokenId ) + { + nToken = SkipToken(); + ++nSkippedTokens; + } + } + ReadAttr( nToken, &xStyle->aAttrSet ); + if (nSkippedTokens && m_nTokenIndex == nEnteringIndex - nSkippedTokens) + { + // we called SkipToken to go back one or two, but ReadAttrs + // read nothing, so on next loop of the outer while we + // would end up in the same state again (assert that) + assert(nEnteringToken == GetNextToken()); + // and loop endlessly, skip format a token + // instead to avoid that + SkipToken(nSkippedTokens); + } + break; + } + break; + } + } + xStyle.reset(); // Delete the Last Style + SkipToken(); // the closing brace is evaluated "above" + + // Flag back to old state + bChkStyleAttr = bSaveChkStyleAttr; + bIsInReadStyleTab = false; +} + +void SvxRTFParser::ReadColorTable() +{ + int nToken; + sal_uInt8 nRed = 0xff, nGreen = 0xff, nBlue = 0xff; + + for (;;) + { + nToken = GetNextToken(); + if ( '}' == nToken || !IsParserWorking() ) + break; + switch( nToken ) + { + case RTF_RED: nRed = sal_uInt8(nTokenValue); break; + case RTF_GREEN: nGreen = sal_uInt8(nTokenValue); break; + case RTF_BLUE: nBlue = sal_uInt8(nTokenValue); break; + + case RTF_TEXTTOKEN: + if( 1 == aToken.getLength() + ? aToken[ 0 ] != ';' + : -1 == aToken.indexOf( ";" ) ) + break; // At least the ';' must be found + + [[fallthrough]]; + + case ';': + if( IsParserWorking() ) + { + // one color is finished, fill in the table + // try to map the values to SV internal names + Color aColor( nRed, nGreen, nBlue ); + if( maColorTable.empty() && + sal_uInt8(-1) == nRed && sal_uInt8(-1) == nGreen && sal_uInt8(-1) == nBlue ) + aColor = COL_AUTO; + maColorTable.push_back( aColor ); + nRed = 0; + nGreen = 0; + nBlue = 0; + + // Color has been completely read, + // so this is still a stable status + SaveState( RTF_COLORTBL ); + } + break; + } + } + SkipToken(); // the closing brace is evaluated "above" +} + +void SvxRTFParser::ReadFontTable() +{ + int _nOpenBrackets = 1; // the first was already detected earlier!! + vcl::Font aFont; + short nFontNo(0), nInsFontNo (0); + OUString sAltNm, sFntNm; + bool bIsAltFntNm = false; + + rtl_TextEncoding nSystemChar = lcl_GetDefaultTextEncodingForRTF(); + aFont.SetCharSet( nSystemChar ); + SetEncoding( nSystemChar ); + + while( _nOpenBrackets && IsParserWorking() ) + { + bool bCheckNewFont = false; + int nToken = GetNextToken(); + switch( nToken ) + { + case '}': + bIsAltFntNm = false; + // Style has been completely read, + // so this is still a stable status + if( --_nOpenBrackets <= 1 && IsParserWorking() ) + SaveState( RTF_FONTTBL ); + bCheckNewFont = true; + nInsFontNo = nFontNo; + break; + case '{': + if( RTF_IGNOREFLAG != GetNextToken() ) + SkipToken(); + // immediately skip unknown and all known but non-evaluated + // groups + else if( RTF_UNKNOWNCONTROL != ( nToken = GetNextToken() ) && + RTF_PANOSE != nToken && RTF_FNAME != nToken && + RTF_FONTEMB != nToken && RTF_FONTFILE != nToken ) + SkipToken( -2 ); + else + { + // filter out at once + ReadUnknownData(); + nToken = GetNextToken(); + if( '}' != nToken ) + eState = SvParserState::Error; + break; + } + ++_nOpenBrackets; + break; + case RTF_FROMAN: + aFont.SetFamily( FAMILY_ROMAN ); + break; + case RTF_FSWISS: + aFont.SetFamily( FAMILY_SWISS ); + break; + case RTF_FMODERN: + aFont.SetFamily( FAMILY_MODERN ); + break; + case RTF_FSCRIPT: + aFont.SetFamily( FAMILY_SCRIPT ); + break; + case RTF_FDECOR: + aFont.SetFamily( FAMILY_DECORATIVE ); + break; + // for technical/symbolic font of the rtl_TextEncoding is changed! + case RTF_FTECH: + aFont.SetCharSet( RTL_TEXTENCODING_SYMBOL ); + [[fallthrough]]; + case RTF_FNIL: + aFont.SetFamily( FAMILY_DONTKNOW ); + break; + case RTF_FCHARSET: + if (-1 != nTokenValue) + { + rtl_TextEncoding nrtl_TextEncoding = rtl_getTextEncodingFromWindowsCharset( + static_cast<sal_uInt8>(nTokenValue)); + aFont.SetCharSet(nrtl_TextEncoding); + //When we're in a font, the fontname is in the font + //charset, except for symbol fonts I believe + if (nrtl_TextEncoding == RTL_TEXTENCODING_SYMBOL) + nrtl_TextEncoding = RTL_TEXTENCODING_DONTKNOW; + SetEncoding(nrtl_TextEncoding); + } + break; + case RTF_FPRQ: + switch( nTokenValue ) + { + case 1: + aFont.SetPitch( PITCH_FIXED ); + break; + case 2: + aFont.SetPitch( PITCH_VARIABLE ); + break; + } + break; + case RTF_F: + bCheckNewFont = true; + nInsFontNo = nFontNo; + nFontNo = static_cast<short>(nTokenValue); + break; + case RTF_FALT: + bIsAltFntNm = true; + break; + case RTF_TEXTTOKEN: + DelCharAtEnd( aToken, ';' ); + if ( !aToken.isEmpty() ) + { + if( bIsAltFntNm ) + sAltNm = aToken; + else + sFntNm = aToken; + } + break; + } + + if( bCheckNewFont && 1 >= _nOpenBrackets && !sFntNm.isEmpty() ) // one font is ready + { + // All data from the font is available, so off to the table + if (!sAltNm.isEmpty()) + sFntNm += ";" + sAltNm; + + aFont.SetFamilyName( sFntNm ); + m_FontTable.insert(std::make_pair(nInsFontNo, aFont)); + aFont = vcl::Font(); + aFont.SetCharSet( nSystemChar ); + sAltNm.clear(); + sFntNm.clear(); + } + } + SkipToken(); // the closing brace is evaluated "above" + + // set the default font in the Document + if( bNewDoc && IsParserWorking() ) + SetDefault( RTF_DEFF, nDfltFont ); +} + +void SvxRTFParser::ClearColorTbl() +{ + maColorTable.clear(); +} + +void SvxRTFParser::ClearAttrStack() +{ + aAttrStack.clear(); +} + +void SvxRTFParser::DelCharAtEnd( OUStringBuffer& rStr, const sal_Unicode cDel ) +{ + rStr.strip(' '); + if( !rStr.isEmpty() && cDel == rStr[ rStr.getLength()-1 ]) + rStr.setLength( rStr.getLength()-1 ); +} + + +const vcl::Font& SvxRTFParser::GetFont( sal_uInt16 nId ) +{ + SvxRTFFontTbl::const_iterator it = m_FontTable.find( nId ); + if (it != m_FontTable.end()) + { + return it->second; + } + const SvxFontItem& rDfltFont = static_cast<const SvxFontItem&>( + pAttrPool->GetDefaultItem(aPlainMap[SID_ATTR_CHAR_FONT])); + pDfltFont->SetFamilyName( rDfltFont.GetStyleName() ); + pDfltFont->SetFamily( rDfltFont.GetFamily() ); + return *pDfltFont; +} + +std::unique_ptr<SvxRTFItemStackType> SvxRTFItemStackType::createSvxRTFItemStackType( + SfxItemPool& rPool, const WhichRangesContainer& pWhichRange, const EditPosition& rEditPosition) +{ + struct MakeUniqueEnabler : public SvxRTFItemStackType + { + MakeUniqueEnabler(SfxItemPool& rPool, const WhichRangesContainer& pWhichRange, const EditPosition& rEditPosition) + : SvxRTFItemStackType(rPool, pWhichRange, rEditPosition) + { + } + }; + return std::make_unique<MakeUniqueEnabler>(rPool, pWhichRange, rEditPosition); +} + +SvxRTFItemStackType* SvxRTFParser::GetAttrSet_() +{ + SvxRTFItemStackType* pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); + std::unique_ptr<SvxRTFItemStackType> xNew; + if( pCurrent ) + xNew = std::make_unique<SvxRTFItemStackType>(*pCurrent, *mxInsertPosition, false/*bCopyAttr*/); + else + xNew = SvxRTFItemStackType::createSvxRTFItemStackType(*pAttrPool, aWhichMap, *mxInsertPosition); + xNew->SetRTFDefaults( GetRTFDefaults() ); + + aAttrStack.push_back( std::move(xNew) ); + + if (aAttrStack.size() > 256 && utl::ConfigManager::IsFuzzing()) + throw std::range_error("ecStackOverflow"); + + bNewGroup = false; + return aAttrStack.back().get(); +} + +void SvxRTFParser::ClearStyleAttr_( SvxRTFItemStackType& rStkType ) +{ + // check attributes to the attributes of the stylesheet or to + // the default attrs of the document + SfxItemSet &rSet = rStkType.GetAttrSet(); + const SfxItemPool& rPool = *rSet.GetPool(); + const SfxPoolItem* pItem; + SfxWhichIter aIter( rSet ); + + if( !IsChkStyleAttr() || + !rStkType.GetAttrSet().Count() || + m_StyleTable.count( rStkType.nStyleNo ) == 0 ) + { + for( sal_uInt16 nWhich = aIter.GetCurWhich(); nWhich; nWhich = aIter.NextWhich() ) + { + if (SfxItemPool::IsWhich(nWhich) && + SfxItemState::SET == aIter.GetItemState( false, &pItem ) && + rPool.GetDefaultItem( nWhich ) == *pItem ) + aIter.ClearItem(); // delete + } + } + else + { + // Delete all Attributes, which are already defined in the Style, + // from the current AttrSet. + auto & rStyle = m_StyleTable.find(rStkType.nStyleNo)->second; + SfxItemSet &rStyleSet = rStyle.aAttrSet; + const SfxPoolItem* pSItem; + for( sal_uInt16 nWhich = aIter.GetCurWhich(); nWhich; nWhich = aIter.NextWhich() ) + { + if( SfxItemState::SET == rStyleSet.GetItemState( nWhich, true, &pSItem )) + { + if( SfxItemState::SET == aIter.GetItemState( false, &pItem ) + && *pItem == *pSItem ) + rSet.ClearItem( nWhich ); // delete + } + else if (SfxItemPool::IsWhich(nWhich) && + SfxItemState::SET == aIter.GetItemState( false, &pItem ) && + rPool.GetDefaultItem( nWhich ) == *pItem ) + rSet.ClearItem( nWhich ); // delete + } + } +} + +void SvxRTFParser::AttrGroupEnd() // process the current, delete from Stack +{ + if( aAttrStack.empty() ) + return; + + std::unique_ptr<SvxRTFItemStackType> pOld = std::move(aAttrStack.back()); + aAttrStack.pop_back(); + SvxRTFItemStackType *pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); + + do { // middle check loop + sal_Int32 nOldSttNdIdx = pOld->mxStartNodeIdx->GetIdx(); + if (pOld->maChildList.empty() && + ((!pOld->aAttrSet.Count() && !pOld->nStyleNo ) || + (nOldSttNdIdx == mxInsertPosition->GetNodeIdx() && + pOld->nSttCnt == mxInsertPosition->GetCntIdx() ))) + break; // no attributes or Area + + // set only the attributes that are different from the parent + if( pCurrent && pOld->aAttrSet.Count() ) + { + SfxItemIter aIter( pOld->aAttrSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(), *pGet; + do + { + if( SfxItemState::SET == pCurrent->aAttrSet.GetItemState( + pItem->Which(), false, &pGet ) && + *pItem == *pGet ) + aIter.ClearItem(); + + pItem = aIter.NextItem(); + } while (pItem); + + if (!pOld->aAttrSet.Count() && pOld->maChildList.empty() && + !pOld->nStyleNo ) + break; + } + + // Set all attributes which have been defined from start until here + bool bCrsrBack = !mxInsertPosition->GetCntIdx(); + if( bCrsrBack ) + { + // at the beginning of a paragraph? Move back one position + sal_Int32 nNd = mxInsertPosition->GetNodeIdx(); + MovePos(false); + // if can not move backward then later don't move forward ! + bCrsrBack = nNd != mxInsertPosition->GetNodeIdx(); + } + + if( pOld->mxStartNodeIdx->GetIdx() < mxInsertPosition->GetNodeIdx() || + ( pOld->mxStartNodeIdx->GetIdx() == mxInsertPosition->GetNodeIdx() && + pOld->nSttCnt <= mxInsertPosition->GetCntIdx() ) ) + { + if( !bCrsrBack ) + { + // all pard attributes are only valid until the previous + // paragraph !! + if( nOldSttNdIdx == mxInsertPosition->GetNodeIdx() ) + { + } + else + { + // Now it gets complicated: + // - all character attributes sre keep the area + // - all paragraph attributes to get the area + // up to the previous paragraph + auto xNew = std::make_unique<SvxRTFItemStackType>(*pOld, *mxInsertPosition, true); + xNew->aAttrSet.SetParent( pOld->aAttrSet.GetParent() ); + + // Delete all paragraph attributes from xNew + for (const auto& pair : aPardMap) + if (sal_uInt16 wid = pair.second) + xNew->aAttrSet.ClearItem(wid); + xNew->SetRTFDefaults( GetRTFDefaults() ); + + // Were there any? + if( xNew->aAttrSet.Count() == pOld->aAttrSet.Count() ) + { + xNew.reset(); + } + else + { + xNew->nStyleNo = 0; + + // Now span the real area of xNew from old + SetEndPrevPara( pOld->mxEndNodeIdx, pOld->nEndCnt ); + xNew->nSttCnt = 0; + + if( IsChkStyleAttr() ) + { + ClearStyleAttr_( *pOld ); + ClearStyleAttr_( *xNew ); //#i10381#, methinks. + } + + if( pCurrent ) + { + pCurrent->Add(std::move(pOld)); + pCurrent->Add(std::move(xNew)); + } + else + { + // Last off the stack, thus cache it until the next text was + // read. (Span no attributes!) + + m_AttrSetList.push_back(std::move(pOld)); + m_AttrSetList.push_back(std::move(xNew)); + } + break; + } + } + } + + pOld->mxEndNodeIdx = mxInsertPosition->MakeNodeIdx(); + pOld->nEndCnt = mxInsertPosition->GetCntIdx(); + + /* + #i21422# + If the parent (pCurrent) sets something e.g. , and the child (pOld) + unsets it and the style both are based on has it unset then + clearing the pOld by looking at the style is clearly a disaster + as the text ends up with pCurrents bold and not pOlds no bold, this + should be rethought out. For the moment its safest to just do + the clean if we have no parent, all we suffer is too many + redundant properties. + */ + if (IsChkStyleAttr() && !pCurrent) + ClearStyleAttr_( *pOld ); + + if( pCurrent ) + { + pCurrent->Add(std::move(pOld)); + // split up and create new entry, because it makes no sense + // to create a "so long" depend list. Bug 95010 + if (bCrsrBack && 50 < pCurrent->maChildList.size()) + { + // at the beginning of a paragraph? Move back one position + MovePos(); + bCrsrBack = false; + + // Open a new Group. + auto xNew(std::make_unique<SvxRTFItemStackType>(*pCurrent, *mxInsertPosition, true)); + xNew->SetRTFDefaults( GetRTFDefaults() ); + + // Set all until here valid Attributes + AttrGroupEnd(); + pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); // can be changed after AttrGroupEnd! + xNew->aAttrSet.SetParent( pCurrent ? &pCurrent->aAttrSet : nullptr ); + aAttrStack.push_back( std::move(xNew) ); + } + } + else + // Last off the stack, thus cache it until the next text was + // read. (Span no attributes!) + m_AttrSetList.push_back(std::move(pOld)); + } + + if( bCrsrBack ) + // at the beginning of a paragraph? Move back one position + MovePos(); + + } while( false ); + + bNewGroup = false; +} + +void SvxRTFParser::SetAllAttrOfStk() // end all Attr. and set it into doc +{ + // repeat until all attributes will be taken from stack + while( !aAttrStack.empty() ) + AttrGroupEnd(); + + for (size_t n = m_AttrSetList.size(); n; ) + { + auto const& pStkSet = m_AttrSetList[--n]; + SetAttrSet( *pStkSet ); + pStkSet->DropChildList(); + m_AttrSetList.pop_back(); + } +} + +// sets all the attributes that are different from the current +void SvxRTFParser::SetAttrSet( SvxRTFItemStackType &rSet ) +{ + // Was DefTab never read? then set to default + if( !bIsSetDfltTab ) + SetDefault( RTF_DEFTAB, 720 ); + + if (!rSet.maChildList.empty()) + rSet.Compress( *this ); + if( rSet.aAttrSet.Count() || rSet.nStyleNo ) + SetAttrInDoc( rSet ); + + // then process all the children + for (size_t n = 0; n < rSet.maChildList.size(); ++n) + SetAttrSet( *(rSet.maChildList[ n ]) ); +} + +// Has no text been inserted yet? (SttPos from the top Stack entry!) +bool SvxRTFParser::IsAttrSttPos() +{ + SvxRTFItemStackType* pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); + return !pCurrent || (pCurrent->mxStartNodeIdx->GetIdx() == mxInsertPosition->GetNodeIdx() && + pCurrent->nSttCnt == mxInsertPosition->GetCntIdx()); +} + + +void SvxRTFParser::SetAttrInDoc( SvxRTFItemStackType & ) +{ +} + +void SvxRTFParser::BuildWhichTable() +{ + aWhichMap.reset(); + + // Here are the IDs for all paragraph attributes, which can be detected by + // SvxParser and can be set in a SfxItemSet. The IDs are set correctly through + // the SlotIds from POOL. + static constexpr sal_uInt16 WIDS1[] { + SID_ATTR_PARA_LINESPACE, + SID_ATTR_PARA_ADJUST, + SID_ATTR_TABSTOP, + SID_ATTR_PARA_HYPHENZONE, + SID_ATTR_LRSPACE, + SID_ATTR_ULSPACE, + SID_ATTR_BRUSH, + SID_ATTR_BORDER_OUTER, + SID_ATTR_BORDER_SHADOW, + SID_ATTR_PARA_OUTLLEVEL, + SID_ATTR_PARA_SPLIT, + SID_ATTR_PARA_KEEP, + SID_PARA_VERTALIGN, + SID_ATTR_PARA_SCRIPTSPACE, + SID_ATTR_PARA_HANGPUNCTUATION, + SID_ATTR_PARA_FORBIDDEN_RULES, + SID_ATTR_FRAMEDIRECTION, + }; + for (sal_uInt16 nWid : WIDS1) + { + sal_uInt16 nTrueWid = pAttrPool->GetTrueWhich(nWid, false); + aPardMap[nWid] = nTrueWid; + if (nTrueWid == 0) + continue; + aWhichMap = aWhichMap.MergeRange(nTrueWid, nTrueWid); + } + + // Here are the IDs for all character attributes, which can be detected by + // SvxParser and can be set in a SfxItemSet. The IDs are set correctly through + // the SlotIds from POOL. + static constexpr sal_uInt16 WIDS[] { + SID_ATTR_CHAR_CASEMAP, SID_ATTR_BRUSH_CHAR, SID_ATTR_CHAR_COLOR, + SID_ATTR_CHAR_CONTOUR, SID_ATTR_CHAR_STRIKEOUT, SID_ATTR_CHAR_ESCAPEMENT, + SID_ATTR_CHAR_FONT, SID_ATTR_CHAR_FONTHEIGHT, SID_ATTR_CHAR_KERNING, + SID_ATTR_CHAR_LANGUAGE, SID_ATTR_CHAR_POSTURE, SID_ATTR_CHAR_SHADOWED, + SID_ATTR_CHAR_UNDERLINE, SID_ATTR_CHAR_OVERLINE, SID_ATTR_CHAR_WEIGHT, + SID_ATTR_CHAR_WORDLINEMODE, SID_ATTR_CHAR_AUTOKERN, SID_ATTR_CHAR_CJK_FONT, + SID_ATTR_CHAR_CJK_FONTHEIGHT, sal_uInt16(SID_ATTR_CHAR_CJK_LANGUAGE), SID_ATTR_CHAR_CJK_POSTURE, + SID_ATTR_CHAR_CJK_WEIGHT, SID_ATTR_CHAR_CTL_FONT, SID_ATTR_CHAR_CTL_FONTHEIGHT, + SID_ATTR_CHAR_CTL_LANGUAGE, SID_ATTR_CHAR_CTL_POSTURE, SID_ATTR_CHAR_CTL_WEIGHT, + SID_ATTR_CHAR_EMPHASISMARK, SID_ATTR_CHAR_TWO_LINES, SID_ATTR_CHAR_SCALEWIDTH, + SID_ATTR_CHAR_ROTATED, SID_ATTR_CHAR_RELIEF, SID_ATTR_CHAR_HIDDEN, + }; + for (sal_uInt16 nWid : WIDS) + { + sal_uInt16 nTrueWid = pAttrPool->GetTrueWhich(nWid, false); + aPlainMap[nWid] = nTrueWid; + if (nTrueWid == 0) + continue; + aWhichMap = aWhichMap.MergeRange(nTrueWid, nTrueWid); + } +} + +const SfxItemSet& SvxRTFParser::GetRTFDefaults() +{ + if( !pRTFDefaults ) + { + pRTFDefaults.reset(new SfxItemSet(*pAttrPool, aWhichMap)); + if (const sal_uInt16 nId = aPardMap[SID_ATTR_PARA_SCRIPTSPACE]) + { + SvxScriptSpaceItem aItem( false, nId ); + if( bNewDoc ) + pAttrPool->SetPoolDefaultItem( aItem ); + else + pRTFDefaults->Put( aItem ); + } + } + return *pRTFDefaults; +} + + +SvxRTFStyleType::SvxRTFStyleType(SfxItemPool& rPool, const WhichRangesContainer& pWhichRange) + : aAttrSet(rPool, pWhichRange) + , nBasedOn(0) + , nOutlineNo(sal_uInt8(-1)) // not set +{ +} + +SvxRTFItemStackType::SvxRTFItemStackType( + SfxItemPool& rPool, const WhichRangesContainer& pWhichRange, + const EditPosition& rPos ) + : aAttrSet( rPool, pWhichRange ) + , mxStartNodeIdx(rPos.MakeNodeIdx()) +#if !defined(__COVERITY__) + // coverity 2020 has difficulty wrt std::optional leading to bogus 'Uninitialized scalar variable' + , mxEndNodeIdx(mxStartNodeIdx) +#endif + , nSttCnt(rPos.GetCntIdx()) + , nEndCnt(nSttCnt) + , nStyleNo(0) +{ +} + +SvxRTFItemStackType::SvxRTFItemStackType( + const SvxRTFItemStackType& rCpy, + const EditPosition& rPos, + bool const bCopyAttr ) + : aAttrSet( *rCpy.aAttrSet.GetPool(), rCpy.aAttrSet.GetRanges() ) + , mxStartNodeIdx(rPos.MakeNodeIdx()) +#if !defined(__COVERITY__) + // coverity 2020 has difficulty wrt std::optional leading to bogus 'Uninitialized scalar variable' + , mxEndNodeIdx(mxStartNodeIdx) +#endif + , nSttCnt(rPos.GetCntIdx()) + , nEndCnt(nSttCnt) + , nStyleNo(rCpy.nStyleNo) +{ + aAttrSet.SetParent( &rCpy.aAttrSet ); + if( bCopyAttr ) + aAttrSet.Put( rCpy.aAttrSet ); +} + +/* ofz#13491 SvxRTFItemStackType dtor recursively + calls the dtor of its m_pChildList. The recurse + depth can grow sufficiently to trigger asan. + + So breadth-first iterate through the nodes + and make a flat vector of them which can + be iterated through in order of most + distant from root first and release + their children linearly +*/ +void SvxRTFItemStackType::DropChildList() +{ + if (maChildList.empty()) + return; + + std::vector<SvxRTFItemStackType*> bfs; + std::queue<SvxRTFItemStackType*> aQueue; + aQueue.push(this); + + while (!aQueue.empty()) + { + auto* front = aQueue.front(); + aQueue.pop(); + if (!front->maChildList.empty()) + { + for (const auto& a : front->maChildList) + aQueue.push(a.get()); + bfs.push_back(front); + } + } + + for (auto it = bfs.rbegin(); it != bfs.rend(); ++it) + { + SvxRTFItemStackType* pNode = *it; + pNode->maChildList.clear(); + } +} + +SvxRTFItemStackType::~SvxRTFItemStackType() +{ +} + +void SvxRTFItemStackType::Add(std::unique_ptr<SvxRTFItemStackType> pIns) +{ + maChildList.push_back(std::move(pIns)); +} + +void SvxRTFItemStackType::SetStartPos( const EditPosition& rPos ) +{ + mxStartNodeIdx = rPos.MakeNodeIdx(); + mxEndNodeIdx = mxStartNodeIdx; + nSttCnt = rPos.GetCntIdx(); +} + +void SvxRTFItemStackType::Compress( const SvxRTFParser& rParser ) +{ + ENSURE_OR_RETURN_VOID(!maChildList.empty(), "Compress: ChildList empty"); + + SvxRTFItemStackType* pTmp = maChildList[0].get(); + + if( !pTmp->aAttrSet.Count() || + mxStartNodeIdx->GetIdx() != pTmp->mxStartNodeIdx->GetIdx() || + nSttCnt != pTmp->nSttCnt ) + return; + + EditNodeIdx aLastNd = *pTmp->mxEndNodeIdx; + sal_Int32 nLastCnt = pTmp->nEndCnt; + + SfxItemSet aMrgSet( pTmp->aAttrSet ); + for (size_t n = 1; n < maChildList.size(); ++n) + { + pTmp = maChildList[n].get(); + if (!pTmp->maChildList.empty()) + pTmp->Compress( rParser ); + + if( !pTmp->nSttCnt + ? (aLastNd.GetIdx()+1 != pTmp->mxStartNodeIdx->GetIdx() || + !rParser.IsEndPara( &aLastNd, nLastCnt ) ) + : ( pTmp->nSttCnt != nLastCnt || + aLastNd.GetIdx() != pTmp->mxStartNodeIdx->GetIdx() )) + { + while (++n < maChildList.size()) + { + pTmp = maChildList[n].get(); + if (!pTmp->maChildList.empty()) + pTmp->Compress( rParser ); + } + return; + } + + if( n ) + { + // Search for all which are set over the whole area + SfxItemIter aIter( aMrgSet ); + const SfxPoolItem* pItem; + const SfxPoolItem* pIterItem = aIter.GetCurItem(); + do { + sal_uInt16 nWhich = pIterItem->Which(); + if( SfxItemState::SET != pTmp->aAttrSet.GetItemState( nWhich, + false, &pItem ) || *pItem != *pIterItem) + aIter.ClearItem(); + + pIterItem = aIter.NextItem(); + } while(pIterItem); + + if( !aMrgSet.Count() ) + return; + } + + aLastNd = *pTmp->mxEndNodeIdx; + nLastCnt = pTmp->nEndCnt; + } + + if( mxEndNodeIdx->GetIdx() != aLastNd.GetIdx() || nEndCnt != nLastCnt ) + return; + + // It can be merged + aAttrSet.Put( aMrgSet ); + + size_t n = 0, nChildLen = maChildList.size(); + while (n < nChildLen) + { + pTmp = maChildList[n].get(); + pTmp->aAttrSet.Differentiate( aMrgSet ); + + if (pTmp->maChildList.empty() && !pTmp->aAttrSet.Count() && !pTmp->nStyleNo) + { + maChildList.erase( maChildList.begin() + n ); + --nChildLen; + continue; + } + ++n; + } +} +void SvxRTFItemStackType::SetRTFDefaults( const SfxItemSet& rDefaults ) +{ + if( rDefaults.Count() ) + { + SfxItemIter aIter( rDefaults ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do { + sal_uInt16 nWhich = pItem->Which(); + if( SfxItemState::SET != aAttrSet.GetItemState( nWhich, false )) + aAttrSet.Put(*pItem); + + pItem = aIter.NextItem(); + } while(pItem); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/UnoForbiddenCharsTable.cxx b/editeng/source/uno/UnoForbiddenCharsTable.cxx new file mode 100644 index 000000000..5e77a9252 --- /dev/null +++ b/editeng/source/uno/UnoForbiddenCharsTable.cxx @@ -0,0 +1,132 @@ +/* -*- 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 <com/sun/star/container/NoSuchElementException.hpp> +#include <editeng/UnoForbiddenCharsTable.hxx> +#include <editeng/forbiddencharacterstable.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::i18n; +using namespace ::cppu; + +SvxUnoForbiddenCharsTable::SvxUnoForbiddenCharsTable(std::shared_ptr<SvxForbiddenCharactersTable> xForbiddenChars) + : mxForbiddenChars(std::move(xForbiddenChars)) +{ +} + +SvxUnoForbiddenCharsTable::~SvxUnoForbiddenCharsTable() +{ +} + +void SvxUnoForbiddenCharsTable::onChange() +{ +} + +ForbiddenCharacters SvxUnoForbiddenCharsTable::getForbiddenCharacters( const lang::Locale& rLocale ) +{ + SolarMutexGuard aGuard; + + if (!mxForbiddenChars) + throw RuntimeException("No Forbidden Characters present"); + + const LanguageType eLang = LanguageTag::convertToLanguageType( rLocale ); + const ForbiddenCharacters* pForbidden = mxForbiddenChars->GetForbiddenCharacters( eLang, false ); + if(!pForbidden) + throw NoSuchElementException(); + + return *pForbidden; +} + +sal_Bool SvxUnoForbiddenCharsTable::hasForbiddenCharacters( const lang::Locale& rLocale ) +{ + SolarMutexGuard aGuard; + + if (!mxForbiddenChars) + return false; + + const LanguageType eLang = LanguageTag::convertToLanguageType( rLocale ); + const ForbiddenCharacters* pForbidden = mxForbiddenChars->GetForbiddenCharacters( eLang, false ); + + return nullptr != pForbidden; +} + +void SvxUnoForbiddenCharsTable::setForbiddenCharacters(const lang::Locale& rLocale, const ForbiddenCharacters& rForbiddenCharacters ) +{ + SolarMutexGuard aGuard; + + if (!mxForbiddenChars) + throw RuntimeException("No Forbidden Characters present"); + + const LanguageType eLang = LanguageTag::convertToLanguageType( rLocale ); + mxForbiddenChars->SetForbiddenCharacters( eLang, rForbiddenCharacters ); + + onChange(); +} + +void SvxUnoForbiddenCharsTable::removeForbiddenCharacters( const lang::Locale& rLocale ) +{ + SolarMutexGuard aGuard; + + if (!mxForbiddenChars) + throw RuntimeException("No Forbidden Characters present"); + + const LanguageType eLang = LanguageTag::convertToLanguageType( rLocale ); + mxForbiddenChars->ClearForbiddenCharacters( eLang ); + + onChange(); +} + +// XSupportedLocales +Sequence< lang::Locale > SAL_CALL SvxUnoForbiddenCharsTable::getLocales() +{ + SolarMutexGuard aGuard; + + const sal_Int32 nCount = mxForbiddenChars ? mxForbiddenChars->GetMap().size() : 0; + + Sequence< lang::Locale > aLocales( nCount ); + if( nCount ) + { + lang::Locale* pLocales = aLocales.getArray(); + + for (auto const& elem : mxForbiddenChars->GetMap()) + { + const LanguageType nLanguage = elem.first; + *pLocales++ = LanguageTag( nLanguage ).getLocale(); + } + } + + return aLocales; +} + +sal_Bool SAL_CALL SvxUnoForbiddenCharsTable::hasLocale( const lang::Locale& aLocale ) +{ + SolarMutexGuard aGuard; + + return hasForbiddenCharacters( aLocale ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoedhlp.cxx b/editeng/source/uno/unoedhlp.cxx new file mode 100644 index 000000000..2a1b1e2bd --- /dev/null +++ b/editeng/source/uno/unoedhlp.cxx @@ -0,0 +1,250 @@ +/* -*- 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 <editeng/unoedhlp.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editeng.hxx> +#include <svl/itemset.hxx> + +#include <osl/diagnose.h> + + +SvxEditSourceHint::SvxEditSourceHint( SfxHintId _nId ) : + TextHint( _nId ), + mnStart( 0 ), + mnEnd( 0 ) +{ +} + +SvxEditSourceHint::SvxEditSourceHint( SfxHintId _nId, sal_Int32 nValue, sal_Int32 nStart, sal_Int32 nEnd ) : + TextHint( _nId, nValue ), + mnStart( nStart), + mnEnd( nEnd ) +{ +} + + +std::unique_ptr<SfxHint> SvxEditSourceHelper::EENotification2Hint( EENotify const * aNotify ) +{ + if( aNotify ) + { + switch( aNotify->eNotificationType ) + { + case EE_NOTIFY_TEXTMODIFIED: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextModified, aNotify->nParagraph ) ); + + case EE_NOTIFY_PARAGRAPHINSERTED: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextParaInserted, aNotify->nParagraph ) ); + + case EE_NOTIFY_PARAGRAPHREMOVED: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextParaRemoved, aNotify->nParagraph ) ); + + case EE_NOTIFY_PARAGRAPHSMOVED: + return std::unique_ptr<SfxHint>( new SvxEditSourceHint( SfxHintId::EditSourceParasMoved, aNotify->nParagraph, aNotify->nParam1, aNotify->nParam2 ) ); + + case EE_NOTIFY_TextHeightChanged: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextHeightChanged, aNotify->nParagraph ) ); + + case EE_NOTIFY_TEXTVIEWSCROLLED: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextViewScrolled ) ); + + case EE_NOTIFY_TEXTVIEWSELECTIONCHANGED: + return std::unique_ptr<SfxHint>( new SvxEditSourceHint( SfxHintId::EditSourceSelectionChanged ) ); + + case EE_NOTIFY_PROCESSNOTIFICATIONS: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextProcessNotifications )); + + case EE_NOTIFY_TEXTVIEWSELECTIONCHANGED_ENDD_PARA: + return std::unique_ptr<SfxHint>( new SvxEditSourceHintEndPara ); + default: + OSL_FAIL( "SvxEditSourceHelper::EENotification2Hint unknown notification" ); + break; + } + } + + return std::make_unique<SfxHint>( ); +} + +void SvxEditSourceHelper::GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, const EditEngine& rEE, sal_Int32 nPara, sal_Int32 nIndex, bool bInCell ) +{ + // IA2 CWS introduced bInCell, but also did many other changes here. + // Need to verify implementation with AT (IA2 and ATK) + // Old implementation at the end of the method for reference... + + //added dummy attributes for the default text + std::vector<EECharAttrib> aCharAttribs, aTempCharAttribs; + rEE.GetCharAttribs( nPara, aTempCharAttribs ); + + if (!aTempCharAttribs.empty()) + { + sal_Int32 nIndex2 = 0; + sal_Int32 nParaLen = rEE.GetTextLen(nPara); + for (size_t nAttr = 0; nAttr < aTempCharAttribs.size(); ++nAttr) + { + if (nIndex2 < aTempCharAttribs[nAttr].nStart) + { + EECharAttrib aEEAttr(nIndex2, aTempCharAttribs[nAttr].nStart); + aCharAttribs.insert(aCharAttribs.begin() + nAttr, aEEAttr); + } + nIndex2 = aTempCharAttribs[nAttr].nEnd; + aCharAttribs.push_back(aTempCharAttribs[nAttr]); + } + if ( nIndex2 != nParaLen ) + { + EECharAttrib aEEAttr(nIndex2, nParaLen); + aCharAttribs.push_back(aEEAttr); + } + } + // find closest index in front of nIndex + sal_Int32 nCurrIndex; + sal_Int32 nClosestStartIndex_s = 0, nClosestStartIndex_e = 0; + for (auto const& charAttrib : aCharAttribs) + { + nCurrIndex = charAttrib.nStart; + + if( nCurrIndex > nClosestStartIndex_s && + nCurrIndex <= nIndex) + { + nClosestStartIndex_s = nCurrIndex; + } + nCurrIndex = charAttrib.nEnd; + if ( nCurrIndex > nClosestStartIndex_e && + nCurrIndex < nIndex ) + { + nClosestStartIndex_e = nCurrIndex; + } + } + sal_Int32 nClosestStartIndex = std::max(nClosestStartIndex_s, nClosestStartIndex_e); + + // find closest index behind of nIndex + sal_Int32 nClosestEndIndex_s, nClosestEndIndex_e; + nClosestEndIndex_s = nClosestEndIndex_e = rEE.GetTextLen(nPara); + for (auto const& charAttrib : aCharAttribs) + { + nCurrIndex = charAttrib.nEnd; + + if( nCurrIndex > nIndex && + nCurrIndex < nClosestEndIndex_e ) + { + nClosestEndIndex_e = nCurrIndex; + } + nCurrIndex = charAttrib.nStart; + if ( nCurrIndex > nIndex && + nCurrIndex < nClosestEndIndex_s) + { + nClosestEndIndex_s = nCurrIndex; + } + } + sal_Int32 nClosestEndIndex = std::min(nClosestEndIndex_s, nClosestEndIndex_e); + + nStartIndex = nClosestStartIndex; + nEndIndex = nClosestEndIndex; + + if ( !bInCell ) + return; + + EPosition aStartPos( nPara, nStartIndex ), aEndPos( nPara, nEndIndex ); + sal_Int32 nParaCount = rEE.GetParagraphCount(); + sal_Int32 nCrrntParaLen = rEE.GetTextLen(nPara); + //need to find closest index in front of nIndex in the previous paragraphs + if ( aStartPos.nIndex == 0 ) + { + SfxItemSet aCrrntSet = rEE.GetAttribs( nPara, 0, 1, GetAttribsFlags::CHARATTRIBS ); + for ( sal_Int32 nParaIdx = nPara-1; nParaIdx >= 0; nParaIdx-- ) + { + sal_uInt32 nLen = rEE.GetTextLen(nParaIdx); + if ( nLen ) + { + sal_Int32 nStartIdx, nEndIdx; + GetAttributeRun( nStartIdx, nEndIdx, rEE, nParaIdx, nLen ); + SfxItemSet aSet = rEE.GetAttribs( nParaIdx, nLen-1, nLen, GetAttribsFlags::CHARATTRIBS ); + if ( aSet == aCrrntSet ) + { + aStartPos.nPara = nParaIdx; + aStartPos.nIndex = nStartIdx; + if ( aStartPos.nIndex != 0 ) + { + break; + } + } + } + } + } + //need find closest index behind nIndex in the following paragraphs + if ( aEndPos.nIndex == nCrrntParaLen ) + { + SfxItemSet aCrrntSet = rEE.GetAttribs( nPara, nCrrntParaLen-1, nCrrntParaLen, GetAttribsFlags::CHARATTRIBS ); + for ( sal_Int32 nParaIdx = nPara+1; nParaIdx < nParaCount; nParaIdx++ ) + { + sal_Int32 nLen = rEE.GetTextLen( nParaIdx ); + if ( nLen ) + { + sal_Int32 nStartIdx, nEndIdx; + GetAttributeRun( nStartIdx, nEndIdx, rEE, nParaIdx, 0 ); + SfxItemSet aSet = rEE.GetAttribs( nParaIdx, 0, 1, GetAttribsFlags::CHARATTRIBS ); + if ( aSet == aCrrntSet ) + { + aEndPos.nPara = nParaIdx; + aEndPos.nIndex = nEndIdx; + if ( aEndPos.nIndex != nLen ) + { + break; + } + } + } + } + } + nStartIndex = 0; + if ( aStartPos.nPara > 0 ) + { + for ( sal_Int32 i = 0; i < aStartPos.nPara; i++ ) + { + nStartIndex += rEE.GetTextLen(i)+1; + } + } + nStartIndex += aStartPos.nIndex; + nEndIndex = 0; + if ( aEndPos.nPara > 0 ) + { + for ( sal_Int32 i = 0; i < aEndPos.nPara; i++ ) + { + nEndIndex += rEE.GetTextLen(i)+1; + } + } + nEndIndex += aEndPos.nIndex; +} + +Point SvxEditSourceHelper::EEToUserSpace( const Point& rPoint, const Size& rEESize, bool bIsVertical ) +{ + return bIsVertical ? Point( -rPoint.Y() + rEESize.Height(), rPoint.X() ) : rPoint; +} + +Point SvxEditSourceHelper::UserSpaceToEE( const Point& rPoint, const Size& rEESize, bool bIsVertical ) +{ + return bIsVertical ? Point( rPoint.Y(), -rPoint.X() + rEESize.Height() ) : rPoint; +} + +tools::Rectangle SvxEditSourceHelper::EEToUserSpace( const tools::Rectangle& rRect, const Size& rEESize, bool bIsVertical ) +{ + return bIsVertical ? tools::Rectangle( EEToUserSpace(rRect.BottomLeft(), rEESize, bIsVertical), + EEToUserSpace(rRect.TopRight(), rEESize, bIsVertical) ) : rRect; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoedprx.cxx b/editeng/source/uno/unoedprx.cxx new file mode 100644 index 000000000..91a4ac15b --- /dev/null +++ b/editeng/source/uno/unoedprx.cxx @@ -0,0 +1,1195 @@ +/* -*- 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 . + */ + + +// Global header + + +#include <utility> +#include <memory> +#include <vector> +#include <algorithm> +#include <osl/diagnose.h> +#include <svl/itemset.hxx> +#include <tools/debug.hxx> + + +// Project-local header + + +#include <editeng/unoedprx.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editeng.hxx> +#include <AccessibleStringWrap.hxx> +#include <editeng/outliner.hxx> + +using namespace ::com::sun::star; + +namespace { + +class SvxAccessibleTextIndex +{ +public: + SvxAccessibleTextIndex() : + mnPara(0), + mnIndex(0), + mnEEIndex(0), + mnFieldOffset(0), + mnFieldLen(0), + mnBulletOffset(0), + mnBulletLen(0), + mbInField(false), + mbInBullet(false) {}; + + // Get/Set current paragraph + void SetParagraph( sal_Int32 nPara ) + { + mnPara = nPara; + } + sal_Int32 GetParagraph() const { return mnPara; } + + /** Set the index in the UAA semantic + + @param nIndex + The index from the UA API (fields and bullets are expanded) + + @param rTF + The text forwarder to use in the calculations + */ + void SetIndex( sal_Int32 nIndex, const SvxTextForwarder& rTF ); + void SetIndex( sal_Int32 nPara, sal_Int32 nIndex, const SvxTextForwarder& rTF ) { SetParagraph(nPara); SetIndex(nIndex, rTF); } + sal_Int32 GetIndex() const { return mnIndex; } + + /** Set the index in the edit engine semantic + + Update the object state to reflect the given index position in + EditEngine/Outliner index values + + @param nEEIndex + The index from the edit engine (fields span exactly one index increment) + + @param rTF + The text forwarder to use in the calculations + */ + void SetEEIndex( sal_Int32 nEEIndex, const SvxTextForwarder& rTF ); + void SetEEIndex( sal_Int32 nPara, sal_Int32 nEEIndex, const SvxTextForwarder& rTF ) { SetParagraph(nPara); SetEEIndex(nEEIndex, rTF); } + sal_Int32 GetEEIndex() const; + + void SetFieldOffset( sal_Int32 nOffset, sal_Int32 nLen ) { mnFieldOffset = nOffset; mnFieldLen = nLen; } + sal_Int32 GetFieldOffset() const { return mnFieldOffset; } + sal_Int32 GetFieldLen() const { return mnFieldLen; } + void AreInField() { mbInField = true; } + bool InField() const { return mbInField; } + + void SetBulletOffset( sal_Int32 nOffset, sal_Int32 nLen ) { mnBulletOffset = nOffset; mnBulletLen = nLen; } + sal_Int32 GetBulletOffset() const { return mnBulletOffset; } + sal_Int32 GetBulletLen() const { return mnBulletLen; } + bool InBullet() const { return mbInBullet; } + + /// returns false if the given range is non-editable (e.g. contains bullets or _parts_ of fields) + bool IsEditableRange( const SvxAccessibleTextIndex& rEnd ) const; + +private: + sal_Int32 mnPara; + sal_Int32 mnIndex; + sal_Int32 mnEEIndex; + sal_Int32 mnFieldOffset; + sal_Int32 mnFieldLen; + sal_Int32 mnBulletOffset; + sal_Int32 mnBulletLen; + bool mbInField; + bool mbInBullet; +}; + +} + +static ESelection MakeEESelection( const SvxAccessibleTextIndex& rStart, const SvxAccessibleTextIndex& rEnd ) +{ + // deal with field special case: to really get a field contained + // within a selection, the start index must be before or on the + // field, the end index after it. + + // The SvxAccessibleTextIndex.GetEEIndex method gives the index on + // the field, as long the input index is on the field. Thus, + // correction necessary for the end index + + // Therefore, for _ranges_, if part of the field is touched, all + // of the field must be selected + if( rStart.GetParagraph() <= rEnd.GetParagraph() || + (rStart.GetParagraph() == rEnd.GetParagraph() && + rStart.GetEEIndex() <= rEnd.GetEEIndex()) ) + { + if( rEnd.InField() && rEnd.GetFieldOffset() ) + return ESelection( rStart.GetParagraph(), rStart.GetEEIndex(), + rEnd.GetParagraph(), rEnd.GetEEIndex()+1 ); + } + else if( rStart.GetParagraph() > rEnd.GetParagraph() || + (rStart.GetParagraph() == rEnd.GetParagraph() && + rStart.GetEEIndex() > rEnd.GetEEIndex()) ) + { + if( rStart.InField() && rStart.GetFieldOffset() ) + return ESelection( rStart.GetParagraph(), rStart.GetEEIndex()+1, + rEnd.GetParagraph(), rEnd.GetEEIndex() ); + } + + return ESelection( rStart.GetParagraph(), rStart.GetEEIndex(), + rEnd.GetParagraph(), rEnd.GetEEIndex() ); +} + +static ESelection MakeEESelection( const SvxAccessibleTextIndex& rIndex ) +{ + return ESelection( rIndex.GetParagraph(), rIndex.GetEEIndex(), + rIndex.GetParagraph(), rIndex.GetEEIndex() + 1 ); +} + +sal_Int32 SvxAccessibleTextIndex::GetEEIndex() const +{ + DBG_ASSERT(mnEEIndex >= 0, + "SvxAccessibleTextIndex::GetEEIndex: index value overflow"); + + return mnEEIndex; +} + +void SvxAccessibleTextIndex::SetEEIndex( sal_Int32 nEEIndex, const SvxTextForwarder& rTF ) +{ + // reset + mnFieldOffset = 0; + mbInField = false; + mnFieldLen = 0; + mnBulletOffset = 0; + mbInBullet = false; + mnBulletLen = 0; + + // set known values + mnEEIndex = nEEIndex; + + // calculate unknowns + sal_Int32 nCurrField, nFieldCount = rTF.GetFieldCount( GetParagraph() ); + + mnIndex = nEEIndex; + + EBulletInfo aBulletInfo = rTF.GetBulletInfo( GetParagraph() ); + + // any text bullets? + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ) + { + mnIndex += aBulletInfo.aText.getLength(); + } + + for( nCurrField=0; nCurrField < nFieldCount; ++nCurrField ) + { + EFieldInfo aFieldInfo( rTF.GetFieldInfo( GetParagraph(), nCurrField ) ); + + if( aFieldInfo.aPosition.nIndex > nEEIndex ) + break; + + if( aFieldInfo.aPosition.nIndex == nEEIndex ) + { + AreInField(); + break; + } + + mnIndex += std::max(aFieldInfo.aCurrentText.getLength()-1, sal_Int32(0)); + } +} + +void SvxAccessibleTextIndex::SetIndex( sal_Int32 nIndex, const SvxTextForwarder& rTF ) +{ + // reset + mnFieldOffset = 0; + mbInField = false; + mnFieldLen = 0; + mnBulletOffset = 0; + mbInBullet = false; + mnBulletLen = 0; + + // set known values + mnIndex = nIndex; + + // calculate unknowns + sal_Int32 nCurrField, nFieldCount = rTF.GetFieldCount( GetParagraph() ); + + DBG_ASSERT(nIndex >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + mnEEIndex = nIndex; + + EBulletInfo aBulletInfo = rTF.GetBulletInfo( GetParagraph() ); + + // any text bullets? + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ) + { + sal_Int32 nBulletLen = aBulletInfo.aText.getLength(); + + if( nIndex < nBulletLen ) + { + mbInBullet = true; + SetBulletOffset( nIndex, nBulletLen ); + mnEEIndex = 0; + return; + } + + mnEEIndex = mnEEIndex - nBulletLen; + } + + for( nCurrField=0; nCurrField < nFieldCount; ++nCurrField ) + { + EFieldInfo aFieldInfo( rTF.GetFieldInfo( GetParagraph(), nCurrField ) ); + + // we're before a field + if( aFieldInfo.aPosition.nIndex > mnEEIndex ) + break; + + mnEEIndex -= std::max(aFieldInfo.aCurrentText.getLength()-1, sal_Int32(0)); + + // we're within a field + if( aFieldInfo.aPosition.nIndex >= mnEEIndex ) + { + AreInField(); + SetFieldOffset( std::max(aFieldInfo.aCurrentText.getLength()-1, sal_Int32(0)) - (aFieldInfo.aPosition.nIndex - mnEEIndex), + aFieldInfo.aCurrentText.getLength() ); + mnEEIndex = aFieldInfo.aPosition.nIndex ; + break; + } + } +} + +bool SvxAccessibleTextIndex::IsEditableRange( const SvxAccessibleTextIndex& rEnd ) const +{ + if( GetIndex() > rEnd.GetIndex() ) + return rEnd.IsEditableRange( *this ); + + if( InBullet() || rEnd.InBullet() ) + return false; + + if( InField() && GetFieldOffset() ) + return false; // within field + + if( rEnd.InField() && rEnd.GetFieldOffset() >= rEnd.GetFieldLen() - 1 ) + return false; // within field + + return true; +} + + +SvxEditSourceAdapter::SvxEditSourceAdapter() : mbEditSourceValid( false ) +{ +} + +SvxEditSourceAdapter::~SvxEditSourceAdapter() +{ +} + +std::unique_ptr<SvxEditSource> SvxEditSourceAdapter::Clone() const +{ + if( mbEditSourceValid && mpAdaptee ) + { + std::unique_ptr< SvxEditSource > pClonedAdaptee( mpAdaptee->Clone() ); + + if (pClonedAdaptee) + { + std::unique_ptr<SvxEditSourceAdapter> pClone(new SvxEditSourceAdapter()); + pClone->SetEditSource( std::move(pClonedAdaptee) ); + return std::unique_ptr< SvxEditSource >(pClone.release()); + } + } + + return nullptr; +} + +SvxAccessibleTextAdapter* SvxEditSourceAdapter::GetTextForwarderAdapter() +{ + if( mbEditSourceValid && mpAdaptee ) + { + SvxTextForwarder* pTextForwarder = mpAdaptee->GetTextForwarder(); + + if( pTextForwarder ) + { + maTextAdapter.SetForwarder(*pTextForwarder); + + return &maTextAdapter; + } + } + + return nullptr; +} + +SvxTextForwarder* SvxEditSourceAdapter::GetTextForwarder() +{ + return GetTextForwarderAdapter(); +} + +SvxViewForwarder* SvxEditSourceAdapter::GetViewForwarder() +{ + if( mbEditSourceValid && mpAdaptee ) + return mpAdaptee->GetViewForwarder(); + + return nullptr; +} + +SvxAccessibleTextEditViewAdapter* SvxEditSourceAdapter::GetEditViewForwarderAdapter( bool bCreate ) +{ + if( mbEditSourceValid && mpAdaptee ) + { + SvxEditViewForwarder* pEditViewForwarder = mpAdaptee->GetEditViewForwarder(bCreate); + + if( pEditViewForwarder ) + { + SvxAccessibleTextAdapter* pTextAdapter = GetTextForwarderAdapter(); + + if( pTextAdapter ) + { + maEditViewAdapter.SetForwarder(*pEditViewForwarder, *pTextAdapter); + + return &maEditViewAdapter; + } + } + } + + return nullptr; +} + +SvxEditViewForwarder* SvxEditSourceAdapter::GetEditViewForwarder( bool bCreate ) +{ + return GetEditViewForwarderAdapter( bCreate ); +} + +void SvxEditSourceAdapter::UpdateData() +{ + if( mbEditSourceValid && mpAdaptee ) + mpAdaptee->UpdateData(); +} + +SfxBroadcaster& SvxEditSourceAdapter::GetBroadcaster() const +{ + if( mbEditSourceValid && mpAdaptee ) + return mpAdaptee->GetBroadcaster(); + + return maDummyBroadcaster; +} + +void SvxEditSourceAdapter::SetEditSource( std::unique_ptr< SvxEditSource > && pAdaptee ) +{ + if (pAdaptee) + { + mpAdaptee = std::move(pAdaptee); + mbEditSourceValid = true; + } + else + { + // do a lazy delete (prevents us from deleting the broadcaster + // from within a broadcast in + // AccessibleTextHelper_Impl::Notify) + mbEditSourceValid = false; + } +} + +SvxAccessibleTextAdapter::SvxAccessibleTextAdapter() + : mpTextForwarder(nullptr) +{ +} + +SvxAccessibleTextAdapter::~SvxAccessibleTextAdapter() +{ +} + +sal_Int32 SvxAccessibleTextAdapter::GetParagraphCount() const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetParagraphCount(); +} + +sal_Int32 SvxAccessibleTextAdapter::GetTextLen( sal_Int32 nParagraph ) const +{ + SvxAccessibleTextIndex aIndex; + aIndex.SetEEIndex( nParagraph, mpTextForwarder->GetTextLen( nParagraph ), *this ); + + return aIndex.GetIndex(); +} + +OUString SvxAccessibleTextAdapter::GetText( const ESelection& rSel ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + // normalize selection + if( rSel.nStartPara > rSel.nEndPara || + (rSel.nStartPara == rSel.nEndPara && rSel.nStartPos > rSel.nEndPos) ) + { + std::swap( aStartIndex, aEndIndex ); + } + + OUString sStr = mpTextForwarder->GetText( MakeEESelection(aStartIndex, aEndIndex) ); + + // trim field text, if necessary + if( aStartIndex.InField() ) + { + DBG_ASSERT(aStartIndex.GetFieldOffset() >= 0, + "SvxAccessibleTextIndex::GetText: index value overflow"); + + sStr = sStr.copy( aStartIndex.GetFieldOffset() ); + } + if( aEndIndex.InField() && aEndIndex.GetFieldOffset() ) + { + DBG_ASSERT(sStr.getLength() - (aEndIndex.GetFieldLen() - aEndIndex.GetFieldOffset()) >= 0, + "SvxAccessibleTextIndex::GetText: index value overflow"); + + sStr = sStr.copy(0, sStr.getLength() - (aEndIndex.GetFieldLen() - aEndIndex.GetFieldOffset()) ); + } + + EBulletInfo aBulletInfo2 = GetBulletInfo( aEndIndex.GetParagraph() ); + + if( aEndIndex.InBullet() ) + { + // append trailing bullet + sStr += aBulletInfo2.aText; + + DBG_ASSERT(sStr.getLength() - (aEndIndex.GetBulletLen() - aEndIndex.GetBulletOffset()) >= 0, + "SvxAccessibleTextIndex::GetText: index value overflow"); + + sStr = sStr.copy(0, sStr.getLength() - (aEndIndex.GetBulletLen() - aEndIndex.GetBulletOffset()) ); + } + else if( aStartIndex.GetParagraph() != aEndIndex.GetParagraph() && + HaveTextBullet( aEndIndex.GetParagraph() ) ) + { + OUString sBullet = aBulletInfo2.aText; + + DBG_ASSERT(sBullet.getLength() - (aEndIndex.GetBulletLen() - aEndIndex.GetBulletOffset()) >= 0, + "SvxAccessibleTextIndex::GetText: index value overflow"); + + sBullet = sBullet.copy(0, sBullet.getLength() - (aEndIndex.GetBulletLen() - aEndIndex.GetBulletOffset()) ); + + // insert bullet + sStr = sStr.replaceAt( GetTextLen(aStartIndex.GetParagraph()) - aStartIndex.GetIndex(), 0, sBullet ); + } + + return sStr; +} + +SfxItemSet SvxAccessibleTextAdapter::GetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + return mpTextForwarder->GetAttribs( MakeEESelection(aStartIndex, aEndIndex), nOnlyHardAttrib ); +} + +SfxItemSet SvxAccessibleTextAdapter::GetParaAttribs( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetParaAttribs( nPara ); +} + +void SvxAccessibleTextAdapter::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + mpTextForwarder->SetParaAttribs( nPara, rSet ); +} + +void SvxAccessibleTextAdapter::RemoveAttribs( const ESelection& ) +{ +} + +void SvxAccessibleTextAdapter::GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + mpTextForwarder->GetPortions( nPara, rList ); +} + +SfxItemState SvxAccessibleTextAdapter::GetItemState( const ESelection& rSel, sal_uInt16 nWhich ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + return mpTextForwarder->GetItemState( MakeEESelection(aStartIndex, aEndIndex), + nWhich ); +} + +SfxItemState SvxAccessibleTextAdapter::GetItemState( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetItemState( nPara, nWhich ); +} + +void SvxAccessibleTextAdapter::QuickInsertText( const OUString& rText, const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + mpTextForwarder->QuickInsertText( rText, + MakeEESelection(aStartIndex, aEndIndex) ); +} + +void SvxAccessibleTextAdapter::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + mpTextForwarder->QuickInsertField( rFld, + MakeEESelection(aStartIndex, aEndIndex) ); +} + +void SvxAccessibleTextAdapter::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + mpTextForwarder->QuickSetAttribs( rSet, + MakeEESelection(aStartIndex, aEndIndex) ); +} + +void SvxAccessibleTextAdapter::QuickInsertLineBreak( const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + mpTextForwarder->QuickInsertLineBreak( MakeEESelection(aStartIndex, aEndIndex) ); +} + +SfxItemPool* SvxAccessibleTextAdapter::GetPool() const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetPool(); +} + +OUString SvxAccessibleTextAdapter::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->CalcFieldValue( rField, nPara, nPos, rpTxtColor, rpFldColor ); +} + +void SvxAccessibleTextAdapter::FieldClicked( const SvxFieldItem& rField ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + mpTextForwarder->FieldClicked( rField ); +} + +sal_Int32 SvxAccessibleTextAdapter::CalcEditEngineIndex( sal_Int32 nPara, sal_Int32 nLogicalIndex ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aIndex; + aIndex.SetIndex(nPara, nLogicalIndex, *mpTextForwarder); + return aIndex.GetEEIndex(); +} + +bool SvxAccessibleTextAdapter::IsValid() const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + if( mpTextForwarder ) + return mpTextForwarder->IsValid(); + else + return false; +} + +LanguageType SvxAccessibleTextAdapter::GetLanguage( sal_Int32 nPara, sal_Int32 nPos ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aIndex; + + aIndex.SetIndex( nPara, nPos, *this ); + + return mpTextForwarder->GetLanguage( nPara, aIndex.GetEEIndex() ); +} + +sal_Int32 SvxAccessibleTextAdapter::GetFieldCount( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetFieldCount( nPara ); +} + +EFieldInfo SvxAccessibleTextAdapter::GetFieldInfo( sal_Int32 nPara, sal_uInt16 nField ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetFieldInfo( nPara, nField ); +} + +EBulletInfo SvxAccessibleTextAdapter::GetBulletInfo( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetBulletInfo( nPara ); +} + +tools::Rectangle SvxAccessibleTextAdapter::GetCharBounds( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aIndex; + aIndex.SetIndex( nPara, nIndex, *this ); + + // preset if anything goes wrong below + // n-th char in GetParagraphIndex's paragraph + tools::Rectangle aRect = mpTextForwarder->GetCharBounds( nPara, aIndex.GetEEIndex() ); + + if( aIndex.InBullet() ) + { + EBulletInfo aBulletInfo = GetBulletInfo( nPara ); + + OutputDevice* pOutDev = GetRefDevice(); + + DBG_ASSERT(pOutDev!=nullptr, "SvxAccessibleTextAdapter::GetCharBounds: No ref device"); + + // preset if anything goes wrong below + aRect = aBulletInfo.aBounds; // better than nothing + if( pOutDev ) + { + AccessibleStringWrap aStringWrap( *pOutDev, aBulletInfo.aFont, aBulletInfo.aText ); + + aStringWrap.GetCharacterBounds( aIndex.GetBulletOffset(), aRect ); + aRect.Move( aBulletInfo.aBounds.Left(), aBulletInfo.aBounds.Top() ); + } + } + else + { + // handle field content manually + if( aIndex.InField() ) + { + OutputDevice* pOutDev = GetRefDevice(); + + DBG_ASSERT(pOutDev!=nullptr, "SvxAccessibleTextAdapter::GetCharBounds: No ref device"); + + if( pOutDev ) + { + ESelection aSel = MakeEESelection( aIndex ); + + SvxFont aFont = EditEngine::CreateSvxFontFromItemSet( mpTextForwarder->GetAttribs( aSel ) ); + AccessibleStringWrap aStringWrap( *pOutDev, + aFont, + mpTextForwarder->GetText( aSel ) ); + + tools::Rectangle aStartRect = mpTextForwarder->GetCharBounds( nPara, aIndex.GetEEIndex() ); + + aStringWrap.GetCharacterBounds( aIndex.GetFieldOffset(), aRect ); + aRect.Move( aStartRect.Left(), aStartRect.Top() ); + } + } + } + + return aRect; +} + +tools::Rectangle SvxAccessibleTextAdapter::GetParaBounds( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + EBulletInfo aBulletInfo = GetBulletInfo( nPara ); + + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ) + { + // include bullet in para bounding box + tools::Rectangle aRect( mpTextForwarder->GetParaBounds( nPara ) ); + + aRect.Union( aBulletInfo.aBounds ); + + return aRect; + } + + return mpTextForwarder->GetParaBounds( nPara ); +} + +MapMode SvxAccessibleTextAdapter::GetMapMode() const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetMapMode(); +} + +OutputDevice* SvxAccessibleTextAdapter::GetRefDevice() const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetRefDevice(); +} + +bool SvxAccessibleTextAdapter::GetIndexAtPoint( const Point& rPoint, sal_Int32& nPara, sal_Int32& nIndex ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + if( !mpTextForwarder->GetIndexAtPoint( rPoint, nPara, nIndex ) ) + return false; + + SvxAccessibleTextIndex aIndex; + aIndex.SetEEIndex(nPara, nIndex, *this); + + DBG_ASSERT(aIndex.GetIndex() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + nIndex = aIndex.GetIndex(); + + EBulletInfo aBulletInfo = GetBulletInfo( nPara ); + + // any text bullets? + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ) + { + if( aBulletInfo.aBounds.Contains( rPoint) ) + { + OutputDevice* pOutDev = GetRefDevice(); + + DBG_ASSERT(pOutDev!=nullptr, "SvxAccessibleTextAdapter::GetIndexAtPoint: No ref device"); + + if( !pOutDev ) + return false; + + AccessibleStringWrap aStringWrap( *pOutDev, aBulletInfo.aFont, aBulletInfo.aText ); + + Point aPoint = rPoint; + aPoint.Move( -aBulletInfo.aBounds.Left(), -aBulletInfo.aBounds.Top() ); + + DBG_ASSERT(aStringWrap.GetIndexAtPoint( aPoint ) >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + nIndex = aStringWrap.GetIndexAtPoint( aPoint ); + return true; + } + } + + if( !aIndex.InField() ) + return true; + + OutputDevice* pOutDev = GetRefDevice(); + + DBG_ASSERT(pOutDev!=nullptr, "SvxAccessibleTextAdapter::GetIndexAtPoint: No ref device"); + + if( !pOutDev ) + return false; + + ESelection aSelection = MakeEESelection( aIndex ); + SvxFont aFont = EditEngine::CreateSvxFontFromItemSet( mpTextForwarder->GetAttribs( aSelection ) ); + AccessibleStringWrap aStringWrap( *pOutDev, + aFont, + mpTextForwarder->GetText( aSelection ) ); + + tools::Rectangle aRect = mpTextForwarder->GetCharBounds( nPara, aIndex.GetEEIndex() ); + Point aPoint = rPoint; + aPoint.Move( -aRect.Left(), -aRect.Top() ); + + DBG_ASSERT(aIndex.GetIndex() + aStringWrap.GetIndexAtPoint( rPoint ) >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + nIndex = (aIndex.GetIndex() + aStringWrap.GetIndexAtPoint( aPoint )); + return true; +} + +bool SvxAccessibleTextAdapter::GetWordIndices( sal_Int32 nPara, sal_Int32 nIndex, sal_Int32& nStart, sal_Int32& nEnd ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aIndex; + aIndex.SetIndex(nPara, nIndex, *this); + nIndex = aIndex.GetEEIndex(); + + if( aIndex.InBullet() ) + { + DBG_ASSERT(aIndex.GetBulletLen() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + // always treat bullet as separate word + nStart = 0; + nEnd = aIndex.GetBulletLen(); + + return true; + } + + if( aIndex.InField() ) + { + DBG_ASSERT(aIndex.GetIndex() - aIndex.GetFieldOffset() >= 0 && + nStart + aIndex.GetFieldLen() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + // always treat field as separate word + // TODO: to circumvent this, _we_ would have to do the break iterator stuff! + nStart = aIndex.GetIndex() - aIndex.GetFieldOffset(); + nEnd = nStart + aIndex.GetFieldLen(); + + return true; + } + + if( !mpTextForwarder->GetWordIndices( nPara, nIndex, nStart, nEnd ) ) + return false; + + aIndex.SetEEIndex( nPara, nStart, *this ); + DBG_ASSERT(aIndex.GetIndex() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + nStart = aIndex.GetIndex(); + + aIndex.SetEEIndex( nPara, nEnd, *this ); + DBG_ASSERT(aIndex.GetIndex() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + nEnd = aIndex.GetIndex(); + + return true; +} + +bool SvxAccessibleTextAdapter::GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, sal_Int32 nPara, sal_Int32 nIndex, bool /* bInCell */ ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aIndex; + aIndex.SetIndex(nPara, nIndex, *this); + nIndex = aIndex.GetEEIndex(); + + if( aIndex.InBullet() ) + { + DBG_ASSERT(aIndex.GetBulletLen() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + // always treat bullet as distinct attribute + nStartIndex = 0; + nEndIndex = aIndex.GetBulletLen(); + + return true; + } + + if( aIndex.InField() ) + { + DBG_ASSERT(aIndex.GetIndex() - aIndex.GetFieldOffset() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + // always treat field as distinct attribute + nStartIndex = aIndex.GetIndex() - aIndex.GetFieldOffset(); + nEndIndex = nStartIndex + aIndex.GetFieldLen(); + + return true; + } + + if( !mpTextForwarder->GetAttributeRun( nStartIndex, nEndIndex, nPara, nIndex ) ) + return false; + + aIndex.SetEEIndex( nPara, nStartIndex, *this ); + DBG_ASSERT(aIndex.GetIndex() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + nStartIndex = aIndex.GetIndex(); + + aIndex.SetEEIndex( nPara, nEndIndex, *this ); + DBG_ASSERT(aIndex.GetIndex() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + nEndIndex = aIndex.GetIndex(); + + return true; +} + +sal_Int32 SvxAccessibleTextAdapter::GetLineCount( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetLineCount( nPara ); +} + +sal_Int32 SvxAccessibleTextAdapter::GetLineLen( sal_Int32 nPara, sal_Int32 nLine ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aEndIndex; + sal_Int32 nCurrLine; + sal_Int32 nCurrIndex, nLastIndex; + for( nCurrLine=0, nCurrIndex=0, nLastIndex=0; nCurrLine<=nLine; ++nCurrLine ) + { + nLastIndex = nCurrIndex; + nCurrIndex = + nCurrIndex + mpTextForwarder->GetLineLen( nPara, nCurrLine ); + } + + aEndIndex.SetEEIndex( nPara, nCurrIndex, *this ); + if( nLine > 0 ) + { + SvxAccessibleTextIndex aStartIndex; + aStartIndex.SetEEIndex( nPara, nLastIndex, *this ); + + return aEndIndex.GetIndex() - aStartIndex.GetIndex(); + } + else + return aEndIndex.GetIndex(); +} + +void SvxAccessibleTextAdapter::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + mpTextForwarder->GetLineBoundaries( rStart, rEnd, nParagraph, nLine ); +} + +sal_Int32 SvxAccessibleTextAdapter::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + return mpTextForwarder->GetLineNumberAtIndex( nPara, nIndex ); +} + +bool SvxAccessibleTextAdapter::Delete( const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + return mpTextForwarder->Delete( MakeEESelection(aStartIndex, aEndIndex ) ); +} + +bool SvxAccessibleTextAdapter::InsertText( const OUString& rStr, const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + return mpTextForwarder->InsertText( rStr, MakeEESelection(aStartIndex, aEndIndex) ); +} + +bool SvxAccessibleTextAdapter::QuickFormatDoc( bool bFull ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->QuickFormatDoc( bFull ); +} + +sal_Int16 SvxAccessibleTextAdapter::GetDepth( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetDepth( nPara ); +} + +bool SvxAccessibleTextAdapter::SetDepth( sal_Int32 nPara, sal_Int16 nNewDepth ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->SetDepth( nPara, nNewDepth ); +} + +void SvxAccessibleTextAdapter::SetForwarder( SvxTextForwarder& rForwarder ) +{ + mpTextForwarder = &rForwarder; +} + +bool SvxAccessibleTextAdapter::HaveImageBullet( sal_Int32 nPara ) const +{ + EBulletInfo aBulletInfo = GetBulletInfo( nPara ); + + return ( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType == SVX_NUM_BITMAP ); +} + +bool SvxAccessibleTextAdapter::HaveTextBullet( sal_Int32 nPara ) const +{ + EBulletInfo aBulletInfo = GetBulletInfo( nPara ); + + return ( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ); +} + +bool SvxAccessibleTextAdapter::IsEditable( const ESelection& rSel ) const +{ + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + // normalize selection + if( rSel.nStartPara > rSel.nEndPara || + (rSel.nStartPara == rSel.nEndPara && rSel.nStartPos > rSel.nEndPos) ) + { + std::swap( aStartIndex, aEndIndex ); + } + + return aStartIndex.IsEditableRange( aEndIndex ); +} + +const SfxItemSet * SvxAccessibleTextAdapter::GetEmptyItemSetPtr() +{ + OSL_FAIL( "not implemented" ); + return nullptr; +} + +void SvxAccessibleTextAdapter::AppendParagraph() +{ + OSL_FAIL( "not implemented" ); +} + +sal_Int32 SvxAccessibleTextAdapter::AppendTextPortion( sal_Int32, const OUString &, const SfxItemSet & ) +{ + OSL_FAIL( "not implemented" ); + return 0; +} +void SvxAccessibleTextAdapter::CopyText(const SvxTextForwarder&) +{ + OSL_FAIL( "not implemented" ); +} + +SvxAccessibleTextEditViewAdapter::SvxAccessibleTextEditViewAdapter() + : mpViewForwarder(nullptr) + , mpTextForwarder(nullptr) +{ +} + +SvxAccessibleTextEditViewAdapter::~SvxAccessibleTextEditViewAdapter() +{ +} + +bool SvxAccessibleTextEditViewAdapter::IsValid() const +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + if( mpViewForwarder ) + return mpViewForwarder->IsValid(); + else + return false; +} + +Point SvxAccessibleTextEditViewAdapter::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + return mpViewForwarder->LogicToPixel(rPoint, rMapMode); +} + +Point SvxAccessibleTextEditViewAdapter::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + return mpViewForwarder->PixelToLogic(rPoint, rMapMode); +} + +bool SvxAccessibleTextEditViewAdapter::GetSelection( ESelection& rSel ) const +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + ESelection aSelection; + + if( !mpViewForwarder->GetSelection( aSelection ) ) + return false; + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetEEIndex( aSelection.nStartPara, aSelection.nStartPos, *mpTextForwarder ); + aEndIndex.SetEEIndex( aSelection.nEndPara, aSelection.nEndPos, *mpTextForwarder ); + + DBG_ASSERT(aStartIndex.GetIndex() >= 0 && + aEndIndex.GetIndex() >= 0, + "SvxAccessibleTextEditViewAdapter::GetSelection: index value overflow"); + + rSel = ESelection( aStartIndex.GetParagraph(), aStartIndex.GetIndex(), + aEndIndex.GetParagraph(), aEndIndex.GetIndex() ); + + return true; +} + +bool SvxAccessibleTextEditViewAdapter::SetSelection( const ESelection& rSel ) +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *mpTextForwarder ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *mpTextForwarder ); + + return mpViewForwarder->SetSelection( MakeEESelection(aStartIndex, aEndIndex) ); +} + +bool SvxAccessibleTextEditViewAdapter::Copy() +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + return mpViewForwarder->Copy(); +} + +bool SvxAccessibleTextEditViewAdapter::Cut() +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + return mpViewForwarder->Cut(); +} + +bool SvxAccessibleTextEditViewAdapter::Paste() +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + return mpViewForwarder->Paste(); +} + +void SvxAccessibleTextEditViewAdapter::SetForwarder( SvxEditViewForwarder& rForwarder, + SvxAccessibleTextAdapter& rTextForwarder ) +{ + mpViewForwarder = &rForwarder; + mpTextForwarder = &rTextForwarder; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoedsrc.cxx b/editeng/source/uno/unoedsrc.cxx new file mode 100644 index 000000000..1c827472d --- /dev/null +++ b/editeng/source/uno/unoedsrc.cxx @@ -0,0 +1,77 @@ +/* -*- 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 <svl/SfxBroadcaster.hxx> + +#include <editeng/unoedsrc.hxx> + +#include <osl/diagnose.h> + + +void SvxEditSource::addRange( SvxUnoTextRangeBase* ) +{ +} + + +void SvxEditSource::removeRange( SvxUnoTextRangeBase* ) +{ +} + + +const SvxUnoTextRangeBaseVec& SvxEditSource::getRanges() const +{ + static SvxUnoTextRangeBaseVec gList; + return gList; +} + + +SvxTextForwarder::~SvxTextForwarder() COVERITY_NOEXCEPT_FALSE +{ +} + + +SvxViewForwarder::~SvxViewForwarder() +{ +} + + +SvxEditSource::~SvxEditSource() +{ +} + +SvxViewForwarder* SvxEditSource::GetViewForwarder() +{ + return nullptr; +} + +SvxEditViewForwarder* SvxEditSource::GetEditViewForwarder( bool ) +{ + return nullptr; +} + +SfxBroadcaster& SvxEditSource::GetBroadcaster() const +{ + OSL_FAIL("SvxEditSource::GetBroadcaster called for implementation missing this feature!"); + + static SfxBroadcaster aBroadcaster; + + return aBroadcaster; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unofdesc.cxx b/editeng/source/uno/unofdesc.cxx new file mode 100644 index 000000000..722ae7d7f --- /dev/null +++ b/editeng/source/uno/unofdesc.cxx @@ -0,0 +1,229 @@ +/* -*- 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 <editeng/eeitem.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/awt/FontDescriptor.hpp> + +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/memberids.h> +#include <svl/itempool.hxx> +#include <vcl/font.hxx> +#include <vcl/unohelp.hxx> +#include <tools/gen.hxx> + +#include <editeng/unofdesc.hxx> + +using namespace ::com::sun::star; + + +void SvxUnoFontDescriptor::ConvertToFont( const awt::FontDescriptor& rDesc, vcl::Font& rFont ) +{ + rFont.SetFamilyName( rDesc.Name ); + rFont.SetStyleName( rDesc.StyleName ); + rFont.SetFontSize( Size( rDesc.Width, rDesc.Height ) ); + rFont.SetFamily( static_cast<FontFamily>(rDesc.Family) ); + rFont.SetCharSet( static_cast<rtl_TextEncoding>(rDesc.CharSet) ); + rFont.SetPitch( static_cast<FontPitch>(rDesc.Pitch) ); + rFont.SetOrientation( Degree10(static_cast<sal_Int16>(rDesc.Orientation*10)) ); + rFont.SetKerning( rDesc.Kerning ? FontKerning::FontSpecific : FontKerning::NONE ); + rFont.SetWeight( vcl::unohelper::ConvertFontWeight(rDesc.Weight) ); + rFont.SetItalic( static_cast<FontItalic>(rDesc.Slant) ); + rFont.SetUnderline( static_cast<FontLineStyle>(rDesc.Underline) ); + rFont.SetStrikeout( static_cast<FontStrikeout>(rDesc.Strikeout) ); + rFont.SetWordLineMode( rDesc.WordLineMode ); +} + +void SvxUnoFontDescriptor::ConvertFromFont( const vcl::Font& rFont, awt::FontDescriptor& rDesc ) +{ + rDesc.Name = rFont.GetFamilyName(); + rDesc.StyleName = rFont.GetStyleName(); + rDesc.Width = sal::static_int_cast< sal_Int16 >(rFont.GetFontSize().Width()); + rDesc.Height = sal::static_int_cast< sal_Int16 >(rFont.GetFontSize().Height()); + rDesc.Family = sal::static_int_cast< sal_Int16 >(rFont.GetFamilyType()); + rDesc.CharSet = rFont.GetCharSet(); + rDesc.Pitch = sal::static_int_cast< sal_Int16 >(rFont.GetPitch()); + rDesc.Orientation = static_cast< float >(rFont.GetOrientation().get() / 10); + rDesc.Kerning = rFont.IsKerning(); + rDesc.Weight = vcl::unohelper::ConvertFontWeight( rFont.GetWeight() ); + rDesc.Slant = vcl::unohelper::ConvertFontSlant( rFont.GetItalic() ); + rDesc.Underline = sal::static_int_cast< sal_Int16 >(rFont.GetUnderline()); + rDesc.Strikeout = sal::static_int_cast< sal_Int16 >(rFont.GetStrikeout()); + rDesc.WordLineMode = rFont.IsWordLineMode(); +} + +void SvxUnoFontDescriptor::FillItemSet( const awt::FontDescriptor& rDesc, SfxItemSet& rSet ) +{ + uno::Any aTemp; + + { + SvxFontItem aFontItem( EE_CHAR_FONTINFO ); + aFontItem.SetFamilyName( rDesc.Name); + aFontItem.SetStyleName( rDesc.StyleName); + aFontItem.SetFamily( static_cast<FontFamily>(rDesc.Family)); + aFontItem.SetCharSet( rDesc.CharSet ); + aFontItem.SetPitch( static_cast<FontPitch>(rDesc.Pitch)); + rSet.Put(aFontItem); + } + + { + SvxFontHeightItem aFontHeightItem( 0, 100, EE_CHAR_FONTHEIGHT ); + aTemp <<= static_cast<float>(rDesc.Height); + static_cast<SfxPoolItem*>(&aFontHeightItem)->PutValue( aTemp, MID_FONTHEIGHT|CONVERT_TWIPS ); + rSet.Put(aFontHeightItem); + } + + { + SvxPostureItem aPostureItem( ITALIC_NONE, EE_CHAR_ITALIC ); + aTemp <<= rDesc.Slant; + static_cast<SfxPoolItem*>(&aPostureItem)->PutValue( aTemp, MID_POSTURE ); + rSet.Put(aPostureItem); + } + + { + SvxUnderlineItem aUnderlineItem( LINESTYLE_NONE, EE_CHAR_UNDERLINE ); + aTemp <<= rDesc.Underline; + static_cast<SfxPoolItem*>(&aUnderlineItem)->PutValue( aTemp, MID_TL_STYLE ); + rSet.Put( aUnderlineItem ); + } + + { + SvxWeightItem aWeightItem( WEIGHT_DONTKNOW, EE_CHAR_WEIGHT ); + aTemp <<= rDesc.Weight; + static_cast<SfxPoolItem*>(&aWeightItem)->PutValue( aTemp, MID_WEIGHT ); + rSet.Put( aWeightItem ); + } + + { + SvxCrossedOutItem aCrossedOutItem( STRIKEOUT_NONE, EE_CHAR_STRIKEOUT ); + aTemp <<= rDesc.Strikeout; + static_cast<SfxPoolItem*>(&aCrossedOutItem)->PutValue( aTemp, MID_CROSS_OUT ); + rSet.Put( aCrossedOutItem ); + } + + { + SvxWordLineModeItem aWLMItem( rDesc.WordLineMode, EE_CHAR_WLM ); + rSet.Put( aWLMItem ); + } +} + +void SvxUnoFontDescriptor::FillFromItemSet( const SfxItemSet& rSet, awt::FontDescriptor& rDesc ) +{ + const SfxPoolItem* pItem = nullptr; + { + const SvxFontItem* pFontItem = &rSet.Get( EE_CHAR_FONTINFO ); + rDesc.Name = pFontItem->GetFamilyName(); + rDesc.StyleName = pFontItem->GetStyleName(); + rDesc.Family = sal::static_int_cast< sal_Int16 >( + pFontItem->GetFamily()); + rDesc.CharSet = pFontItem->GetCharSet(); + rDesc.Pitch = sal::static_int_cast< sal_Int16 >( + pFontItem->GetPitch()); + } + { + pItem = &rSet.Get( EE_CHAR_FONTHEIGHT ); + uno::Any aHeight; + if( pItem->QueryValue( aHeight, MID_FONTHEIGHT ) ) + aHeight >>= rDesc.Height; + } + { + pItem = &rSet.Get( EE_CHAR_ITALIC ); + uno::Any aFontSlant; + if(pItem->QueryValue( aFontSlant, MID_POSTURE )) + aFontSlant >>= rDesc.Slant; + } + { + pItem = &rSet.Get( EE_CHAR_UNDERLINE ); + uno::Any aUnderline; + if(pItem->QueryValue( aUnderline, MID_TL_STYLE )) + aUnderline >>= rDesc.Underline; + } + { + pItem = &rSet.Get( EE_CHAR_WEIGHT ); + uno::Any aWeight; + if(pItem->QueryValue( aWeight, MID_WEIGHT )) + aWeight >>= rDesc.Weight; + } + { + pItem = &rSet.Get( EE_CHAR_STRIKEOUT ); + uno::Any aStrikeOut; + if(pItem->QueryValue( aStrikeOut, MID_CROSS_OUT )) + aStrikeOut >>= rDesc.Strikeout; + } + { + const SvxWordLineModeItem* pWLMItem = &rSet.Get( EE_CHAR_WLM ); + rDesc.WordLineMode = pWLMItem->GetValue(); + } +} + +void SvxUnoFontDescriptor::setPropertyToDefault( SfxItemSet& rSet ) +{ + rSet.InvalidateItem( EE_CHAR_FONTINFO ); + rSet.InvalidateItem( EE_CHAR_FONTHEIGHT ); + rSet.InvalidateItem( EE_CHAR_ITALIC ); + rSet.InvalidateItem( EE_CHAR_UNDERLINE ); + rSet.InvalidateItem( EE_CHAR_WEIGHT ); + rSet.InvalidateItem( EE_CHAR_STRIKEOUT ); + rSet.InvalidateItem( EE_CHAR_WLM ); +} + +uno::Any SvxUnoFontDescriptor::getPropertyDefault( SfxItemPool* pPool ) +{ + SfxItemSetFixed< + EE_CHAR_FONTINFO, EE_CHAR_FONTHEIGHT, + EE_CHAR_WEIGHT, EE_CHAR_ITALIC, + EE_CHAR_WLM, EE_CHAR_WLM> aSet(*pPool); + + uno::Any aAny; + + if(!SfxItemPool::IsWhich(EE_CHAR_FONTINFO)|| + !SfxItemPool::IsWhich(EE_CHAR_FONTHEIGHT)|| + !SfxItemPool::IsWhich(EE_CHAR_ITALIC)|| + !SfxItemPool::IsWhich(EE_CHAR_UNDERLINE)|| + !SfxItemPool::IsWhich(EE_CHAR_WEIGHT)|| + !SfxItemPool::IsWhich(EE_CHAR_STRIKEOUT)|| + !SfxItemPool::IsWhich(EE_CHAR_WLM)) + return aAny; + + aSet.Put(pPool->GetDefaultItem(EE_CHAR_FONTINFO)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_FONTHEIGHT)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_ITALIC)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_UNDERLINE)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_WEIGHT)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_STRIKEOUT)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_WLM)); + + awt::FontDescriptor aDesc; + + FillFromItemSet( aSet, aDesc ); + + aAny <<= aDesc; + + return aAny; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unofield.cxx b/editeng/source/uno/unofield.cxx new file mode 100644 index 000000000..c3317f6c9 --- /dev/null +++ b/editeng/source/uno/unofield.cxx @@ -0,0 +1,964 @@ +/* -*- 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 <string_view> + +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/text/FilenameDisplayFormat.hpp> +#include <o3tl/string_view.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <tools/debug.hxx> +#include <svl/itemprop.hxx> + +#include <editeng/flditem.hxx> +#include <editeng/CustomPropertyField.hxx> +#include <editeng/measfld.hxx> +#include <editeng/unofield.hxx> +#include <editeng/unotext.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> + +#include <editeng/unonames.hxx> + +using namespace ::cppu; +using namespace ::com::sun::star; + +#define QUERYINT( xint ) \ + if( rType == cppu::UnoType<xint>::get() ) \ + aAny <<= uno::Reference< xint >(this) + + +#define WID_DATE 0 +#define WID_BOOL1 1 +#define WID_BOOL2 2 +#define WID_INT32 3 +#define WID_INT16 4 +#define WID_STRING1 5 +#define WID_STRING2 6 +#define WID_STRING3 7 + +class SvxUnoFieldData_Impl +{ +public: + bool mbBoolean1; + bool mbBoolean2; + sal_Int32 mnInt32; + sal_Int16 mnInt16; + OUString msString1; + OUString msString2; + OUString msString3; + util::DateTime maDateTime; + + OUString msPresentation; +}; + +static const SfxItemPropertySet* ImplGetFieldItemPropertySet( sal_Int32 mnId ) +{ + static const SfxItemPropertyMapEntry aExDateTimeFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_DATE_TIME, WID_DATE, ::cppu::UnoType<util::DateTime>::get(), 0, 0 }, + { UNO_TC_PROP_IS_FIXED, WID_BOOL1, cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_TC_PROP_IS_DATE, WID_BOOL2, cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_TC_PROP_NUMFORMAT, WID_INT32, ::cppu::UnoType<sal_Int32>::get(), 0, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aExDateTimeFieldPropertySet_Impl(aExDateTimeFieldPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aDateTimeFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_IS_DATE, WID_BOOL2, cppu::UnoType<bool>::get(), 0, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aDateTimeFieldPropertySet_Impl(aDateTimeFieldPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aUrlFieldPropertyMap_Impl[] = + { + + { UNO_TC_PROP_URL_FORMAT, WID_INT16, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + { UNO_TC_PROP_URL_REPRESENTATION, WID_STRING1, ::cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_URL_TARGET, WID_STRING2, ::cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_URL, WID_STRING3, ::cppu::UnoType<OUString>::get(), 0, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aUrlFieldPropertySet_Impl(aUrlFieldPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aEmptyPropertyMap_Impl[] = + { + { u"", 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aEmptyPropertySet_Impl(aEmptyPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aExtFileFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_IS_FIXED, WID_BOOL1, cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_TC_PROP_FILE_FORMAT, WID_INT16, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + { UNO_TC_PROP_CURRENT_PRESENTATION, WID_STRING1, ::cppu::UnoType<OUString>::get(), 0, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aExtFileFieldPropertySet_Impl(aExtFileFieldPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aAuthorFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_IS_FIXED, WID_BOOL1, cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_TC_PROP_CURRENT_PRESENTATION, WID_STRING1,::cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_AUTHOR_CONTENT, WID_STRING2,::cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_AUTHOR_FORMAT, WID_INT16, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + { UNO_TC_PROP_AUTHOR_FULLNAME, WID_BOOL2, cppu::UnoType<bool>::get(), 0, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aAuthorFieldPropertySet_Impl(aAuthorFieldPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aMeasureFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_MEASURE_KIND, WID_INT16, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aMeasureFieldPropertySet_Impl(aMeasureFieldPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aDocInfoCustomFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_NAME, WID_STRING1, cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_CURRENT_PRESENTATION, WID_STRING2, cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_IS_FIXED, WID_BOOL1, cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_TC_PROP_NUMFORMAT, WID_INT32, cppu::UnoType<sal_Int32>::get(), 0, 0 }, + { UNO_TC_PROP_IS_FIXED_LANGUAGE, WID_BOOL2, cppu::UnoType<bool>::get(), 0, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static const SfxItemPropertySet aDocInfoCustomFieldPropertySet_Impl(aDocInfoCustomFieldPropertyMap_Impl); + + switch( mnId ) + { + case text::textfield::Type::EXTENDED_TIME: + case text::textfield::Type::DATE: + return &aExDateTimeFieldPropertySet_Impl; + case text::textfield::Type::URL: + return &aUrlFieldPropertySet_Impl; + case text::textfield::Type::TIME: + return &aDateTimeFieldPropertySet_Impl; + case text::textfield::Type::EXTENDED_FILE: + return &aExtFileFieldPropertySet_Impl; + case text::textfield::Type::AUTHOR: + return &aAuthorFieldPropertySet_Impl; + case text::textfield::Type::MEASURE: + return &aMeasureFieldPropertySet_Impl; + case text::textfield::Type::DOCINFO_CUSTOM: + return &aDocInfoCustomFieldPropertySet_Impl; + default: + return &aEmptyPropertySet_Impl; + } +} + +/* conversion routines */ + +static sal_Int16 getFileNameDisplayFormat( SvxFileFormat nFormat ) +{ + switch( nFormat ) + { + case SvxFileFormat::NameAndExt: return text::FilenameDisplayFormat::NAME_AND_EXT; + case SvxFileFormat::PathFull: return text::FilenameDisplayFormat::FULL; + case SvxFileFormat::PathOnly: return text::FilenameDisplayFormat::PATH; +// case SvxFileFormat::NameOnly: + default: return text::FilenameDisplayFormat::NAME; + } +} + +static SvxFileFormat setFileNameDisplayFormat( sal_Int16 nFormat ) +{ + switch( nFormat ) + { + case text::FilenameDisplayFormat::FULL: return SvxFileFormat::PathFull; + case text::FilenameDisplayFormat::PATH: return SvxFileFormat::PathOnly; + case text::FilenameDisplayFormat::NAME: return SvxFileFormat::NameOnly; +// case text::FilenameDisplayFormat::NAME_AND_EXT: + default: + return SvxFileFormat::NameAndExt; + } +} + +static util::DateTime getDate( sal_Int32 nDate ) +{ + util::DateTime aDate; + + Date aTempDate( nDate ); + + aDate.Day = aTempDate.GetDay(); + aDate.Month = aTempDate.GetMonth(); + aDate.Year = aTempDate.GetYear(); + + return aDate; +} + +static Date setDate( util::DateTime const & rDate ) +{ + return Date( rDate.Day, rDate.Month, rDate.Year ); +} + +static util::DateTime getTime(sal_Int64 const nTime) +{ + util::DateTime aTime; + + tools::Time aTempTime( nTime ); + + aTime.NanoSeconds = aTempTime.GetNanoSec(); + aTime.Seconds = aTempTime.GetSec(); + aTime.Minutes = aTempTime.GetMin(); + aTime.Hours = aTempTime.GetHour(); + + return aTime; +} + +static tools::Time setTime( util::DateTime const & rDate ) +{ + return tools::Time( rDate ); +} + + + +const css::uno::Sequence< sal_Int8 > & SvxUnoTextField::getUnoTunnelId() noexcept +{ + static const comphelper::UnoIdInit theSvxUnoTextFieldUnoTunnelId; + return theSvxUnoTextFieldUnoTunnelId.getSeq(); +} + +sal_Int64 SAL_CALL SvxUnoTextField::getSomething( const css::uno::Sequence< sal_Int8 >& rId ) +{ + return comphelper::getSomethingImpl(rId, this); +} + +SvxUnoTextField::SvxUnoTextField( sal_Int32 nServiceId ) noexcept +: OComponentHelper( m_aMutex ) +, mpPropSet(nullptr) +, mnServiceId(nServiceId) +, mpImpl( new SvxUnoFieldData_Impl ) +{ + mpPropSet = ImplGetFieldItemPropertySet(mnServiceId); + + mpImpl->maDateTime.NanoSeconds = 0; + mpImpl->maDateTime.Seconds = 0; + mpImpl->maDateTime.Minutes = 0; + mpImpl->maDateTime.Hours = 0; + mpImpl->maDateTime.Day = 0; + mpImpl->maDateTime.Month = 0; + mpImpl->maDateTime.Year = 0; + mpImpl->maDateTime.IsUTC = false; + + switch( nServiceId ) + { + case text::textfield::Type::DATE: + mpImpl->mbBoolean2 = true; + mpImpl->mnInt32 = static_cast<sal_Int32>(SvxDateFormat::StdSmall); + mpImpl->mbBoolean1 = false; + break; + + case text::textfield::Type::EXTENDED_TIME: + case text::textfield::Type::TIME: + mpImpl->mbBoolean2 = false; + mpImpl->mbBoolean1 = false; + mpImpl->mnInt32 = static_cast<sal_Int32>(SvxTimeFormat::Standard); + break; + + case text::textfield::Type::URL: + mpImpl->mnInt16 = static_cast<sal_uInt16>(SvxURLFormat::Repr); + break; + + case text::textfield::Type::EXTENDED_FILE: + mpImpl->mbBoolean1 = false; + mpImpl->mnInt16 = text::FilenameDisplayFormat::FULL; + break; + + case text::textfield::Type::AUTHOR: + mpImpl->mnInt16 = static_cast<sal_uInt16>(SvxAuthorFormat::FullName); + mpImpl->mbBoolean1 = false; + mpImpl->mbBoolean2 = true; + break; + + case text::textfield::Type::MEASURE: + mpImpl->mnInt16 = static_cast<sal_uInt16>(SdrMeasureFieldKind::Value); + break; + + case text::textfield::Type::DOCINFO_CUSTOM: + mpImpl->mbBoolean1 = true; + mpImpl->mbBoolean2 = true; + mpImpl->mnInt32 = 0; + break; + + default: + mpImpl->mbBoolean1 = false; + mpImpl->mbBoolean2 = false; + mpImpl->mnInt32 = 0; + mpImpl->mnInt16 = 0; + + } +} + +SvxUnoTextField::SvxUnoTextField( uno::Reference< text::XTextRange > xAnchor, const OUString& rPresentation, const SvxFieldData* pData ) noexcept +: OComponentHelper( m_aMutex ) +, mxAnchor(std::move( xAnchor )) +, mpPropSet(nullptr) +, mnServiceId(text::textfield::Type::UNSPECIFIED) +, mpImpl( new SvxUnoFieldData_Impl ) +{ + DBG_ASSERT(pData, "pFieldData == NULL! [CL]" ); + + mpImpl->msPresentation = rPresentation; + + if(pData) + { + mnServiceId = pData->GetClassId(); + DBG_ASSERT(mnServiceId != text::textfield::Type::UNSPECIFIED, "unknown SvxFieldData! [CL]"); + if (mnServiceId != text::textfield::Type::UNSPECIFIED) + { + // extract field properties from data class + switch( mnServiceId ) + { + case text::textfield::Type::DATE: + { + mpImpl->mbBoolean2 = true; + // #i35416# for variable date field, don't use invalid "0000-00-00" date, + // use current date instead + bool bFixed = static_cast<const SvxDateField*>(pData)->GetType() == SvxDateType::Fix; + mpImpl->maDateTime = getDate( bFixed ? + static_cast<const SvxDateField*>(pData)->GetFixDate() : + Date( Date::SYSTEM ).GetDate() ); + mpImpl->mnInt32 = static_cast<sal_Int32>(static_cast<const SvxDateField*>(pData)->GetFormat()); + mpImpl->mbBoolean1 = bFixed; + } + break; + + case text::textfield::Type::TIME: + mpImpl->mbBoolean2 = false; + mpImpl->mbBoolean1 = false; + mpImpl->mnInt32 = static_cast<sal_Int32>(SvxTimeFormat::Standard); + break; + + case text::textfield::Type::EXTENDED_TIME: + mpImpl->mbBoolean2 = false; + mpImpl->maDateTime = getTime( static_cast<const SvxExtTimeField*>(pData)->GetFixTime() ); + mpImpl->mbBoolean1 = static_cast<const SvxExtTimeField*>(pData)->GetType() == SvxTimeType::Fix; + mpImpl->mnInt32 = static_cast<sal_Int32>(static_cast<const SvxExtTimeField*>(pData)->GetFormat()); + break; + + case text::textfield::Type::URL: + mpImpl->msString1 = static_cast<const SvxURLField*>(pData)->GetRepresentation(); + mpImpl->msString2 = static_cast<const SvxURLField*>(pData)->GetTargetFrame(); + mpImpl->msString3 = static_cast<const SvxURLField*>(pData)->GetURL(); + mpImpl->mnInt16 = sal::static_int_cast< sal_Int16 >( + static_cast<const SvxURLField*>(pData)->GetFormat()); + break; + + case text::textfield::Type::EXTENDED_FILE: + mpImpl->msString1 = static_cast<const SvxExtFileField*>(pData)->GetFile(); + mpImpl->mbBoolean1 = static_cast<const SvxExtFileField*>(pData)->GetType() == SvxFileType::Fix; + mpImpl->mnInt16 = getFileNameDisplayFormat(static_cast<const SvxExtFileField*>(pData)->GetFormat()); + break; + + case text::textfield::Type::AUTHOR: + mpImpl->msString1 = static_cast<const SvxAuthorField*>(pData)->GetFormatted(); + mpImpl->msString2 = static_cast<const SvxAuthorField*>(pData)->GetFormatted(); + mpImpl->mnInt16 = sal::static_int_cast< sal_Int16 >( + static_cast<const SvxAuthorField*>(pData)->GetFormat()); + mpImpl->mbBoolean1 = static_cast<const SvxAuthorField*>(pData)->GetType() == SvxAuthorType::Fix; + mpImpl->mbBoolean2 = static_cast<const SvxAuthorField*>(pData)->GetFormat() != SvxAuthorFormat::ShortName; + break; + + case text::textfield::Type::MEASURE: + mpImpl->mnInt16 = sal::static_int_cast< sal_Int16 >(static_cast<const SdrMeasureField*>(pData)->GetMeasureFieldKind()); + break; + + case text::textfield::Type::DOCINFO_CUSTOM: + mpImpl->msString1 = static_cast<const editeng::CustomPropertyField*>(pData)->GetName(); + mpImpl->msString2 = static_cast<const editeng::CustomPropertyField*>(pData)->GetCurrentPresentation(); + mpImpl->mbBoolean1 = false; + mpImpl->mbBoolean2 = false; + mpImpl->mnInt32 = 0; + break; + + default: + SAL_WARN("editeng", "Id service unknown: " << mnServiceId); + break; + } + } + } + + mpPropSet = ImplGetFieldItemPropertySet(mnServiceId); +} + +SvxUnoTextField::~SvxUnoTextField() noexcept +{ +} + +std::unique_ptr<SvxFieldData> SvxUnoTextField::CreateFieldData() const noexcept +{ + std::unique_ptr<SvxFieldData> pData; + + switch( mnServiceId ) + { + case text::textfield::Type::TIME: + case text::textfield::Type::EXTENDED_TIME: + case text::textfield::Type::DATE: + { + if( mpImpl->mbBoolean2 ) // IsDate? + { + Date aDate( setDate( mpImpl->maDateTime ) ); + pData.reset( new SvxDateField( aDate, mpImpl->mbBoolean1?SvxDateType::Fix:SvxDateType::Var ) ); + if( mpImpl->mnInt32 >= static_cast<sal_Int32>(SvxDateFormat::AppDefault) && + mpImpl->mnInt32 <= static_cast<sal_Int32>(SvxDateFormat::F) ) + static_cast<SvxDateField*>(pData.get())->SetFormat( static_cast<SvxDateFormat>(mpImpl->mnInt32) ); + } + else + { + if( mnServiceId != text::textfield::Type::TIME && mnServiceId != text::textfield::Type::DATE ) + { + tools::Time aTime( setTime( mpImpl->maDateTime ) ); + pData.reset( new SvxExtTimeField( aTime, mpImpl->mbBoolean1?SvxTimeType::Fix:SvxTimeType::Var ) ); + + if( static_cast<SvxTimeFormat>(mpImpl->mnInt32) >= SvxTimeFormat::AppDefault && + static_cast<SvxTimeFormat>(mpImpl->mnInt32) <= SvxTimeFormat::HH12_MM_SS_00_AMPM ) + static_cast<SvxExtTimeField*>(pData.get())->SetFormat( static_cast<SvxTimeFormat>(mpImpl->mnInt32) ); + } + else + { + pData.reset( new SvxTimeField() ); + } + } + + } + break; + + case text::textfield::Type::URL: + pData.reset( new SvxURLField( mpImpl->msString3, mpImpl->msString1, !mpImpl->msString1.isEmpty() ? SvxURLFormat::Repr : SvxURLFormat::Url ) ); + static_cast<SvxURLField*>(pData.get())->SetTargetFrame( mpImpl->msString2 ); + if( static_cast<SvxURLFormat>(mpImpl->mnInt16) >= SvxURLFormat::AppDefault && + static_cast<SvxURLFormat>(mpImpl->mnInt16) <= SvxURLFormat::Repr ) + static_cast<SvxURLField*>(pData.get())->SetFormat( static_cast<SvxURLFormat>(mpImpl->mnInt16) ); + break; + + case text::textfield::Type::PAGE: + pData.reset( new SvxPageField() ); + break; + + case text::textfield::Type::PAGES: + pData.reset( new SvxPagesField() ); + break; + + case text::textfield::Type::DOCINFO_TITLE: + pData.reset( new SvxFileField() ); + break; + + case text::textfield::Type::TABLE: + pData.reset( new SvxTableField() ); + break; + + case text::textfield::Type::EXTENDED_FILE: + { + // #92009# pass fixed attribute to constructor + pData.reset( new SvxExtFileField( mpImpl->msString1, + mpImpl->mbBoolean1 ? SvxFileType::Fix : SvxFileType::Var, + setFileNameDisplayFormat(mpImpl->mnInt16 ) ) ); + break; + } + + case text::textfield::Type::AUTHOR: + { + OUString aContent; + OUString aFirstName; + OUString aLastName; + + // do we have CurrentPresentation given? + // mimic behaviour of writer, which means: + // prefer CurrentPresentation over Content + // if both are given. + if( !mpImpl->msString1.isEmpty() ) + aContent = mpImpl->msString1; + else + aContent = mpImpl->msString2; + + sal_Int32 nPos = aContent.lastIndexOf( ' ', 0 ); + if( nPos > 0 ) + { + aFirstName = aContent.copy( 0, nPos ); + aLastName = aContent.copy( nPos + 1 ); + } + else + { + aLastName = aContent; + } + + // #92009# pass fixed attribute to constructor + pData.reset( new SvxAuthorField( aFirstName, aLastName, "", + mpImpl->mbBoolean1 ? SvxAuthorType::Fix : SvxAuthorType::Var ) ); + + if( !mpImpl->mbBoolean2 ) + { + static_cast<SvxAuthorField*>(pData.get())->SetFormat( SvxAuthorFormat::ShortName ); + } + else if( static_cast<SvxAuthorFormat>(mpImpl->mnInt16) >= SvxAuthorFormat::FullName && + static_cast<SvxAuthorFormat>(mpImpl->mnInt16) <= SvxAuthorFormat::ShortName ) + { + static_cast<SvxAuthorField*>(pData.get())->SetFormat( static_cast<SvxAuthorFormat>(mpImpl->mnInt16) ); + } + + break; + } + + case text::textfield::Type::MEASURE: + { + SdrMeasureFieldKind eKind = SdrMeasureFieldKind::Value; + if( mpImpl->mnInt16 == sal_Int16(SdrMeasureFieldKind::Unit) || mpImpl->mnInt16 == sal_Int16(SdrMeasureFieldKind::Rotate90Blanks) ) + eKind = static_cast<SdrMeasureFieldKind>(mpImpl->mnInt16); + pData.reset( new SdrMeasureField( eKind) ); + break; + } + case text::textfield::Type::PRESENTATION_HEADER: + pData.reset( new SvxHeaderField() ); + break; + case text::textfield::Type::PRESENTATION_FOOTER: + pData.reset( new SvxFooterField() ); + break; + case text::textfield::Type::PRESENTATION_DATE_TIME: + pData.reset( new SvxDateTimeField() ); + break; + case text::textfield::Type::PAGE_NAME: + pData.reset( new SvxPageTitleField() ); + break; + case text::textfield::Type::DOCINFO_CUSTOM: + pData.reset( new editeng::CustomPropertyField(mpImpl->msString1, mpImpl->msString2) ); + break; + } + + return pData; +} + +// uno::XInterface +uno::Any SAL_CALL SvxUnoTextField::queryAggregation( const uno::Type & rType ) +{ + uno::Any aAny; + + QUERYINT( beans::XPropertySet ); + else QUERYINT( text::XTextContent ); + else QUERYINT( text::XTextField ); + else QUERYINT( lang::XServiceInfo ); + else QUERYINT( lang::XUnoTunnel ); + else + return OComponentHelper::queryAggregation( rType ); + + return aAny; +} + +// XTypeProvider + +uno::Sequence< uno::Type > SAL_CALL SvxUnoTextField::getTypes() +{ + if( !maTypeSequence.hasElements() ) + { + maTypeSequence = comphelper::concatSequences( + OComponentHelper::getTypes(), + uno::Sequence { + cppu::UnoType<text::XTextField>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<lang::XServiceInfo>::get(), + cppu::UnoType<lang::XUnoTunnel>::get() }); + } + return maTypeSequence; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoTextField::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +uno::Any SAL_CALL SvxUnoTextField::queryInterface( const uno::Type & rType ) +{ + return OComponentHelper::queryInterface(rType); +} + +void SAL_CALL SvxUnoTextField::acquire() noexcept +{ + OComponentHelper::acquire(); +} + +void SAL_CALL SvxUnoTextField::release() noexcept +{ + OComponentHelper::release(); +} + +// Interface text::XTextField +OUString SAL_CALL SvxUnoTextField::getPresentation( sal_Bool bShowCommand ) +{ + SolarMutexGuard aGuard; + if (bShowCommand) + { + switch (mnServiceId) + { + case text::textfield::Type::DATE: + return "Date"; + case text::textfield::Type::URL: + return "URL"; + case text::textfield::Type::PAGE: + return "Page"; + case text::textfield::Type::PAGES: + return "Pages"; + case text::textfield::Type::TIME: + return "Time"; + case text::textfield::Type::DOCINFO_TITLE: + return "File"; + case text::textfield::Type::TABLE: + return "Table"; + case text::textfield::Type::EXTENDED_TIME: + return "ExtTime"; + case text::textfield::Type::EXTENDED_FILE: + return "ExtFile"; + case text::textfield::Type::AUTHOR: + return "Author"; + case text::textfield::Type::MEASURE: + return "Measure"; + case text::textfield::Type::PRESENTATION_HEADER: + return "Header"; + case text::textfield::Type::PRESENTATION_FOOTER: + return "Footer"; + case text::textfield::Type::PRESENTATION_DATE_TIME: + return "DateTime"; + case text::textfield::Type::PAGE_NAME: + return "PageName"; + case text::textfield::Type::DOCINFO_CUSTOM: + return "Custom"; + default: + return "Unknown"; + } + } + else + { + return mpImpl->msPresentation; + } +} + +// Interface text::XTextContent +void SAL_CALL SvxUnoTextField::attach( const uno::Reference< text::XTextRange >& xTextRange ) +{ + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRange>( xTextRange ); + if(pRange == nullptr) + throw lang::IllegalArgumentException(); + + std::unique_ptr<SvxFieldData> pData = CreateFieldData(); + if( pData ) + pRange->attachField( std::move(pData) ); +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextField::getAnchor() +{ + return mxAnchor; +} + +// lang::XComponent +void SAL_CALL SvxUnoTextField::dispose() +{ + OComponentHelper::dispose(); +} + +void SAL_CALL SvxUnoTextField::addEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + OComponentHelper::addEventListener(xListener); +} + +void SAL_CALL SvxUnoTextField::removeEventListener( const uno::Reference< lang::XEventListener >& aListener ) +{ + OComponentHelper::removeEventListener(aListener); +} + + +// Interface beans::XPropertySet +uno::Reference< beans::XPropertySetInfo > SAL_CALL SvxUnoTextField::getPropertySetInfo( ) +{ + SolarMutexGuard aGuard; + return mpPropSet->getPropertySetInfo(); +} + +void SAL_CALL SvxUnoTextField::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + SolarMutexGuard aGuard; + + if( mpImpl == nullptr ) + throw uno::RuntimeException(); + + if (aPropertyName == UNO_TC_PROP_ANCHOR) + { + aValue >>= mxAnchor; + return; + } + + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMap().getByName( aPropertyName ); + if ( !pMap ) + throw beans::UnknownPropertyException(aPropertyName); + + switch( pMap->nWID ) + { + case WID_DATE: + if(aValue >>= mpImpl->maDateTime) + return; + break; + case WID_BOOL1: + if(aValue >>= mpImpl->mbBoolean1) + return; + break; + case WID_BOOL2: + if(aValue >>= mpImpl->mbBoolean2) + return; + break; + case WID_INT16: + if(aValue >>= mpImpl->mnInt16) + return; + break; + case WID_INT32: + if(aValue >>= mpImpl->mnInt32) + return; + break; + case WID_STRING1: + if(aValue >>= mpImpl->msString1) + return; + break; + case WID_STRING2: + if(aValue >>= mpImpl->msString2) + return; + break; + case WID_STRING3: + if(aValue >>= mpImpl->msString3) + return; + break; + } + + throw lang::IllegalArgumentException(); +} + +uno::Any SAL_CALL SvxUnoTextField::getPropertyValue( const OUString& PropertyName ) +{ + SolarMutexGuard aGuard; + + if (PropertyName == UNO_TC_PROP_ANCHOR) + return uno::Any(mxAnchor); + + if (PropertyName == UNO_TC_PROP_TEXTFIELD_TYPE) + return uno::Any(mnServiceId); + + uno::Any aValue; + + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMap().getByName( PropertyName ); + if ( !pMap ) + throw beans::UnknownPropertyException(PropertyName); + + switch( pMap->nWID ) + { + case WID_DATE: + aValue <<= mpImpl->maDateTime; + break; + case WID_BOOL1: + aValue <<= mpImpl->mbBoolean1; + break; + case WID_BOOL2: + aValue <<= mpImpl->mbBoolean2; + break; + case WID_INT16: + aValue <<= mpImpl->mnInt16; + break; + case WID_INT32: + aValue <<= mpImpl->mnInt32; + break; + case WID_STRING1: + aValue <<= mpImpl->msString1; + break; + case WID_STRING2: + aValue <<= mpImpl->msString2; + break; + case WID_STRING3: + aValue <<= mpImpl->msString3; + break; + } + + return aValue; +} + +void SAL_CALL SvxUnoTextField::addPropertyChangeListener( const OUString&, const uno::Reference< beans::XPropertyChangeListener >& ) {} +void SAL_CALL SvxUnoTextField::removePropertyChangeListener( const OUString&, const uno::Reference< beans::XPropertyChangeListener >& ) {} +void SAL_CALL SvxUnoTextField::addVetoableChangeListener( const OUString&, const uno::Reference< beans::XVetoableChangeListener >& ) {} +void SAL_CALL SvxUnoTextField::removeVetoableChangeListener( const OUString&, const uno::Reference< beans::XVetoableChangeListener >& ) {} + +// OComponentHelper +void SvxUnoTextField::disposing() +{ + // nothing to do +} + +// lang::XServiceInfo +OUString SAL_CALL SvxUnoTextField::getImplementationName() +{ + return "SvxUnoTextField"; +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextField::getSupportedServiceNames() +{ + uno::Sequence<OUString> aSeq(4); + OUString* pServices = aSeq.getArray(); + pServices[0] = "com.sun.star.text.TextContent"; + pServices[1] = "com.sun.star.text.TextField"; + + switch (mnServiceId) + { + case text::textfield::Type::DATE: + pServices[2] = "com.sun.star.text.TextField.DateTime"; + pServices[3] = "com.sun.star.text.textfield.DateTime"; + break; + case text::textfield::Type::URL: + pServices[2] = "com.sun.star.text.TextField.URL"; + pServices[3] = "com.sun.star.text.textfield.URL"; + break; + case text::textfield::Type::PAGE: + pServices[2] = "com.sun.star.text.TextField.PageNumber"; + pServices[3] = "com.sun.star.text.textfield.PageNumber"; + break; + case text::textfield::Type::PAGES: + pServices[2] = "com.sun.star.text.TextField.PageCount"; + pServices[3] = "com.sun.star.text.textfield.PageCount"; + break; + case text::textfield::Type::TIME: + pServices[2] = "com.sun.star.text.TextField.DateTime"; + pServices[3] = "com.sun.star.text.textfield.DateTime"; + break; + case text::textfield::Type::DOCINFO_TITLE: + pServices[2] = "com.sun.star.text.TextField.docinfo.Title"; + pServices[3] = "com.sun.star.text.textfield.docinfo.Title"; + break; + case text::textfield::Type::TABLE: + pServices[2] = "com.sun.star.text.TextField.SheetName"; + pServices[3] = "com.sun.star.text.textfield.SheetName"; + break; + case text::textfield::Type::EXTENDED_TIME: + pServices[2] = "com.sun.star.text.TextField.DateTime"; + pServices[3] = "com.sun.star.text.textfield.DateTime"; + break; + case text::textfield::Type::EXTENDED_FILE: + pServices[2] = "com.sun.star.text.TextField.FileName"; + pServices[3] = "com.sun.star.text.textfield.FileName"; + break; + case text::textfield::Type::AUTHOR: + pServices[2] = "com.sun.star.text.TextField.Author"; + pServices[3] = "com.sun.star.text.textfield.Author"; + break; + case text::textfield::Type::MEASURE: + pServices[2] = "com.sun.star.text.TextField.Measure"; + pServices[3] = "com.sun.star.text.textfield.Measure"; + break; + case text::textfield::Type::PRESENTATION_HEADER: + pServices[2] = "com.sun.star.presentation.TextField.Header"; + pServices[3] = "com.sun.star.presentation.textfield.Header"; + break; + case text::textfield::Type::PRESENTATION_FOOTER: + pServices[2] = "com.sun.star.presentation.TextField.Footer"; + pServices[3] = "com.sun.star.presentation.textfield.Footer"; + break; + case text::textfield::Type::PRESENTATION_DATE_TIME: + pServices[2] = "com.sun.star.presentation.TextField.DateTime"; + pServices[3] = "com.sun.star.presentation.textfield.DateTime"; + break; + case text::textfield::Type::PAGE_NAME: + pServices[2] = "com.sun.star.text.TextField.PageName"; + pServices[3] = "com.sun.star.text.textfield.PageName"; + break; + case text::textfield::Type::DOCINFO_CUSTOM: + pServices[2] = "com.sun.star.text.TextField.DocInfo.Custom"; + pServices[3] = "com.sun.star.text.textfield.DocInfo.Custom"; + break; + default: + aSeq.realloc(0); + } + + return aSeq; +} + +sal_Bool SAL_CALL SvxUnoTextField::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +uno::Reference< uno::XInterface > SvxUnoTextCreateTextField( std::u16string_view ServiceSpecifier ) +{ + uno::Reference< uno::XInterface > xRet; + + // #i93308# up to OOo 3.2 we used this wrong namespace name with the capital T & F. This is + // fixed since OOo 3.2 but for compatibility we will still provide support for the wrong notation. + + std::u16string_view aFieldType; + if( (o3tl::starts_with( ServiceSpecifier, u"com.sun.star.text.textfield.", &aFieldType )) || + (o3tl::starts_with( ServiceSpecifier, u"com.sun.star.text.TextField.", &aFieldType )) ) + { + sal_Int32 nId = text::textfield::Type::UNSPECIFIED; + + if ( aFieldType == u"DateTime" ) + { + nId = text::textfield::Type::DATE; + } + else if ( aFieldType == u"URL" ) + { + nId = text::textfield::Type::URL; + } + else if ( aFieldType == u"PageNumber" ) + { + nId = text::textfield::Type::PAGE; + } + else if ( aFieldType == u"PageCount" ) + { + nId = text::textfield::Type::PAGES; + } + else if ( aFieldType == u"SheetName" ) + { + nId = text::textfield::Type::TABLE; + } + else if ( aFieldType == u"FileName" ) + { + nId = text::textfield::Type::EXTENDED_FILE; + } + else if (aFieldType == u"docinfo.Title" || + aFieldType == u"DocInfo.Title" ) + { + nId = text::textfield::Type::DOCINFO_TITLE; + } + else if ( aFieldType == u"Author" ) + { + nId = text::textfield::Type::AUTHOR; + } + else if ( aFieldType == u"Measure" ) + { + nId = text::textfield::Type::MEASURE; + } + else if (aFieldType == u"DocInfo.Custom") + { + nId = text::textfield::Type::DOCINFO_CUSTOM; + } + + if (nId != text::textfield::Type::UNSPECIFIED) + xRet = static_cast<cppu::OWeakObject *>(new SvxUnoTextField( nId )); + } + + return xRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unofored.cxx b/editeng/source/uno/unofored.cxx new file mode 100644 index 000000000..425e07bb8 --- /dev/null +++ b/editeng/source/uno/unofored.cxx @@ -0,0 +1,506 @@ +/* -*- 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 <osl/diagnose.h> +#include <tools/debug.hxx> +#include <editeng/eeitem.hxx> +#include <com/sun/star/i18n/WordType.hpp> + +#include <svl/itemset.hxx> +#include <editeng/editeng.hxx> +#include <editeng/unoedhlp.hxx> +#include <editeng/editdata.hxx> +#include <editeng/outliner.hxx> +#include <editeng/editobj.hxx> + +#include <editeng/unofored.hxx> +#include "unofored_internal.hxx" + +using namespace ::com::sun::star; + + +SvxEditEngineForwarder::SvxEditEngineForwarder( EditEngine& rEngine ) : + rEditEngine( rEngine ) +{ +} + +SvxEditEngineForwarder::~SvxEditEngineForwarder() +{ + // the EditEngine may need to be deleted from the outside +} + +sal_Int32 SvxEditEngineForwarder::GetParagraphCount() const +{ + return rEditEngine.GetParagraphCount(); +} + +sal_Int32 SvxEditEngineForwarder::GetTextLen( sal_Int32 nParagraph ) const +{ + return rEditEngine.GetTextLen( nParagraph ); +} + +OUString SvxEditEngineForwarder::GetText( const ESelection& rSel ) const +{ + return convertLineEnd(rEditEngine.GetText(rSel), GetSystemLineEnd()); +} + +SfxItemSet SvxEditEngineForwarder::GetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib ) const +{ + if( rSel.nStartPara == rSel.nEndPara ) + { + GetAttribsFlags nFlags = GetAttribsFlags::NONE; + switch( nOnlyHardAttrib ) + { + case EditEngineAttribs::All: + nFlags = GetAttribsFlags::ALL; + break; + case EditEngineAttribs::OnlyHard: + nFlags = GetAttribsFlags::CHARATTRIBS; + break; + default: + OSL_FAIL("unknown flags for SvxOutlinerForwarder::GetAttribs"); + } + + return rEditEngine.GetAttribs( rSel.nStartPara, rSel.nStartPos, rSel.nEndPos, nFlags ); + } + else + { + return rEditEngine.GetAttribs( rSel, nOnlyHardAttrib ); + } +} + +SfxItemSet SvxEditEngineForwarder::GetParaAttribs( sal_Int32 nPara ) const +{ + SfxItemSet aSet( rEditEngine.GetParaAttribs( nPara ) ); + + sal_uInt16 nWhich = EE_PARA_START; + while( nWhich <= EE_PARA_END ) + { + if( aSet.GetItemState( nWhich ) != SfxItemState::SET ) + { + if( rEditEngine.HasParaAttrib( nPara, nWhich ) ) + aSet.Put( rEditEngine.GetParaAttrib( nPara, nWhich ) ); + } + nWhich++; + } + + return aSet; +} + +void SvxEditEngineForwarder::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + rEditEngine.SetParaAttribs( nPara, rSet ); +} + +void SvxEditEngineForwarder::RemoveAttribs( const ESelection& rSelection ) +{ + rEditEngine.RemoveAttribs( rSelection, false/*bRemoveParaAttribs*/, 0 ); +} + +SfxItemPool* SvxEditEngineForwarder::GetPool() const +{ + return rEditEngine.GetEmptyItemSet().GetPool(); +} + +void SvxEditEngineForwarder::GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList ) const +{ + rEditEngine.GetPortions( nPara, rList ); +} + +void SvxEditEngineForwarder::QuickInsertText( const OUString& rText, const ESelection& rSel ) +{ + rEditEngine.QuickInsertText( rText, rSel ); +} + +void SvxEditEngineForwarder::QuickInsertLineBreak( const ESelection& rSel ) +{ + rEditEngine.QuickInsertLineBreak( rSel ); +} + +void SvxEditEngineForwarder::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) +{ + rEditEngine.QuickInsertField( rFld, rSel ); +} + +void SvxEditEngineForwarder::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) +{ + rEditEngine.QuickSetAttribs( rSet, rSel ); +} + +bool SvxEditEngineForwarder::IsValid() const +{ + // cannot reliably query EditEngine state + // while in the middle of an update + return rEditEngine.IsUpdateLayout(); +} + +OUString SvxEditEngineForwarder::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor ) +{ + return rEditEngine.CalcFieldValue( rField, nPara, nPos, rpTxtColor, rpFldColor ); +} + +void SvxEditEngineForwarder::FieldClicked( const SvxFieldItem& rField ) +{ + rEditEngine.FieldClicked( rField ); +} + +SfxItemState GetSvxEditEngineItemState( EditEngine const & rEditEngine, const ESelection& rSel, sal_uInt16 nWhich ) +{ + std::vector<EECharAttrib> aAttribs; + + const SfxPoolItem* pLastItem = nullptr; + + SfxItemState eState = SfxItemState::DEFAULT; + + // check all paragraphs inside the selection + for( sal_Int32 nPara = rSel.nStartPara; nPara <= rSel.nEndPara; nPara++ ) + { + SfxItemState eParaState = SfxItemState::DEFAULT; + + // calculate start and endpos for this paragraph + sal_Int32 nPos = 0; + if( rSel.nStartPara == nPara ) + nPos = rSel.nStartPos; + + sal_Int32 nEndPos = rSel.nEndPos; + if( rSel.nEndPara != nPara ) + nEndPos = rEditEngine.GetTextLen( nPara ); + + + // get list of char attribs + rEditEngine.GetCharAttribs( nPara, aAttribs ); + + bool bEmpty = true; // we found no item inside the selection of this paragraph + bool bGaps = false; // we found items but there are gaps between them + sal_Int32 nLastEnd = nPos; + + const SfxPoolItem* pParaItem = nullptr; + + for (auto const& attrib : aAttribs) + { + DBG_ASSERT(attrib.pAttr, "GetCharAttribs gives corrupt data"); + + const bool bEmptyPortion = attrib.nStart == attrib.nEnd; + if((!bEmptyPortion && attrib.nStart >= nEndPos) || + (bEmptyPortion && attrib.nStart > nEndPos)) + break; // break if we are already behind our selection + + if((!bEmptyPortion && attrib.nEnd <= nPos) || + (bEmptyPortion && attrib.nEnd < nPos)) + continue; // or if the attribute ends before our selection + + if(attrib.pAttr->Which() != nWhich) + continue; // skip if is not the searched item + + // if we already found an item + if( pParaItem ) + { + // ... and its different to this one than the state is don't care + if(*pParaItem != *(attrib.pAttr)) + return SfxItemState::DONTCARE; + } + else + pParaItem = attrib.pAttr; + + if( bEmpty ) + bEmpty = false; + + if(!bGaps && attrib.nStart > nLastEnd) + bGaps = true; + + nLastEnd = attrib.nEnd; + } + + if( !bEmpty && !bGaps && nLastEnd < ( nEndPos - 1 ) ) + bGaps = true; + + if( bEmpty ) + eParaState = SfxItemState::DEFAULT; + else if( bGaps ) + eParaState = SfxItemState::DONTCARE; + else + eParaState = SfxItemState::SET; + + // if we already found an item check if we found the same + if( pLastItem ) + { + if( (pParaItem == nullptr) || (*pLastItem != *pParaItem) ) + return SfxItemState::DONTCARE; + } + else + { + pLastItem = pParaItem; + eState = eParaState; + } + } + + return eState; +} + +SfxItemState SvxEditEngineForwarder::GetItemState( const ESelection& rSel, sal_uInt16 nWhich ) const +{ + return GetSvxEditEngineItemState( rEditEngine, rSel, nWhich ); +} + +SfxItemState SvxEditEngineForwarder::GetItemState( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + const SfxItemSet& rSet = rEditEngine.GetParaAttribs( nPara ); + return rSet.GetItemState( nWhich ); +} + +LanguageType SvxEditEngineForwarder::GetLanguage( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + return rEditEngine.GetLanguage(nPara, nIndex).nLang; +} + +sal_Int32 SvxEditEngineForwarder::GetFieldCount( sal_Int32 nPara ) const +{ + return rEditEngine.GetFieldCount(nPara); +} + +EFieldInfo SvxEditEngineForwarder::GetFieldInfo( sal_Int32 nPara, sal_uInt16 nField ) const +{ + return rEditEngine.GetFieldInfo( nPara, nField ); +} + +EBulletInfo SvxEditEngineForwarder::GetBulletInfo( sal_Int32 ) const +{ + return EBulletInfo(); +} + +tools::Rectangle SvxEditEngineForwarder::GetCharBounds( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + // EditEngine's 'internal' methods like GetCharacterBounds() + // don't rotate for vertical text. + Size aSize( rEditEngine.CalcTextWidth(), rEditEngine.GetTextHeight() ); + // swap width and height + tools::Long tmp = aSize.Width(); + aSize.setWidth(aSize.Height()); + aSize.setHeight(tmp); + bool bIsVertical( rEditEngine.IsEffectivelyVertical() ); + + // #108900# Handle virtual position one-past-the end of the string + if( nIndex >= rEditEngine.GetTextLen(nPara) ) + { + tools::Rectangle aLast; + + if( nIndex ) + { + // use last character, if possible + aLast = rEditEngine.GetCharacterBounds( EPosition(nPara, nIndex-1) ); + + // move at end of this last character, make one pixel wide + aLast.Move( aLast.Right() - aLast.Left(), 0 ); + aLast.SetSize( Size(1, aLast.GetHeight()) ); + + // take care for CTL + aLast = SvxEditSourceHelper::EEToUserSpace( aLast, aSize, bIsVertical ); + } + else + { + // #109864# Bounds must lie within the paragraph + aLast = GetParaBounds( nPara ); + + // #109151# Don't use paragraph height, but line height + // instead. aLast is already CTL-correct + if( bIsVertical) + aLast.SetSize( Size( rEditEngine.GetLineHeight(nPara), 1 ) ); + else + aLast.SetSize( Size( 1, rEditEngine.GetLineHeight(nPara) ) ); + } + + return aLast; + } + else + { + return SvxEditSourceHelper::EEToUserSpace( rEditEngine.GetCharacterBounds( EPosition(nPara, nIndex) ), + aSize, bIsVertical ); + } +} + +tools::Rectangle SvxEditEngineForwarder::GetParaBounds( sal_Int32 nPara ) const +{ + const Point aPnt = rEditEngine.GetDocPosTopLeft( nPara ); + sal_uInt32 nWidth; + sal_uInt32 nHeight; + + if( rEditEngine.IsEffectivelyVertical() ) + { + // Hargl. EditEngine's 'external' methods return the rotated + // dimensions, 'internal' methods like GetTextHeight( n ) + // don't rotate. + nWidth = rEditEngine.GetTextHeight( nPara ); + nHeight = rEditEngine.GetTextHeight(); + sal_uInt32 nTextWidth = rEditEngine.GetTextHeight(); + + return tools::Rectangle( nTextWidth - aPnt.Y() - nWidth, 0, nTextWidth - aPnt.Y(), nHeight ); + } + else + { + nWidth = rEditEngine.CalcTextWidth(); + nHeight = rEditEngine.GetTextHeight( nPara ); + + return tools::Rectangle( 0, aPnt.Y(), nWidth, aPnt.Y() + nHeight ); + } +} + +MapMode SvxEditEngineForwarder::GetMapMode() const +{ + return rEditEngine.GetRefMapMode(); +} + +OutputDevice* SvxEditEngineForwarder::GetRefDevice() const +{ + return rEditEngine.GetRefDevice(); +} + +bool SvxEditEngineForwarder::GetIndexAtPoint( const Point& rPos, sal_Int32& nPara, sal_Int32& nIndex ) const +{ + Size aSize( rEditEngine.CalcTextWidth(), rEditEngine.GetTextHeight() ); + // swap width and height + tools::Long tmp = aSize.Width(); + aSize.setWidth(aSize.Height()); + aSize.setHeight(tmp); + Point aEEPos( SvxEditSourceHelper::UserSpaceToEE( rPos, + aSize, + rEditEngine.IsEffectivelyVertical() )); + + EPosition aDocPos = rEditEngine.FindDocPosition( aEEPos ); + + nPara = aDocPos.nPara; + nIndex = aDocPos.nIndex; + + return true; +} + +bool SvxEditEngineForwarder::GetWordIndices( sal_Int32 nPara, sal_Int32 nIndex, sal_Int32& nStart, sal_Int32& nEnd ) const +{ + ESelection aRes = rEditEngine.GetWord( ESelection(nPara, nIndex, nPara, nIndex), css::i18n::WordType::DICTIONARY_WORD ); + + if( aRes.nStartPara == nPara && + aRes.nStartPara == aRes.nEndPara ) + { + nStart = aRes.nStartPos; + nEnd = aRes.nEndPos; + + return true; + } + + return false; +} + +bool SvxEditEngineForwarder::GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, sal_Int32 nPara, sal_Int32 nIndex, bool bInCell ) const +{ + SvxEditSourceHelper::GetAttributeRun( nStartIndex, nEndIndex, rEditEngine, nPara, nIndex, bInCell ); + return true; +} + +sal_Int32 SvxEditEngineForwarder::GetLineCount( sal_Int32 nPara ) const +{ + return rEditEngine.GetLineCount(nPara); +} + +sal_Int32 SvxEditEngineForwarder::GetLineLen( sal_Int32 nPara, sal_Int32 nLine ) const +{ + return rEditEngine.GetLineLen(nPara, nLine); +} + +void SvxEditEngineForwarder::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nPara, sal_Int32 nLine ) const +{ + rEditEngine.GetLineBoundaries(rStart, rEnd, nPara, nLine); +} + +sal_Int32 SvxEditEngineForwarder::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + return rEditEngine.GetLineNumberAtIndex(nPara, nIndex); +} + + +bool SvxEditEngineForwarder::QuickFormatDoc( bool ) +{ + rEditEngine.QuickFormatDoc(); + + return true; +} + +bool SvxEditEngineForwarder::Delete( const ESelection& rSelection ) +{ + rEditEngine.QuickDelete( rSelection ); + rEditEngine.QuickFormatDoc(); + + return true; +} + +bool SvxEditEngineForwarder::InsertText( const OUString& rStr, const ESelection& rSelection ) +{ + rEditEngine.QuickInsertText( rStr, rSelection ); + rEditEngine.QuickFormatDoc(); + + return true; +} + +sal_Int16 SvxEditEngineForwarder::GetDepth( sal_Int32 ) const +{ + // EditEngine does not support outline depth + return -1; +} + +bool SvxEditEngineForwarder::SetDepth( sal_Int32, sal_Int16 nNewDepth ) +{ + // EditEngine does not support outline depth + return nNewDepth == -1; +} + +const SfxItemSet * SvxEditEngineForwarder::GetEmptyItemSetPtr() +{ + return &rEditEngine.GetEmptyItemSet(); +} + +void SvxEditEngineForwarder::AppendParagraph() +{ + rEditEngine.InsertParagraph( rEditEngine.GetParagraphCount(), OUString() ); +} + +sal_Int32 SvxEditEngineForwarder::AppendTextPortion( sal_Int32 nPara, const OUString &rText, const SfxItemSet & /*rSet*/ ) +{ + sal_Int32 nLen = 0; + + sal_Int32 nParaCount = rEditEngine.GetParagraphCount(); + DBG_ASSERT( nPara < nParaCount, "paragraph index out of bounds" ); + if (0 <= nPara && nPara < nParaCount) + { + nLen = rEditEngine.GetTextLen( nPara ); + rEditEngine.QuickInsertText( rText, ESelection( nPara, nLen, nPara, nLen ) ); + } + + return nLen; +} + +void SvxEditEngineForwarder::CopyText(const SvxTextForwarder& rSource) +{ + const SvxEditEngineForwarder* pSourceForwarder = dynamic_cast< const SvxEditEngineForwarder* >( &rSource ); + if( !pSourceForwarder ) + return; + std::unique_ptr<EditTextObject> pNewTextObject = pSourceForwarder->rEditEngine.CreateTextObject(); + rEditEngine.SetText( *pNewTextObject ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unofored_internal.hxx b/editeng/source/uno/unofored_internal.hxx new file mode 100644 index 000000000..cadb19415 --- /dev/null +++ b/editeng/source/uno/unofored_internal.hxx @@ -0,0 +1,28 @@ +/* -*- 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 <editeng/editeng.hxx> +#include <svl/poolitem.hxx> + +SfxItemState GetSvxEditEngineItemState( EditEngine const & rEditEngine, const ESelection& rSel, sal_uInt16 nWhich ); + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoforou.cxx b/editeng/source/uno/unoforou.cxx new file mode 100644 index 000000000..7edf6923d --- /dev/null +++ b/editeng/source/uno/unoforou.cxx @@ -0,0 +1,568 @@ +/* -*- 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 <osl/diagnose.h> +#include <tools/debug.hxx> +#include <svl/style.hxx> +#include <com/sun/star/i18n/WordType.hpp> + +#include <svl/itemset.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editdata.hxx> +#include <editeng/outliner.hxx> +#include <editeng/unoedhlp.hxx> +#include <svl/poolitem.hxx> + +#include <editeng/unoforou.hxx> +#include <editeng/outlobj.hxx> +#include "unofored_internal.hxx" + +using namespace ::com::sun::star; + + +SvxOutlinerForwarder::SvxOutlinerForwarder( Outliner& rOutl, bool bOutlText /* = false */ ) : + rOutliner( rOutl ), + bOutlinerText( bOutlText ), + mnParaAttribsCache( 0 ) +{ +} + +SvxOutlinerForwarder::~SvxOutlinerForwarder() +{ + flushCache(); +} + +sal_Int32 SvxOutlinerForwarder::GetParagraphCount() const +{ + return rOutliner.GetParagraphCount(); +} + +sal_Int32 SvxOutlinerForwarder::GetTextLen( sal_Int32 nParagraph ) const +{ + return rOutliner.GetEditEngine().GetTextLen( nParagraph ); +} + +OUString SvxOutlinerForwarder::GetText( const ESelection& rSel ) const +{ + //! GetText (ESelection) should probably also be in the Outliner + // in the time being use as the hack for the EditEngine: + EditEngine* pEditEngine = const_cast<EditEngine*>(&rOutliner.GetEditEngine()); + return pEditEngine->GetText( rSel ); +} + +static SfxItemSet ImplOutlinerForwarderGetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib, EditEngine& rEditEngine ) +{ + if( rSel.nStartPara == rSel.nEndPara ) + { + GetAttribsFlags nFlags = GetAttribsFlags::NONE; + + switch( nOnlyHardAttrib ) + { + case EditEngineAttribs::All: + nFlags = GetAttribsFlags::ALL; + break; + case EditEngineAttribs::OnlyHard: + nFlags = GetAttribsFlags::CHARATTRIBS; + break; + default: + OSL_FAIL("unknown flags for SvxOutlinerForwarder::GetAttribs"); + } + return rEditEngine.GetAttribs( rSel.nStartPara, rSel.nStartPos, rSel.nEndPos, nFlags ); + } + else + { + return rEditEngine.GetAttribs( rSel, nOnlyHardAttrib ); + } +} + +SfxItemSet SvxOutlinerForwarder::GetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib ) const +{ + if( moAttribsCache && ( EditEngineAttribs::All == nOnlyHardAttrib ) ) + { + // have we the correct set in cache? + if( maAttribCacheSelection == rSel ) + { + // yes! just return the cache + return *moAttribsCache; + } + else + { + // no, we need delete the old cache + moAttribsCache.reset(); + } + } + + //! Does it not exist on the Outliner? + //! and why is the GetAttribs on the EditEngine not a const? + EditEngine& rEditEngine = const_cast<EditEngine&>(rOutliner.GetEditEngine()); + + SfxItemSet aSet( ImplOutlinerForwarderGetAttribs( rSel, nOnlyHardAttrib, rEditEngine ) ); + + if( EditEngineAttribs::All == nOnlyHardAttrib ) + { + moAttribsCache.emplace( aSet ); + maAttribCacheSelection = rSel; + } + + SfxStyleSheet* pStyle = rEditEngine.GetStyleSheet( rSel.nStartPara ); + if( pStyle ) + aSet.SetParent( &(pStyle->GetItemSet() ) ); + + return aSet; +} + +SfxItemSet SvxOutlinerForwarder::GetParaAttribs( sal_Int32 nPara ) const +{ + if( moParaAttribsCache ) + { + // have we the correct set in cache? + if( nPara == mnParaAttribsCache ) + { + // yes! just return the cache + return *moParaAttribsCache; + } + else + { + // no, we need delete the old cache + moParaAttribsCache.reset(); + } + } + + moParaAttribsCache.emplace( rOutliner.GetParaAttribs( nPara ) ); + mnParaAttribsCache = nPara; + + EditEngine& rEditEngine = const_cast<EditEngine&>(rOutliner.GetEditEngine()); + + SfxStyleSheet* pStyle = rEditEngine.GetStyleSheet( nPara ); + if( pStyle ) + moParaAttribsCache->SetParent( &(pStyle->GetItemSet() ) ); + + return *moParaAttribsCache; +} + +void SvxOutlinerForwarder::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + flushCache(); + + const SfxItemSet* pOldParent = rSet.GetParent(); + if( pOldParent ) + const_cast<SfxItemSet*>(&rSet)->SetParent( nullptr ); + + rOutliner.SetParaAttribs( nPara, rSet ); + + if( pOldParent ) + const_cast<SfxItemSet*>(&rSet)->SetParent( pOldParent ); +} + +void SvxOutlinerForwarder::RemoveAttribs( const ESelection& rSelection ) +{ + rOutliner.RemoveAttribs( rSelection, false/*bRemoveParaAttribs*/, 0 ); +} + +SfxItemPool* SvxOutlinerForwarder::GetPool() const +{ + return rOutliner.GetEmptyItemSet().GetPool(); +} + +void SvxOutlinerForwarder::GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList ) const +{ + const_cast<EditEngine&>(rOutliner.GetEditEngine()).GetPortions( nPara, rList ); +} + +void SvxOutlinerForwarder::QuickInsertText( const OUString& rText, const ESelection& rSel ) +{ + flushCache(); + if( rText.isEmpty() ) + { + rOutliner.QuickDelete( rSel ); + } + else + { + rOutliner.QuickInsertText( rText, rSel ); + } +} + +void SvxOutlinerForwarder::QuickInsertLineBreak( const ESelection& rSel ) +{ + flushCache(); + rOutliner.QuickInsertLineBreak( rSel ); +} + +void SvxOutlinerForwarder::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) +{ + flushCache(); + rOutliner.QuickInsertField( rFld, rSel ); +} + +void SvxOutlinerForwarder::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) +{ + flushCache(); + rOutliner.QuickSetAttribs( rSet, rSel ); +} + +OUString SvxOutlinerForwarder::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor ) +{ + return rOutliner.CalcFieldValue( rField, nPara, nPos, rpTxtColor, rpFldColor ); +} + +void SvxOutlinerForwarder::FieldClicked( const SvxFieldItem& /*rField*/ ) +{ +} + +bool SvxOutlinerForwarder::IsValid() const +{ + // cannot reliably query outliner state + // while in the middle of an update + return rOutliner.IsUpdateLayout(); +} + +SfxItemState SvxOutlinerForwarder::GetItemState( const ESelection& rSel, sal_uInt16 nWhich ) const +{ + return GetSvxEditEngineItemState( rOutliner.GetEditEngine(), rSel, nWhich ); +} + +SfxItemState SvxOutlinerForwarder::GetItemState( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + const SfxItemSet& rSet = rOutliner.GetParaAttribs( nPara ); + return rSet.GetItemState( nWhich ); +} + + +void SvxOutlinerForwarder::flushCache() +{ + moAttribsCache.reset(); + moParaAttribsCache.reset(); +} + +LanguageType SvxOutlinerForwarder::GetLanguage( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + return rOutliner.GetLanguage(nPara, nIndex); +} + +sal_Int32 SvxOutlinerForwarder::GetFieldCount( sal_Int32 nPara ) const +{ + return rOutliner.GetEditEngine().GetFieldCount(nPara); +} + +EFieldInfo SvxOutlinerForwarder::GetFieldInfo( sal_Int32 nPara, sal_uInt16 nField ) const +{ + return rOutliner.GetEditEngine().GetFieldInfo( nPara, nField ); +} + +EBulletInfo SvxOutlinerForwarder::GetBulletInfo( sal_Int32 nPara ) const +{ + return rOutliner.GetBulletInfo( nPara ); +} + +tools::Rectangle SvxOutlinerForwarder::GetCharBounds( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + // EditEngine's 'internal' methods like GetCharacterBounds() + // don't rotate for vertical text. + Size aSize( rOutliner.CalcTextSize() ); + // swap width and height + tools::Long tmp = aSize.Width(); + aSize.setWidth(aSize.Height()); + aSize.setHeight(tmp); + bool bIsVertical( rOutliner.IsVertical() ); + + // #108900# Handle virtual position one-past-the end of the string + if( nIndex >= GetTextLen(nPara) ) + { + tools::Rectangle aLast; + + if( nIndex ) + { + // use last character, if possible + aLast = rOutliner.GetEditEngine().GetCharacterBounds( EPosition(nPara, nIndex-1) ); + + // move at end of this last character, make one pixel wide + aLast.Move( aLast.Right() - aLast.Left(), 0 ); + aLast.SetSize( Size(1, aLast.GetHeight()) ); + + // take care for CTL + aLast = SvxEditSourceHelper::EEToUserSpace( aLast, aSize, bIsVertical ); + } + else + { + // #109864# Bounds must lie within the paragraph + aLast = GetParaBounds( nPara ); + + // #109151# Don't use paragraph height, but line height + // instead. aLast is already CTL-correct + if( bIsVertical) + aLast.SetSize( Size( rOutliner.GetLineHeight(nPara), 1 ) ); + else + aLast.SetSize( Size( 1, rOutliner.GetLineHeight(nPara) ) ); + } + + return aLast; + } + else + { + return SvxEditSourceHelper::EEToUserSpace( rOutliner.GetEditEngine().GetCharacterBounds( EPosition(nPara, nIndex) ), + aSize, bIsVertical ); + } +} + +tools::Rectangle SvxOutlinerForwarder::GetParaBounds( sal_Int32 nPara ) const +{ + return rOutliner.GetParaBounds( nPara ); +} + +MapMode SvxOutlinerForwarder::GetMapMode() const +{ + return rOutliner.GetRefMapMode(); +} + +OutputDevice* SvxOutlinerForwarder::GetRefDevice() const +{ + return rOutliner.GetRefDevice(); +} + +bool SvxOutlinerForwarder::GetIndexAtPoint( const Point& rPos, sal_Int32& nPara, sal_Int32& nIndex ) const +{ + Size aSize( rOutliner.CalcTextSize() ); + // swap width and height + tools::Long tmp = aSize.Width(); + aSize.setWidth(aSize.Height()); + aSize.setHeight(tmp); + Point aEEPos( SvxEditSourceHelper::UserSpaceToEE( rPos, + aSize, + rOutliner.IsVertical() )); + + EPosition aDocPos = rOutliner.GetEditEngine().FindDocPosition( aEEPos ); + + nPara = aDocPos.nPara; + nIndex = aDocPos.nIndex; + + return true; +} + +bool SvxOutlinerForwarder::GetWordIndices( sal_Int32 nPara, sal_Int32 nIndex, sal_Int32& nStart, sal_Int32& nEnd ) const +{ + ESelection aRes = rOutliner.GetEditEngine().GetWord( ESelection(nPara, nIndex, nPara, nIndex), css::i18n::WordType::DICTIONARY_WORD ); + + if( aRes.nStartPara == nPara && + aRes.nStartPara == aRes.nEndPara ) + { + nStart = aRes.nStartPos; + nEnd = aRes.nEndPos; + + return true; + } + + return false; +} + +bool SvxOutlinerForwarder::GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, sal_Int32 nPara, sal_Int32 nIndex, bool bInCell ) const +{ + SvxEditSourceHelper::GetAttributeRun( nStartIndex, nEndIndex, rOutliner.GetEditEngine(), nPara, nIndex, bInCell ); + return true; +} + +sal_Int32 SvxOutlinerForwarder::GetLineCount( sal_Int32 nPara ) const +{ + return rOutliner.GetLineCount(nPara); +} + +sal_Int32 SvxOutlinerForwarder::GetLineLen( sal_Int32 nPara, sal_Int32 nLine ) const +{ + return rOutliner.GetLineLen(nPara, nLine); +} + +void SvxOutlinerForwarder::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nPara, sal_Int32 nLine ) const +{ + return rOutliner.GetEditEngine().GetLineBoundaries( rStart, rEnd, nPara, nLine ); +} + +sal_Int32 SvxOutlinerForwarder::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + return rOutliner.GetEditEngine().GetLineNumberAtIndex( nPara, nIndex ); +} + +bool SvxOutlinerForwarder::QuickFormatDoc( bool ) +{ + rOutliner.QuickFormatDoc(); + + return true; +} + +bool SvxOutlinerForwarder::Delete( const ESelection& rSelection ) +{ + flushCache(); + rOutliner.QuickDelete( rSelection ); + rOutliner.QuickFormatDoc(); + + return true; +} + +bool SvxOutlinerForwarder::InsertText( const OUString& rStr, const ESelection& rSelection ) +{ + flushCache(); + rOutliner.QuickInsertText( rStr, rSelection ); + rOutliner.QuickFormatDoc(); + + return true; +} + +sal_Int16 SvxOutlinerForwarder::GetDepth( sal_Int32 nPara ) const +{ + DBG_ASSERT( 0 <= nPara && nPara < GetParagraphCount(), "SvxOutlinerForwarder::GetDepth: Invalid paragraph index"); + + Paragraph* pPara = rOutliner.GetParagraph( nPara ); + + sal_Int16 nLevel = -1; + + if( pPara ) + nLevel = rOutliner.GetDepth( nPara ); + + return nLevel; +} + +bool SvxOutlinerForwarder::SetDepth( sal_Int32 nPara, sal_Int16 nNewDepth ) +{ + DBG_ASSERT( 0 <= nPara && nPara < GetParagraphCount(), "SvxOutlinerForwarder::SetDepth: Invalid paragraph index"); + + if( (nNewDepth >= -1) && (nNewDepth <= 9) && (0 <= nPara && nPara < GetParagraphCount()) ) + { + Paragraph* pPara = rOutliner.GetParagraph( nPara ); + if( pPara ) + { + rOutliner.SetDepth( pPara, nNewDepth ); + +// const bool bOutlinerText = pSdrObject && (pSdrObject->GetObjInventor() == SdrInventor::Default) && (pSdrObject->GetObjIdentifier() == OBJ_OUTLINETEXT); + if( bOutlinerText ) + rOutliner.SetLevelDependentStyleSheet( nPara ); + + return true; + } + } + + return false; +} + +sal_Int32 SvxOutlinerForwarder::GetNumberingStartValue( sal_Int32 nPara ) +{ + if( 0 <= nPara && nPara < GetParagraphCount() ) + { + return rOutliner.GetNumberingStartValue( nPara ); + } + else + { + OSL_FAIL( "SvxOutlinerForwarder::GetNumberingStartValue)(), Invalid paragraph index"); + return -1; + } +} + +void SvxOutlinerForwarder::SetNumberingStartValue( sal_Int32 nPara, sal_Int32 nNumberingStartValue ) +{ + if( 0 <= nPara && nPara < GetParagraphCount() ) + { + rOutliner.SetNumberingStartValue( nPara, nNumberingStartValue ); + } + else + { + OSL_FAIL( "SvxOutlinerForwarder::SetNumberingStartValue)(), Invalid paragraph index"); + } +} + +bool SvxOutlinerForwarder::IsParaIsNumberingRestart( sal_Int32 nPara ) +{ + if( 0 <= nPara && nPara < GetParagraphCount() ) + { + return rOutliner.IsParaIsNumberingRestart( nPara ); + } + else + { + OSL_FAIL( "SvxOutlinerForwarder::IsParaIsNumberingRestart)(), Invalid paragraph index"); + return false; + } +} + +void SvxOutlinerForwarder::SetParaIsNumberingRestart( sal_Int32 nPara, bool bParaIsNumberingRestart ) +{ + if( 0 <= nPara && nPara < GetParagraphCount() ) + { + rOutliner.SetParaIsNumberingRestart( nPara, bParaIsNumberingRestart ); + } + else + { + OSL_FAIL( "SvxOutlinerForwarder::SetParaIsNumberingRestart)(), Invalid paragraph index"); + } +} + +const SfxItemSet * SvxOutlinerForwarder::GetEmptyItemSetPtr() +{ + EditEngine& rEditEngine = const_cast< EditEngine& >( rOutliner.GetEditEngine() ); + return &rEditEngine.GetEmptyItemSet(); +} + +void SvxOutlinerForwarder::AppendParagraph() +{ + EditEngine& rEditEngine = const_cast< EditEngine& >( rOutliner.GetEditEngine() ); + rEditEngine.InsertParagraph( rEditEngine.GetParagraphCount(), OUString() ); +} + +sal_Int32 SvxOutlinerForwarder::AppendTextPortion( sal_Int32 nPara, const OUString &rText, const SfxItemSet & /*rSet*/ ) +{ + sal_Int32 nLen = 0; + + EditEngine& rEditEngine = const_cast< EditEngine& >( rOutliner.GetEditEngine() ); + sal_Int32 nParaCount = rEditEngine.GetParagraphCount(); + DBG_ASSERT( 0 <= nPara && nPara < nParaCount, "paragraph index out of bounds" ); + if (0 <= nPara && nPara < nParaCount) + { + nLen = rEditEngine.GetTextLen( nPara ); + rEditEngine.QuickInsertText( rText, ESelection( nPara, nLen, nPara, nLen ) ); + } + + return nLen; +} + +void SvxOutlinerForwarder::CopyText(const SvxTextForwarder& rSource) +{ + const SvxOutlinerForwarder* pSourceForwarder = dynamic_cast< const SvxOutlinerForwarder* >( &rSource ); + if( !pSourceForwarder ) + return; + std::optional<OutlinerParaObject> pNewOutlinerParaObject = pSourceForwarder->rOutliner.CreateParaObject(); + rOutliner.SetText( *pNewOutlinerParaObject ); +} + + +sal_Int32 SvxTextForwarder::GetNumberingStartValue( sal_Int32 ) +{ + return -1; +} + +void SvxTextForwarder::SetNumberingStartValue( sal_Int32, sal_Int32 ) +{ +} + +bool SvxTextForwarder::IsParaIsNumberingRestart( sal_Int32 ) +{ + return false; +} + +void SvxTextForwarder::SetParaIsNumberingRestart( sal_Int32, bool ) +{ +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoipset.cxx b/editeng/source/uno/unoipset.cxx new file mode 100644 index 000000000..2aa7e5746 --- /dev/null +++ b/editeng/source/uno/unoipset.cxx @@ -0,0 +1,333 @@ +/* -*- 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/beans/XPropertySet.hpp> +#include <svl/itemprop.hxx> +#include <tools/UnitConversion.hxx> +#include <editeng/unoipset.hxx> +#include <svl/itempool.hxx> +#include <svl/solar.hrc> +#include <o3tl/any.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <algorithm> + +using namespace ::com::sun::star; + + +SvxItemPropertySet::SvxItemPropertySet( const SfxItemPropertyMapEntry* pMap, SfxItemPool& rItemPool ) +: m_aPropertyMap( pMap ), + mrItemPool( rItemPool ) +{ +} + + +SvxItemPropertySet::~SvxItemPropertySet() +{ +} + + +static bool SvxUnoCheckForPositiveValue( const uno::Any& rVal ) +{ + bool bConvert = true; // the default is that all metric items must be converted + sal_Int32 nValue = 0; + if( rVal >>= nValue ) + bConvert = (nValue > 0); + return bConvert; +} + + +uno::Any SvxItemPropertySet::getPropertyValue( const SfxItemPropertyMapEntry* pMap, const SfxItemSet& rSet, bool bSearchInParent, bool bDontConvertNegativeValues ) +{ + uno::Any aVal; + if(!pMap || !pMap->nWID) + return aVal; + + const SfxPoolItem* pItem = nullptr; + SfxItemPool* pPool = rSet.GetPool(); + (void)rSet.GetItemState( pMap->nWID, bSearchInParent, &pItem ); + if( nullptr == pItem && pPool ) + pItem = &(pPool->GetDefaultItem( pMap->nWID )); + + const MapUnit eMapUnit = pPool ? pPool->GetMetric(pMap->nWID) : MapUnit::Map100thMM; + sal_uInt8 nMemberId = pMap->nMemberId; + if( eMapUnit == MapUnit::Map100thMM ) + nMemberId &= (~CONVERT_TWIPS); + + if(pItem) + { + pItem->QueryValue( aVal, nMemberId ); + if( pMap->nMoreFlags & PropertyMoreFlags::METRIC_ITEM ) + { + if( eMapUnit != MapUnit::Map100thMM ) + { + if ( !bDontConvertNegativeValues || SvxUnoCheckForPositiveValue( aVal ) ) + SvxUnoConvertToMM( eMapUnit, aVal ); + } + } + else if ( pMap->aType.getTypeClass() == uno::TypeClass_ENUM && + aVal.getValueType() == ::cppu::UnoType<sal_Int32>::get() ) + { + // convert typeless SfxEnumItem to enum type + sal_Int32 nEnum; + aVal >>= nEnum; + aVal.setValue( &nEnum, pMap->aType ); + } + } + else + { + OSL_FAIL( "No SfxPoolItem found for property!" ); + } + + return aVal; +} + + +void SvxItemPropertySet::setPropertyValue( const SfxItemPropertyMapEntry* pMap, const uno::Any& rVal, SfxItemSet& rSet, bool bDontConvertNegativeValues ) +{ + if(!pMap || !pMap->nWID) + return; + + // Get item + const SfxPoolItem* pItem = nullptr; + SfxItemState eState = rSet.GetItemState( pMap->nWID, true, &pItem ); + SfxItemPool* pPool = rSet.GetPool(); + + // Put UnoAny in the item value + if(eState < SfxItemState::DEFAULT || pItem == nullptr) + { + if( pPool == nullptr ) + { + OSL_FAIL( "No default item and no pool?" ); + return; + } + + pItem = &pPool->GetDefaultItem( pMap->nWID ); + } + + uno::Any aValue(rVal); + + const MapUnit eMapUnit = pPool ? pPool->GetMetric(pMap->nWID) : MapUnit::Map100thMM; + + // check for needed metric translation + if ((pMap->nMoreFlags & PropertyMoreFlags::METRIC_ITEM) && eMapUnit != MapUnit::Map100thMM) + { + if (!bDontConvertNegativeValues || SvxUnoCheckForPositiveValue(aValue)) + SvxUnoConvertFromMM(eMapUnit, aValue); + } + + std::unique_ptr<SfxPoolItem> pNewItem(pItem->Clone()); + + sal_uInt8 nMemberId = pMap->nMemberId; + if (eMapUnit == MapUnit::Map100thMM) + nMemberId &= (~CONVERT_TWIPS); + + if (pNewItem->PutValue(aValue, nMemberId)) + { + // Set new item in item set + pNewItem->SetWhich(pMap->nWID); + rSet.Put(std::move(pNewItem)); + } +} + + +uno::Any SvxItemPropertySet::getPropertyValue( const SfxItemPropertyMapEntry* pMap, SvxItemPropertySetUsrAnys& rAnys ) const +{ + // Already entered a value? Then finish quickly + uno::Any* pUsrAny = rAnys.GetUsrAnyForID(*pMap); + if(pUsrAny) + return *pUsrAny; + + // No UsrAny detected yet, generate Default entry and return this + const MapUnit eMapUnit = mrItemPool.GetMetric(pMap->nWID); + sal_uInt8 nMemberId = pMap->nMemberId; + if( eMapUnit == MapUnit::Map100thMM ) + nMemberId &= (~CONVERT_TWIPS); + uno::Any aVal; + SfxItemSet aSet( mrItemPool, pMap->nWID, pMap->nWID); + + if( (pMap->nWID < OWN_ATTR_VALUE_START) || (pMap->nWID > OWN_ATTR_VALUE_END ) ) + { + // Get Default from ItemPool + if(SfxItemPool::IsWhich(pMap->nWID)) + aSet.Put(mrItemPool.GetDefaultItem(pMap->nWID)); + } + + if(aSet.Count()) + { + const SfxPoolItem* pItem = nullptr; + SfxItemState eState = aSet.GetItemState( pMap->nWID, true, &pItem ); + if(eState >= SfxItemState::DEFAULT && pItem) + { + pItem->QueryValue( aVal, nMemberId ); + rAnys.AddUsrAnyForID(aVal, *pMap); + } + } + + // check for needed metric translation + if(pMap->nMoreFlags & PropertyMoreFlags::METRIC_ITEM && eMapUnit != MapUnit::Map100thMM) + { + SvxUnoConvertToMM( eMapUnit, aVal ); + } + + if ( pMap->aType.getTypeClass() == uno::TypeClass_ENUM && + aVal.getValueType() == ::cppu::UnoType<sal_Int32>::get() ) + { + sal_Int32 nEnum; + aVal >>= nEnum; + + aVal.setValue( &nEnum, pMap->aType ); + } + + return aVal; +} + + +void SvxItemPropertySet::setPropertyValue( const SfxItemPropertyMapEntry* pMap, const uno::Any& rVal, SvxItemPropertySetUsrAnys& rAnys ) +{ + uno::Any* pUsrAny = rAnys.GetUsrAnyForID(*pMap); + if(!pUsrAny) + rAnys.AddUsrAnyForID(rVal, *pMap); + else + *pUsrAny = rVal; +} + + +const SfxItemPropertyMapEntry* SvxItemPropertySet::getPropertyMapEntry(std::u16string_view rName) const +{ + return m_aPropertyMap.getByName( rName ); + } + + +uno::Reference< beans::XPropertySetInfo > const & SvxItemPropertySet::getPropertySetInfo() const +{ + if( !m_xInfo.is() ) + m_xInfo = new SfxItemPropertySetInfo( m_aPropertyMap ); + return m_xInfo; +} + + +/** converts the given any with a metric to 100th/mm if needed */ +void SvxUnoConvertToMM( const MapUnit eSourceMapUnit, uno::Any & rMetric ) noexcept +{ + // map the metric of the itempool to 100th mm + switch(eSourceMapUnit) + { + case MapUnit::MapTwip : + { + switch( rMetric.getValueTypeClass() ) + { + case uno::TypeClass_BYTE: + rMetric <<= static_cast<sal_Int8>(convertTwipToMm100(*o3tl::forceAccess<sal_Int8>(rMetric))); + break; + case uno::TypeClass_SHORT: + rMetric <<= static_cast<sal_Int16>(convertTwipToMm100(*o3tl::forceAccess<sal_Int16>(rMetric))); + break; + case uno::TypeClass_UNSIGNED_SHORT: + rMetric <<= static_cast<sal_uInt16>(convertTwipToMm100(*o3tl::forceAccess<sal_uInt16>(rMetric))); + break; + case uno::TypeClass_LONG: + rMetric <<= static_cast<sal_Int32>(convertTwipToMm100(*o3tl::forceAccess<sal_Int32>(rMetric))); + break; + case uno::TypeClass_UNSIGNED_LONG: + rMetric <<= static_cast<sal_uInt32>(convertTwipToMm100(*o3tl::forceAccess<sal_uInt32>(rMetric))); + break; + default: + SAL_WARN("editeng", "AW: Missing unit translation to 100th mm, " << OString::number(static_cast<sal_Int32>(rMetric.getValueTypeClass()))); + assert(false); + } + break; + } + default: + { + OSL_FAIL("AW: Missing unit translation to 100th mm!"); + } + } +} + + +/** converts the given any with a metric from 100th/mm to the given metric if needed */ +void SvxUnoConvertFromMM( const MapUnit eDestinationMapUnit, uno::Any & rMetric ) noexcept +{ + switch(eDestinationMapUnit) + { + case MapUnit::MapTwip : + { + switch( rMetric.getValueTypeClass() ) + { + case uno::TypeClass_BYTE: + rMetric <<= static_cast<sal_Int8>(sanitiseMm100ToTwip(*o3tl::forceAccess<sal_Int8>(rMetric))); + break; + case uno::TypeClass_SHORT: + rMetric <<= static_cast<sal_Int16>(sanitiseMm100ToTwip(*o3tl::forceAccess<sal_Int16>(rMetric))); + break; + case uno::TypeClass_UNSIGNED_SHORT: + rMetric <<= static_cast<sal_uInt16>(sanitiseMm100ToTwip(*o3tl::forceAccess<sal_uInt16>(rMetric))); + break; + case uno::TypeClass_LONG: + rMetric <<= static_cast<sal_Int32>(sanitiseMm100ToTwip(*o3tl::forceAccess<sal_Int32>(rMetric))); + break; + case uno::TypeClass_UNSIGNED_LONG: + rMetric <<= static_cast<sal_uInt32>(sanitiseMm100ToTwip(*o3tl::forceAccess<sal_uInt32>(rMetric))); + break; + default: + OSL_FAIL("AW: Missing unit translation to 100th mm!"); + } + break; + } + default: + { + OSL_FAIL("AW: Missing unit translation to PoolMetrics!"); + } + } +} + +SvxItemPropertySetUsrAnys::SvxItemPropertySetUsrAnys() = default; + +SvxItemPropertySetUsrAnys::~SvxItemPropertySetUsrAnys() +{ + ClearAllUsrAny(); +} + +uno::Any* SvxItemPropertySetUsrAnys::GetUsrAnyForID(SfxItemPropertyMapEntry const & entry) const +{ + for (auto const & rActual : aCombineList) + { + if( rActual.nWID == entry.nWID && rActual.memberId == entry.nMemberId ) + return const_cast<uno::Any*>(&rActual.aAny); + } + return nullptr; +} + +void SvxItemPropertySetUsrAnys::AddUsrAnyForID( + const uno::Any& rAny, SfxItemPropertyMapEntry const & entry) +{ + SvxIDPropertyCombine aNew; + aNew.nWID = entry.nWID; + aNew.memberId = entry.nMemberId; + aNew.aAny = rAny; + aCombineList.push_back( std::move(aNew) ); +} + +void SvxItemPropertySetUsrAnys::ClearAllUsrAny() +{ + aCombineList.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unonrule.cxx b/editeng/source/uno/unonrule.cxx new file mode 100644 index 000000000..3e33d8e46 --- /dev/null +++ b/editeng/source/uno/unonrule.cxx @@ -0,0 +1,548 @@ +/* -*- 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 <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/implbase1.hxx> +#include <utility> +#include <vcl/font.hxx> +#include <vcl/svapp.hxx> +#include <vcl/graph.hxx> +#include <vcl/GraphicObject.hxx> +#include <vcl/GraphicLoader.hxx> +#include <tools/debug.hxx> + +#include <editeng/brushitem.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/numitem.hxx> +#include <editeng/unofdesc.hxx> +#include <editeng/unonrule.hxx> +#include <editeng/editids.hrc> +#include <o3tl/enumarray.hxx> +#include <o3tl/temporary.hxx> +#include <memory> + +using ::com::sun::star::util::XCloneable; +using ::com::sun::star::ucb::XAnyCompare; + + +using namespace ::std; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; + +const SvxAdjust aUnoToSvxAdjust[] = +{ + SvxAdjust::Left, + SvxAdjust::Right, + SvxAdjust::Center, + SvxAdjust::Left, + SvxAdjust::Left, + SvxAdjust::Left, + SvxAdjust::Block +}; + +const o3tl::enumarray<SvxAdjust, unsigned short> aSvxToUnoAdjust +{ + text::HoriOrientation::LEFT, + text::HoriOrientation::RIGHT, + text::HoriOrientation::FULL, + text::HoriOrientation::CENTER, + text::HoriOrientation::FULL, + text::HoriOrientation::LEFT +}; + +static SvxAdjust ConvertUnoAdjust( unsigned short nAdjust ) +{ + DBG_ASSERT( nAdjust <= 7, "Enum has changed! [CL]" ); + return aUnoToSvxAdjust[nAdjust]; +} + +static unsigned short ConvertUnoAdjust( SvxAdjust eAdjust ) +{ + DBG_ASSERT( static_cast<int>(eAdjust) <= 6, "Enum has changed! [CL]" ); + return aSvxToUnoAdjust[eAdjust]; +} + +UNO3_GETIMPLEMENTATION_IMPL( SvxUnoNumberingRules ); + +SvxUnoNumberingRules::SvxUnoNumberingRules(SvxNumRule aRule) +: maRule(std::move( aRule )) +{ +} + +SvxUnoNumberingRules::~SvxUnoNumberingRules() noexcept +{ +} + +//XIndexReplace +void SAL_CALL SvxUnoNumberingRules::replaceByIndex( sal_Int32 Index, const uno::Any& Element ) +{ + SolarMutexGuard aGuard; + + if( Index < 0 || Index >= maRule.GetLevelCount() ) + throw IndexOutOfBoundsException(); + + Sequence< beans::PropertyValue > aSeq; + + if( !( Element >>= aSeq) ) + throw IllegalArgumentException(); + setNumberingRuleByIndex( aSeq, Index ); +} + +// XIndexAccess +sal_Int32 SAL_CALL SvxUnoNumberingRules::getCount() +{ + SolarMutexGuard aGuard; + + return maRule.GetLevelCount(); +} + +Any SAL_CALL SvxUnoNumberingRules::getByIndex( sal_Int32 Index ) +{ + SolarMutexGuard aGuard; + + if( Index < 0 || Index >= maRule.GetLevelCount() ) + throw IndexOutOfBoundsException(); + + return Any( getNumberingRuleByIndex(Index) ); +} + +//XElementAccess +Type SAL_CALL SvxUnoNumberingRules::getElementType() +{ + return cppu::UnoType<Sequence< beans::PropertyValue >>::get(); +} + +sal_Bool SAL_CALL SvxUnoNumberingRules::hasElements() +{ + return true; +} + +// XAnyCompare +sal_Int16 SAL_CALL SvxUnoNumberingRules::compare( const Any& rAny1, const Any& rAny2 ) +{ + return SvxUnoNumberingRules::Compare( rAny1, rAny2 ); +} + +// XCloneable +Reference< XCloneable > SAL_CALL SvxUnoNumberingRules::createClone( ) +{ + return new SvxUnoNumberingRules(maRule); +} + +OUString SAL_CALL SvxUnoNumberingRules::getImplementationName( ) +{ + return "SvxUnoNumberingRules"; +} + +sal_Bool SAL_CALL SvxUnoNumberingRules::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL SvxUnoNumberingRules::getSupportedServiceNames( ) +{ + return { "com.sun.star.text.NumberingRules" }; +} + +Sequence<beans::PropertyValue> SvxUnoNumberingRules::getNumberingRuleByIndex(sal_Int32 nIndex) const +{ + // NumberingRule aRule; + const SvxNumberFormat& rFmt = maRule.GetLevel(static_cast<sal_uInt16>(nIndex)); + sal_uInt16 nIdx = 0; + + const int nProps = 15; + std::unique_ptr<beans::PropertyValue[]> pArray(new beans::PropertyValue[nProps]); + + Any aVal; + { + aVal <<= static_cast<sal_uInt16>(rFmt.GetNumberingType()); + beans::PropertyValue aAlignProp( UNO_NAME_NRULE_NUMBERINGTYPE, -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[nIdx++] = aAlignProp; + } + + { + SvxAdjust eAdj = rFmt.GetNumAdjust(); + aVal <<= ConvertUnoAdjust(eAdj); + pArray[nIdx++] = beans::PropertyValue( UNO_NAME_NRULE_ADJUST, -1, aVal, beans::PropertyState_DIRECT_VALUE); + } + + { + aVal <<= rFmt.GetPrefix(); + beans::PropertyValue aPrefixProp( UNO_NAME_NRULE_PREFIX, -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[nIdx++] = aPrefixProp; + } + + { + aVal <<= rFmt.GetSuffix(); + beans::PropertyValue aSuffixProp( UNO_NAME_NRULE_SUFFIX, -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[nIdx++] = aSuffixProp; + } + + if(SVX_NUM_CHAR_SPECIAL == rFmt.GetNumberingType()) + { + sal_UCS4 nCode = rFmt.GetBulletChar(); + OUString aStr( &nCode, 1 ); + aVal <<= aStr; + beans::PropertyValue aBulletProp( "BulletChar", -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[nIdx++] = aBulletProp; + } + + if( rFmt.GetBulletFont() ) + { + awt::FontDescriptor aDesc; + SvxUnoFontDescriptor::ConvertFromFont( *rFmt.GetBulletFont(), aDesc ); + aVal <<= aDesc; + pArray[nIdx++] = beans::PropertyValue( UNO_NAME_NRULE_BULLET_FONT, -1, aVal, beans::PropertyState_DIRECT_VALUE); + } + + { + const SvxBrushItem* pBrush = rFmt.GetBrush(); + const Graphic* pGraphic = nullptr; + if (pBrush) + pGraphic = pBrush->GetGraphic(); + if (pGraphic) + { + uno::Reference<awt::XBitmap> xBitmap(pGraphic->GetXGraphic(), uno::UNO_QUERY); + aVal <<= xBitmap; + + const beans::PropertyValue aGraphicProp("GraphicBitmap", -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[nIdx++] = aGraphicProp; + } + } + + { + const Size aSize( rFmt.GetGraphicSize() ); + const awt::Size aUnoSize( aSize.Width(), aSize.Height() ); + aVal <<= aUnoSize; + const beans::PropertyValue aGraphicSizeProp("GraphicSize", -1, aVal, beans::PropertyState_DIRECT_VALUE ); + pArray[nIdx++] = aGraphicSizeProp; + } + + aVal <<= static_cast<sal_Int16>(rFmt.GetStart()); + pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_START_WITH, -1, aVal, beans::PropertyState_DIRECT_VALUE); + + aVal <<= rFmt.GetAbsLSpace(); + pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_LEFT_MARGIN, -1, aVal, beans::PropertyState_DIRECT_VALUE); + + aVal <<= rFmt.GetFirstLineOffset(); + pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_FIRST_LINE_OFFSET, -1, aVal, beans::PropertyState_DIRECT_VALUE); + + pArray[nIdx++] = beans::PropertyValue("SymbolTextDistance", -1, aVal, beans::PropertyState_DIRECT_VALUE); + + aVal <<= rFmt.GetBulletColor(); + pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_BULLET_COLOR, -1, aVal, beans::PropertyState_DIRECT_VALUE); + + aVal <<= static_cast<sal_Int16>(rFmt.GetBulletRelSize()); + pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_BULLET_RELSIZE, -1, aVal, beans::PropertyState_DIRECT_VALUE); + + DBG_ASSERT( nIdx <= nProps, "FixMe: overflow in Array!!! [CL]" ); + Sequence< beans::PropertyValue> aSeq(pArray.get(), nIdx); + + return aSeq; +} + +void SvxUnoNumberingRules::setNumberingRuleByIndex(const Sequence<beans::PropertyValue >& rProperties, sal_Int32 nIndex) +{ + SvxNumberFormat aFmt(maRule.GetLevel( static_cast<sal_uInt16>(nIndex) )); + for(const beans::PropertyValue& rProp : rProperties) + { + const OUString& rPropName = rProp.Name; + const Any& aVal = rProp.Value; + + if ( rPropName == UNO_NAME_NRULE_NUMBERINGTYPE ) + { + sal_Int16 nSet = sal_Int16(); + aVal >>= nSet; + + // There is no reason to limit numbering types. + if ( nSet>=0 ) + { + aFmt.SetNumberingType(static_cast<SvxNumType>(nSet)); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_PREFIX ) + { + OUString aPrefix; + if( aVal >>= aPrefix ) + { + aFmt.SetPrefix(aPrefix); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_SUFFIX ) + { + OUString aSuffix; + if( aVal >>= aSuffix ) + { + aFmt.SetSuffix(aSuffix); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_BULLETID ) + { + sal_Int16 nSet = sal_Int16(); + if( aVal >>= nSet ) + { + if(nSet < 0x100) + { + aFmt.SetBulletChar(nSet); + continue; + } + } + } + else if ( rPropName == "BulletChar" ) + { + OUString aStr; + if( aVal >>= aStr ) + { + if(!aStr.isEmpty()) + { + aFmt.SetBulletChar(aStr.iterateCodePoints(&o3tl::temporary(sal_Int32(0)))); + } + else + { + aFmt.SetBulletChar(0); + } + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_ADJUST ) + { + sal_Int16 nAdjust = sal_Int16(); + if( aVal >>= nAdjust ) + { + aFmt.SetNumAdjust(ConvertUnoAdjust( static_cast<unsigned short>(nAdjust) )); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_BULLET_FONT ) + { + awt::FontDescriptor aDesc; + if( aVal >>= aDesc ) + { + vcl::Font aFont; + SvxUnoFontDescriptor::ConvertToFont( aDesc, aFont ); + aFmt.SetBulletFont(&aFont); + continue; + } + } + else if ( rPropName == "GraphicURL" ) + { + OUString aURL; + if (aVal >>= aURL) + { + Graphic aGraphic = vcl::graphic::loadFromURL(aURL); + if (!aGraphic.IsNone()) + { + SvxBrushItem aBrushItem(aGraphic, GPOS_AREA, SID_ATTR_BRUSH); + aFmt.SetGraphicBrush(&aBrushItem); + } + continue; + } + } + else if ( rPropName == "GraphicBitmap" ) + { + uno::Reference<awt::XBitmap> xBitmap; + if (aVal >>= xBitmap) + { + uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY); + Graphic aGraphic(xGraphic); + SvxBrushItem aBrushItem(aGraphic, GPOS_AREA, SID_ATTR_BRUSH); + aFmt.SetGraphicBrush( &aBrushItem ); + continue; + } + } + else if ( rPropName == "GraphicSize" ) + { + awt::Size aUnoSize; + if( aVal >>= aUnoSize ) + { + aFmt.SetGraphicSize( Size( aUnoSize.Width, aUnoSize.Height ) ); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_START_WITH ) + { + sal_Int16 nStart = sal_Int16(); + if( aVal >>= nStart ) + { + aFmt.SetStart( nStart ); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_LEFT_MARGIN ) + { + sal_Int32 nMargin = 0; + if( aVal >>= nMargin ) + { + aFmt.SetAbsLSpace(nMargin); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_FIRST_LINE_OFFSET ) + { + sal_Int32 nMargin = 0; + if( aVal >>= nMargin ) + { + aFmt.SetFirstLineOffset(nMargin); + continue; + } + } + else if ( rPropName == "SymbolTextDistance" ) + { + sal_Int32 nTextDistance = 0; + if( aVal >>= nTextDistance ) + { + aFmt.SetCharTextDistance(static_cast<sal_uInt16>(nTextDistance)); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_BULLET_COLOR ) + { + Color aColor; + if( aVal >>= aColor ) + { + aFmt.SetBulletColor( aColor ); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_BULLET_RELSIZE ) + { + sal_Int16 nSize = sal_Int16(); + if( aVal >>= nSize ) + { + // [AOO Bug 120650] the slide content corrupt when open in Aoo + // [TDF# 126234] when MS Office document being imported, the value of the relative size + // of the bullet could be as high as 400% + if ((nSize>400)||(nSize<=0)) + { + nSize = 100; + } + + aFmt.SetBulletRelSize( static_cast<short>(nSize) ); + continue; + } + } + else + { + continue; + } + + throw IllegalArgumentException(); + } + + // check that we always have a brush item for bitmap numbering + if( aFmt.GetNumberingType() == SVX_NUM_BITMAP ) + { + if( nullptr == aFmt.GetBrush() ) + { + GraphicObject aGrafObj; + SvxBrushItem aBrushItem( aGrafObj, GPOS_AREA, SID_ATTR_BRUSH ); + aFmt.SetGraphicBrush( &aBrushItem ); + } + } + maRule.SetLevel( static_cast<sal_uInt16>(nIndex), aFmt ); +} + +const SvxNumRule& SvxGetNumRule( Reference< XIndexReplace > const & xRule ) +{ + SvxUnoNumberingRules* pRule = comphelper::getFromUnoTunnel<SvxUnoNumberingRules>( xRule ); + if( pRule == nullptr ) + throw IllegalArgumentException(); + + return pRule->getNumRule(); +} + +css::uno::Reference< css::container::XIndexReplace > SvxCreateNumRule(const SvxNumRule& rRule) +{ + return new SvxUnoNumberingRules( rRule ); +} + +namespace { + +class SvxUnoNumberingRulesCompare : public ::cppu::WeakAggImplHelper1< XAnyCompare > +{ +public: + virtual sal_Int16 SAL_CALL compare( const Any& Any1, const Any& Any2 ) override; +}; + +} + +sal_Int16 SAL_CALL SvxUnoNumberingRulesCompare::compare( const Any& Any1, const Any& Any2 ) +{ + return SvxUnoNumberingRules::Compare( Any1, Any2 ); +} + +sal_Int16 SvxUnoNumberingRules::Compare( const Any& Any1, const Any& Any2 ) +{ + Reference< XIndexReplace > x1( Any1, UNO_QUERY ), x2( Any2, UNO_QUERY ); + if( !x1 || !x2 ) + return -1; + + if( x1.get() == x2.get() ) + return 0; + + SvxUnoNumberingRules* pRule1 = comphelper::getFromUnoTunnel<SvxUnoNumberingRules>( x1 ); + if( !pRule1 ) + return -1; + SvxUnoNumberingRules* pRule2 = comphelper::getFromUnoTunnel<SvxUnoNumberingRules>( x2 ); + if( !pRule2 ) + return -1; + + const SvxNumRule& rRule1 = pRule1->getNumRule(); + const SvxNumRule& rRule2 = pRule2->getNumRule(); + + const sal_uInt16 nLevelCount1 = rRule1.GetLevelCount(); + const sal_uInt16 nLevelCount2 = rRule2.GetLevelCount(); + + if( nLevelCount1 == 0 || nLevelCount2 == 0 ) + return -1; + + for( sal_uInt16 i = 0; (i < nLevelCount1) && (i < nLevelCount2); i++ ) + { + if( rRule1.GetLevel(i) != rRule2.GetLevel(i) ) + return -1; + } + return 0; +} + +Reference< XAnyCompare > SvxCreateNumRuleCompare() noexcept +{ + return new SvxUnoNumberingRulesCompare; +} + +css::uno::Reference< css::container::XIndexReplace > SvxCreateNumRule() +{ + SvxNumRule aTempRule( SvxNumRuleFlags::NONE, 10, false ); + return SvxCreateNumRule( aTempRule ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unopracc.cxx b/editeng/source/uno/unopracc.cxx new file mode 100644 index 000000000..c36fc152e --- /dev/null +++ b/editeng/source/uno/unopracc.cxx @@ -0,0 +1,92 @@ +/* -*- 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 <cppuhelper/typeprovider.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unopracc.hxx> +#include <editeng/unoedsrc.hxx> + +using namespace ::com::sun::star; + + +SvxAccessibleTextPropertySet::SvxAccessibleTextPropertySet( const SvxEditSource* pEditSrc, const SvxItemPropertySet* pPropSet ) + : SvxUnoTextRangeBase( pEditSrc, pPropSet ) +{ +} + +SvxAccessibleTextPropertySet::~SvxAccessibleTextPropertySet() noexcept +{ +} + +uno::Reference< text::XText > SAL_CALL SvxAccessibleTextPropertySet::getText() +{ + // TODO (empty?) + return uno::Reference< text::XText > (); +} + +uno::Any SAL_CALL SvxAccessibleTextPropertySet::queryInterface( const uno::Type & rType ) +{ + return OWeakObject::queryInterface(rType); +} + +void SAL_CALL SvxAccessibleTextPropertySet::acquire() + noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL SvxAccessibleTextPropertySet::release() + noexcept +{ + OWeakObject::release(); +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SvxAccessibleTextPropertySet::getTypes() +{ + static ::cppu::OTypeCollection ourTypeCollection( + ::cppu::UnoType<beans::XPropertySet>::get(), + ::cppu::UnoType<beans::XMultiPropertySet>::get(), + ::cppu::UnoType<beans::XPropertyState>::get(), + ::cppu::UnoType<lang::XServiceInfo>::get(), + ::cppu::UnoType<lang::XTypeProvider>::get() ); + + return ourTypeCollection.getTypes() ; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxAccessibleTextPropertySet::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XServiceInfo +OUString SAL_CALL SAL_CALL SvxAccessibleTextPropertySet::getImplementationName() +{ + return "SvxAccessibleTextPropertySet"; +} + +sal_Bool SAL_CALL SvxAccessibleTextPropertySet::supportsService (const OUString& sServiceName) +{ + return cppu::supportsService(this, sServiceName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unotext.cxx b/editeng/source/uno/unotext.cxx new file mode 100644 index 000000000..bf085c647 --- /dev/null +++ b/editeng/source/uno/unotext.cxx @@ -0,0 +1,2469 @@ +/* -*- 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 <com/sun/star/text/ControlCharacter.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/text/TextRangeSelection.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/container/XNameContainer.hpp> + +#include <svl/itemset.hxx> +#include <svl/itempool.hxx> +#include <svl/eitem.hxx> +#include <tools/debug.hxx> + +#include <editeng/unoprnms.hxx> +#include <editeng/unotext.hxx> +#include <editeng/unoedsrc.hxx> +#include <editeng/unonrule.hxx> +#include <editeng/unofdesc.hxx> +#include <editeng/unofield.hxx> +#include <editeng/flditem.hxx> +#include <editeng/numitem.hxx> +#include <editeng/editeng.hxx> +#include <editeng/outliner.hxx> +#include <editeng/unoipset.hxx> +#include <editeng/colritem.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <editeng/unonames.hxx> + +#include <initializer_list> +#include <memory> +#include <string_view> + +using namespace ::cppu; +using namespace ::com::sun::star; + +namespace { + +ESelection toESelection(const text::TextRangeSelection& rSel) +{ + ESelection aESel; + aESel.nStartPara = rSel.Start.Paragraph; + aESel.nStartPos = rSel.Start.PositionInParagraph; + aESel.nEndPara = rSel.End.Paragraph; + aESel.nEndPos = rSel.End.PositionInParagraph; + return aESel; +} + +} + +#define QUERYINT( xint ) \ + if( rType == cppu::UnoType<xint>::get() ) \ + return uno::Any(uno::Reference< xint >(this)) + +const SvxItemPropertySet* ImplGetSvxUnoOutlinerTextCursorSvxPropertySet() +{ + static SvxItemPropertySet aTextCursorSvxPropertySet( ImplGetSvxUnoOutlinerTextCursorPropertyMap(), EditEngine::GetGlobalItemPool() ); + return &aTextCursorSvxPropertySet; +} + +const SfxItemPropertyMapEntry* ImplGetSvxTextPortionPropertyMap() +{ + // Propertymap for an Outliner Text + static const SfxItemPropertyMapEntry aSvxTextPortionPropertyMap[] = + { + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_FONT_PROPERTIES, + SVX_UNOEDIT_OUTLINER_PROPERTIES, + SVX_UNOEDIT_PARA_PROPERTIES, + { u"TextField", EE_FEATURE_FIELD, cppu::UnoType<text::XTextField>::get(), beans::PropertyAttribute::READONLY, 0 }, + { u"TextPortionType", WID_PORTIONTYPE, ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::READONLY, 0 }, + { u"TextUserDefinedAttributes", EE_CHAR_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + { u"ParaUserDefinedAttributes", EE_PARA_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + { u"", 0, css::uno::Type(), 0, 0 } + }; + return aSvxTextPortionPropertyMap; +} +const SvxItemPropertySet* ImplGetSvxTextPortionSvxPropertySet() +{ + static SvxItemPropertySet aSvxTextPortionPropertySet( ImplGetSvxTextPortionPropertyMap(), EditEngine::GetGlobalItemPool() ); + return &aSvxTextPortionPropertySet; +} + +static const SfxItemPropertySet* ImplGetSvxTextPortionSfxPropertySet() +{ + static SfxItemPropertySet aSvxTextPortionSfxPropertySet( ImplGetSvxTextPortionPropertyMap() ); + return &aSvxTextPortionSfxPropertySet; +} + +const SfxItemPropertyMapEntry* ImplGetSvxUnoOutlinerTextCursorPropertyMap() +{ + // Propertymap for an Outliner Text + static const SfxItemPropertyMapEntry aSvxUnoOutlinerTextCursorPropertyMap[] = + { + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_FONT_PROPERTIES, + SVX_UNOEDIT_OUTLINER_PROPERTIES, + SVX_UNOEDIT_PARA_PROPERTIES, + { u"TextUserDefinedAttributes", EE_CHAR_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + { u"ParaUserDefinedAttributes", EE_PARA_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + { u"", 0, css::uno::Type(), 0, 0 } + }; + + return aSvxUnoOutlinerTextCursorPropertyMap; +} +static const SfxItemPropertySet* ImplGetSvxUnoOutlinerTextCursorSfxPropertySet() +{ + static SfxItemPropertySet aTextCursorSfxPropertySet( ImplGetSvxUnoOutlinerTextCursorPropertyMap() ); + return &aTextCursorSfxPropertySet; +} + + +// helper for Item/Property conversion + + +void GetSelection( struct ESelection& rSel, SvxTextForwarder const * pForwarder ) noexcept +{ + DBG_ASSERT( pForwarder, "I need a valid SvxTextForwarder!" ); + if( pForwarder ) + { + sal_Int32 nParaCount = pForwarder->GetParagraphCount(); + if(nParaCount>0) + nParaCount--; + + rSel = ESelection( 0,0, nParaCount, pForwarder->GetTextLen( nParaCount )); + } +} + +void CheckSelection( struct ESelection& rSel, SvxTextForwarder const * pForwarder ) noexcept +{ + DBG_ASSERT( pForwarder, "I need a valid SvxTextForwarder!" ); + if( !pForwarder ) + return; + + if( rSel.nStartPara == EE_PARA_MAX_COUNT ) + { + ::GetSelection( rSel, pForwarder ); + } + else + { + ESelection aMaxSelection; + GetSelection( aMaxSelection, pForwarder ); + + // check start position + if( rSel.nStartPara < aMaxSelection.nStartPara ) + { + rSel.nStartPara = aMaxSelection.nStartPara; + rSel.nStartPos = aMaxSelection.nStartPos; + } + else if( rSel.nStartPara > aMaxSelection.nEndPara ) + { + rSel.nStartPara = aMaxSelection.nEndPara; + rSel.nStartPos = aMaxSelection.nEndPos; + } + else if( rSel.nStartPos > pForwarder->GetTextLen( rSel.nStartPara ) ) + { + rSel.nStartPos = pForwarder->GetTextLen( rSel.nStartPara ); + } + + // check end position + if( rSel.nEndPara < aMaxSelection.nStartPara ) + { + rSel.nEndPara = aMaxSelection.nStartPara; + rSel.nEndPos = aMaxSelection.nStartPos; + } + else if( rSel.nEndPara > aMaxSelection.nEndPara ) + { + rSel.nEndPara = aMaxSelection.nEndPara; + rSel.nEndPos = aMaxSelection.nEndPos; + } + else if( rSel.nEndPos > pForwarder->GetTextLen( rSel.nEndPara ) ) + { + rSel.nEndPos = pForwarder->GetTextLen( rSel.nEndPara ); + } + } +} + +static void CheckSelection( struct ESelection& rSel, SvxEditSource *pEdit ) noexcept +{ + if (!pEdit) + return; + CheckSelection( rSel, pEdit->GetTextForwarder() ); +} + + + + +UNO3_GETIMPLEMENTATION_IMPL( SvxUnoTextRangeBase ); + +SvxUnoTextRangeBase::SvxUnoTextRangeBase(const SvxItemPropertySet* _pSet) + : mpPropSet(_pSet) +{ +} + +SvxUnoTextRangeBase::SvxUnoTextRangeBase(const SvxEditSource* pSource, const SvxItemPropertySet* _pSet) +: mpPropSet(_pSet) +{ + SolarMutexGuard aGuard; + + DBG_ASSERT(pSource,"SvxUnoTextRangeBase: I need a valid SvxEditSource!"); + + mpEditSource = pSource->Clone(); + if (mpEditSource != nullptr) + { + ESelection aSelection; + ::GetSelection( aSelection, mpEditSource->GetTextForwarder() ); + SetSelection( aSelection ); + + mpEditSource->addRange( this ); + } +} + +SvxUnoTextRangeBase::SvxUnoTextRangeBase(const SvxUnoTextRangeBase& rRange) +: text::XTextRange() +, beans::XPropertySet() +, beans::XMultiPropertySet() +, beans::XMultiPropertyStates() +, beans::XPropertyState() +, lang::XServiceInfo() +, text::XTextRangeCompare() +, lang::XUnoTunnel() +, osl::DebugBase<SvxUnoTextRangeBase>() +, mpPropSet(rRange.getPropertySet()) +{ + SolarMutexGuard aGuard; + + if (rRange.mpEditSource) + mpEditSource = rRange.mpEditSource->Clone(); + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + maSelection = rRange.maSelection; + CheckSelection( maSelection, pForwarder ); + } + + if( mpEditSource ) + mpEditSource->addRange( this ); +} + +SvxUnoTextRangeBase::~SvxUnoTextRangeBase() noexcept +{ + if( mpEditSource ) + mpEditSource->removeRange( this ); +} + +void SvxUnoTextRangeBase::SetEditSource( SvxEditSource* pSource ) noexcept +{ + DBG_ASSERT(pSource,"SvxUnoTextRangeBase: I need a valid SvxEditSource!"); + DBG_ASSERT(mpEditSource==nullptr,"SvxUnoTextRangeBase::SetEditSource called while SvxEditSource already set" ); + + mpEditSource.reset( pSource ); + + maSelection.nStartPara = EE_PARA_MAX_COUNT; + + if( mpEditSource ) + mpEditSource->addRange( this ); +} + +/** puts a field item with a copy of the given FieldData into the itemset + corresponding with this range */ +void SvxUnoTextRangeBase::attachField( std::unique_ptr<SvxFieldData> pData ) noexcept +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + SvxFieldItem aField( std::move(pData), EE_FEATURE_FIELD ); + pForwarder->QuickInsertField( std::move(aField), maSelection ); + } +} + +void SvxUnoTextRangeBase::SetSelection( const ESelection& rSelection ) noexcept +{ + SolarMutexGuard aGuard; + + maSelection = rSelection; + CheckSelection( maSelection, mpEditSource.get() ); +} + +// Interface XTextRange ( XText ) + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextRangeBase::getStart() +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextRange > xRange; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + CheckSelection( maSelection, pForwarder ); + + SvxUnoTextBase* pText = comphelper::getFromUnoTunnel<SvxUnoTextBase>( getText() ); + + if(pText == nullptr) + throw uno::RuntimeException(); + + rtl::Reference<SvxUnoTextRange> pRange = new SvxUnoTextRange( *pText ); + xRange = pRange; + + ESelection aNewSel = maSelection; + aNewSel.nEndPara = aNewSel.nStartPara; + aNewSel.nEndPos = aNewSel.nStartPos; + pRange->SetSelection( aNewSel ); + } + + return xRange; +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextRangeBase::getEnd() +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextRange > xRet; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + CheckSelection( maSelection, pForwarder ); + + SvxUnoTextBase* pText = comphelper::getFromUnoTunnel<SvxUnoTextBase>( getText() ); + + if(pText == nullptr) + throw uno::RuntimeException(); + + rtl::Reference<SvxUnoTextRange> pNew = new SvxUnoTextRange( *pText ); + xRet = pNew; + + ESelection aNewSel = maSelection; + aNewSel.nStartPara = aNewSel.nEndPara; + aNewSel.nStartPos = aNewSel.nEndPos; + pNew->SetSelection( aNewSel ); + } + return xRet; +} + +OUString SAL_CALL SvxUnoTextRangeBase::getString() +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + CheckSelection( maSelection, pForwarder ); + + return pForwarder->GetText( maSelection ); + } + else + { + return OUString(); + } +} + +void SAL_CALL SvxUnoTextRangeBase::setString(const OUString& aString) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( !pForwarder ) + return; + + CheckSelection( maSelection, pForwarder ); + + OUString aConverted(convertLineEnd(aString, LINEEND_LF)); // Simply count the number of line endings + + pForwarder->QuickInsertText( aConverted, maSelection ); + mpEditSource->UpdateData(); + + // Adapt selection + //! It would be easier if the EditEngine would return the selection + //! on QuickInsertText... + CollapseToStart(); + + sal_Int32 nLen = aConverted.getLength(); + if (nLen) + GoRight( nLen, true ); +} + +// Interface beans::XPropertySet +uno::Reference< beans::XPropertySetInfo > SAL_CALL SvxUnoTextRangeBase::getPropertySetInfo() +{ + return mpPropSet->getPropertySetInfo(); +} + +void SAL_CALL SvxUnoTextRangeBase::setPropertyValue(const OUString& PropertyName, const uno::Any& aValue) +{ + if (PropertyName == UNO_TR_PROP_SELECTION) + { + text::TextRangeSelection aSel = aValue.get<text::TextRangeSelection>(); + SetSelection(toESelection(aSel)); + + return; + } + + _setPropertyValue( PropertyName, aValue ); +} + +void SvxUnoTextRangeBase::_setPropertyValue( const OUString& PropertyName, const uno::Any& aValue, sal_Int32 nPara ) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + CheckSelection( maSelection, pForwarder ); + + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry(PropertyName ); + if ( pMap ) + { + ESelection aSel( GetSelection() ); + bool bParaAttrib = (pMap->nWID >= EE_PARA_START) && ( pMap->nWID <= EE_PARA_END ); + + if( nPara == -1 && !bParaAttrib ) + { + SfxItemSet aOldSet( pForwarder->GetAttribs( aSel ) ); + // we have a selection and no para attribute + SfxItemSet aNewSet( *aOldSet.GetPool(), aOldSet.GetRanges() ); + + setPropertyValue( pMap, aValue, maSelection, aOldSet, aNewSet ); + + + pForwarder->QuickSetAttribs( aNewSet, GetSelection() ); + } + else + { + sal_Int32 nEndPara; + + if( nPara == -1 ) + { + nPara = aSel.nStartPara; + nEndPara = aSel.nEndPara; + } + else + { + // only one paragraph + nEndPara = nPara; + } + + while( nPara <= nEndPara ) + { + // we have a paragraph + SfxItemSet aSet( pForwarder->GetParaAttribs( nPara ) ); + setPropertyValue( pMap, aValue, maSelection, aSet, aSet ); + pForwarder->SetParaAttribs( nPara, aSet ); + nPara++; + } + } + + GetEditSource()->UpdateData(); + return; + } + } + + throw beans::UnknownPropertyException(PropertyName); +} + +void SvxUnoTextRangeBase::setPropertyValue( const SfxItemPropertyMapEntry* pMap, const uno::Any& rValue, const ESelection& rSelection, const SfxItemSet& rOldSet, SfxItemSet& rNewSet ) +{ + if(!SetPropertyValueHelper( pMap, rValue, rNewSet, &rSelection, GetEditSource() )) + { + // For parts of composite items with multiple properties (eg background) + // must be taken from the document before the old item. + rNewSet.Put(rOldSet.Get(pMap->nWID)); // Old Item in new Set + SvxItemPropertySet::setPropertyValue(pMap, rValue, rNewSet, false ); + } +} + +bool SvxUnoTextRangeBase::SetPropertyValueHelper( const SfxItemPropertyMapEntry* pMap, const uno::Any& aValue, SfxItemSet& rNewSet, const ESelection* pSelection /* = NULL */, SvxEditSource* pEditSource /* = NULL*/ ) +{ + switch( pMap->nWID ) + { + case WID_FONTDESC: + { + awt::FontDescriptor aDesc; + if(aValue >>= aDesc) + { + SvxUnoFontDescriptor::FillItemSet( aDesc, rNewSet ); + return true; + } + } + break; + + case EE_PARA_NUMBULLET: + { + uno::Reference< container::XIndexReplace > xRule; + return !aValue.hasValue() || ((aValue >>= xRule) && !xRule.is()); + } + + case WID_NUMLEVEL: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + { + sal_Int16 nLevel = sal_Int16(); + if( aValue >>= nLevel ) + { + // #101004# Call interface method instead of unsafe cast + if(! pForwarder->SetDepth( pSelection->nStartPara, nLevel ) ) + throw lang::IllegalArgumentException(); + + return true; + } + } + } + break; + case WID_NUMBERINGSTARTVALUE: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + { + sal_Int16 nStartValue = -1; + if( aValue >>= nStartValue ) + { + pForwarder->SetNumberingStartValue( pSelection->nStartPara, nStartValue ); + return true; + } + } + } + break; + case WID_PARAISNUMBERINGRESTART: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + { + bool bParaIsNumberingRestart = false; + if( aValue >>= bParaIsNumberingRestart ) + { + pForwarder->SetParaIsNumberingRestart( pSelection->nStartPara, bParaIsNumberingRestart ); + return true; + } + } + } + break; + case EE_PARA_BULLETSTATE: + { + bool bBullet = true; + if( aValue >>= bBullet ) + { + SfxBoolItem aItem( EE_PARA_BULLETSTATE, bBullet ); + rNewSet.Put(aItem); + return true; + } + } + break; + + default: + return false; + } + + throw lang::IllegalArgumentException(); +} + +uno::Any SAL_CALL SvxUnoTextRangeBase::getPropertyValue(const OUString& PropertyName) +{ + if (PropertyName == UNO_TR_PROP_SELECTION) + { + const ESelection& rSel = GetSelection(); + text::TextRangeSelection aSel; + aSel.Start.Paragraph = rSel.nStartPara; + aSel.Start.PositionInParagraph = rSel.nStartPos; + aSel.End.Paragraph = rSel.nEndPara; + aSel.End.PositionInParagraph = rSel.nEndPos; + return uno::Any(aSel); + } + + return _getPropertyValue( PropertyName ); +} + +uno::Any SvxUnoTextRangeBase::_getPropertyValue(const OUString& PropertyName, sal_Int32 nPara ) +{ + SolarMutexGuard aGuard; + + uno::Any aAny; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry(PropertyName ); + if( pMap ) + { + std::optional<SfxItemSet> oAttribs; + if( nPara != -1 ) + oAttribs.emplace(pForwarder->GetParaAttribs( nPara ).CloneAsValue()); + else + oAttribs.emplace(pForwarder->GetAttribs( GetSelection() ).CloneAsValue()); + + // Replace Dontcare with Default, so that one always has a mirror + oAttribs->ClearInvalidItems(); + + getPropertyValue( pMap, aAny, *oAttribs ); + + return aAny; + } + } + + throw beans::UnknownPropertyException(PropertyName); +} + +void SvxUnoTextRangeBase::getPropertyValue( const SfxItemPropertyMapEntry* pMap, uno::Any& rAny, const SfxItemSet& rSet ) +{ + switch( pMap->nWID ) + { + case EE_FEATURE_FIELD: + if ( rSet.GetItemState( EE_FEATURE_FIELD, false ) == SfxItemState::SET ) + { + const SvxFieldItem* pItem = rSet.GetItem<SvxFieldItem>( EE_FEATURE_FIELD ); + const SvxFieldData* pData = pItem->GetField(); + uno::Reference< text::XTextRange > xAnchor( this ); + + // get presentation string for field + std::optional<Color> pTColor; + std::optional<Color> pFColor; + + SvxTextForwarder* pForwarder = mpEditSource->GetTextForwarder(); + OUString aPresentation( pForwarder->CalcFieldValue( SvxFieldItem(*pData, EE_FEATURE_FIELD), maSelection.nStartPara, maSelection.nStartPos, pTColor, pFColor ) ); + + uno::Reference< text::XTextField > xField( new SvxUnoTextField( xAnchor, aPresentation, pData ) ); + rAny <<= xField; + } + break; + + case WID_PORTIONTYPE: + if ( rSet.GetItemState( EE_FEATURE_FIELD, false ) == SfxItemState::SET ) + { + rAny <<= OUString("TextField"); + } + else + { + rAny <<= OUString("Text"); + } + break; + + default: + if(!GetPropertyValueHelper( *const_cast<SfxItemSet*>(&rSet), pMap, rAny, &maSelection, GetEditSource() )) + rAny = SvxItemPropertySet::getPropertyValue(pMap, rSet, true, false ); + } +} + +bool SvxUnoTextRangeBase::GetPropertyValueHelper( SfxItemSet const & rSet, const SfxItemPropertyMapEntry* pMap, uno::Any& aAny, const ESelection* pSelection /* = NULL */, SvxEditSource* pEditSource /* = NULL */ ) +{ + switch( pMap->nWID ) + { + case WID_FONTDESC: + { + awt::FontDescriptor aDesc; + SvxUnoFontDescriptor::FillFromItemSet( rSet, aDesc ); + aAny <<= aDesc; + } + break; + + case EE_PARA_NUMBULLET: + { + SfxItemState eState = rSet.GetItemState( EE_PARA_NUMBULLET ); + if( eState != SfxItemState::SET && eState != SfxItemState::DEFAULT) + throw uno::RuntimeException(); + + const SvxNumBulletItem* pBulletItem = rSet.GetItem( EE_PARA_NUMBULLET ); + + if( pBulletItem == nullptr ) + throw uno::RuntimeException(); + + aAny <<= SvxCreateNumRule( pBulletItem->GetNumRule() ); + } + break; + + case WID_NUMLEVEL: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + { + sal_Int16 nLevel = pForwarder->GetDepth( pSelection->nStartPara ); + if( nLevel >= 0 ) + aAny <<= nLevel; + } + } + break; + case WID_NUMBERINGSTARTVALUE: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + aAny <<= pForwarder->GetNumberingStartValue( pSelection->nStartPara ); + } + break; + case WID_PARAISNUMBERINGRESTART: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + aAny <<= pForwarder->IsParaIsNumberingRestart( pSelection->nStartPara ); + } + break; + + case EE_PARA_BULLETSTATE: + { + bool bState = false; + SfxItemState eState = rSet.GetItemState( EE_PARA_BULLETSTATE ); + if( eState == SfxItemState::SET || eState == SfxItemState::DEFAULT ) + { + const SfxBoolItem* pItem = rSet.GetItem<SfxBoolItem>( EE_PARA_BULLETSTATE ); + bState = pItem->GetValue(); + } + + aAny <<= bState; + } + break; + default: + + return false; + } + + return true; +} + +// is not (yet) supported +void SAL_CALL SvxUnoTextRangeBase::addPropertyChangeListener( const OUString& , const uno::Reference< beans::XPropertyChangeListener >& ) {} +void SAL_CALL SvxUnoTextRangeBase::removePropertyChangeListener( const OUString& , const uno::Reference< beans::XPropertyChangeListener >& ) {} +void SAL_CALL SvxUnoTextRangeBase::addVetoableChangeListener( const OUString& , const uno::Reference< beans::XVetoableChangeListener >& ) {} +void SAL_CALL SvxUnoTextRangeBase::removeVetoableChangeListener( const OUString& , const uno::Reference< beans::XVetoableChangeListener >& ) {} + +// XMultiPropertySet +void SAL_CALL SvxUnoTextRangeBase::setPropertyValues( const uno::Sequence< OUString >& aPropertyNames, const uno::Sequence< uno::Any >& aValues ) +{ + _setPropertyValues( aPropertyNames, aValues ); +} + +void SvxUnoTextRangeBase::_setPropertyValues( const uno::Sequence< OUString >& aPropertyNames, const uno::Sequence< uno::Any >& aValues, sal_Int32 nPara ) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( !pForwarder ) + return; + + CheckSelection( maSelection, pForwarder ); + + ESelection aSel( GetSelection() ); + + const OUString* pPropertyNames = aPropertyNames.getConstArray(); + const uno::Any* pValues = aValues.getConstArray(); + sal_Int32 nCount = aPropertyNames.getLength(); + + sal_Int32 nEndPara = nPara; + sal_Int32 nTempPara = nPara; + + if( nTempPara == -1 ) + { + nTempPara = aSel.nStartPara; + nEndPara = aSel.nEndPara; + } + + std::optional<SfxItemSet> pOldAttrSet; + std::optional<SfxItemSet> pNewAttrSet; + + std::optional<SfxItemSet> pOldParaSet; + std::optional<SfxItemSet> pNewParaSet; + + for( ; nCount; nCount--, pPropertyNames++, pValues++ ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry( *pPropertyNames ); + + if( pMap ) + { + bool bParaAttrib = (pMap->nWID >= EE_PARA_START) && ( pMap->nWID <= EE_PARA_END ); + + if( (nPara == -1) && !bParaAttrib ) + { + if( !pNewAttrSet ) + { + pOldAttrSet.emplace( pForwarder->GetAttribs( aSel ) ); + pNewAttrSet.emplace( *pOldAttrSet->GetPool(), pOldAttrSet->GetRanges() ); + } + + setPropertyValue( pMap, *pValues, GetSelection(), *pOldAttrSet, *pNewAttrSet ); + + if( pMap->nWID >= EE_ITEMS_START && pMap->nWID <= EE_ITEMS_END ) + { + const SfxPoolItem* pItem; + if( pNewAttrSet->GetItemState( pMap->nWID, true, &pItem ) == SfxItemState::SET ) + { + pOldAttrSet->Put( *pItem ); + } + } + } + else + { + if( !pNewParaSet ) + { + const SfxItemSet & rSet = pForwarder->GetParaAttribs( nTempPara ); + pOldParaSet.emplace( rSet ); + pNewParaSet.emplace( *pOldParaSet->GetPool(), pOldParaSet->GetRanges() ); + } + + setPropertyValue( pMap, *pValues, GetSelection(), *pOldParaSet, *pNewParaSet ); + + if( pMap->nWID >= EE_ITEMS_START && pMap->nWID <= EE_ITEMS_END ) + { + const SfxPoolItem* pItem; + if( pNewParaSet->GetItemState( pMap->nWID, true, &pItem ) == SfxItemState::SET ) + { + pOldParaSet->Put( *pItem ); + } + } + + } + } + } + + bool bNeedsUpdate = false; + + if( pNewParaSet ) + { + if( pNewParaSet->Count() ) + { + while( nTempPara <= nEndPara ) + { + SfxItemSet aSet( pForwarder->GetParaAttribs( nTempPara ) ); + aSet.Put( *pNewParaSet ); + pForwarder->SetParaAttribs( nTempPara, aSet ); + nTempPara++; + } + bNeedsUpdate = true; + } + + pNewParaSet.reset(); + pOldParaSet.reset(); + } + + if( pNewAttrSet ) + { + if( pNewAttrSet->Count() ) + { + pForwarder->QuickSetAttribs( *pNewAttrSet, GetSelection() ); + bNeedsUpdate = true; + } + pNewAttrSet.reset(); + pOldAttrSet.reset(); + } + + if( bNeedsUpdate ) + GetEditSource()->UpdateData(); +} + +uno::Sequence< uno::Any > SAL_CALL SvxUnoTextRangeBase::getPropertyValues( const uno::Sequence< OUString >& aPropertyNames ) +{ + return _getPropertyValues( aPropertyNames ); +} + +uno::Sequence< uno::Any > SvxUnoTextRangeBase::_getPropertyValues( const uno::Sequence< OUString >& aPropertyNames, sal_Int32 nPara ) +{ + SolarMutexGuard aGuard; + + sal_Int32 nCount = aPropertyNames.getLength(); + + + uno::Sequence< uno::Any > aValues( nCount ); + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + std::optional<SfxItemSet> oAttribs; + if( nPara != -1 ) + oAttribs.emplace(pForwarder->GetParaAttribs( nPara ).CloneAsValue()); + else + oAttribs.emplace(pForwarder->GetAttribs( GetSelection() ).CloneAsValue() ); + + oAttribs->ClearInvalidItems(); + + const OUString* pPropertyNames = aPropertyNames.getConstArray(); + uno::Any* pValues = aValues.getArray(); + + for( ; nCount; nCount--, pPropertyNames++, pValues++ ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry( *pPropertyNames ); + if( pMap ) + { + getPropertyValue( pMap, *pValues, *oAttribs ); + } + } + } + + return aValues; +} + +void SAL_CALL SvxUnoTextRangeBase::addPropertiesChangeListener( const uno::Sequence< OUString >& , const uno::Reference< beans::XPropertiesChangeListener >& ) +{ +} + +void SAL_CALL SvxUnoTextRangeBase::removePropertiesChangeListener( const uno::Reference< beans::XPropertiesChangeListener >& ) +{ +} + +void SAL_CALL SvxUnoTextRangeBase::firePropertiesChangeEvent( const uno::Sequence< OUString >& , const uno::Reference< beans::XPropertiesChangeListener >& ) +{ +} + +// beans::XPropertyState +beans::PropertyState SAL_CALL SvxUnoTextRangeBase::getPropertyState( const OUString& PropertyName ) +{ + return _getPropertyState( PropertyName ); +} + +const sal_uInt16 aSvxUnoFontDescriptorWhichMap[] = { EE_CHAR_FONTINFO, EE_CHAR_FONTHEIGHT, EE_CHAR_ITALIC, + EE_CHAR_UNDERLINE, EE_CHAR_WEIGHT, EE_CHAR_STRIKEOUT, EE_CHAR_CASEMAP, + EE_CHAR_WLM, 0 }; + +beans::PropertyState SvxUnoTextRangeBase::_getPropertyState(const SfxItemPropertyMapEntry* pMap, sal_Int32 nPara) +{ + if ( pMap ) + { + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + SfxItemState eItemState(SfxItemState::DEFAULT); + bool bItemStateSet(false); + + switch( pMap->nWID ) + { + case WID_FONTDESC: + { + const sal_uInt16* pWhichId = aSvxUnoFontDescriptorWhichMap; + while( *pWhichId ) + { + const SfxItemState eTempItemState(nPara != -1 + ? pForwarder->GetItemState( nPara, *pWhichId ) + : pForwarder->GetItemState( GetSelection(), *pWhichId )); + + switch( eTempItemState ) + { + case SfxItemState::DISABLED: + case SfxItemState::DONTCARE: + eItemState = SfxItemState::DONTCARE; + bItemStateSet = true; + break; + + case SfxItemState::DEFAULT: + if( !bItemStateSet ) + { + eItemState = SfxItemState::DEFAULT; + bItemStateSet = true; + } + break; + + case SfxItemState::SET: + if( !bItemStateSet ) + { + eItemState = SfxItemState::SET; + bItemStateSet = true; + } + break; + default: + throw beans::UnknownPropertyException(); + } + + pWhichId++; + } + } + break; + + case WID_NUMLEVEL: + case WID_NUMBERINGSTARTVALUE: + case WID_PARAISNUMBERINGRESTART: + eItemState = SfxItemState::SET; + bItemStateSet = true; + break; + + default: + if(0 != pMap->nWID) + { + if( nPara != -1 ) + eItemState = pForwarder->GetItemState( nPara, pMap->nWID ); + else + eItemState = pForwarder->GetItemState( GetSelection(), pMap->nWID ); + + bItemStateSet = true; + } + break; + } + + if(bItemStateSet) + { + switch( eItemState ) + { + case SfxItemState::DONTCARE: + case SfxItemState::DISABLED: + return beans::PropertyState_AMBIGUOUS_VALUE; + case SfxItemState::SET: + return beans::PropertyState_DIRECT_VALUE; + case SfxItemState::DEFAULT: + return beans::PropertyState_DEFAULT_VALUE; + default: break; + } + } + } + } + throw beans::UnknownPropertyException(); +} + +beans::PropertyState SvxUnoTextRangeBase::_getPropertyState(std::u16string_view PropertyName, sal_Int32 nPara /* = -1 */) +{ + SolarMutexGuard aGuard; + + return _getPropertyState( mpPropSet->getPropertyMapEntry( PropertyName ), nPara); +} + +uno::Sequence< beans::PropertyState > SAL_CALL SvxUnoTextRangeBase::getPropertyStates( const uno::Sequence< OUString >& aPropertyName ) +{ + return _getPropertyStates( aPropertyName ); +} + +uno::Sequence< beans::PropertyState > SvxUnoTextRangeBase::_getPropertyStates(const uno::Sequence< OUString >& PropertyName, sal_Int32 nPara /* = -1 */) +{ + uno::Sequence< beans::PropertyState > aRet( PropertyName.getLength() ); + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + std::optional<SfxItemSet> pSet; + if( nPara != -1 ) + { + pSet.emplace( pForwarder->GetParaAttribs( nPara ) ); + } + else + { + ESelection aSel( GetSelection() ); + CheckSelection( aSel, pForwarder ); + pSet.emplace( pForwarder->GetAttribs( aSel, EditEngineAttribs::OnlyHard ) ); + } + + beans::PropertyState* pState = aRet.getArray(); + for( const OUString& rName : PropertyName ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry( rName ); + if( !_getOnePropertyStates(&*pSet, pMap, *pState++) ) + { + throw beans::UnknownPropertyException(rName); + } + } + } + + return aRet; +} + +bool SvxUnoTextRangeBase::_getOnePropertyStates(const SfxItemSet* pSet, const SfxItemPropertyMapEntry* pMap, beans::PropertyState& rState) +{ + if(!pSet || !pMap) + return true; + SfxItemState eItemState = SfxItemState::DEFAULT; + bool bItemStateSet(false); + + bool bUnknownPropertyFound = false; + switch( pMap->nWID ) + { + case WID_FONTDESC: + { + const sal_uInt16* pWhichId = aSvxUnoFontDescriptorWhichMap; + while( *pWhichId ) + { + const SfxItemState eTempItemState(pSet->GetItemState( *pWhichId )); + + switch( eTempItemState ) + { + case SfxItemState::DISABLED: + case SfxItemState::DONTCARE: + eItemState = SfxItemState::DONTCARE; + bItemStateSet = true; + break; + + case SfxItemState::DEFAULT: + if( !bItemStateSet ) + { + eItemState = SfxItemState::DEFAULT; + bItemStateSet = true; + } + break; + + case SfxItemState::SET: + if( !bItemStateSet ) + { + eItemState = SfxItemState::SET; + bItemStateSet = true; + } + break; + default: + bUnknownPropertyFound = true; + break; + } + + pWhichId++; + } + } + break; + + case WID_NUMLEVEL: + case WID_NUMBERINGSTARTVALUE: + case WID_PARAISNUMBERINGRESTART: + eItemState = SfxItemState::SET; + bItemStateSet = true; + break; + + default: + if(0 != pMap->nWID) + { + eItemState = pSet->GetItemState( pMap->nWID, false ); + bItemStateSet = true; + } + break; + } + + if( bUnknownPropertyFound ) + return false; + + if(bItemStateSet) + { + if (pMap->nWID == EE_CHAR_COLOR) + { + // Theme & effects can be DEFAULT_VALUE, even if the same pool item has a color + // which is a DIRECT_VALUE. + const SvxColorItem* pColor = pSet->GetItem<SvxColorItem>(EE_CHAR_COLOR); + switch (pMap->nMemberId) + { + case MID_COLOR_THEME_INDEX: + if (pColor->GetThemeColor().GetThemeIndex() == -1) + { + eItemState = SfxItemState::DEFAULT; + } + break; + case MID_COLOR_LUM_MOD: + if (pColor->GetThemeColor().GetLumMod() == 10000) + { + eItemState = SfxItemState::DEFAULT; + } + break; + case MID_COLOR_LUM_OFF: + if (pColor->GetThemeColor().GetLumOff() == 0) + { + eItemState = SfxItemState::DEFAULT; + } + break; + } + } + + switch( eItemState ) + { + case SfxItemState::SET: + rState = beans::PropertyState_DIRECT_VALUE; + break; + case SfxItemState::DEFAULT: + rState = beans::PropertyState_DEFAULT_VALUE; + break; +// case SfxItemState::DONTCARE: +// case SfxItemState::DISABLED: + default: + rState = beans::PropertyState_AMBIGUOUS_VALUE; + } + } + else + { + rState = beans::PropertyState_AMBIGUOUS_VALUE; + } + return true; +} + +void SAL_CALL SvxUnoTextRangeBase::setPropertyToDefault( const OUString& PropertyName ) +{ + _setPropertyToDefault( PropertyName ); +} + +void SvxUnoTextRangeBase::_setPropertyToDefault(const OUString& PropertyName, sal_Int32 nPara /* = -1 */) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + + if( pForwarder ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry( PropertyName ); + if ( pMap ) + { + CheckSelection( maSelection, mpEditSource->GetTextForwarder() ); + _setPropertyToDefault( pForwarder, pMap, nPara ); + return; + } + } + + throw beans::UnknownPropertyException(PropertyName); +} + +void SvxUnoTextRangeBase::_setPropertyToDefault(SvxTextForwarder* pForwarder, const SfxItemPropertyMapEntry* pMap, sal_Int32 nPara ) +{ + do + { + SfxItemSet aSet(*pForwarder->GetPool()); + + if( pMap->nWID == WID_FONTDESC ) + { + SvxUnoFontDescriptor::setPropertyToDefault( aSet ); + } + else if( pMap->nWID == WID_NUMLEVEL ) + { + // #101004# Call interface method instead of unsafe cast + pForwarder->SetDepth( maSelection.nStartPara, -1 ); + return; + } + else if( pMap->nWID == WID_NUMBERINGSTARTVALUE ) + { + pForwarder->SetNumberingStartValue( maSelection.nStartPara, -1 ); + } + else if( pMap->nWID == WID_PARAISNUMBERINGRESTART ) + { + pForwarder->SetParaIsNumberingRestart( maSelection.nStartPara, false ); + } + else + { + aSet.InvalidateItem( pMap->nWID ); + } + + if(nPara != -1) + pForwarder->SetParaAttribs( nPara, aSet ); + else + pForwarder->QuickSetAttribs( aSet, GetSelection() ); + + GetEditSource()->UpdateData(); + + return; + } + while(false); +} + +uno::Any SAL_CALL SvxUnoTextRangeBase::getPropertyDefault( const OUString& aPropertyName ) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry( aPropertyName ); + if( pMap ) + { + SfxItemPool* pPool = pForwarder->GetPool(); + + switch( pMap->nWID ) + { + case WID_FONTDESC: + return SvxUnoFontDescriptor::getPropertyDefault( pPool ); + + case WID_NUMLEVEL: + { + uno::Any aAny; + return aAny; + } + + case WID_NUMBERINGSTARTVALUE: + return uno::Any( sal_Int16(-1) ); + + case WID_PARAISNUMBERINGRESTART: + return uno::Any( false ); + + default: + { + // Get Default from ItemPool + if(SfxItemPool::IsWhich(pMap->nWID)) + { + SfxItemSet aSet( *pPool, pMap->nWID, pMap->nWID ); + aSet.Put(pPool->GetDefaultItem(pMap->nWID)); + return SvxItemPropertySet::getPropertyValue(pMap, aSet, true, false ); + } + } + } + } + } + throw beans::UnknownPropertyException(aPropertyName); +} + +// beans::XMultiPropertyStates +void SAL_CALL SvxUnoTextRangeBase::setAllPropertiesToDefault() +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + + if( pForwarder ) + { + for (const SfxItemPropertyMapEntry* entry : mpPropSet->getPropertyMap().getPropertyEntries()) + { + _setPropertyToDefault( pForwarder, entry, -1 ); + } + } +} + +void SAL_CALL SvxUnoTextRangeBase::setPropertiesToDefault( const uno::Sequence< OUString >& aPropertyNames ) +{ + for( const OUString& rName : aPropertyNames ) + { + setPropertyToDefault( rName ); + } +} + +uno::Sequence< uno::Any > SAL_CALL SvxUnoTextRangeBase::getPropertyDefaults( const uno::Sequence< OUString >& aPropertyNames ) +{ + uno::Sequence< uno::Any > ret( aPropertyNames.getLength() ); + uno::Any* pDefaults = ret.getArray(); + + for( const OUString& rName : aPropertyNames ) + { + *pDefaults++ = getPropertyDefault( rName ); + } + + return ret; +} + +// internal +void SvxUnoTextRangeBase::CollapseToStart() noexcept +{ + CheckSelection( maSelection, mpEditSource.get() ); + + maSelection.nEndPara = maSelection.nStartPara; + maSelection.nEndPos = maSelection.nStartPos; +} + +void SvxUnoTextRangeBase::CollapseToEnd() noexcept +{ + CheckSelection( maSelection, mpEditSource.get() ); + + maSelection.nStartPara = maSelection.nEndPara; + maSelection.nStartPos = maSelection.nEndPos; +} + +bool SvxUnoTextRangeBase::IsCollapsed() noexcept +{ + CheckSelection( maSelection, mpEditSource.get() ); + + return ( maSelection.nStartPara == maSelection.nEndPara && + maSelection.nStartPos == maSelection.nEndPos ); +} + +bool SvxUnoTextRangeBase::GoLeft(sal_Int32 nCount, bool Expand) noexcept +{ + CheckSelection( maSelection, mpEditSource.get() ); + + // #75098# use end position, as in Writer (start is anchor, end is cursor) + sal_Int32 nNewPos = maSelection.nEndPos; + sal_Int32 nNewPar = maSelection.nEndPara; + + bool bOk = true; + SvxTextForwarder* pForwarder = nullptr; + while ( nCount > nNewPos && bOk ) + { + if ( nNewPar == 0 ) + bOk = false; + else + { + if ( !pForwarder ) + pForwarder = mpEditSource->GetTextForwarder(); // first here, it is necessary... + + --nNewPar; + nCount -= nNewPos + 1; + nNewPos = pForwarder->GetTextLen( nNewPar ); + } + } + + if ( bOk ) + { + nNewPos = nNewPos - nCount; + maSelection.nStartPara = nNewPar; + maSelection.nStartPos = nNewPos; + } + + if (!Expand) + CollapseToStart(); + + return bOk; +} + +bool SvxUnoTextRangeBase::GoRight(sal_Int32 nCount, bool Expand) noexcept +{ + if (!mpEditSource) + return false; + SvxTextForwarder* pForwarder = mpEditSource->GetTextForwarder(); + if( !pForwarder ) + return false; + + CheckSelection( maSelection, pForwarder ); + + sal_Int32 nNewPos = maSelection.nEndPos + nCount; + sal_Int32 nNewPar = maSelection.nEndPara; + + bool bOk = true; + sal_Int32 nParCount = pForwarder->GetParagraphCount(); + sal_Int32 nThisLen = pForwarder->GetTextLen( nNewPar ); + while ( nNewPos > nThisLen && bOk ) + { + if ( nNewPar + 1 >= nParCount ) + bOk = false; + else + { + nNewPos -= nThisLen+1; + ++nNewPar; + nThisLen = pForwarder->GetTextLen( nNewPar ); + } + } + + if (bOk) + { + maSelection.nEndPara = nNewPar; + maSelection.nEndPos = nNewPos; + } + + if (!Expand) + CollapseToEnd(); + + return bOk; +} + +void SvxUnoTextRangeBase::GotoStart(bool Expand) noexcept +{ + maSelection.nStartPara = 0; + maSelection.nStartPos = 0; + + if (!Expand) + CollapseToStart(); +} + +void SvxUnoTextRangeBase::GotoEnd(bool Expand) noexcept +{ + CheckSelection( maSelection, mpEditSource.get() ); + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( !pForwarder ) + return; + + sal_Int32 nPar = pForwarder->GetParagraphCount(); + if (nPar) + --nPar; + + maSelection.nEndPara = nPar; + maSelection.nEndPos = pForwarder->GetTextLen( nPar ); + + if (!Expand) + CollapseToEnd(); +} + +// lang::XServiceInfo +sal_Bool SAL_CALL SvxUnoTextRangeBase::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextRangeBase::getSupportedServiceNames() +{ + return getSupportedServiceNames_Static(); +} + +uno::Sequence< OUString > SvxUnoTextRangeBase::getSupportedServiceNames_Static() +{ + return { "com.sun.star.style.CharacterProperties", + "com.sun.star.style.CharacterPropertiesComplex", + "com.sun.star.style.CharacterPropertiesAsian" }; +} + +// XTextRangeCompare +sal_Int16 SAL_CALL SvxUnoTextRangeBase::compareRegionStarts( const uno::Reference< text::XTextRange >& xR1, const uno::Reference< text::XTextRange >& xR2 ) +{ + SvxUnoTextRangeBase* pR1 = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( xR1 ); + SvxUnoTextRangeBase* pR2 = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( xR2 ); + + if( (pR1 == nullptr) || (pR2 == nullptr) ) + throw lang::IllegalArgumentException(); + + const ESelection& r1 = pR1->maSelection; + const ESelection& r2 = pR2->maSelection; + + if( r1.nStartPara == r2.nStartPara ) + { + if( r1.nStartPos == r2.nStartPos ) + return 0; + else + return r1.nStartPos < r2.nStartPos ? 1 : -1; + } + else + { + return r1.nStartPara < r2.nStartPara ? 1 : -1; + } +} + +sal_Int16 SAL_CALL SvxUnoTextRangeBase::compareRegionEnds( const uno::Reference< text::XTextRange >& xR1, const uno::Reference< text::XTextRange >& xR2 ) +{ + SvxUnoTextRangeBase* pR1 = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( xR1 ); + SvxUnoTextRangeBase* pR2 = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( xR2 ); + + if( (pR1 == nullptr) || (pR2 == nullptr) ) + throw lang::IllegalArgumentException(); + + const ESelection& r1 = pR1->maSelection; + const ESelection& r2 = pR2->maSelection; + + if( r1.nEndPara == r2.nEndPara ) + { + if( r1.nEndPos == r2.nEndPos ) + return 0; + else + return r1.nEndPos < r2.nEndPos ? 1 : -1; + } + else + { + return r1.nEndPara < r2.nEndPara ? 1 : -1; + } +} + +SvxUnoTextRange::SvxUnoTextRange(const SvxUnoTextBase& rParent, bool bPortion /* = false */) +:SvxUnoTextRangeBase( rParent.GetEditSource(), bPortion ? ImplGetSvxTextPortionSvxPropertySet() : rParent.getPropertySet() ), + mbPortion( bPortion ) +{ + xParentText = static_cast<text::XText*>(const_cast<SvxUnoTextBase *>(&rParent)); +} + +SvxUnoTextRange::~SvxUnoTextRange() noexcept +{ +} + +uno::Any SAL_CALL SvxUnoTextRange::queryAggregation( const uno::Type & rType ) +{ + QUERYINT( text::XTextRange ); + else if( rType == cppu::UnoType<beans::XMultiPropertyStates>::get()) + return uno::Any(uno::Reference< beans::XMultiPropertyStates >(this)); + else if( rType == cppu::UnoType<beans::XPropertySet>::get()) + return uno::Any(uno::Reference< beans::XPropertySet >(this)); + else QUERYINT( beans::XPropertyState ); + else QUERYINT( text::XTextRangeCompare ); + else if( rType == cppu::UnoType<beans::XMultiPropertySet>::get()) + return uno::Any(uno::Reference< beans::XMultiPropertySet >(this)); + else QUERYINT( lang::XServiceInfo ); + else QUERYINT( lang::XTypeProvider ); + else QUERYINT( lang::XUnoTunnel ); + else + return OWeakAggObject::queryAggregation( rType ); +} + +uno::Any SAL_CALL SvxUnoTextRange::queryInterface( const uno::Type & rType ) +{ + return OWeakAggObject::queryInterface(rType); +} + +void SAL_CALL SvxUnoTextRange::acquire() + noexcept +{ + OWeakAggObject::acquire(); +} + +void SAL_CALL SvxUnoTextRange::release() + noexcept +{ + OWeakAggObject::release(); +} + +// XTypeProvider + +uno::Sequence< uno::Type > SAL_CALL SvxUnoTextRange::getTypes() +{ + static const uno::Sequence< uno::Type > TYPES { + cppu::UnoType<text::XTextRange>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertyStates>::get(), + cppu::UnoType<beans::XPropertyState>::get(), + cppu::UnoType<lang::XServiceInfo>::get(), + cppu::UnoType<lang::XTypeProvider>::get(), + cppu::UnoType<lang::XUnoTunnel>::get(), + cppu::UnoType<text::XTextRangeCompare>::get() }; + return TYPES; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoTextRange::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XTextRange +uno::Reference< text::XText > SAL_CALL SvxUnoTextRange::getText() +{ + return xParentText; +} + +// lang::XServiceInfo +OUString SAL_CALL SvxUnoTextRange::getImplementationName() +{ + return "SvxUnoTextRange"; +} + + + + +SvxUnoTextBase::SvxUnoTextBase(const SvxItemPropertySet* _pSet) + : SvxUnoTextRangeBase(_pSet) +{ +} + +SvxUnoTextBase::SvxUnoTextBase(const SvxEditSource* pSource, const SvxItemPropertySet* _pSet, uno::Reference < text::XText > const & xParent) + : SvxUnoTextRangeBase(pSource, _pSet) +{ + xParentText = xParent; + ESelection aSelection; + ::GetSelection( aSelection, GetEditSource()->GetTextForwarder() ); + SetSelection( aSelection ); +} + +SvxUnoTextBase::SvxUnoTextBase(const SvxUnoTextBase& rText) +: SvxUnoTextRangeBase( rText ) +, text::XTextAppend() +, text::XTextCopy() +, container::XEnumerationAccess() +, text::XTextRangeMover() +, lang::XTypeProvider() +{ + xParentText = rText.xParentText; +} + +SvxUnoTextBase::~SvxUnoTextBase() noexcept +{ +} + +// XInterface +uno::Any SAL_CALL SvxUnoTextBase::queryAggregation( const uno::Type & rType ) +{ + QUERYINT( text::XText ); + QUERYINT( text::XSimpleText ); + if( rType == cppu::UnoType<text::XTextRange>::get()) + return uno::Any(uno::Reference< text::XTextRange >(static_cast<text::XText*>(this))); + QUERYINT(container::XEnumerationAccess ); + QUERYINT( container::XElementAccess ); + QUERYINT( beans::XMultiPropertyStates ); + QUERYINT( beans::XPropertySet ); + QUERYINT( beans::XMultiPropertySet ); + QUERYINT( beans::XPropertyState ); + QUERYINT( text::XTextRangeCompare ); + QUERYINT( lang::XServiceInfo ); + QUERYINT( text::XTextRangeMover ); + QUERYINT( text::XTextCopy ); + QUERYINT( text::XTextAppend ); + QUERYINT( text::XParagraphAppend ); + QUERYINT( text::XTextPortionAppend ); + QUERYINT( lang::XTypeProvider ); + QUERYINT( lang::XUnoTunnel ); + + return uno::Any(); +} + +// XTypeProvider + +uno::Sequence< uno::Type > SAL_CALL SvxUnoTextBase::getTypes() +{ + static const uno::Sequence< uno::Type > TYPES { + cppu::UnoType<text::XText>::get(), + cppu::UnoType<container::XEnumerationAccess>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertyStates>::get(), + cppu::UnoType<beans::XPropertyState>::get(), + cppu::UnoType<text::XTextRangeMover>::get(), + cppu::UnoType<text::XTextAppend>::get(), + cppu::UnoType<text::XTextCopy>::get(), + cppu::UnoType<text::XParagraphAppend>::get(), + cppu::UnoType<text::XTextPortionAppend>::get(), + cppu::UnoType<lang::XServiceInfo>::get(), + cppu::UnoType<lang::XTypeProvider>::get(), + cppu::UnoType<lang::XUnoTunnel>::get(), + cppu::UnoType<text::XTextRangeCompare>::get() }; + return TYPES; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoTextBase::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +uno::Reference< text::XTextCursor > SvxUnoTextBase::createTextCursorBySelection( const ESelection& rSel ) +{ + rtl::Reference<SvxUnoTextCursor> pCursor = new SvxUnoTextCursor( *this ); + pCursor->SetSelection( rSel ); + return pCursor; +} + +// XSimpleText + +uno::Reference< text::XTextCursor > SAL_CALL SvxUnoTextBase::createTextCursor() +{ + SolarMutexGuard aGuard; + return new SvxUnoTextCursor( *this ); +} + +uno::Reference< text::XTextCursor > SAL_CALL SvxUnoTextBase::createTextCursorByRange( const uno::Reference< text::XTextRange >& aTextPosition ) +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextCursor > xCursor; + + if( aTextPosition.is() ) + { + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( aTextPosition ); + if(pRange) + xCursor = createTextCursorBySelection( pRange->GetSelection() ); + } + + return xCursor; +} + +void SAL_CALL SvxUnoTextBase::insertString( const uno::Reference< text::XTextRange >& xRange, const OUString& aString, sal_Bool bAbsorb ) +{ + SolarMutexGuard aGuard; + + if( !xRange.is() ) + return; + + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRange>( xRange ); + if(!pRange) + return; + + // setString on SvxUnoTextRangeBase instead of itself QuickInsertText + // and UpdateData, so that the selection will be adjusted to + // SvxUnoTextRangeBase. Actually all cursor objects of this Text must + // to be statement to be adapted! + + if (!bAbsorb) // do not replace -> append on tail + pRange->CollapseToEnd(); + + pRange->setString( aString ); + + pRange->CollapseToEnd(); + + if (GetEditSource()) + { + ESelection aSelection; + ::GetSelection( aSelection, GetEditSource()->GetTextForwarder() ); + SetSelection( aSelection ); + } +} + +void SAL_CALL SvxUnoTextBase::insertControlCharacter( const uno::Reference< text::XTextRange >& xRange, sal_Int16 nControlCharacter, sal_Bool bAbsorb ) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = GetEditSource() ? GetEditSource()->GetTextForwarder() : nullptr; + + if( !pForwarder ) + return; + + ESelection aSelection; + ::GetSelection( aSelection, pForwarder ); + SetSelection( aSelection ); + + switch( nControlCharacter ) + { + case text::ControlCharacter::PARAGRAPH_BREAK: + { + insertString( xRange, "\x0D", bAbsorb ); + + return; + } + case text::ControlCharacter::LINE_BREAK: + { + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRange>( xRange ); + if(pRange) + { + ESelection aRange = pRange->GetSelection(); + + if( bAbsorb ) + { + pForwarder->QuickInsertText( "", aRange ); + + aRange.nEndPos = aRange.nStartPos; + aRange.nEndPara = aRange.nStartPara; + } + else + { + aRange.nStartPara = aRange.nEndPara; + aRange.nStartPos = aRange.nEndPos; + } + + pForwarder->QuickInsertLineBreak( aRange ); + GetEditSource()->UpdateData(); + + aRange.nEndPos += 1; + if( !bAbsorb ) + aRange.nStartPos += 1; + + pRange->SetSelection( aRange ); + } + return; + } + case text::ControlCharacter::APPEND_PARAGRAPH: + { + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRange>( xRange ); + if(pRange) + { + ESelection aRange = pRange->GetSelection(); +// ESelection aOldSelection = aRange; + + aRange.nStartPos = pForwarder->GetTextLen( aRange.nStartPara ); + + aRange.nEndPara = aRange.nStartPara; + aRange.nEndPos = aRange.nStartPos; + + pRange->SetSelection( aRange ); + pRange->setString( "\x0D" ); + + aRange.nStartPos = 0; + aRange.nStartPara += 1; + aRange.nEndPos = 0; + aRange.nEndPara += 1; + + pRange->SetSelection( aRange ); + + return; + } + [[fallthrough]]; + } + default: + throw lang::IllegalArgumentException(); + } +} + +// XText +void SAL_CALL SvxUnoTextBase::insertTextContent( const uno::Reference< text::XTextRange >& xRange, const uno::Reference< text::XTextContent >& xContent, sal_Bool bAbsorb ) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = GetEditSource() ? GetEditSource()->GetTextForwarder() : nullptr; + if (!pForwarder) + return; + + uno::Reference<beans::XPropertySet> xPropSet(xRange, uno::UNO_QUERY); + if (!xPropSet.is()) + throw lang::IllegalArgumentException(); + + uno::Any aAny = xPropSet->getPropertyValue(UNO_TR_PROP_SELECTION); + text::TextRangeSelection aSel = aAny.get<text::TextRangeSelection>(); + if (!bAbsorb) + aSel.Start = aSel.End; + + std::unique_ptr<SvxFieldData> pFieldData(SvxFieldData::Create(xContent)); + if (!pFieldData) + throw lang::IllegalArgumentException(); + + SvxFieldItem aField( *pFieldData, EE_FEATURE_FIELD ); + pForwarder->QuickInsertField(aField, toESelection(aSel)); + GetEditSource()->UpdateData(); + + uno::Reference<beans::XPropertySet> xPropSetContent(xContent, uno::UNO_QUERY); + if (!xContent.is()) + throw lang::IllegalArgumentException(); + + xPropSetContent->setPropertyValue(UNO_TC_PROP_ANCHOR, uno::Any(xRange)); + + aSel.End.PositionInParagraph += 1; + aSel.Start.PositionInParagraph = aSel.End.PositionInParagraph; + xPropSet->setPropertyValue(UNO_TR_PROP_SELECTION, uno::Any(aSel)); +} + +void SAL_CALL SvxUnoTextBase::removeTextContent( const uno::Reference< text::XTextContent >& ) +{ +} + +// XTextRange + +uno::Reference< text::XText > SAL_CALL SvxUnoTextBase::getText() +{ + SolarMutexGuard aGuard; + + if (GetEditSource()) + { + ESelection aSelection; + ::GetSelection( aSelection, GetEditSource()->GetTextForwarder() ); + SetSelection( aSelection ); + } + + return static_cast<text::XText*>(this); +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::getStart() +{ + return SvxUnoTextRangeBase::getStart(); +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::getEnd() +{ + return SvxUnoTextRangeBase::getEnd(); +} + +OUString SAL_CALL SvxUnoTextBase::getString() +{ + return SvxUnoTextRangeBase::getString(); +} + +void SAL_CALL SvxUnoTextBase::setString( const OUString& aString ) +{ + SvxUnoTextRangeBase::setString(aString); +} + + +// XEnumerationAccess +uno::Reference< container::XEnumeration > SAL_CALL SvxUnoTextBase::createEnumeration() +{ + SolarMutexGuard aGuard; + + if (!GetEditSource()) + return uno::Reference< container::XEnumeration >(); + + if( maSelection == ESelection(0,0,0,0) || maSelection == ESelection(EE_PARA_MAX_COUNT,0,0,0) ) + { + ESelection aSelection; + ::GetSelection( aSelection, GetEditSource()->GetTextForwarder() ); + return new SvxUnoTextContentEnumeration(*this, aSelection); + } + else + { + return new SvxUnoTextContentEnumeration(*this, maSelection); + } +} + +// XElementAccess ( container::XEnumerationAccess ) +uno::Type SAL_CALL SvxUnoTextBase::getElementType( ) +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SAL_CALL SvxUnoTextBase::hasElements( ) +{ + SolarMutexGuard aGuard; + + if(GetEditSource()) + { + SvxTextForwarder* pForwarder = GetEditSource()->GetTextForwarder(); + if(pForwarder) + return pForwarder->GetParagraphCount() != 0; + } + + return false; +} + +// text::XTextRangeMover +void SAL_CALL SvxUnoTextBase::moveTextRange( const uno::Reference< text::XTextRange >&, sal_Int16 ) +{ +} + +/// @throws lang::IllegalArgumentException +/// @throws beans::UnknownPropertyException +/// @throws uno::RuntimeException +static void SvxPropertyValuesToItemSet( + SfxItemSet &rItemSet, + const uno::Sequence< beans::PropertyValue >& rPropertyValues, + const SfxItemPropertySet *pPropSet, + SvxTextForwarder *pForwarder /*needed for WID_NUMLEVEL*/, + sal_Int32 nPara /*needed for WID_NUMLEVEL*/) +{ + for (const beans::PropertyValue& rProp : rPropertyValues) + { + const SfxItemPropertyMapEntry *pEntry = pPropSet->getPropertyMap().getByName( rProp.Name ); + if (!pEntry) + throw beans::UnknownPropertyException( "Unknown property: " + rProp.Name, static_cast < cppu::OWeakObject * > ( nullptr ) ); + // Note: there is no need to take special care of the properties + // TextField (EE_FEATURE_FIELD) and + // TextPortionType (WID_PORTIONTYPE) + // since they are read-only and thus are already taken care of below. + + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + // should be PropertyVetoException which is not yet defined for the new import API's functions + throw uno::RuntimeException("Property is read-only: " + rProp.Name, static_cast < cppu::OWeakObject * > ( nullptr ) ); + //throw PropertyVetoException ("Property is read-only: " + rProp.Name, static_cast < cppu::OWeakObject * > ( 0 ) ); + + if (pEntry->nWID == WID_FONTDESC) + { + awt::FontDescriptor aDesc; + if (rProp.Value >>= aDesc) + SvxUnoFontDescriptor::FillItemSet( aDesc, rItemSet ); + } + else if (pEntry->nWID == WID_NUMLEVEL) + { + if (pForwarder) + { + sal_Int16 nLevel = -1; + rProp.Value >>= nLevel; + + // #101004# Call interface method instead of unsafe cast + if (!pForwarder->SetDepth( nPara, nLevel )) + throw lang::IllegalArgumentException(); + } + } + else if (pEntry->nWID == WID_NUMBERINGSTARTVALUE ) + { + if( pForwarder ) + { + sal_Int16 nStartValue = -1; + if( !(rProp.Value >>= nStartValue) ) + throw lang::IllegalArgumentException(); + + pForwarder->SetNumberingStartValue( nPara, nStartValue ); + } + } + else if (pEntry->nWID == WID_PARAISNUMBERINGRESTART ) + { + if( pForwarder ) + { + bool bParaIsNumberingRestart = false; + if( !(rProp.Value >>= bParaIsNumberingRestart) ) + throw lang::IllegalArgumentException(); + + pForwarder->SetParaIsNumberingRestart( nPara, bParaIsNumberingRestart ); + } + } + else + pPropSet->setPropertyValue( rProp.Name, rProp.Value, rItemSet ); + } +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::finishParagraphInsert( + const uno::Sequence< beans::PropertyValue >& /*rCharAndParaProps*/, + const uno::Reference< text::XTextRange >& /*rTextRange*/ ) +{ + uno::Reference< text::XTextRange > xRet; + return xRet; +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::finishParagraph( + const uno::Sequence< beans::PropertyValue >& rCharAndParaProps ) +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextRange > xRet; + SvxEditSource *pEditSource = GetEditSource(); + SvxTextForwarder *pTextForwarder = pEditSource ? pEditSource->GetTextForwarder() : nullptr; + if (pTextForwarder) + { + sal_Int32 nParaCount = pTextForwarder->GetParagraphCount(); + DBG_ASSERT( nParaCount > 0, "paragraph count is 0 or negative" ); + pTextForwarder->AppendParagraph(); + + // set properties for the previously last paragraph + sal_Int32 nPara = nParaCount - 1; + ESelection aSel( nPara, 0, nPara, 0 ); + SfxItemSet aItemSet( *pTextForwarder->GetEmptyItemSetPtr() ); + SvxPropertyValuesToItemSet( aItemSet, rCharAndParaProps, + ImplGetSvxUnoOutlinerTextCursorSfxPropertySet(), pTextForwarder, nPara ); + pTextForwarder->QuickSetAttribs( aItemSet, aSel ); + pEditSource->UpdateData(); + rtl::Reference<SvxUnoTextRange> pRange = new SvxUnoTextRange( *this ); + xRet = pRange; + pRange->SetSelection( aSel ); + } + return xRet; +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::insertTextPortion( + const OUString& /*rText*/, + const uno::Sequence< beans::PropertyValue >& /*rCharAndParaProps*/, + const uno::Reference< text::XTextRange>& /*rTextRange*/ ) +{ + uno::Reference< text::XTextRange > xRet; + return xRet; +} + +// css::text::XTextPortionAppend (new import API) +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::appendTextPortion( + const OUString& rText, + const uno::Sequence< beans::PropertyValue >& rCharAndParaProps ) +{ + SolarMutexGuard aGuard; + + SvxEditSource *pEditSource = GetEditSource(); + SvxTextForwarder *pTextForwarder = pEditSource ? pEditSource->GetTextForwarder() : nullptr; + uno::Reference< text::XTextRange > xRet; + if (pTextForwarder) + { + sal_Int32 nParaCount = pTextForwarder->GetParagraphCount(); + DBG_ASSERT( nParaCount > 0, "paragraph count is 0 or negative" ); + sal_Int32 nPara = nParaCount - 1; + SfxItemSet aSet( pTextForwarder->GetParaAttribs( nPara ) ); + sal_Int32 nStart = pTextForwarder->AppendTextPortion( nPara, rText, aSet ); + pEditSource->UpdateData(); + sal_Int32 nEnd = pTextForwarder->GetTextLen( nPara ); + + // set properties for the new text portion + ESelection aSel( nPara, nStart, nPara, nEnd ); + pTextForwarder->RemoveAttribs( aSel ); + pEditSource->UpdateData(); + + SfxItemSet aItemSet( *pTextForwarder->GetEmptyItemSetPtr() ); + SvxPropertyValuesToItemSet( aItemSet, rCharAndParaProps, + ImplGetSvxTextPortionSfxPropertySet(), pTextForwarder, nPara ); + pTextForwarder->QuickSetAttribs( aItemSet, aSel ); + rtl::Reference<SvxUnoTextRange> pRange = new SvxUnoTextRange( *this ); + xRet = pRange; + pRange->SetSelection( aSel ); + for( const beans::PropertyValue& rProp : rCharAndParaProps ) + pRange->setPropertyValue( rProp.Name, rProp.Value ); + } + return xRet; +} + +void SvxUnoTextBase::copyText( + const uno::Reference< text::XTextCopy >& xSource ) +{ + SolarMutexGuard aGuard; + uno::Reference< lang::XUnoTunnel > xUT( xSource, uno::UNO_QUERY ); + SvxEditSource *pEditSource = GetEditSource(); + SvxTextForwarder *pTextForwarder = pEditSource ? pEditSource->GetTextForwarder() : nullptr; + if( !pTextForwarder ) + return; + if (auto pSource = comphelper::getFromUnoTunnel<SvxUnoTextBase>(xUT)) + { + SvxEditSource *pSourceEditSource = pSource->GetEditSource(); + SvxTextForwarder *pSourceTextForwarder = pSourceEditSource ? pSourceEditSource->GetTextForwarder() : nullptr; + if( pSourceTextForwarder ) + { + pTextForwarder->CopyText( *pSourceTextForwarder ); + pEditSource->UpdateData(); + } + } + else + { + uno::Reference< text::XText > xSourceText( xSource, uno::UNO_QUERY ); + if( xSourceText.is() ) + { + setString( xSourceText->getString() ); + } + } +} + +// lang::XServiceInfo +OUString SAL_CALL SvxUnoTextBase::getImplementationName() +{ + return "SvxUnoTextBase"; +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextBase::getSupportedServiceNames( ) +{ + return getSupportedServiceNames_Static(); +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextBase::getSupportedServiceNames_Static( ) +{ + return comphelper::concatSequences( + SvxUnoTextRangeBase::getSupportedServiceNames_Static(), + std::initializer_list<std::u16string_view>{ u"com.sun.star.text.Text" }); +} + +const uno::Sequence< sal_Int8 > & SvxUnoTextBase::getUnoTunnelId() noexcept +{ + static const comphelper::UnoIdInit theSvxUnoTextBaseUnoTunnelId; + return theSvxUnoTextBaseUnoTunnelId.getSeq(); +} + +sal_Int64 SAL_CALL SvxUnoTextBase::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + return comphelper::getSomethingImpl( + rId, this, comphelper::FallbackToGetSomethingOf<SvxUnoTextRangeBase>{}); +} + +SvxUnoText::SvxUnoText( const SvxItemPropertySet* _pSet ) noexcept +: SvxUnoTextBase( _pSet ) +{ +} + +SvxUnoText::SvxUnoText( const SvxEditSource* pSource, const SvxItemPropertySet* _pSet, uno::Reference < text::XText > const & xParent ) noexcept +: SvxUnoTextBase( pSource, _pSet, xParent ) +{ +} + +SvxUnoText::SvxUnoText( const SvxUnoText& rText ) noexcept +: SvxUnoTextBase( rText ) +, cppu::OWeakAggObject() +{ +} + +SvxUnoText::~SvxUnoText() noexcept +{ +} + +// uno::XInterface +uno::Any SAL_CALL SvxUnoText::queryAggregation( const uno::Type & rType ) +{ + uno::Any aAny( SvxUnoTextBase::queryAggregation( rType ) ); + if( !aAny.hasValue() ) + aAny = OWeakAggObject::queryAggregation( rType ); + + return aAny; +} + +uno::Any SAL_CALL SvxUnoText::queryInterface( const uno::Type & rType ) +{ + return OWeakAggObject::queryInterface( rType ); +} + +void SAL_CALL SvxUnoText::acquire() noexcept +{ + OWeakAggObject::acquire(); +} + +void SAL_CALL SvxUnoText::release() noexcept +{ + OWeakAggObject::release(); +} + +// lang::XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SvxUnoText::getTypes( ) +{ + return SvxUnoTextBase::getTypes(); +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoText::getImplementationId( ) +{ + return css::uno::Sequence<sal_Int8>(); +} + +const uno::Sequence< sal_Int8 > & SvxUnoText::getUnoTunnelId() noexcept +{ + static const comphelper::UnoIdInit theSvxUnoTextUnoTunnelId; + return theSvxUnoTextUnoTunnelId.getSeq(); +} + +sal_Int64 SAL_CALL SvxUnoText::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + return comphelper::getSomethingImpl(rId, this, + comphelper::FallbackToGetSomethingOf<SvxUnoTextBase>{}); +} + + +SvxDummyTextSource::~SvxDummyTextSource() +{ +}; + +std::unique_ptr<SvxEditSource> SvxDummyTextSource::Clone() const +{ + return std::unique_ptr<SvxEditSource>(new SvxDummyTextSource); +} + +SvxTextForwarder* SvxDummyTextSource::GetTextForwarder() +{ + return this; +} + +void SvxDummyTextSource::UpdateData() +{ +} + +sal_Int32 SvxDummyTextSource::GetParagraphCount() const +{ + return 0; +} + +sal_Int32 SvxDummyTextSource::GetTextLen( sal_Int32 ) const +{ + return 0; +} + +OUString SvxDummyTextSource::GetText( const ESelection& ) const +{ + return OUString(); +} + +SfxItemSet SvxDummyTextSource::GetAttribs( const ESelection&, EditEngineAttribs ) const +{ + // Very dangerous: The former implementation used a SfxItemPool created on the + // fly which of course was deleted again ASAP. Thus, the returned SfxItemSet was using + // a deleted Pool by design. + return SfxItemSet(EditEngine::GetGlobalItemPool()); +} + +SfxItemSet SvxDummyTextSource::GetParaAttribs( sal_Int32 ) const +{ + return GetAttribs(ESelection()); +} + +void SvxDummyTextSource::SetParaAttribs( sal_Int32, const SfxItemSet& ) +{ +} + +void SvxDummyTextSource::RemoveAttribs( const ESelection& ) +{ +} + +void SvxDummyTextSource::GetPortions( sal_Int32, std::vector<sal_Int32>& ) const +{ +} + +SfxItemState SvxDummyTextSource::GetItemState( const ESelection&, sal_uInt16 ) const +{ + return SfxItemState::UNKNOWN; +} + +SfxItemState SvxDummyTextSource::GetItemState( sal_Int32, sal_uInt16 ) const +{ + return SfxItemState::UNKNOWN; +} + +SfxItemPool* SvxDummyTextSource::GetPool() const +{ + return nullptr; +} + +void SvxDummyTextSource::QuickInsertText( const OUString&, const ESelection& ) +{ +} + +void SvxDummyTextSource::QuickInsertField( const SvxFieldItem&, const ESelection& ) +{ +} + +void SvxDummyTextSource::QuickSetAttribs( const SfxItemSet&, const ESelection& ) +{ +} + +void SvxDummyTextSource::QuickInsertLineBreak( const ESelection& ) +{ +}; + +OUString SvxDummyTextSource::CalcFieldValue( const SvxFieldItem&, sal_Int32, sal_Int32, std::optional<Color>&, std::optional<Color>& ) +{ + return OUString(); +} + +void SvxDummyTextSource::FieldClicked( const SvxFieldItem& ) +{ +} + +bool SvxDummyTextSource::IsValid() const +{ + return false; +} + +LanguageType SvxDummyTextSource::GetLanguage( sal_Int32, sal_Int32 ) const +{ + return LANGUAGE_DONTKNOW; +} + +sal_Int32 SvxDummyTextSource::GetFieldCount( sal_Int32 ) const +{ + return 0; +} + +EFieldInfo SvxDummyTextSource::GetFieldInfo( sal_Int32, sal_uInt16 ) const +{ + return EFieldInfo(); +} + +EBulletInfo SvxDummyTextSource::GetBulletInfo( sal_Int32 ) const +{ + return EBulletInfo(); +} + +tools::Rectangle SvxDummyTextSource::GetCharBounds( sal_Int32, sal_Int32 ) const +{ + return tools::Rectangle(); +} + +tools::Rectangle SvxDummyTextSource::GetParaBounds( sal_Int32 ) const +{ + return tools::Rectangle(); +} + +MapMode SvxDummyTextSource::GetMapMode() const +{ + return MapMode(); +} + +OutputDevice* SvxDummyTextSource::GetRefDevice() const +{ + return nullptr; +} + +bool SvxDummyTextSource::GetIndexAtPoint( const Point&, sal_Int32&, sal_Int32& ) const +{ + return false; +} + +bool SvxDummyTextSource::GetWordIndices( sal_Int32, sal_Int32, sal_Int32&, sal_Int32& ) const +{ + return false; +} + +bool SvxDummyTextSource::GetAttributeRun( sal_Int32&, sal_Int32&, sal_Int32, sal_Int32, bool ) const +{ + return false; +} + +sal_Int32 SvxDummyTextSource::GetLineCount( sal_Int32 ) const +{ + return 0; +} + +sal_Int32 SvxDummyTextSource::GetLineLen( sal_Int32, sal_Int32 ) const +{ + return 0; +} + +void SvxDummyTextSource::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 /*nParagraph*/, sal_Int32 /*nLine*/ ) const +{ + rStart = rEnd = 0; +} + +sal_Int32 SvxDummyTextSource::GetLineNumberAtIndex( sal_Int32 /*nPara*/, sal_Int32 /*nIndex*/ ) const +{ + return 0; +} + +bool SvxDummyTextSource::QuickFormatDoc( bool ) +{ + return false; +} + +sal_Int16 SvxDummyTextSource::GetDepth( sal_Int32 ) const +{ + return -1; +} + +bool SvxDummyTextSource::SetDepth( sal_Int32, sal_Int16 nNewDepth ) +{ + return nNewDepth == 0; +} + +bool SvxDummyTextSource::Delete( const ESelection& ) +{ + return false; +} + +bool SvxDummyTextSource::InsertText( const OUString&, const ESelection& ) +{ + return false; +} + +const SfxItemSet * SvxDummyTextSource::GetEmptyItemSetPtr() +{ + return nullptr; +} + +void SvxDummyTextSource::AppendParagraph() +{ +} + +sal_Int32 SvxDummyTextSource::AppendTextPortion( sal_Int32, const OUString &, const SfxItemSet & ) +{ + return 0; +} + +void SvxDummyTextSource::CopyText(const SvxTextForwarder& ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unotext2.cxx b/editeng/source/uno/unotext2.cxx new file mode 100644 index 000000000..ca2a45bae --- /dev/null +++ b/editeng/source/uno/unotext2.cxx @@ -0,0 +1,623 @@ +/* -*- 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 <initializer_list> +#include <string_view> + +#include <o3tl/safeint.hxx> +#include <vcl/svapp.hxx> + +#include <editeng/unotext.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/supportsservice.hxx> + +using namespace ::cppu; +using namespace ::com::sun::star; + +#define QUERYINT( xint ) \ + if( rType == cppu::UnoType<xint>::get() ) \ + return uno::Any(uno::Reference< xint >(this)) + + +// SvxUnoTextContentEnumeration + + +SvxUnoTextContentEnumeration::SvxUnoTextContentEnumeration( const SvxUnoTextBase& rText, const ESelection& rSel ) noexcept +{ + mxParentText = const_cast<SvxUnoTextBase*>(&rText); + if( rText.GetEditSource() ) + mpEditSource = rText.GetEditSource()->Clone(); + mnNextParagraph = 0; + + const SvxTextForwarder* pTextForwarder = rText.GetEditSource()->GetTextForwarder(); + const sal_Int32 maxParaIndex = std::min( rSel.nEndPara + 1, pTextForwarder->GetParagraphCount() ); + + for( sal_Int32 currentPara = rSel.nStartPara; currentPara < maxParaIndex; currentPara++ ) + { + const SvxUnoTextRangeBaseVec& rRanges( mpEditSource->getRanges() ); + rtl::Reference<SvxUnoTextContent> pContent; + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = pTextForwarder->GetTextLen( currentPara ); + if( currentPara == rSel.nStartPara ) + nStartPos = std::max(nStartPos, rSel.nStartPos); + if( currentPara == rSel.nEndPara ) + nEndPos = std::min(nEndPos, rSel.nEndPos); + ESelection aCurrentParaSel( currentPara, nStartPos, currentPara, nEndPos ); + for (auto const& elemRange : rRanges) + { + if (pContent) + break; + SvxUnoTextContent* pIterContent = dynamic_cast< SvxUnoTextContent* >( elemRange ); + if( pIterContent && (pIterContent->mnParagraph == currentPara) ) + { + ESelection aIterSel = pIterContent->GetSelection(); + if( aIterSel == aCurrentParaSel ) + { + pContent = pIterContent; + maContents.emplace_back(pContent ); + } + } + } + if( pContent == nullptr ) + { + pContent = new SvxUnoTextContent( rText, currentPara ); + pContent->SetSelection( aCurrentParaSel ); + maContents.emplace_back(pContent ); + } + } +} + +SvxUnoTextContentEnumeration::~SvxUnoTextContentEnumeration() noexcept +{ +} + +// container::XEnumeration +sal_Bool SAL_CALL SvxUnoTextContentEnumeration::hasMoreElements() +{ + SolarMutexGuard aGuard; + if( mpEditSource && !maContents.empty() ) + return o3tl::make_unsigned(mnNextParagraph) < maContents.size(); + else + return false; +} + +uno::Any SvxUnoTextContentEnumeration::nextElement() +{ + SolarMutexGuard aGuard; + + if(!hasMoreElements()) + throw container::NoSuchElementException(); + + uno::Reference< text::XTextContent > xRef( maContents.at(mnNextParagraph) ); + mnNextParagraph++; + return uno::Any( xRef ); +} + + + + +SvxUnoTextContent::SvxUnoTextContent( const SvxUnoTextBase& rText, sal_Int32 nPara ) noexcept +: SvxUnoTextRangeBase(rText) +, mnParagraph(nPara) +, mrParentText(rText) +, mbDisposing( false ) +{ + mxParentText = const_cast<SvxUnoTextBase*>(&rText); +} + +SvxUnoTextContent::SvxUnoTextContent( const SvxUnoTextContent& rContent ) noexcept +: SvxUnoTextRangeBase(rContent) +, text::XTextContent() +, container::XEnumerationAccess() +, lang::XTypeProvider() +, cppu::OWeakAggObject() +, mrParentText(rContent.mrParentText) +, mbDisposing( false ) +{ + mxParentText = rContent.mxParentText; + mnParagraph = rContent.mnParagraph; + SetSelection( rContent.GetSelection() ); +} + +SvxUnoTextContent::~SvxUnoTextContent() noexcept +{ +} + +// uno::XInterface +uno::Any SAL_CALL SvxUnoTextContent::queryAggregation( const uno::Type & rType ) +{ + QUERYINT( text::XTextRange ); + else QUERYINT( beans::XMultiPropertyStates ); + else QUERYINT( beans::XPropertySet ); + else QUERYINT( beans::XMultiPropertySet ); + else QUERYINT( beans::XPropertyState ); + else QUERYINT( text::XTextContent ); + else QUERYINT( text::XTextRangeCompare ); + else QUERYINT( lang::XComponent ); + else QUERYINT( container::XEnumerationAccess ); + else QUERYINT( container::XElementAccess ); + else QUERYINT( lang::XServiceInfo ); + else QUERYINT( lang::XTypeProvider ); + else QUERYINT( lang::XUnoTunnel ); + else + return OWeakAggObject::queryAggregation( rType ); +} + +uno::Any SAL_CALL SvxUnoTextContent::queryInterface( const uno::Type & rType ) +{ + return OWeakAggObject::queryInterface(rType); +} + +void SAL_CALL SvxUnoTextContent::acquire() noexcept +{ + OWeakAggObject::acquire(); +} + +void SAL_CALL SvxUnoTextContent::release() noexcept +{ + OWeakAggObject::release(); +} + +// XTypeProvider + +uno::Sequence< uno::Type > SAL_CALL SvxUnoTextContent::getTypes() +{ + static const uno::Sequence< uno::Type > TYPES { + cppu::UnoType<text::XTextRange>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertyStates>::get(), + cppu::UnoType<beans::XPropertyState>::get(), + cppu::UnoType<text::XTextRangeCompare>::get(), + cppu::UnoType<text::XTextContent>::get(), + cppu::UnoType<container::XEnumerationAccess>::get(), + cppu::UnoType<lang::XServiceInfo>::get(), + cppu::UnoType<lang::XTypeProvider>::get(), + cppu::UnoType<lang::XUnoTunnel>::get() }; + return TYPES; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoTextContent::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// text::XTextRange + +uno::Reference< text::XText > SAL_CALL SvxUnoTextContent::getText() +{ + return mxParentText; +} + +// text::XTextContent +void SAL_CALL SvxUnoTextContent::attach( const uno::Reference< text::XTextRange >& ) +{ +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextContent::getAnchor() +{ + return mxParentText; +} + +// XComponent + +void SAL_CALL SvxUnoTextContent::dispose() +{ + SolarMutexGuard aGuard; + + if( mbDisposing ) + return; // caught a recursion + + mbDisposing = true; + + lang::EventObject aEvt; + aEvt.Source = *static_cast<OWeakAggObject*>(this); + { + std::unique_lock aMutexGuard(maDisposeContainerMutex); + maDisposeListeners.disposeAndClear(aMutexGuard, aEvt); + } + + if( mxParentText.is() ) + mxParentText->removeTextContent( this ); +} + +void SAL_CALL SvxUnoTextContent::addEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + std::unique_lock aGuard(maDisposeContainerMutex); + maDisposeListeners.addInterface(aGuard, xListener); +} + +void SAL_CALL SvxUnoTextContent::removeEventListener( const uno::Reference< lang::XEventListener >& aListener ) +{ + std::unique_lock aGuard(maDisposeContainerMutex); + maDisposeListeners.removeInterface(aGuard, aListener); +} + +// XEnumerationAccess + +uno::Reference< container::XEnumeration > SAL_CALL SvxUnoTextContent::createEnumeration() +{ + SolarMutexGuard aGuard; + + return new SvxUnoTextRangeEnumeration( mrParentText, mnParagraph, maSelection ); +} + +// XElementAccess ( container::XEnumerationAccess ) + +uno::Type SAL_CALL SvxUnoTextContent::getElementType() +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SAL_CALL SvxUnoTextContent::hasElements() +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = GetEditSource() ? GetEditSource()->GetTextForwarder() : nullptr; + if( pForwarder ) + { + std::vector<sal_Int32> aPortions; + pForwarder->GetPortions( mnParagraph, aPortions ); + return !aPortions.empty(); + } + else + { + return false; + } +} + +// XPropertySet + +void SAL_CALL SvxUnoTextContent::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + _setPropertyValue( aPropertyName, aValue, mnParagraph ); +} + +uno::Any SAL_CALL SvxUnoTextContent::getPropertyValue( const OUString& PropertyName ) +{ + return _getPropertyValue( PropertyName, mnParagraph ); +} + +// XMultiPropertySet +void SAL_CALL SvxUnoTextContent::setPropertyValues( const uno::Sequence< OUString >& aPropertyNames, const uno::Sequence< uno::Any >& aValues ) +{ + _setPropertyValues( aPropertyNames, aValues, mnParagraph ); +} + +uno::Sequence< uno::Any > SAL_CALL SvxUnoTextContent::getPropertyValues( const uno::Sequence< OUString >& aPropertyNames ) +{ + return _getPropertyValues( aPropertyNames, mnParagraph ); +} + +/*// XTolerantMultiPropertySet +uno::Sequence< beans::SetPropertyTolerantFailed > SAL_CALL SvxUnoTextContent::setPropertyValuesTolerant( const uno::Sequence< OUString >& aPropertyNames, const uno::Sequence< uno::Any >& aValues ) throw (lang::IllegalArgumentException, uno::RuntimeException) +{ + return _setPropertyValuesTolerant(aPropertyNames, aValues, mnParagraph); +} + +uno::Sequence< beans::GetPropertyTolerantResult > SAL_CALL SvxUnoTextContent::getPropertyValuesTolerant( const uno::Sequence< OUString >& aPropertyNames ) throw (uno::RuntimeException) +{ + return _getPropertyValuesTolerant(aPropertyNames, mnParagraph); +} + +uno::Sequence< beans::GetDirectPropertyTolerantResult > SAL_CALL SvxUnoTextContent::getDirectPropertyValuesTolerant( const uno::Sequence< OUString >& aPropertyNames ) + throw (uno::RuntimeException) +{ + return _getDirectPropertyValuesTolerant(aPropertyNames, mnParagraph); +}*/ + +// beans::XPropertyState +beans::PropertyState SAL_CALL SvxUnoTextContent::getPropertyState( const OUString& PropertyName ) +{ + return _getPropertyState( PropertyName, mnParagraph ); +} + +uno::Sequence< beans::PropertyState > SAL_CALL SvxUnoTextContent::getPropertyStates( const uno::Sequence< OUString >& aPropertyName ) +{ + return _getPropertyStates( aPropertyName, mnParagraph ); +} + +void SAL_CALL SvxUnoTextContent::setPropertyToDefault( const OUString& PropertyName ) +{ + _setPropertyToDefault( PropertyName, mnParagraph ); +} + +// lang::XServiceInfo + +OUString SAL_CALL SvxUnoTextContent::getImplementationName() +{ + return "SvxUnoTextContent"; +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextContent::getSupportedServiceNames() +{ + return comphelper::concatSequences( + SvxUnoTextRangeBase::getSupportedServiceNames(), + std::initializer_list<std::u16string_view>{ u"com.sun.star.style.ParagraphProperties", + u"com.sun.star.style.ParagraphPropertiesComplex", + u"com.sun.star.style.ParagraphPropertiesAsian", + u"com.sun.star.text.TextContent", + u"com.sun.star.text.Paragraph" }); +} + + + + +SvxUnoTextRangeEnumeration::SvxUnoTextRangeEnumeration(const SvxUnoTextBase& rParentText, sal_Int32 nParagraph, const ESelection& rSel) +: mxParentText( const_cast<SvxUnoTextBase*>(&rParentText) ), + mnNextPortion( 0 ) +{ + if (rParentText.GetEditSource()) + mpEditSource = rParentText.GetEditSource()->Clone(); + + if( !(mpEditSource && mpEditSource->GetTextForwarder() && (nParagraph == rSel.nStartPara && nParagraph == rSel.nEndPara)) ) + return; + + std::vector<sal_Int32> aPortions; + mpEditSource->GetTextForwarder()->GetPortions( nParagraph, aPortions ); + for( size_t aPortionIndex = 0; aPortionIndex < aPortions.size(); aPortionIndex++ ) + { + sal_uInt16 nStartPos = 0; + if ( aPortionIndex > 0 ) + nStartPos = aPortions.at( aPortionIndex - 1 ); + if( nStartPos > rSel.nEndPos ) + continue; + sal_uInt16 nEndPos = aPortions.at( aPortionIndex ); + if( nEndPos < rSel.nStartPos ) + continue; + + nStartPos = std::max<int>(nStartPos, rSel.nStartPos); + nEndPos = std::min<sal_uInt16>(nEndPos, rSel.nEndPos); + ESelection aSel( nParagraph, nStartPos, nParagraph, nEndPos ); + + const SvxUnoTextRangeBaseVec& rRanges( mpEditSource->getRanges() ); + rtl::Reference<SvxUnoTextRange> pRange; + for (auto const& elemRange : rRanges) + { + if (pRange) + break; + SvxUnoTextRange* pIterRange = dynamic_cast< SvxUnoTextRange* >( elemRange ); + if( pIterRange && pIterRange->mbPortion && (aSel == pIterRange->maSelection) ) + pRange = pIterRange; + } + if( pRange == nullptr ) + { + pRange = new SvxUnoTextRange( rParentText, true ); + pRange->SetSelection( aSel ); + } + maPortions.emplace_back(pRange ); + } +} + +SvxUnoTextRangeEnumeration::~SvxUnoTextRangeEnumeration() noexcept +{ +} + +// container::XEnumeration + +sal_Bool SAL_CALL SvxUnoTextRangeEnumeration::hasMoreElements() +{ + SolarMutexGuard aGuard; + + return !maPortions.empty() && mnNextPortion < maPortions.size(); +} + +uno::Any SAL_CALL SvxUnoTextRangeEnumeration::nextElement() +{ + SolarMutexGuard aGuard; + + if( maPortions.empty() || mnNextPortion >= maPortions.size() ) + throw container::NoSuchElementException(); + + uno::Reference< text::XTextRange > xRange = maPortions.at(mnNextPortion); + mnNextPortion++; + return uno::Any( xRange ); +} + +SvxUnoTextCursor::SvxUnoTextCursor( const SvxUnoTextBase& rText ) noexcept +: SvxUnoTextRangeBase(rText), + mxParentText( const_cast<SvxUnoTextBase*>(&rText) ) +{ +} + +SvxUnoTextCursor::SvxUnoTextCursor( const SvxUnoTextCursor& rCursor ) noexcept +: SvxUnoTextRangeBase(rCursor) +, text::XTextCursor() +, lang::XTypeProvider() +, cppu::OWeakAggObject() +, mxParentText(rCursor.mxParentText) +{ +} + +SvxUnoTextCursor::~SvxUnoTextCursor() noexcept +{ +} + +// Comment out automatically - [getIdlClass(es) or queryInterface] +// Please use the XTypeProvider! +//sal_Bool SvxUnoTextCursor::queryInterface( uno::Uik aUIK, Reference< uno::XInterface > & xRef) +uno::Any SAL_CALL SvxUnoTextCursor::queryAggregation( const uno::Type & rType ) +{ + if( rType == cppu::UnoType<text::XTextRange>::get()) + return uno::Any(uno::Reference< text::XTextRange >(static_cast<SvxUnoTextRangeBase *>(this))); + else QUERYINT( text::XTextCursor ); + else QUERYINT( beans::XMultiPropertyStates ); + else QUERYINT( beans::XPropertySet ); + else QUERYINT( beans::XMultiPropertySet ); + else QUERYINT( beans::XPropertyState ); + else QUERYINT( text::XTextRangeCompare ); + else QUERYINT( lang::XServiceInfo ); + else QUERYINT( lang::XTypeProvider ); + else QUERYINT( lang::XUnoTunnel ); + else + return OWeakAggObject::queryAggregation( rType ); +} + +uno::Any SAL_CALL SvxUnoTextCursor::queryInterface( const uno::Type & rType ) +{ + return OWeakAggObject::queryInterface(rType); +} + +void SAL_CALL SvxUnoTextCursor::acquire() noexcept +{ + OWeakAggObject::acquire(); +} + +void SAL_CALL SvxUnoTextCursor::release() noexcept +{ + OWeakAggObject::release(); +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SvxUnoTextCursor::getTypes() +{ + static const uno::Sequence< uno::Type > TYPES { + cppu::UnoType<text::XTextRange>::get(), + cppu::UnoType<text::XTextCursor>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertyStates>::get(), + cppu::UnoType<beans::XPropertyState>::get(), + cppu::UnoType<text::XTextRangeCompare>::get(), + cppu::UnoType<lang::XServiceInfo>::get(), + cppu::UnoType<lang::XTypeProvider>::get(), + cppu::UnoType<lang::XUnoTunnel>::get() }; + return TYPES; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoTextCursor::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// text::XTextCursor +void SAL_CALL SvxUnoTextCursor::collapseToStart() +{ + SolarMutexGuard aGuard; + CollapseToStart(); +} + +void SAL_CALL SvxUnoTextCursor::collapseToEnd() +{ + SolarMutexGuard aGuard; + CollapseToEnd(); +} + +sal_Bool SAL_CALL SvxUnoTextCursor::isCollapsed() +{ + SolarMutexGuard aGuard; + return IsCollapsed(); +} + +sal_Bool SAL_CALL SvxUnoTextCursor::goLeft( sal_Int16 nCount, sal_Bool bExpand ) +{ + SolarMutexGuard aGuard; + return GoLeft( nCount, bExpand ); +} + +sal_Bool SAL_CALL SvxUnoTextCursor::goRight( sal_Int16 nCount, sal_Bool bExpand ) +{ + SolarMutexGuard aGuard; + return GoRight( nCount, bExpand ); +} + +void SAL_CALL SvxUnoTextCursor::gotoStart( sal_Bool bExpand ) +{ + SolarMutexGuard aGuard; + GotoStart( bExpand ); +} + +void SAL_CALL SvxUnoTextCursor::gotoEnd( sal_Bool bExpand ) +{ + SolarMutexGuard aGuard; + GotoEnd( bExpand ); +} + +void SAL_CALL SvxUnoTextCursor::gotoRange( const uno::Reference< text::XTextRange >& xRange, sal_Bool bExpand ) +{ + if( !xRange.is() ) + return; + + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( xRange ); + + if( !pRange ) + return; + + ESelection aNewSel = pRange->GetSelection(); + + if( bExpand ) + { + const ESelection& rOldSel = GetSelection(); + aNewSel.nStartPara = rOldSel.nStartPara; + aNewSel.nStartPos = rOldSel.nStartPos; + } + + SetSelection( aNewSel ); +} + +// text::XTextRange (rest in SvxTextRange) +uno::Reference< text::XText > SAL_CALL SvxUnoTextCursor::getText() +{ + return mxParentText; +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextCursor::getStart() +{ + return SvxUnoTextRangeBase::getStart(); +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextCursor::getEnd() +{ + return SvxUnoTextRangeBase::getEnd(); +} + +OUString SAL_CALL SvxUnoTextCursor::getString() +{ + return SvxUnoTextRangeBase::getString(); +} + +void SAL_CALL SvxUnoTextCursor::setString( const OUString& aString ) +{ + SvxUnoTextRangeBase::setString(aString); +} +// lang::XServiceInfo +OUString SAL_CALL SvxUnoTextCursor::getImplementationName() +{ + return "SvxUnoTextCursor"; +} + +sal_Bool SAL_CALL SvxUnoTextCursor::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextCursor::getSupportedServiceNames() +{ + return comphelper::concatSequences( + SvxUnoTextRangeBase::getSupportedServiceNames(), + std::initializer_list<std::u16string_view>{ u"com.sun.star.style.ParagraphProperties", + u"com.sun.star.style.ParagraphPropertiesComplex", + u"com.sun.star.style.ParagraphPropertiesAsian", + u"com.sun.star.text.TextCursor" }); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoviwou.cxx b/editeng/source/uno/unoviwou.cxx new file mode 100644 index 000000000..19f38794e --- /dev/null +++ b/editeng/source/uno/unoviwou.cxx @@ -0,0 +1,128 @@ +/* -*- 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/outdev.hxx> +#include <vcl/window.hxx> + +#include <editeng/unoviwou.hxx> +#include <editeng/outliner.hxx> + +SvxDrawOutlinerViewForwarder::SvxDrawOutlinerViewForwarder( OutlinerView& rOutl ) : + mrOutlinerView ( rOutl ) +{ +} + +SvxDrawOutlinerViewForwarder::SvxDrawOutlinerViewForwarder( OutlinerView& rOutl, const Point& rShapePosTopLeft ) : + mrOutlinerView ( rOutl ), maTextShapeTopLeft( rShapePosTopLeft ) +{ +} + +SvxDrawOutlinerViewForwarder::~SvxDrawOutlinerViewForwarder() +{ +} + +Point SvxDrawOutlinerViewForwarder::GetTextOffset() const +{ + // calc text offset from shape anchor + tools::Rectangle aOutputRect( mrOutlinerView.GetOutputArea() ); + + return aOutputRect.TopLeft() - maTextShapeTopLeft; +} + +bool SvxDrawOutlinerViewForwarder::IsValid() const +{ + return true; +} + +Point SvxDrawOutlinerViewForwarder::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const +{ + OutputDevice* pOutDev = mrOutlinerView.GetWindow()->GetOutDev(); + + if( pOutDev ) + { + Point aPoint1( rPoint ); + Point aTextOffset( GetTextOffset() ); + + aPoint1.AdjustX(aTextOffset.X() ); + aPoint1.AdjustY(aTextOffset.Y() ); + + MapMode aMapMode(pOutDev->GetMapMode()); + Point aPoint2( OutputDevice::LogicToLogic( aPoint1, rMapMode, + MapMode(aMapMode.GetMapUnit()))); + aMapMode.SetOrigin(Point()); + return pOutDev->LogicToPixel( aPoint2, aMapMode ); + } + + return Point(); +} + +Point SvxDrawOutlinerViewForwarder::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const +{ + OutputDevice* pOutDev = mrOutlinerView.GetWindow()->GetOutDev(); + + if( pOutDev ) + { + MapMode aMapMode(pOutDev->GetMapMode()); + aMapMode.SetOrigin(Point()); + Point aPoint1( pOutDev->PixelToLogic( rPoint, aMapMode ) ); + Point aPoint2( OutputDevice::LogicToLogic( aPoint1, + MapMode(aMapMode.GetMapUnit()), + rMapMode ) ); + Point aTextOffset( GetTextOffset() ); + + aPoint2.AdjustX( -(aTextOffset.X()) ); + aPoint2.AdjustY( -(aTextOffset.Y()) ); + + return aPoint2; + } + + return Point(); +} + +bool SvxDrawOutlinerViewForwarder::GetSelection( ESelection& rSelection ) const +{ + rSelection = mrOutlinerView.GetSelection(); + return true; +} + +bool SvxDrawOutlinerViewForwarder::SetSelection( const ESelection& rSelection ) +{ + mrOutlinerView.SetSelection( rSelection ); + return true; +} + +bool SvxDrawOutlinerViewForwarder::Copy() +{ + mrOutlinerView.Copy(); + return true; +} + +bool SvxDrawOutlinerViewForwarder::Cut() +{ + mrOutlinerView.Cut(); + return true; +} + +bool SvxDrawOutlinerViewForwarder::Paste() +{ + mrOutlinerView.PasteSpecial(); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/xml/editsource.hxx b/editeng/source/xml/editsource.hxx new file mode 100644 index 000000000..726ddabc8 --- /dev/null +++ b/editeng/source/xml/editsource.hxx @@ -0,0 +1,43 @@ +/* -*- 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 <editeng/unoedsrc.hxx> + +class EditEngine; +class SvxEditEngineSourceImpl; + +class SvxEditEngineSource : public SvxEditSource +{ +public: + explicit SvxEditEngineSource( EditEngine* pEditEngine ); + virtual ~SvxEditEngineSource() override; + + virtual std::unique_ptr<SvxEditSource> Clone() const override; + virtual SvxTextForwarder* GetTextForwarder() override; + virtual void UpdateData() override; + +private: + explicit SvxEditEngineSource( SvxEditEngineSourceImpl* pImpl ); + + rtl::Reference<SvxEditEngineSourceImpl> mxImpl; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/xml/xmltxtexp.cxx b/editeng/source/xml/xmltxtexp.cxx new file mode 100644 index 000000000..ae57bd84c --- /dev/null +++ b/editeng/source/xml/xmltxtexp.cxx @@ -0,0 +1,355 @@ +/* -*- 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 . + */ + + +/** this file implements an export of a selected EditEngine content into + a xml stream. See editeng/source/inc/xmledit.hxx for interface */ +#include <memory> +#include <com/sun/star/ucb/XAnyCompareFactory.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <svl/itemprop.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <xmloff/xmlmetae.hxx> +#include <comphelper/processfactory.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/streamwrap.hxx> +#include <xmloff/xmlexp.hxx> +#include <editeng/unoedsrc.hxx> +#include <editeng/unofored.hxx> +#include <editeng/unotext.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/unofield.hxx> +#include <editeng/editeng.hxx> +#include "editsource.hxx" +#include <editxml.hxx> +#include <editeng/unonrule.hxx> +#include <editeng/unoipset.hxx> +#include <unomodel.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::container; +using namespace com::sun::star::document; +using namespace com::sun::star::uno; +using namespace com::sun::star::awt; +using namespace com::sun::star::lang; +using namespace com::sun::star::xml::sax; +using namespace cppu; + +class SvxEditEngineSourceImpl; + +class SvxEditEngineSourceImpl : public salhelper::SimpleReferenceObject +{ +private: + EditEngine* mpEditEngine; + std::unique_ptr<SvxTextForwarder> mpTextForwarder; + + virtual ~SvxEditEngineSourceImpl() override; + +public: + explicit SvxEditEngineSourceImpl( EditEngine* pEditEngine ); + + SvxTextForwarder* GetTextForwarder(); +}; + +SvxEditEngineSourceImpl::SvxEditEngineSourceImpl( EditEngine* pEditEngine ) +: mpEditEngine( pEditEngine ) +{ +} + +SvxEditEngineSourceImpl::~SvxEditEngineSourceImpl() +{ +} + +SvxTextForwarder* SvxEditEngineSourceImpl::GetTextForwarder() +{ + if (!mpTextForwarder) + mpTextForwarder.reset( new SvxEditEngineForwarder( *mpEditEngine ) ); + + return mpTextForwarder.get(); +} + +// SvxTextEditSource +SvxEditEngineSource::SvxEditEngineSource( EditEngine* pEditEngine ) + : mxImpl( new SvxEditEngineSourceImpl( pEditEngine ) ) +{ +} + +SvxEditEngineSource::SvxEditEngineSource( SvxEditEngineSourceImpl* pImpl ) + : mxImpl(pImpl) +{ +} + +SvxEditEngineSource::~SvxEditEngineSource() +{ +} + +std::unique_ptr<SvxEditSource> SvxEditEngineSource::Clone() const +{ + return std::unique_ptr<SvxEditSource>(new SvxEditEngineSource( mxImpl.get() )); +} + +SvxTextForwarder* SvxEditEngineSource::GetTextForwarder() +{ + return mxImpl->GetTextForwarder(); +} + + +void SvxEditEngineSource::UpdateData() +{ +} + + +SvxSimpleUnoModel::SvxSimpleUnoModel() +{ +} + +// XMultiServiceFactory ( SvxFmMSFactory ) +uno::Reference< uno::XInterface > SAL_CALL SvxSimpleUnoModel::createInstance( const OUString& aServiceSpecifier ) +{ + if( aServiceSpecifier == "com.sun.star.text.NumberingRules" ) + { + return uno::Reference< uno::XInterface >( + SvxCreateNumRule(), uno::UNO_QUERY ); + } + if ( aServiceSpecifier == "com.sun.star.text.textfield.DateTime" + || aServiceSpecifier == "com.sun.star.text.TextField.DateTime" + ) + { + return static_cast<cppu::OWeakObject *>(new SvxUnoTextField( text::textfield::Type::DATE )); + } + + if( aServiceSpecifier == "com.sun.star.text.TextField.URL" ) + { + return static_cast<cppu::OWeakObject *>(new SvxUnoTextField(text::textfield::Type::URL)); + } + + return SvxUnoTextCreateTextField( aServiceSpecifier ); + +} + +uno::Reference< css::uno::XInterface > SAL_CALL SvxSimpleUnoModel::createInstanceWithArguments( const OUString& ServiceSpecifier, const css::uno::Sequence< css::uno::Any >& ) +{ + return createInstance( ServiceSpecifier ); +} + +Sequence< OUString > SAL_CALL SvxSimpleUnoModel::getAvailableServiceNames( ) +{ + Sequence< OUString > aSeq; + return aSeq; +} + +// XAnyCompareFactory +uno::Reference< css::ucb::XAnyCompare > SAL_CALL SvxSimpleUnoModel::createAnyCompareByName( const OUString& ) +{ + return SvxCreateNumRuleCompare(); +} + +// XStyleFamiliesSupplier +uno::Reference< container::XNameAccess > SAL_CALL SvxSimpleUnoModel::getStyleFamilies( ) +{ + uno::Reference< container::XNameAccess > xStyles; + return xStyles; +} + +// XModel +sal_Bool SAL_CALL SvxSimpleUnoModel::attachResource( const OUString&, const css::uno::Sequence< css::beans::PropertyValue >& ) +{ + return false; +} + +OUString SAL_CALL SvxSimpleUnoModel::getURL( ) +{ + return OUString(); +} + +css::uno::Sequence< css::beans::PropertyValue > SAL_CALL SvxSimpleUnoModel::getArgs( ) +{ + Sequence< beans::PropertyValue > aSeq; + return aSeq; +} + +void SAL_CALL SvxSimpleUnoModel::connectController( const css::uno::Reference< css::frame::XController >& ) +{ +} + +void SAL_CALL SvxSimpleUnoModel::disconnectController( const css::uno::Reference< css::frame::XController >& ) +{ +} + +void SAL_CALL SvxSimpleUnoModel::lockControllers( ) +{ +} + +void SAL_CALL SvxSimpleUnoModel::unlockControllers( ) +{ +} + +sal_Bool SAL_CALL SvxSimpleUnoModel::hasControllersLocked( ) +{ + return true; +} + +css::uno::Reference< css::frame::XController > SAL_CALL SvxSimpleUnoModel::getCurrentController( ) +{ + uno::Reference< frame::XController > xRet; + return xRet; +} + +void SAL_CALL SvxSimpleUnoModel::setCurrentController( const css::uno::Reference< css::frame::XController >& ) +{ +} + +css::uno::Reference< css::uno::XInterface > SAL_CALL SvxSimpleUnoModel::getCurrentSelection( ) +{ + uno::Reference< XInterface > xRet; + return xRet; +} + + +// XComponent +void SAL_CALL SvxSimpleUnoModel::dispose( ) +{ +} + +void SAL_CALL SvxSimpleUnoModel::addEventListener( const css::uno::Reference< css::lang::XEventListener >& ) +{ +} + +void SAL_CALL SvxSimpleUnoModel::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& ) +{ +} + +namespace { + +class SvxXMLTextExportComponent : public SvXMLExport +{ +public: + SvxXMLTextExportComponent( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + EditEngine* pEditEngine, + const ESelection& rSel, + const css::uno::Reference< css::xml::sax::XDocumentHandler >& rHandler ); + + // methods without content: + virtual void ExportAutoStyles_() override; + virtual void ExportMasterStyles_() override; + virtual void ExportContent_() override; + +private: + css::uno::Reference< css::text::XText > mxText; +}; + +} + +SvxXMLTextExportComponent::SvxXMLTextExportComponent( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + EditEngine* pEditEngine, + const ESelection& rSel, + const css::uno::Reference< css::xml::sax::XDocumentHandler > & xHandler) +: SvXMLExport( xContext, "", /*rFileName*/"", xHandler, static_cast<frame::XModel*>(new SvxSimpleUnoModel()), FieldUnit::CM, + SvXMLExportFlags::OASIS | SvXMLExportFlags::AUTOSTYLES | SvXMLExportFlags::CONTENT ) +{ + SvxEditEngineSource aEditSource( pEditEngine ); + + static const SfxItemPropertyMapEntry SvxXMLTextExportComponentPropertyMap[] = + { + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_FONT_PROPERTIES, + { UNO_NAME_NUMBERING_RULES, EE_PARA_NUMBULLET, cppu::UnoType<css::container::XIndexReplace>::get(), 0, 0 }, + { UNO_NAME_NUMBERING, EE_PARA_BULLETSTATE,cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_NAME_NUMBERING_LEVEL, EE_PARA_OUTLLEVEL, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + SVX_UNOEDIT_PARA_PROPERTIES, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static SvxItemPropertySet aSvxXMLTextExportComponentPropertySet( SvxXMLTextExportComponentPropertyMap, EditEngine::GetGlobalItemPool() ); + + rtl::Reference<SvxUnoText> pUnoText = new SvxUnoText( &aEditSource, &aSvxXMLTextExportComponentPropertySet, mxText ); + pUnoText->SetSelection( rSel ); + mxText = pUnoText; + +} + +void SvxWriteXML( EditEngine& rEditEngine, SvStream& rStream, const ESelection& rSel ) +{ + try + { + do + { + // create service factory + uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext() ); + + // create document handler + uno::Reference< xml::sax::XWriter > xWriter = xml::sax::Writer::create( xContext ); + + // create output stream and active data source + uno::Reference<io::XOutputStream> xOut( new utl::OOutputStreamWrapper( rStream ) ); + +/* testcode + static const OUStringLiteral aURL( u"file:///e:/test.xml" ); + SvFileStream aStream(aURL, StreamMode::WRITE | StreamMode::TRUNC); + xOut = new utl::OOutputStreamWrapper(aStream); +*/ + + + xWriter->setOutputStream( xOut ); + + // export text + + // SvxXMLTextExportComponent aExporter( &rEditEngine, rSel, aName, xHandler ); + uno::Reference< xml::sax::XDocumentHandler > xHandler(xWriter, UNO_QUERY_THROW); + rtl::Reference< SvxXMLTextExportComponent > xExporter( new SvxXMLTextExportComponent( xContext, &rEditEngine, rSel, xHandler ) ); + + xExporter->exportDoc(); + +/* testcode + aStream.Close(); +*/ + + } + while( false ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("editeng", "exception during xml export"); + } +} + +// methods without content: +void SvxXMLTextExportComponent::ExportAutoStyles_() +{ + rtl::Reference< XMLTextParagraphExport > xTextExport( GetTextParagraphExport() ); + + xTextExport->collectTextAutoStyles( mxText ); + xTextExport->exportTextAutoStyles(); +} + +void SvxXMLTextExportComponent::ExportContent_() +{ + rtl::Reference< XMLTextParagraphExport > xTextExport( GetTextParagraphExport() ); + + xTextExport->exportText( mxText ); +} + +void SvxXMLTextExportComponent::ExportMasterStyles_() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/xml/xmltxtimp.cxx b/editeng/source/xml/xmltxtimp.cxx new file mode 100644 index 000000000..1122ea473 --- /dev/null +++ b/editeng/source/xml/xmltxtimp.cxx @@ -0,0 +1,234 @@ +/* -*- 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/text/XText.hpp> +#include <comphelper/processfactory.hxx> +#include <unotools/streamwrap.hxx> +#include <svl/itemprop.hxx> +#include <utility> +#include <xmloff/xmlimp.hxx> +#include <xmloff/xmlictxt.hxx> +#include <xmloff/xmltoken.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <xmloff/xmlstyle.hxx> +#include "editsource.hxx" +#include <editxml.hxx> +#include <editdoc.hxx> +#include <editeng/editeng.hxx> +#include <editeng/unotext.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/unoipset.hxx> +#include <cassert> +#include <unomodel.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::document; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::xml::sax; +using namespace com::sun::star::text; +using namespace cppu; +using namespace xmloff::token; + +namespace { + +class SvxXMLTextImportContext : public SvXMLImportContext +{ +public: + SvxXMLTextImportContext( SvXMLImport& rImport, uno::Reference< XText > xText ); + + virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 nElement, + const uno::Reference< xml::sax::XFastAttributeList >& xAttrList) override; + +private: + const uno::Reference< XText > mxText; +}; + +} + +SvxXMLTextImportContext::SvxXMLTextImportContext( SvXMLImport& rImport, uno::Reference< XText > xText ) +: SvXMLImportContext( rImport ), mxText(std::move( xText )) +{ +} + +css::uno::Reference< css::xml::sax::XFastContextHandler > SvxXMLTextImportContext::createFastChildContext( + sal_Int32 nElement, + const uno::Reference< xml::sax::XFastAttributeList >& xAttrList) +{ + SvXMLImportContext* pContext = nullptr; + if(nElement == XML_ELEMENT(OFFICE, XML_BODY )) + { + pContext = new SvxXMLTextImportContext( GetImport(), mxText ); + } + else if( nElement == XML_ELEMENT(OFFICE, XML_AUTOMATIC_STYLES ) ) + { + pContext = new SvXMLStylesContext( GetImport() ); + GetImport().GetTextImport()->SetAutoStyles( static_cast<SvXMLStylesContext*>(pContext) ); + } + else + pContext = GetImport().GetTextImport()->CreateTextChildContext( GetImport(), nElement, xAttrList ); + return pContext; +} + +namespace { + +class SvxXMLXTextImportComponent : public SvXMLImport +{ +public: + SvxXMLXTextImportComponent( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + uno::Reference< XText > xText ); + + virtual SvXMLImportContext* CreateFastContext(sal_Int32 nElement, + const ::css::uno::Reference< ::css::xml::sax::XFastAttributeList >& xAttrList ) override; + +private: + const uno::Reference< XText > mxText; +}; + +} + +SvXMLImportContext *SvxXMLXTextImportComponent::CreateFastContext( + sal_Int32 nElement, + const uno::Reference< xml::sax::XFastAttributeList >& /*xAttrList*/) +{ + SvXMLImportContext* pContext = nullptr; + + if(nElement == XML_ELEMENT(OFFICE, XML_DOCUMENT_CONTENT ) ) + { + pContext = new SvxXMLTextImportContext( *this, mxText ); + } + + return pContext; +} + +SvxXMLXTextImportComponent::SvxXMLXTextImportComponent( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + uno::Reference< XText > xText ) +: SvXMLImport(xContext, ""), + mxText(std::move( xText )) +{ + GetTextImport()->SetCursor( mxText->createTextCursor() ); + SvXMLImport::setTargetDocument(new SvxSimpleUnoModel); +} + +EditPaM SvxReadXML( EditEngine& rEditEngine, SvStream& rStream, const ESelection& rSel ) +{ + SvxEditEngineSource aEditSource( &rEditEngine ); + + static const SfxItemPropertyMapEntry SvxXMLTextImportComponentPropertyMap[] = + { + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_FONT_PROPERTIES, +// bullets & numbering props, tdf#128046 + { UNO_NAME_NUMBERING_RULES, EE_PARA_NUMBULLET, cppu::UnoType<css::container::XIndexReplace>::get(), 0, 0 }, + { UNO_NAME_NUMBERING, EE_PARA_BULLETSTATE,cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_NAME_NUMBERING_LEVEL, EE_PARA_OUTLLEVEL, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + SVX_UNOEDIT_PARA_PROPERTIES, + { u"", 0, css::uno::Type(), 0, 0 } + }; + static SvxItemPropertySet aSvxXMLTextImportComponentPropertySet( SvxXMLTextImportComponentPropertyMap, EditEngine::GetGlobalItemPool() ); + + assert(!rSel.HasRange()); + //get the initial para count before paste + sal_uInt32 initialParaCount = rEditEngine.GetEditDoc().Count(); + //insert para breaks before inserting the copied text + rEditEngine.InsertParaBreak( rEditEngine.CreateSelection( rSel ).Max() ); + rEditEngine.InsertParaBreak( rEditEngine.CreateSelection( rSel ).Max() ); + + // Init return PaM. + EditPaM aPaM( rEditEngine.CreateSelection( rSel ).Max()); + + ESelection aSel(rSel.nStartPara+1, 0, rSel.nEndPara+1, 0); + uno::Reference<text::XText > xParent; + rtl::Reference<SvxUnoText> pUnoText = new SvxUnoText( &aEditSource, &aSvxXMLTextImportComponentPropertySet, xParent ); + pUnoText->SetSelection( aSel ); + + try + { + do + { + uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext() ); + + uno::Reference<io::XInputStream> xInputStream = new utl::OInputStreamWrapper( rStream ); + +/* testcode + static const OUStringLiteral aURL( u"file:///e:/test.xml" ); + SfxMedium aMedium( aURL, StreamMode::READ | STREAM_NOCREATE, sal_True ); + uno::Reference<io::XOutputStream> xOut( new utl::OOutputStreamWrapper( *aMedium.GetOutStream() ) ); + + aMedium.GetInStream()->Seek( 0 ); + uno::Reference< io::XActiveDataSource > xSource( aMedium.GetDataSource() ); + + if( !xSource.is() ) + { + OSL_FAIL( "got no data source from medium" ); + break; + } + + uno::Reference< XInterface > xPipe( Pipe::create(comphelper::getComponentContext(xServiceFactory)), UNO_QUERY ); + + // connect pipe's output stream to the data source + xSource->setOutputStream( uno::Reference< io::XOutputStream >::query( xPipe ) ); + + xml::sax::InputSource aParserInput; + aParserInput.aInputStream.set( xPipe, UNO_QUERY ); + aParserInput.sSystemId = aMedium.GetName(); + + + if( xSource.is() ) + { + uno::Reference< io::XActiveDataControl > xSourceControl( xSource, UNO_QUERY ); + xSourceControl->start(); + } + +*/ + + // uno::Reference< XDocumentHandler > xHandler( new SvxXMLXTextImportComponent( xText ) ); + rtl::Reference< SvxXMLXTextImportComponent > xImport( new SvxXMLXTextImportComponent( xContext, pUnoText ) ); + + xml::sax::InputSource aParserInput; + aParserInput.aInputStream = xInputStream; +// aParserInput.sSystemId = aMedium.GetName(); + xImport->parseStream( aParserInput ); + } + while(false); + + //remove the extra para breaks + EditDoc& pDoc = rEditEngine.GetEditDoc(); + rEditEngine.ParaAttribsToCharAttribs( pDoc.GetObject( rSel.nEndPara ) ); + rEditEngine.ConnectParagraphs( pDoc.GetObject( rSel.nEndPara ), + pDoc.GetObject( rSel.nEndPara + 1 ), true ); + rEditEngine.ParaAttribsToCharAttribs( pDoc.GetObject( pDoc.Count() - initialParaCount + aSel.nEndPara - 2 ) ); + rEditEngine.ConnectParagraphs( pDoc.GetObject( pDoc.Count() - initialParaCount + aSel.nEndPara - 2 ), + pDoc.GetObject( pDoc.Count() - initialParaCount + aSel.nEndPara -1 ), true ); + + // The final join is to be returned. + aPaM = rEditEngine.ConnectParagraphs( pDoc.GetObject( pDoc.Count() - initialParaCount + aSel.nEndPara - 2 ), + pDoc.GetObject( pDoc.Count() - initialParaCount + aSel.nEndPara -1 ), true ); + } + catch( const uno::Exception& ) + { + } + + return aPaM; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/uiconfig/ui/spellmenu.ui b/editeng/uiconfig/ui/spellmenu.ui new file mode 100644 index 000000000..ea3bd9486 --- /dev/null +++ b/editeng/uiconfig/ui/spellmenu.ui @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.38.1 --> +<interface domain="editeng"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkMenu" id="editviewspellmenu"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkMenuItem" id="ignore"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="spellmenu|ignore">I_gnore All</property> + <property name="use_underline">True</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="insert"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="spellmenu|insert">_Add to Dictionary</property> + <property name="use_underline">True</property> + <child type="submenu"> + <object class="GtkMenu" id="insertmenu"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkMenuItem" id="add"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="spellmenu|add">_Add to Dictionary</property> + <property name="use_underline">True</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="check"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="spellmenu|check">_Spellcheck...</property> + <property name="use_underline">True</property> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem" id="menuitem1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + <child> + <object class="GtkMenuItem" id="autocorrect"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="spellmenu|autocorrect">AutoCorrect _To</property> + <property name="use_underline">True</property> + <child type="submenu"> + <object class="GtkMenu" id="automenu"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkMenuItem" id="autocorrectdlg"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" context="spellmenu|autocorrectdlg">Auto_Correct Options...</property> + <property name="use_underline">True</property> + </object> + </child> + </object> +</interface> |