summaryrefslogtreecommitdiffstats
path: root/sw/source/core/crsr
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/core/crsr
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/crsr')
-rw-r--r--sw/source/core/crsr/BlockCursor.cxx27
-rw-r--r--sw/source/core/crsr/BlockCursor.hxx91
-rw-r--r--sw/source/core/crsr/DateFormFieldButton.cxx64
-rw-r--r--sw/source/core/crsr/DropDownFormFieldButton.cxx117
-rw-r--r--sw/source/core/crsr/FormFieldButton.cxx160
-rw-r--r--sw/source/core/crsr/annotationmark.cxx95
-rw-r--r--sw/source/core/crsr/bookmark.cxx1329
-rw-r--r--sw/source/core/crsr/callnk.cxx271
-rw-r--r--sw/source/core/crsr/callnk.hxx51
-rw-r--r--sw/source/core/crsr/contentcontrolbutton.cxx179
-rw-r--r--sw/source/core/crsr/crbm.cxx321
-rw-r--r--sw/source/core/crsr/crossrefbookmark.cxx94
-rw-r--r--sw/source/core/crsr/crsrsh.cxx4222
-rw-r--r--sw/source/core/crsr/crstrvl.cxx2860
-rw-r--r--sw/source/core/crsr/crstrvl1.cxx126
-rw-r--r--sw/source/core/crsr/datecontentcontrolbutton.cxx71
-rw-r--r--sw/source/core/crsr/dropdowncontentcontrolbutton.cxx96
-rw-r--r--sw/source/core/crsr/findattr.cxx1446
-rw-r--r--sw/source/core/crsr/findcoll.cxx113
-rw-r--r--sw/source/core/crsr/findfmt.cxx98
-rw-r--r--sw/source/core/crsr/findtxt.cxx1172
-rw-r--r--sw/source/core/crsr/overlayrangesoutline.cxx104
-rw-r--r--sw/source/core/crsr/overlayrangesoutline.hxx58
-rw-r--r--sw/source/core/crsr/pam.cxx1407
-rw-r--r--sw/source/core/crsr/paminit.cxx63
-rw-r--r--sw/source/core/crsr/swcrsr.cxx2646
-rw-r--r--sw/source/core/crsr/trvlcol.cxx103
-rw-r--r--sw/source/core/crsr/trvlfnfl.cxx384
-rw-r--r--sw/source/core/crsr/trvlreg.cxx280
-rw-r--r--sw/source/core/crsr/trvltbl.cxx926
-rw-r--r--sw/source/core/crsr/viscrs.cxx1258
31 files changed, 20232 insertions, 0 deletions
diff --git a/sw/source/core/crsr/BlockCursor.cxx b/sw/source/core/crsr/BlockCursor.cxx
new file mode 100644
index 0000000000..0dd0f5dff6
--- /dev/null
+++ b/sw/source/core/crsr/BlockCursor.cxx
@@ -0,0 +1,27 @@
+/* -*- 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 <viscrs.hxx>
+#include "BlockCursor.hxx"
+
+SwBlockCursor::~SwBlockCursor() {}
+
+SwShellCursor& SwBlockCursor::getShellCursor() { return maCursor; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/BlockCursor.hxx b/sw/source/core/crsr/BlockCursor.hxx
new file mode 100644
index 0000000000..28b09e0e84
--- /dev/null
+++ b/sw/source/core/crsr/BlockCursor.hxx
@@ -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 .
+ */
+#ifndef INCLUDED_SW_SOURCE_CORE_CRSR_BLOCKCURSOR_HXX
+#define INCLUDED_SW_SOURCE_CORE_CRSR_BLOCKCURSOR_HXX
+
+#include <optional>
+#include <tools/gen.hxx>
+
+#include <viscrs.hxx>
+
+class SwCursorShell;
+struct SwPosition;
+
+ /** Access to the block cursor
+
+ A block cursor contains a SwShellCursor and additional information about
+ the rectangle which has been created by pressing the mouse button and
+ moving the mouse.
+
+ It's simply an aggregation of a SwShellCursor and a rectangle defined by
+ a start and an end point.
+*/
+class SwBlockCursor
+{
+ SwShellCursor maCursor;
+ std::optional<Point> maStartPt;
+ std::optional<Point> maEndPt;
+
+public:
+ SwBlockCursor( const SwCursorShell& rCursorSh, const SwPosition &rPos ) :
+ maCursor( rCursorSh, rPos ) {}
+ /** Access to the shell cursor
+
+ @return SwShellCursor& which represents the start and end position of the
+ current block selection
+ */
+ SwShellCursor& getShellCursor();
+ /** Defines the starting vertex of the block selection
+
+ @param rPt
+ rPt should contain the document coordinates of the mouse cursor when
+ the block selection starts (MouseButtonDown)
+ */
+ void setStartPoint( const Point &rPt ) { maStartPt = rPt; }
+ /** Defines the ending vertex of the block selection
+
+ @param rPt
+ rPt should contain the document coordinates of the mouse cursor when
+ the block selection has started and the mouse has been moved (MouseMove)
+ */
+ void setEndPoint( const Point &rPt ) { maEndPt = rPt; }
+ /** The document coordinates where the block selection has been started
+
+ @return 0, if no start point has been set
+ */
+ std::optional<Point> const & getStartPoint() const { return maStartPt; }
+ /** The document coordinates where the block selection ends (at the moment)
+
+ @return 0, if no end point has been set
+ */
+ std::optional<Point> const & getEndPoint() const { return maEndPt; }
+ /** Deletion of the mouse created rectangle
+
+ When start and end points exist, the block cursor depends on this. If the
+ cursor is moved by cursor keys (e.g. up/down, home/end) the mouse rectangle
+ is obsolete and has to be deleted.
+ */
+ void clearPoints() { maStartPt.reset(); maEndPt.reset(); }
+ ~SwBlockCursor();
+};
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/DateFormFieldButton.cxx b/sw/source/core/crsr/DateFormFieldButton.cxx
new file mode 100644
index 0000000000..b16845a633
--- /dev/null
+++ b/sw/source/core/crsr/DateFormFieldButton.cxx
@@ -0,0 +1,64 @@
+/* -*- 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 <DateFormFieldButton.hxx>
+#include <edtwin.hxx>
+#include <bookmark.hxx>
+#include <tools/date.hxx>
+#include <svl/numformat.hxx>
+#include <vcl/svapp.hxx>
+
+IMPL_LINK(DateFormFieldButton, ImplSelectHdl, weld::Calendar&, rCalendar, void)
+{
+ if (m_pDateFieldmark)
+ {
+ const Date& rNullDate = m_pNumberFormatter->GetNullDate();
+ double dDate = rCalendar.get_date() - rNullDate;
+ m_pDateFieldmark->SetCurrentDate(dDate);
+ }
+ m_xFieldPopup->popdown();
+}
+
+DateFormFieldButton::DateFormFieldButton(SwEditWin* pEditWin, sw::mark::DateFieldmark& rFieldmark,
+ SvNumberFormatter* pNumberFormatter)
+ : FormFieldButton(pEditWin, rFieldmark)
+ , m_pNumberFormatter(pNumberFormatter)
+ , m_pDateFieldmark(dynamic_cast<sw::mark::DateFieldmark*>(&m_rFieldmark))
+{
+}
+
+DateFormFieldButton::~DateFormFieldButton() { disposeOnce(); }
+
+void DateFormFieldButton::LaunchPopup()
+{
+ m_xFieldPopupBuilder
+ = Application::CreateBuilder(GetFrameWeld(), "modules/swriter/ui/calendar.ui");
+ m_xFieldPopup = m_xFieldPopupBuilder->weld_popover("Calendar");
+ m_xCalendar = m_xFieldPopupBuilder->weld_calendar("date");
+ if (m_pDateFieldmark)
+ {
+ std::pair<bool, double> aResult = m_pDateFieldmark->GetCurrentDate();
+ if (aResult.first)
+ {
+ const Date& rNullDate = m_pNumberFormatter->GetNullDate();
+ m_xCalendar->set_date(rNullDate + sal_Int32(aResult.second));
+ }
+ }
+ m_xCalendar->connect_activated(LINK(this, DateFormFieldButton, ImplSelectHdl));
+ FormFieldButton::LaunchPopup();
+ m_xCalendar->grab_focus();
+}
+
+void DateFormFieldButton::DestroyPopup()
+{
+ m_xCalendar.reset();
+ FormFieldButton::DestroyPopup();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sw/source/core/crsr/DropDownFormFieldButton.cxx b/sw/source/core/crsr/DropDownFormFieldButton.cxx
new file mode 100644
index 0000000000..eb06bcc5ac
--- /dev/null
+++ b/sw/source/core/crsr/DropDownFormFieldButton.cxx
@@ -0,0 +1,117 @@
+/* -*- 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 <DropDownFormFieldButton.hxx>
+#include <edtwin.hxx>
+#include <bookmark.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <xmloff/odffields.hxx>
+#include <IMark.hxx>
+#include <view.hxx>
+#include <docsh.hxx>
+#include <strings.hrc>
+
+/**
+ * Popup dialog for drop-down form field showing the list items of the field.
+ * The user can select the item using this popup while filling in a form.
+ */
+
+void DropDownFormFieldButton::InitDropdown()
+{
+ const sw::mark::IFieldmark::parameter_map_t* const pParameters = m_rFieldmark.GetParameters();
+
+ sw::mark::IFieldmark::parameter_map_t::const_iterator pListEntries
+ = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
+ css::uno::Sequence<OUString> vListEntries;
+ if (pListEntries != pParameters->end())
+ {
+ pListEntries->second >>= vListEntries;
+ for (OUString const& i : std::as_const(vListEntries))
+ m_xTreeView->append_text(i);
+ }
+
+ if (!vListEntries.hasElements())
+ {
+ m_xTreeView->append_text(SwResId(STR_DROP_DOWN_EMPTY_LIST));
+ }
+
+ // Select the current one
+ sw::mark::IFieldmark::parameter_map_t::const_iterator pResult
+ = pParameters->find(ODF_FORMDROPDOWN_RESULT);
+ if (pResult != pParameters->end())
+ {
+ sal_Int32 nSelection = -1;
+ pResult->second >>= nSelection;
+ m_xTreeView->set_cursor(nSelection);
+ m_xTreeView->select(nSelection);
+ }
+
+ auto nHeight = m_xTreeView->get_height_rows(
+ std::min<int>(Application::GetSettings().GetStyleSettings().GetListBoxMaximumLineCount(),
+ m_xTreeView->n_children()));
+ m_xTreeView->set_size_request(-1, nHeight);
+ Size lbSize(m_xTreeView->get_preferred_size());
+ lbSize.AdjustWidth(4);
+ lbSize.AdjustHeight(4);
+ auto nMinListWidth = GetSizePixel().Width();
+ lbSize.setWidth(std::max(lbSize.Width(), nMinListWidth));
+ m_xTreeView->set_size_request(lbSize.Width(), lbSize.Height());
+}
+
+IMPL_LINK(DropDownFormFieldButton, MyListBoxHandler, weld::TreeView&, rBox, bool)
+{
+ OUString sSelection = rBox.get_selected_text();
+ if (sSelection == SwResId(STR_DROP_DOWN_EMPTY_LIST))
+ {
+ m_xFieldPopup->popdown();
+ return true;
+ }
+
+ sal_Int32 nSelection = rBox.get_selected_index();
+ if (nSelection >= 0)
+ {
+ (*m_rFieldmark.GetParameters())[ODF_FORMDROPDOWN_RESULT] <<= nSelection;
+ m_rFieldmark.Invalidate();
+ SwView& rView = static_cast<SwEditWin*>(GetParent())->GetView();
+ rView.GetDocShell()->SetModified();
+ }
+
+ m_xFieldPopup->popdown();
+
+ return true;
+}
+
+DropDownFormFieldButton::DropDownFormFieldButton(SwEditWin* pEditWin,
+ sw::mark::DropDownFieldmark& rFieldmark)
+ : FormFieldButton(pEditWin, rFieldmark)
+{
+}
+
+DropDownFormFieldButton::~DropDownFormFieldButton() { disposeOnce(); }
+
+void DropDownFormFieldButton::LaunchPopup()
+{
+ m_xFieldPopupBuilder
+ = Application::CreateBuilder(GetFrameWeld(), "modules/swriter/ui/formdropdown.ui");
+ m_xFieldPopup = m_xFieldPopupBuilder->weld_popover("FormDropDown");
+ m_xTreeView = m_xFieldPopupBuilder->weld_tree_view("list");
+ InitDropdown();
+ m_xTreeView->connect_row_activated(LINK(this, DropDownFormFieldButton, MyListBoxHandler));
+ FormFieldButton::LaunchPopup();
+ m_xTreeView->grab_focus();
+}
+
+void DropDownFormFieldButton::DestroyPopup()
+{
+ m_xTreeView.reset();
+ FormFieldButton::DestroyPopup();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sw/source/core/crsr/FormFieldButton.cxx b/sw/source/core/crsr/FormFieldButton.cxx
new file mode 100644
index 0000000000..319dcdb9c0
--- /dev/null
+++ b/sw/source/core/crsr/FormFieldButton.cxx
@@ -0,0 +1,160 @@
+/* -*- 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 <DropDownFormFieldButton.hxx>
+#include <edtwin.hxx>
+#include <basegfx/color/bcolortools.hxx>
+#include <bookmark.hxx>
+#include <vcl/weldutils.hxx>
+#include <vcl/event.hxx>
+
+FormFieldButton::FormFieldButton(SwEditWin* pEditWin, sw::mark::Fieldmark& rFieldmark)
+ : Control(pEditWin, WB_DIALOGCONTROL)
+ , m_rFieldmark(rFieldmark)
+{
+ assert(GetParent());
+ assert(dynamic_cast<SwEditWin*>(GetParent()));
+
+ SetBackground();
+ EnableChildTransparentMode();
+ SetParentClipMode(ParentClipMode::NoClip);
+ SetPaintTransparent(true);
+}
+
+FormFieldButton::~FormFieldButton() { disposeOnce(); }
+
+void FormFieldButton::LaunchPopup()
+{
+ m_xFieldPopup->connect_closed(LINK(this, DropDownFormFieldButton, FieldPopupModeEndHdl));
+
+ tools::Rectangle aRect(Point(0, 0), GetSizePixel());
+ weld::Window* pParent = weld::GetPopupParent(*this, aRect);
+ m_xFieldPopup->popup_at_rect(pParent, aRect);
+}
+
+void FormFieldButton::DestroyPopup()
+{
+ m_xFieldPopup.reset();
+ m_xFieldPopupBuilder.reset();
+}
+
+void FormFieldButton::dispose()
+{
+ DestroyPopup();
+ Control::dispose();
+}
+
+void FormFieldButton::CalcPosAndSize(const SwRect& rPortionPaintArea)
+{
+ assert(GetParent());
+
+ Point aBoxPos = GetParent()->LogicToPixel(rPortionPaintArea.Pos());
+ Size aBoxSize = GetParent()->LogicToPixel(rPortionPaintArea.SSize());
+
+ // First calculate the size of the frame around the field
+ int nPadding = aBoxSize.Height() / 4;
+ aBoxPos.AdjustX(-nPadding);
+ aBoxPos.AdjustY(-nPadding);
+ aBoxSize.AdjustWidth(2 * nPadding);
+ aBoxSize.AdjustHeight(2 * nPadding);
+
+ m_aFieldFramePixel = tools::Rectangle(aBoxPos, aBoxSize);
+
+ // Then extend the size with the button area
+ aBoxSize.AdjustWidth(GetParent()->LogicToPixel(rPortionPaintArea.SSize()).Height());
+
+ if (aBoxPos != GetPosPixel() || aBoxSize != GetSizePixel())
+ {
+ SetPosSizePixel(aBoxPos, aBoxSize);
+ Invalidate();
+ }
+}
+
+void FormFieldButton::MouseButtonDown(const MouseEvent&)
+{
+ LaunchPopup();
+ Invalidate();
+}
+
+IMPL_LINK_NOARG(FormFieldButton, FieldPopupModeEndHdl, weld::Popover&, void)
+{
+ DestroyPopup();
+ m_rFieldmark.Invalidate();
+ // Hide the button here and make it visible later, to make transparent background work with SAL_USE_VCLPLUGIN=gen
+ Show(false);
+ Invalidate();
+}
+
+static basegfx::BColor lcl_GetFillColor(const basegfx::BColor& rLineColor, double aLuminance)
+{
+ basegfx::BColor aHslLine = basegfx::utils::rgb2hsl(rLineColor);
+ aHslLine.setZ(aLuminance);
+ return basegfx::utils::hsl2rgb(aHslLine);
+}
+
+void FormFieldButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ SetMapMode(MapMode(MapUnit::MapPixel));
+
+ //const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+ Color aLineColor = COL_BLACK;
+ Color aFillColor(lcl_GetFillColor(aLineColor.getBColor(), (m_xFieldPopup ? 0.5 : 0.75)));
+
+ // Draw the frame around the field
+ // GTK3 backend cuts down the frame's top and left border, to avoid that add a padding around the frame
+ int nPadding = 1;
+ Point aPos(nPadding, nPadding);
+ Size aSize(m_aFieldFramePixel.GetSize().Width() - nPadding,
+ m_aFieldFramePixel.GetSize().Height() - nPadding);
+ const tools::Rectangle aFrameRect(tools::Rectangle(aPos, aSize));
+ rRenderContext.SetLineColor(aLineColor);
+ rRenderContext.SetFillColor(COL_TRANSPARENT);
+ rRenderContext.DrawRect(aFrameRect);
+
+ // Draw the button next to the frame
+ Point aButtonPos(aFrameRect.TopLeft());
+ aButtonPos.AdjustX(aFrameRect.GetSize().getWidth() - 1);
+ Size aButtonSize(aFrameRect.GetSize());
+ aButtonSize.setWidth(GetSizePixel().getWidth() - aFrameRect.getOpenWidth() - nPadding);
+ const tools::Rectangle aButtonRect(tools::Rectangle(aButtonPos, aButtonSize));
+
+ // Background & border
+ rRenderContext.SetLineColor(aLineColor);
+ rRenderContext.SetFillColor(aFillColor);
+ rRenderContext.DrawRect(aButtonRect);
+
+ // the arrowhead
+ rRenderContext.SetLineColor(aLineColor);
+ rRenderContext.SetFillColor(aLineColor);
+
+ Point aCenter(aButtonPos.X() + (aButtonSize.Width() / 2),
+ aButtonPos.Y() + (aButtonSize.Height() / 2));
+ Size aArrowSize(aButtonSize.Width() / 4, aButtonSize.Height() / 10);
+
+ tools::Polygon aPoly(3);
+ aPoly.SetPoint(Point(aCenter.X() - aArrowSize.Width(), aCenter.Y() - aArrowSize.Height()), 0);
+ aPoly.SetPoint(Point(aCenter.X() + aArrowSize.Width(), aCenter.Y() - aArrowSize.Height()), 1);
+ aPoly.SetPoint(Point(aCenter.X(), aCenter.Y() + aArrowSize.Height()), 2);
+ rRenderContext.DrawPolygon(aPoly);
+}
+
+WindowHitTest FormFieldButton::ImplHitTest(const Point& rFramePos)
+{
+ // We need to check whether the position hits the button (the frame should be mouse transparent)
+ WindowHitTest aResult = Control::ImplHitTest(rFramePos);
+ if (aResult != WindowHitTest::Inside)
+ return aResult;
+ else
+ {
+ return rFramePos.X() >= m_aFieldFramePixel.Right() ? WindowHitTest::Inside
+ : WindowHitTest::Transparent;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sw/source/core/crsr/annotationmark.cxx b/sw/source/core/crsr/annotationmark.cxx
new file mode 100644
index 0000000000..28e0bcf85b
--- /dev/null
+++ b/sw/source/core/crsr/annotationmark.cxx
@@ -0,0 +1,95 @@
+/* -*- 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 <annotationmark.hxx>
+
+#include <doc.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <IDocumentState.hxx>
+#include <fldbas.hxx>
+#include <fmtfld.hxx>
+#include <docufld.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <UndoBookmark.hxx>
+#include <ndtxt.hxx>
+#include <txtfld.hxx>
+
+namespace sw::mark
+{
+ AnnotationMark::AnnotationMark(
+ const SwPaM& rPaM,
+ const OUString& rName )
+ : MarkBase( rPaM, rName )
+ {
+ if ( rName.getLength() == 0 )
+ {
+ SetName( MarkBase::GenerateNewName(u"__Annotation__") );
+ }
+ }
+
+ AnnotationMark::~AnnotationMark()
+ {
+ }
+
+ void AnnotationMark::InitDoc(SwDoc& io_rDoc,
+ sw::mark::InsertMode const, SwPosition const*const)
+ {
+ SwTextNode *pTextNode = GetMarkEnd().GetNode().GetTextNode();
+ assert(pTextNode);
+ SwTextField *const pTextField = pTextNode->GetFieldTextAttrAt(
+ GetMarkEnd().GetContentIndex()-1, ::sw::GetTextAttrMode::Default);
+ assert(pTextField != nullptr);
+ auto pPostItField
+ = dynamic_cast<const SwPostItField*>(pTextField->GetFormatField().GetField());
+ assert(pPostItField);
+ // use the annotation mark's name as the annotation name, if
+ // - the annotation field has an empty annotation name or
+ // - the annotation mark's name differs (on mark creation a name clash had been detected)
+ if ( pPostItField->GetName().isEmpty()
+ || pPostItField->GetName() != GetName() )
+ {
+ const_cast<SwPostItField*>(pPostItField)->SetName( GetName() );
+ }
+
+ if (io_rDoc.GetIDocumentUndoRedo().DoesUndo())
+ {
+ io_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsBookmark>(*this) );
+ }
+ io_rDoc.getIDocumentState().SetModified();
+ }
+
+ const SwFormatField* AnnotationMark::GetAnnotationFormatField() const
+ {
+ SwDoc& rDoc = GetMarkPos().GetDoc();
+
+ const auto sName = GetName();
+ SwFieldType* pType = rDoc.getIDocumentFieldsAccess().GetFieldType( SwFieldIds::Postit, OUString(), false );
+ std::vector<SwFormatField*> vFields;
+ pType->GatherFields(vFields);
+ auto ppFound = std::find_if(vFields.begin(), vFields.end(), [&sName](SwFormatField* pF)
+ {
+ auto pPF = dynamic_cast<const SwPostItField*>(pF->GetField());
+ return pPF && pPF->GetName() == sName;
+ });
+ return ppFound != vFields.end() ? *ppFound : nullptr;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/bookmark.cxx b/sw/source/core/crsr/bookmark.cxx
new file mode 100644
index 0000000000..24114b141b
--- /dev/null
+++ b/sw/source/core/crsr/bookmark.cxx
@@ -0,0 +1,1329 @@
+/* -*- 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 <bookmark.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentLinksAdministration.hxx>
+#include <IDocumentState.hxx>
+#include <doc.hxx>
+#include <ndtxt.hxx>
+#include <pam.hxx>
+#include <swserv.hxx>
+#include <sfx2/linkmgr.hxx>
+#include <sfx2/viewsh.hxx>
+#include <UndoBookmark.hxx>
+#include <unobookmark.hxx>
+#include <utility>
+#include <xmloff/odffields.hxx>
+#include <libxml/xmlwriter.h>
+#include <comphelper/random.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/anytostring.hxx>
+#include <sal/log.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <edtwin.hxx>
+#include <DateFormFieldButton.hxx>
+#include <DropDownFormFieldButton.hxx>
+#include <DocumentContentOperationsManager.hxx>
+#include <comphelper/lok.hxx>
+#include <txtfrm.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <rtl/strbuf.hxx>
+#include <strings.hrc>
+#include <tools/json_writer.hxx>
+
+using namespace ::sw::mark;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace sw::mark
+{
+
+ SwPosition FindFieldSep(IFieldmark const& rMark)
+ {
+ SwPosition const& rStartPos(rMark.GetMarkStart());
+ SwPosition const& rEndPos(rMark.GetMarkEnd());
+ SwNodes const& rNodes(rStartPos.GetNodes());
+ SwNodeOffset const nStartNode(rStartPos.GetNodeIndex());
+ SwNodeOffset const nEndNode(rEndPos.GetNodeIndex());
+ int nFields(0);
+ std::optional<SwPosition> ret;
+ for (SwNodeOffset n = nEndNode; nStartNode <= n; --n)
+ {
+ SwNode *const pNode(rNodes[n]);
+ if (pNode->IsTextNode())
+ {
+ SwTextNode & rTextNode(*pNode->GetTextNode());
+ sal_Int32 const nStart(n == nStartNode
+ ? rStartPos.GetContentIndex() + 1
+ : 0);
+ sal_Int32 const nEnd(n == nEndNode
+ // subtract 1 to ignore the end char
+ ? rEndPos.GetContentIndex() - 1
+ : rTextNode.Len());
+ for (sal_Int32 i = nEnd; nStart < i; --i)
+ {
+ const sal_Unicode c(rTextNode.GetText()[i - 1]);
+ switch (c)
+ {
+ case CH_TXT_ATR_FIELDSTART:
+ --nFields;
+ assert(0 <= nFields);
+ break;
+ case CH_TXT_ATR_FIELDEND:
+ ++nFields;
+ // fields in field result could happen by manual
+ // editing, although the field update deletes them
+ break;
+ case CH_TXT_ATR_FIELDSEP:
+ if (nFields == 0)
+ {
+ assert(!ret); // one per field
+ ret.emplace(rTextNode, i - 1);
+#ifndef DBG_UTIL
+ return *ret;
+#endif
+ }
+ break;
+ }
+ }
+ }
+ else if (pNode->IsEndNode() && !pNode->StartOfSectionNode()->IsSectionNode())
+ {
+ assert(nStartNode <= pNode->StartOfSectionIndex());
+ // fieldmark cannot overlap node section, unless it's a section
+ n = pNode->StartOfSectionIndex();
+ }
+ else
+ {
+ assert(pNode->IsNoTextNode() || pNode->IsSectionNode()
+ || (pNode->IsEndNode() && pNode->StartOfSectionNode()->IsSectionNode()));
+ }
+ }
+ assert(ret); // must have found it
+ return *ret;
+ }
+} // namespace sw::mark
+
+namespace
+{
+ void lcl_FixPosition(SwPosition& rPos)
+ {
+ // make sure the position has 1) the proper node, and 2) a proper index
+ SwTextNode* pTextNode = rPos.GetNode().GetTextNode();
+ if(pTextNode == nullptr && rPos.GetContentIndex() > 0)
+ {
+ SAL_INFO(
+ "sw.core",
+ "illegal position: " << rPos.GetContentIndex()
+ << " without proper TextNode");
+ rPos.nContent.Assign(nullptr, 0);
+ }
+ else if(pTextNode != nullptr && rPos.GetContentIndex() > pTextNode->Len())
+ {
+ SAL_INFO(
+ "sw.core",
+ "illegal position: " << rPos.GetContentIndex()
+ << " is beyond " << pTextNode->Len());
+ rPos.nContent.Assign(pTextNode, pTextNode->Len());
+ }
+ }
+
+ void lcl_AssertFieldMarksSet(const Fieldmark& rField,
+ const sal_Unicode aStartMark,
+ const sal_Unicode aEndMark)
+ {
+ if (aEndMark != CH_TXT_ATR_FORMELEMENT)
+ {
+ SwPosition const& rStart(rField.GetMarkStart());
+ assert(rStart.GetNode().GetTextNode()->GetText()[rStart.GetContentIndex()] == aStartMark); (void) rStart; (void) aStartMark;
+ SwPosition const sepPos(sw::mark::FindFieldSep(rField));
+ assert(sepPos.GetNode().GetTextNode()->GetText()[sepPos.GetContentIndex()] == CH_TXT_ATR_FIELDSEP); (void) sepPos;
+ }
+ else
+ { // must be m_pPos1 < m_pPos2 because of asymmetric SplitNode update
+ assert(rField.GetMarkPos().GetContentIndex() + 1 == rField.GetOtherMarkPos().GetContentIndex());
+ }
+ SwPosition const& rEnd(rField.GetMarkEnd());
+ assert(rEnd.GetNode().GetTextNode()->GetText()[rEnd.GetContentIndex() - 1] == aEndMark); (void) rEnd;
+ }
+
+ void lcl_SetFieldMarks(Fieldmark& rField,
+ SwDoc& io_rDoc,
+ const sal_Unicode aStartMark,
+ const sal_Unicode aEndMark,
+ SwPosition const*const pSepPos)
+ {
+ io_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr);
+ OUString startChar(aStartMark);
+ if (aEndMark != CH_TXT_ATR_FORMELEMENT
+ && rField.GetMarkStart() == rField.GetMarkEnd())
+ {
+ // do only 1 InsertString call - to expand existing bookmarks at the
+ // position over the whole field instead of just aStartMark
+ startChar += OUStringChar(CH_TXT_ATR_FIELDSEP) + OUStringChar(aEndMark);
+ }
+
+ SwPosition start = rField.GetMarkStart();
+ if (aEndMark != CH_TXT_ATR_FORMELEMENT)
+ {
+ SwPaM aStartPaM(start);
+ io_rDoc.getIDocumentContentOperations().InsertString(aStartPaM, startChar);
+ start.AdjustContent( -startChar.getLength() ); // restore, it was moved by InsertString
+ // do not manipulate via reference directly but call SetMarkStartPos
+ // which works even if start and end pos were the same
+ rField.SetMarkStartPos( start );
+ SwPosition& rEnd = rField.GetMarkEnd(); // note: retrieve after
+ // setting start, because if start==end it can go stale, see SetMarkPos()
+ assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd));
+ if (startChar.getLength() == 1)
+ {
+ *aStartPaM.GetPoint() = pSepPos ? *pSepPos : rEnd;
+ io_rDoc.getIDocumentContentOperations().InsertString(aStartPaM, OUString(CH_TXT_ATR_FIELDSEP));
+ if (!pSepPos || rEnd < *pSepPos)
+ { // rEnd is not moved automatically if it's same as insert pos
+ rEnd.AdjustContent(1);
+ }
+ }
+ assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd));
+ }
+ else
+ {
+ assert(pSepPos == nullptr);
+ }
+
+ SwPosition& rEnd = rField.GetMarkEnd();
+ if (aEndMark && startChar.getLength() == 1)
+ {
+ SwPaM aEndPaM(rEnd);
+ io_rDoc.getIDocumentContentOperations().InsertString(aEndPaM, OUString(aEndMark));
+ if (aEndMark != CH_TXT_ATR_FORMELEMENT)
+ {
+ rEnd.AdjustContent(1); // InsertString didn't move non-empty mark
+ }
+ else
+ { // InsertString moved the mark's end, not its start
+ assert(rField.GetMarkPos().GetContentIndex() + 1 == rField.GetOtherMarkPos().GetContentIndex());
+ }
+ }
+ lcl_AssertFieldMarksSet(rField, aStartMark, aEndMark);
+
+ io_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr);
+ }
+
+ void lcl_RemoveFieldMarks(const Fieldmark& rField,
+ SwDoc& io_rDoc,
+ const sal_Unicode aStartMark,
+ const sal_Unicode aEndMark)
+ {
+ io_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr);
+
+ const SwPosition& rStart = rField.GetMarkStart();
+ SwTextNode const*const pStartTextNode = rStart.GetNode().GetTextNode();
+ assert(pStartTextNode);
+ if (aEndMark != CH_TXT_ATR_FORMELEMENT)
+ {
+ (void) pStartTextNode;
+ // check this before start / end because of the +1 / -1 ...
+ SwPosition const sepPos(sw::mark::FindFieldSep(rField));
+ io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(rStart, aStartMark);
+ io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(sepPos, CH_TXT_ATR_FIELDSEP);
+ }
+
+ const SwPosition& rEnd = rField.GetMarkEnd();
+ SwTextNode *const pEndTextNode = rEnd.GetNode().GetTextNode();
+ assert(pEndTextNode);
+ const sal_Int32 nEndPos = (rEnd == rStart)
+ ? rEnd.GetContentIndex()
+ : rEnd.GetContentIndex() - 1;
+ assert(pEndTextNode->GetText()[nEndPos] == aEndMark);
+ SwPosition const aEnd(*pEndTextNode, nEndPos);
+ io_rDoc.GetDocumentContentOperationsManager().DeleteDummyChar(aEnd, aEndMark);
+
+ io_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr);
+ }
+
+ auto InvalidatePosition(SwPosition const& rPos) -> void
+ {
+ SwUpdateAttr const aHint(rPos.GetContentIndex(), rPos.GetContentIndex(), 0);
+ rPos.GetNode().GetTextNode()->CallSwClientNotify(sw::LegacyModifyHint(&aHint, &aHint));
+ }
+}
+
+namespace sw::mark
+{
+ MarkBase::MarkBase(const SwPaM& aPaM,
+ OUString aName)
+ : m_oPos1(*aPaM.GetPoint())
+ , m_aName(std::move(aName))
+ {
+ m_oPos1->SetMark(this);
+ lcl_FixPosition(*m_oPos1);
+ if (aPaM.HasMark() && (*aPaM.GetMark() != *aPaM.GetPoint()))
+ {
+ MarkBase::SetOtherMarkPos(*(aPaM.GetMark()));
+ lcl_FixPosition(*m_oPos2);
+ }
+ }
+
+ void MarkBase::SetXBookmark(rtl::Reference<SwXBookmark> const& xBkmk)
+ { m_wXBookmark = xBkmk.get(); }
+
+ // For fieldmarks, the CH_TXT_ATR_FIELDSTART and CH_TXT_ATR_FIELDEND
+ // themselves are part of the covered range. This is guaranteed by
+ // TextFieldmark::InitDoc/lcl_AssureFieldMarksSet.
+ bool MarkBase::IsCoveringPosition(const SwPosition& rPos) const
+ {
+ return GetMarkStart() <= rPos && rPos < GetMarkEnd();
+ }
+
+ void MarkBase::SetMarkPos(const SwPosition& rNewPos)
+ {
+ m_oPos1.emplace(rNewPos);
+ m_oPos1->SetMark(this);
+ }
+
+ void MarkBase::SetOtherMarkPos(const SwPosition& rNewPos)
+ {
+ m_oPos2.emplace(rNewPos);
+ m_oPos2->SetMark(this);
+ }
+
+ OUString MarkBase::ToString( ) const
+ {
+ return "Mark: ( Name, [ Node1, Index1 ] ): ( " + m_aName + ", [ "
+ + OUString::number( sal_Int32(GetMarkPos().GetNodeIndex()) ) + ", "
+ + OUString::number( GetMarkPos().GetContentIndex( ) ) + " ] )";
+ }
+
+ void MarkBase::dumpAsXml(xmlTextWriterPtr pWriter) const
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("MarkBase"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(m_aName.toUtf8().getStr()));
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("markPos"));
+ GetMarkPos().dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+ if (IsExpanded())
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("otherMarkPos"));
+ GetOtherMarkPos().dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ MarkBase::~MarkBase()
+ { }
+
+ OUString MarkBase::GenerateNewName(std::u16string_view rPrefix)
+ {
+ static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr);
+
+ if (bHack)
+ {
+ static sal_Int64 nIdCounter = SAL_CONST_INT64(6000000000);
+ return rPrefix + OUString::number(nIdCounter++);
+ }
+ else
+ {
+ static OUString sUniquePostfix;
+ static sal_Int32 nCount = SAL_MAX_INT32;
+ if(nCount == SAL_MAX_INT32)
+ {
+ unsigned int const n(comphelper::rng::uniform_uint_distribution(0,
+ std::numeric_limits<unsigned int>::max()));
+ sUniquePostfix = "_" + OUString::number(n);
+ nCount = 0;
+ }
+ // putting the counter in front of the random parts will speed up string comparisons
+ return rPrefix + OUString::number(nCount++) + sUniquePostfix;
+ }
+ }
+
+ void MarkBase::SwClientNotify(const SwModify&, const SfxHint& rHint)
+ {
+ CallSwClientNotify(rHint);
+ if (rHint.GetId() != SfxHintId::SwLegacyModify)
+ return;
+ auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ if(RES_REMOVE_UNO_OBJECT == pLegacy->GetWhich())
+ { // invalidate cached uno object
+ SetXBookmark(nullptr);
+ }
+ }
+
+ auto MarkBase::InvalidateFrames() -> void
+ {
+ }
+
+ NavigatorReminder::NavigatorReminder(const SwPaM& rPaM)
+ : MarkBase(rPaM, MarkBase::GenerateNewName(u"__NavigatorReminder__"))
+ { }
+
+ UnoMark::UnoMark(const SwPaM& aPaM)
+ : MarkBase(aPaM, MarkBase::GenerateNewName(u"__UnoMark__"))
+ { }
+
+ DdeBookmark::DdeBookmark(const SwPaM& aPaM)
+ : MarkBase(aPaM, MarkBase::GenerateNewName(u"__DdeLink__"))
+ { }
+
+ void DdeBookmark::SetRefObject(SwServerObject* pObj)
+ {
+ m_aRefObj = pObj;
+ }
+
+ void DdeBookmark::DeregisterFromDoc(SwDoc& rDoc)
+ {
+ if(m_aRefObj.is())
+ rDoc.getIDocumentLinksAdministration().GetLinkManager().RemoveServer(m_aRefObj.get());
+ }
+
+ DdeBookmark::~DdeBookmark()
+ {
+ if( m_aRefObj.is() )
+ {
+ if(m_aRefObj->HasDataLinks())
+ {
+ ::sfx2::SvLinkSource* p = m_aRefObj.get();
+ p->SendDataChanged();
+ }
+ m_aRefObj->SetNoServer();
+ }
+ }
+
+ Bookmark::Bookmark(const SwPaM& aPaM,
+ const vcl::KeyCode& rCode,
+ const OUString& rName)
+ : DdeBookmark(aPaM)
+ , m_aCode(rCode)
+ , m_bHidden(false)
+ {
+ m_aName = rName;
+ }
+
+ void Bookmark::sendLOKDeleteCallback()
+ {
+ if (!comphelper::LibreOfficeKit::isActive() || GetMarkPos().GetDoc().IsClipBoard())
+ return;
+
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ if (!pViewShell)
+ return;
+
+ OUString fieldCommand = GetName();
+ tools::JsonWriter aJson;
+ aJson.put("commandName", ".uno:DeleteBookmark");
+ aJson.put("success", true);
+ {
+ auto result = aJson.startNode("result");
+ aJson.put("DeleteBookmark", fieldCommand);
+ }
+
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString());
+ }
+
+ void Bookmark::InitDoc(SwDoc& io_rDoc,
+ sw::mark::InsertMode const, SwPosition const*const)
+ {
+ if (io_rDoc.GetIDocumentUndoRedo().DoesUndo())
+ {
+ io_rDoc.GetIDocumentUndoRedo().AppendUndo(
+ std::make_unique<SwUndoInsBookmark>(*this));
+ }
+ io_rDoc.getIDocumentState().SetModified();
+ InvalidateFrames();
+ }
+
+ void Bookmark::DeregisterFromDoc(SwDoc& io_rDoc)
+ {
+ DdeBookmark::DeregisterFromDoc(io_rDoc);
+
+ if (io_rDoc.GetIDocumentUndoRedo().DoesUndo())
+ {
+ io_rDoc.GetIDocumentUndoRedo().AppendUndo(
+ std::make_unique<SwUndoDeleteBookmark>(*this));
+ }
+ io_rDoc.getIDocumentState().SetModified();
+ InvalidateFrames();
+ }
+
+ // invalidate text frames in case it's hidden or Formatting Marks enabled
+ auto Bookmark::InvalidateFrames() -> void
+ {
+ InvalidatePosition(GetMarkPos());
+ if (IsExpanded())
+ {
+ InvalidatePosition(GetOtherMarkPos());
+ }
+ }
+
+ void Bookmark::Hide(bool const isHide)
+ {
+ if (isHide != m_bHidden)
+ {
+ m_bHidden = isHide;
+ InvalidateFrames();
+ }
+ }
+
+ void Bookmark::SetHideCondition(OUString const& rHideCondition)
+ {
+ if (m_sHideCondition != rHideCondition)
+ {
+ m_sHideCondition = rHideCondition;
+ // don't eval condition here yet - probably only needed for
+ // UI editing condition and that doesn't exist yet
+ }
+ }
+
+ ::sfx2::IXmlIdRegistry& Bookmark::GetRegistry()
+ {
+ SwDoc& rDoc( GetMarkPos().GetDoc() );
+ return rDoc.GetXmlIdRegistry();
+ }
+
+ bool Bookmark::IsInClipboard() const
+ {
+ SwDoc& rDoc( GetMarkPos().GetDoc() );
+ return rDoc.IsClipBoard();
+ }
+
+ bool Bookmark::IsInUndo() const
+ {
+ return false;
+ }
+
+ bool Bookmark::IsInContent() const
+ {
+ SwDoc& rDoc( GetMarkPos().GetDoc() );
+ return !rDoc.IsInHeaderFooter( GetMarkPos().GetNode() );
+ }
+
+ uno::Reference< rdf::XMetadatable > Bookmark::MakeUnoObject()
+ {
+ SwDoc& rDoc( GetMarkPos().GetDoc() );
+ const uno::Reference< rdf::XMetadatable> xMeta(
+ SwXBookmark::CreateXBookmark(rDoc, this) );
+ return xMeta;
+ }
+
+ Fieldmark::Fieldmark(const SwPaM& rPaM)
+ : MarkBase(rPaM, MarkBase::GenerateNewName(u"__Fieldmark__"))
+ {
+ if(!IsExpanded())
+ SetOtherMarkPos(GetMarkPos());
+ }
+
+ void Fieldmark::SetMarkStartPos( const SwPosition& rNewStartPos )
+ {
+ if ( GetMarkPos( ) <= GetOtherMarkPos( ) )
+ return SetMarkPos( rNewStartPos );
+ else
+ return SetOtherMarkPos( rNewStartPos );
+ }
+
+ OUString Fieldmark::ToString( ) const
+ {
+ return "Fieldmark: ( Name, Type, [ Nd1, Id1 ], [ Nd2, Id2 ] ): ( " + m_aName + ", "
+ + m_aFieldname + ", [ " + OUString::number( sal_Int32(GetMarkPos().GetNodeIndex( )) )
+ + ", " + OUString::number( GetMarkPos( ).GetContentIndex( ) ) + " ], ["
+ + OUString::number( sal_Int32(GetOtherMarkPos().GetNodeIndex( )) ) + ", "
+ + OUString::number( GetOtherMarkPos( ).GetContentIndex( ) ) + " ] ) ";
+ }
+
+ void Fieldmark::Invalidate( )
+ {
+ // TODO: Does exist a better solution to trigger a format of the
+ // fieldmark portion? If yes, please use it.
+ SwPaM aPaM( GetMarkPos(), GetOtherMarkPos() );
+ aPaM.InvalidatePaM();
+ }
+
+ void Fieldmark::dumpAsXml(xmlTextWriterPtr pWriter) const
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("Fieldmark"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldname"), BAD_CAST(m_aFieldname.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldHelptext"), BAD_CAST(m_aFieldHelptext.toUtf8().getStr()));
+ MarkBase::dumpAsXml(pWriter);
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("parameters"));
+ for (auto& rParam : m_vParams)
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("parameter"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(rParam.first.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(comphelper::anyToString(rParam.second).toUtf8().getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ TextFieldmark::TextFieldmark(const SwPaM& rPaM, const OUString& rName)
+ : Fieldmark(rPaM)
+ , m_pDocumentContentOperationsManager(nullptr)
+ {
+ if ( !rName.isEmpty() )
+ m_aName = rName;
+ }
+
+ TextFieldmark::~TextFieldmark()
+ {
+ if (!comphelper::LibreOfficeKit::isActive() || GetMarkPos().GetDoc().IsClipBoard())
+ return;
+
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ if (!pViewShell)
+ return;
+
+ OUString fieldCommand;
+ (*GetParameters())[ODF_CODE_PARAM] >>= fieldCommand;
+ tools::JsonWriter aJson;
+ aJson.put("commandName", ".uno:DeleteTextFormField");
+ aJson.put("success", true);
+ {
+ auto result = aJson.startNode("result");
+ aJson.put("DeleteTextFormField", fieldCommand);
+ }
+
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString());
+ }
+
+ void TextFieldmark::InitDoc(SwDoc& io_rDoc,
+ sw::mark::InsertMode const eMode, SwPosition const*const pSepPos)
+ {
+ m_pDocumentContentOperationsManager = &io_rDoc.GetDocumentContentOperationsManager();
+ if (eMode == sw::mark::InsertMode::New)
+ {
+ lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos);
+ }
+ else
+ {
+ lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
+ }
+ }
+
+ void TextFieldmark::ReleaseDoc(SwDoc& rDoc)
+ {
+ IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo());
+ if (rIDUR.DoesUndo())
+ {
+ rIDUR.AppendUndo(std::make_unique<SwUndoDelTextFieldmark>(*this));
+ }
+ ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
+ lcl_RemoveFieldMarks(*this, rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
+ // notify layouts to unhide - for the entire fieldmark, as in InitDoc()
+ SwPaM const tmp(GetMarkPos(), GetOtherMarkPos());
+ sw::UpdateFramesForRemoveDeleteRedline(rDoc, tmp);
+ }
+
+ OUString TextFieldmark::GetContent() const
+ {
+ const SwTextNode& rTextNode = *GetMarkEnd().GetNode().GetTextNode();
+ SwPosition const sepPos(sw::mark::FindFieldSep(*this));
+ const sal_Int32 nStart(sepPos.GetContentIndex());
+ const sal_Int32 nEnd(GetMarkEnd().GetContentIndex());
+
+ OUString sContent;
+ const sal_Int32 nLen = rTextNode.GetText().getLength();
+ if (nStart + 1 < nLen && nEnd <= nLen && nEnd > nStart + 2)
+ sContent = rTextNode.GetText().copy(nStart + 1, nEnd - nStart - 2);
+
+ return sContent;
+ }
+
+ void TextFieldmark::ReplaceContent(const OUString& sNewContent)
+ {
+ if (!m_pDocumentContentOperationsManager)
+ return;
+
+ SwPosition const sepPos(sw::mark::FindFieldSep(*this));
+ const sal_Int32 nStart(sepPos.GetContentIndex());
+ const sal_Int32 nEnd(GetMarkEnd().GetContentIndex());
+
+ const sal_Int32 nLen = GetMarkEnd().GetNode().GetTextNode()->GetText().getLength();
+ if (nStart + 1 < nLen && nEnd <= nLen && nEnd > nStart + 2)
+ {
+ SwPaM aFieldPam(GetMarkStart().GetNode(), nStart + 1,
+ GetMarkStart().GetNode(), nEnd - 1);
+ m_pDocumentContentOperationsManager->ReplaceRange(aFieldPam, sNewContent, false);
+ }
+ else
+ {
+ SwPaM aFieldStartPam(GetMarkStart().GetNode(), nStart + 1);
+ m_pDocumentContentOperationsManager->InsertString(aFieldStartPam, sNewContent);
+ }
+ Invalidate();
+ }
+
+ NonTextFieldmark::NonTextFieldmark(const SwPaM& rPaM)
+ : Fieldmark(rPaM)
+ { }
+
+ void NonTextFieldmark::InitDoc(SwDoc& io_rDoc,
+ sw::mark::InsertMode const eMode, SwPosition const*const pSepPos)
+ {
+ assert(pSepPos == nullptr);
+ if (eMode == sw::mark::InsertMode::New)
+ {
+ lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT, pSepPos);
+ }
+ else
+ {
+ lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT);
+ }
+ }
+
+ void NonTextFieldmark::ReleaseDoc(SwDoc& rDoc)
+ {
+ IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo());
+ if (rIDUR.DoesUndo())
+ {
+ rIDUR.AppendUndo(std::make_unique<SwUndoDelNoTextFieldmark>(*this));
+ }
+ ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
+ lcl_RemoveFieldMarks(*this, rDoc,
+ CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT);
+ }
+
+
+ CheckboxFieldmark::CheckboxFieldmark(const SwPaM& rPaM, const OUString& rName)
+ : NonTextFieldmark(rPaM)
+ {
+ if (!rName.isEmpty())
+ m_aName = rName;
+ }
+
+ void CheckboxFieldmark::SetChecked(bool checked)
+ {
+ if ( IsChecked() != checked )
+ {
+ (*GetParameters())[ODF_FORMCHECKBOX_RESULT] <<= checked;
+ // mark document as modified
+ SwDoc& rDoc( GetMarkPos().GetDoc() );
+ rDoc.getIDocumentState().SetModified();
+ }
+ }
+
+ bool CheckboxFieldmark::IsChecked() const
+ {
+ bool bResult = false;
+ parameter_map_t::const_iterator pResult = GetParameters()->find(ODF_FORMCHECKBOX_RESULT);
+ if(pResult != GetParameters()->end())
+ pResult->second >>= bResult;
+ return bResult;
+ }
+
+ OUString CheckboxFieldmark::GetContent() const
+ {
+ return IsChecked() ? "1" : "0";
+ }
+
+ void CheckboxFieldmark::ReplaceContent(const OUString& sNewContent)
+ {
+ SetChecked(sNewContent.toBoolean());
+ Invalidate();
+ }
+
+ FieldmarkWithDropDownButton::FieldmarkWithDropDownButton(const SwPaM& rPaM)
+ : NonTextFieldmark(rPaM)
+ , m_pButton(nullptr)
+ {
+ }
+
+ FieldmarkWithDropDownButton::~FieldmarkWithDropDownButton()
+ {
+ m_pButton.disposeAndClear();
+ }
+
+ void FieldmarkWithDropDownButton::RemoveButton()
+ {
+ if(m_pButton)
+ m_pButton.disposeAndClear();
+ }
+
+ void FieldmarkWithDropDownButton::LaunchPopup()
+ {
+ if (!m_pButton)
+ return;
+
+ m_pButton->Invalidate();
+ m_pButton->LaunchPopup();
+ }
+
+ DropDownFieldmark::DropDownFieldmark(const SwPaM& rPaM, const OUString& rName)
+ : FieldmarkWithDropDownButton(rPaM)
+ {
+ if (!rName.isEmpty())
+ m_aName = rName;
+ }
+
+ DropDownFieldmark::~DropDownFieldmark()
+ {
+ }
+
+ void DropDownFieldmark::ShowButton(SwEditWin* pEditWin)
+ {
+ if(pEditWin)
+ {
+ if(!m_pButton)
+ m_pButton = VclPtr<DropDownFormFieldButton>::Create(pEditWin, *this);
+ m_pButton->CalcPosAndSize(m_aPortionPaintArea);
+ m_pButton->Show();
+ }
+ }
+
+ void DropDownFieldmark::RemoveButton()
+ {
+ FieldmarkWithDropDownButton::RemoveButton();
+ }
+
+ /** GetContent
+ * @param pIndex The zero-based index to retrieve
+ * [in] if pIndex is null or negative, return the listbox's chosen result,
+ * else return the indicated entry (or last entry for invalid choice).
+ * [out] the index of the returned result or -1 if error
+ */
+ OUString DropDownFieldmark::GetContent(sal_Int32* pIndex) const
+ {
+ sal_Int32 nIndex = pIndex ? *pIndex : -1;
+ auto rParameters = *GetParameters();
+ if (nIndex < 0)
+ rParameters[ODF_FORMDROPDOWN_RESULT] >>= nIndex;
+
+ uno::Sequence<OUString> aSeq;
+ rParameters[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq;
+ nIndex = std::min(nIndex, aSeq.getLength() - 1);
+
+ if (nIndex < 0)
+ {
+ if (pIndex)
+ *pIndex = -1;
+ return OUString();
+ }
+
+ if (pIndex)
+ *pIndex = nIndex;
+
+ return aSeq[nIndex];
+ }
+
+ OUString DropDownFieldmark::GetContent() const
+ {
+ return GetContent(nullptr);
+ }
+
+ /** AddContent : INSERTS a new choice
+ * @param rText: The choice to add to the list choices.
+ *
+ * @param pIndex [optional]
+ * [in] If pIndex is null or invalid, append to the end of the list.
+ * [out] Modified to point to the position of the choice if it already exists.
+ */
+ void DropDownFieldmark::AddContent(const OUString& rText, sal_Int32* pIndex)
+ {
+ uno::Sequence<OUString> aSeq;
+ sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq;
+
+ // no duplicates: if it already exists, modify the given index to point to it
+ const sal_Int32 nCurrentTextPos = comphelper::findValue(aSeq, rText);
+ if (nCurrentTextPos != -1)
+ {
+ if (pIndex)
+ *pIndex = nCurrentTextPos;
+ return;
+ }
+
+ const sal_Int32 nLen = aSeq.getLength();
+ const sal_Int32 nNewPos = pIndex && *pIndex > -1 ? std::min(*pIndex, nLen) : nLen;
+
+ // need to shift list result index up if adding new entry before it
+ sal_Int32 nResultIndex = -1;
+ (*pParameters)[ODF_FORMDROPDOWN_RESULT] >>= nResultIndex;
+ if (nNewPos <= nResultIndex)
+ (*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= nResultIndex + 1;
+
+ auto aList = comphelper::sequenceToContainer<std::vector<OUString>>(aSeq);
+ if (nNewPos < nLen)
+ aList.insert(aList.begin() + nNewPos, rText);
+ else
+ {
+ if (pIndex)
+ *pIndex = nLen;
+ aList.push_back(rText);
+ }
+
+ (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= comphelper::containerToSequence(aList);
+ Invalidate();
+ }
+
+ /**
+ * ReplaceContent : changes the list result index or renames the existing choices
+ * @param pText
+ * [in] If pIndex is null, change the list result index to this provided choice
+ * (but do nothing if pText is an invalid choice)
+ * else rename that entry.
+ *
+ * @param pIndex
+ * [in] If pText is null, change the list result index to this provided Index
+ * (or the last position if it is an invalid choice)
+ * else rename this entry (doing nothing for invalid indexes).
+ * [out] If pIndex is invalid, it is modified to use the last position.
+ *
+ * This function allows duplicate entries - which is also allowed in MS Word.
+ */
+ void DropDownFieldmark::ReplaceContent(const OUString* pText, sal_Int32* pIndex)
+ {
+ if (!pIndex && !pText)
+ return;
+
+ uno::Sequence<OUString> aSeq;
+ sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq;
+ const sal_Int32 nLen = aSeq.getLength();
+
+ if (!pText)
+ {
+ if (*pIndex < 0 || *pIndex >= nLen)
+ *pIndex = nLen - 1;
+
+ // select pIndex as the new value for the list box
+ (*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= *pIndex;
+ Invalidate();
+ return;
+ }
+
+ if (!pIndex)
+ {
+ const sal_Int32 nNewPos = comphelper::findValue(aSeq, *pText);
+ if (nNewPos != -1)
+ {
+ (*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= nNewPos;
+ Invalidate();
+ }
+ return;
+ }
+
+ if (*pIndex > -1 && *pIndex < nLen)
+ {
+ auto aList = comphelper::sequenceToContainer<std::vector<OUString>>(aSeq);
+ aList[*pIndex] = *pText;
+ (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= comphelper::containerToSequence(aList);
+ Invalidate();
+ }
+ }
+
+ void DropDownFieldmark::ReplaceContent(const OUString& rNewContent)
+ {
+ ReplaceContent(&rNewContent, nullptr);
+ }
+
+ /**
+ * Remove everything if the given index is negative, else remove the given index (if valid).
+ * If deleting the currently selected choice, reset the selection to the first choice.
+ */
+ void DropDownFieldmark::DelContent(sal_Int32 nDelIndex)
+ {
+ sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ uno::Sequence<OUString> aSeq;
+ if (nDelIndex < 0)
+ {
+ pParameters->erase(ODF_FORMDROPDOWN_RESULT);
+ (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= aSeq;
+ Invalidate();
+ return;
+ }
+
+ (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] >>= aSeq;
+ if (nDelIndex >= aSeq.getLength())
+ return;
+
+ // If deleting the current choice, select the first entry instead
+ // else need to shift list result index down if deleting an entry before it
+ sal_Int32 nResultIndex = -1;
+ (*pParameters)[ODF_FORMDROPDOWN_RESULT] >>= nResultIndex;
+ if (nDelIndex == nResultIndex)
+ nResultIndex = 0;
+ else if (nDelIndex < nResultIndex)
+ --nResultIndex;
+
+ comphelper::removeElementAt(aSeq, nDelIndex);
+ if (nResultIndex != -1)
+ (*pParameters)[ODF_FORMDROPDOWN_RESULT] <<= nResultIndex;
+ (*pParameters)[ODF_FORMDROPDOWN_LISTENTRY] <<= aSeq;
+ Invalidate();
+ }
+
+ void DropDownFieldmark::SetPortionPaintArea(const SwRect& rPortionPaintArea)
+ {
+ m_aPortionPaintArea = rPortionPaintArea;
+ if(m_pButton)
+ {
+ m_pButton->Show();
+ m_pButton->CalcPosAndSize(m_aPortionPaintArea);
+ }
+ }
+
+ void DropDownFieldmark::SendLOKShowMessage(const SfxViewShell* pViewShell)
+ {
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ if (!pViewShell || pViewShell->isLOKMobilePhone())
+ return;
+
+ if (m_aPortionPaintArea.IsEmpty())
+ return;
+
+ OStringBuffer sPayload;
+ sPayload = OString::Concat("{\"action\": \"show\","
+ " \"type\": \"drop-down\", \"textArea\": \"") +
+ m_aPortionPaintArea.SVRect().toString() + "\",";
+ // Add field params to the message
+ sPayload.append(" \"params\": { \"items\": [");
+
+ // List items
+ auto pParameters = this->GetParameters();
+ auto pListEntriesIter = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
+ css::uno::Sequence<OUString> vListEntries;
+ if (pListEntriesIter != pParameters->end())
+ {
+ pListEntriesIter->second >>= vListEntries;
+ for (const OUString& sItem : std::as_const(vListEntries))
+ sPayload.append("\"" + OUStringToOString(sItem, RTL_TEXTENCODING_UTF8) + "\", ");
+ sPayload.setLength(sPayload.getLength() - 2);
+ }
+ sPayload.append("], ");
+
+ // Selected item
+ auto pSelectedItemIter = pParameters->find(ODF_FORMDROPDOWN_RESULT);
+ sal_Int32 nSelection = -1;
+ if (pSelectedItemIter != pParameters->end())
+ {
+ pSelectedItemIter->second >>= nSelection;
+ }
+ sPayload.append("\"selected\": \"" + OString::number(nSelection) + "\", ");
+
+ // Placeholder text
+ sPayload.append("\"placeholderText\": \"" + OUStringToOString(SwResId(STR_DROP_DOWN_EMPTY_LIST), RTL_TEXTENCODING_UTF8) + "\"}}");
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON, sPayload.toString());
+ }
+
+ void DropDownFieldmark::SendLOKHideMessage(const SfxViewShell* pViewShell)
+ {
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON,
+ "{\"action\": \"hide\", \"type\": \"drop-down\"}"_ostr);
+ }
+
+ DateFieldmark::DateFieldmark(const SwPaM& rPaM)
+ : FieldmarkWithDropDownButton(rPaM)
+ , m_pNumberFormatter(nullptr)
+ , m_pDocumentContentOperationsManager(nullptr)
+ {
+ }
+
+ DateFieldmark::~DateFieldmark()
+ {
+ }
+
+ void DateFieldmark::InitDoc(SwDoc& io_rDoc,
+ sw::mark::InsertMode eMode, SwPosition const*const pSepPos)
+ {
+ m_pNumberFormatter = io_rDoc.GetNumberFormatter();
+ m_pDocumentContentOperationsManager = &io_rDoc.GetDocumentContentOperationsManager();
+ if (eMode == sw::mark::InsertMode::New)
+ {
+ lcl_SetFieldMarks(*this, io_rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos);
+ }
+ else
+ {
+ lcl_AssertFieldMarksSet(*this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
+ }
+ }
+
+ void DateFieldmark::ReleaseDoc(SwDoc& rDoc)
+ {
+ IDocumentUndoRedo & rIDUR(rDoc.GetIDocumentUndoRedo());
+ if (rIDUR.DoesUndo())
+ {
+ // TODO does this need a 3rd Undo class?
+ rIDUR.AppendUndo(std::make_unique<SwUndoDelTextFieldmark>(*this));
+ }
+ ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes
+ lcl_RemoveFieldMarks(*this, rDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND);
+ // notify layouts to unhide - for the entire fieldmark, as in InitDoc()
+ SwPaM const tmp(GetMarkPos(), GetOtherMarkPos());
+ sw::UpdateFramesForRemoveDeleteRedline(rDoc, tmp);
+ }
+
+ void DateFieldmark::ShowButton(SwEditWin* pEditWin)
+ {
+ if(pEditWin)
+ {
+ if(!m_pButton)
+ m_pButton = VclPtr<DateFormFieldButton>::Create(pEditWin, *this, m_pNumberFormatter);
+ SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight());
+ m_pButton->CalcPosAndSize(aPaintArea);
+ m_pButton->Show();
+ }
+ }
+
+ void DateFieldmark::SetPortionPaintAreaStart(const SwRect& rPortionPaintArea)
+ {
+ if (rPortionPaintArea.IsEmpty())
+ return;
+
+ m_aPaintAreaStart = rPortionPaintArea;
+ InvalidateCurrentDateParam();
+ }
+
+ void DateFieldmark::SetPortionPaintAreaEnd(const SwRect& rPortionPaintArea)
+ {
+ if (rPortionPaintArea.IsEmpty())
+ return;
+
+ if(m_aPaintAreaEnd == rPortionPaintArea &&
+ m_pButton && m_pButton->IsVisible())
+ return;
+
+ m_aPaintAreaEnd = rPortionPaintArea;
+ if(m_pButton)
+ {
+ m_pButton->Show();
+ SwRect aPaintArea(m_aPaintAreaStart.TopLeft(), m_aPaintAreaEnd.BottomRight());
+ m_pButton->CalcPosAndSize(aPaintArea);
+ m_pButton->Invalidate();
+ }
+ InvalidateCurrentDateParam();
+ }
+
+ OUString DateFieldmark::GetContent() const
+ {
+ const SwTextNode* const pTextNode = GetMarkEnd().GetNode().GetTextNode();
+ SwPosition const sepPos(sw::mark::FindFieldSep(*this));
+ const sal_Int32 nStart(sepPos.GetContentIndex());
+ const sal_Int32 nEnd (GetMarkEnd().GetContentIndex());
+
+ OUString sContent;
+ if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() &&
+ nEnd > nStart + 2)
+ sContent = pTextNode->GetText().copy(nStart + 1, nEnd - nStart - 2);
+ return sContent;
+ }
+
+ void DateFieldmark::ReplaceContent(const OUString& sNewContent)
+ {
+ if(!m_pDocumentContentOperationsManager)
+ return;
+
+ const SwTextNode* const pTextNode = GetMarkEnd().GetNode().GetTextNode();
+ SwPosition const sepPos(sw::mark::FindFieldSep(*this));
+ const sal_Int32 nStart(sepPos.GetContentIndex());
+ const sal_Int32 nEnd (GetMarkEnd().GetContentIndex());
+
+ if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() &&
+ nEnd > nStart + 2)
+ {
+ SwPaM aFieldPam(GetMarkStart().GetNode(), nStart + 1,
+ GetMarkStart().GetNode(), nEnd - 1);
+ m_pDocumentContentOperationsManager->ReplaceRange(aFieldPam, sNewContent, false);
+ }
+ else
+ {
+ SwPaM aFieldStartPam(GetMarkStart().GetNode(), nStart + 1);
+ m_pDocumentContentOperationsManager->InsertString(aFieldStartPam, sNewContent);
+ }
+
+ }
+
+ std::pair<bool, double> DateFieldmark::GetCurrentDate() const
+ {
+ // Check current date param first
+ std::pair<bool, double> aResult = ParseCurrentDateParam();
+ if(aResult.first)
+ return aResult;
+
+ const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ bool bFoundValidDate = false;
+ double dCurrentDate = 0;
+ OUString sDateFormat;
+ auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sDateFormat;
+ }
+
+ OUString sLang;
+ pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sLang;
+ }
+
+ // Get current content of the field
+ OUString sContent = GetContent();
+
+ sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType());
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType;
+ m_pNumberFormatter->PutEntry(sDateFormat,
+ nCheckPos,
+ nType,
+ nFormat,
+ LanguageTag(sLang).getLanguageType());
+ }
+
+ if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sContent, nFormat, dCurrentDate);
+ }
+ return std::pair<bool, double>(bFoundValidDate, dCurrentDate);
+ }
+
+ void DateFieldmark::SetCurrentDate(double fDate)
+ {
+ // Replace current content with the selected date
+ ReplaceContent(GetDateInCurrentDateFormat(fDate));
+
+ // Also save the current date in a standard format
+ sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ (*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= GetDateInStandardDateFormat(fDate);
+ }
+
+ OUString DateFieldmark::GetDateInStandardDateFormat(double fDate) const
+ {
+ OUString sCurrentDate;
+ sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE);
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType;
+ OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT;
+ m_pNumberFormatter->PutEntry(sFormat,
+ nCheckPos,
+ nType,
+ nFormat,
+ ODF_FORMDATE_CURRENTDATE_LANGUAGE);
+ }
+
+ if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ const Color* pCol = nullptr;
+ m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentDate, &pCol, false);
+ }
+ return sCurrentDate;
+ }
+
+ std::pair<bool, double> DateFieldmark::ParseCurrentDateParam() const
+ {
+ bool bFoundValidDate = false;
+ double dCurrentDate = 0;
+
+ const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ auto pResult = pParameters->find(ODF_FORMDATE_CURRENTDATE);
+ OUString sCurrentDate;
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sCurrentDate;
+ }
+ if(!sCurrentDate.isEmpty())
+ {
+ sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(ODF_FORMDATE_CURRENTDATE_FORMAT, ODF_FORMDATE_CURRENTDATE_LANGUAGE);
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType;
+ OUString sFormat = ODF_FORMDATE_CURRENTDATE_FORMAT;
+ m_pNumberFormatter->PutEntry(sFormat,
+ nCheckPos,
+ nType,
+ nFormat,
+ ODF_FORMDATE_CURRENTDATE_LANGUAGE);
+ }
+
+ if(nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ bFoundValidDate = m_pNumberFormatter->IsNumberFormat(sCurrentDate, nFormat, dCurrentDate);
+ }
+ }
+ return std::pair<bool, double>(bFoundValidDate, dCurrentDate);
+ }
+
+
+ OUString DateFieldmark::GetDateInCurrentDateFormat(double fDate) const
+ {
+ // Get current date format and language
+ OUString sDateFormat;
+ const sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ auto pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT);
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sDateFormat;
+ }
+
+ OUString sLang;
+ pResult = pParameters->find(ODF_FORMDATE_DATEFORMAT_LANGUAGE);
+ if (pResult != pParameters->end())
+ {
+ pResult->second >>= sLang;
+ }
+
+ // Fill the content with the specified format
+ OUString sCurrentContent;
+ sal_uInt32 nFormat = m_pNumberFormatter->GetEntryKey(sDateFormat, LanguageTag(sLang).getLanguageType());
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType;
+ OUString sFormat = sDateFormat;
+ m_pNumberFormatter->PutEntry(sFormat,
+ nCheckPos,
+ nType,
+ nFormat,
+ LanguageTag(sLang).getLanguageType());
+ }
+
+ if (nFormat != NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ const Color* pCol = nullptr;
+ m_pNumberFormatter->GetOutputString(fDate, nFormat, sCurrentContent, &pCol, false);
+ }
+ return sCurrentContent;
+ }
+
+ void DateFieldmark::InvalidateCurrentDateParam()
+ {
+ std::pair<bool, double> aResult = ParseCurrentDateParam();
+ if(!aResult.first)
+ return;
+
+ // Current date became invalid
+ if(GetDateInCurrentDateFormat(aResult.second) != GetContent())
+ {
+ sw::mark::IFieldmark::parameter_map_t* pParameters = GetParameters();
+ (*pParameters)[ODF_FORMDATE_CURRENTDATE] <<= OUString();
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/callnk.cxx b/sw/source/core/crsr/callnk.cxx
new file mode 100644
index 0000000000..e3f08add1e
--- /dev/null
+++ b/sw/source/core/crsr/callnk.cxx
@@ -0,0 +1,271 @@
+/* -*- 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/i18n/XBreakIterator.hpp>
+#include <osl/diagnose.h>
+#include <fmtcntnt.hxx>
+#include <txatbase.hxx>
+#include "callnk.hxx"
+#include <crsrsh.hxx>
+#include <doc.hxx>
+#include <frmfmt.hxx>
+#include <txtfrm.hxx>
+#include <rowfrm.hxx>
+#include <fmtfsize.hxx>
+#include <ndtxt.hxx>
+#include <flyfrm.hxx>
+#include <breakit.hxx>
+#include <UndoTable.hxx>
+
+SwCallLink::SwCallLink( SwCursorShell & rSh )
+ : m_rShell( rSh )
+{
+ // remember SPoint-values of current cursor
+ SwPaM* pCursor = m_rShell.IsTableMode() ? m_rShell.GetTableCrs() : m_rShell.GetCursor();
+ SwNode& rNd = pCursor->GetPoint()->GetNode();
+ m_nNode = rNd.GetIndex();
+ m_nContent = pCursor->GetPoint()->GetContentIndex();
+ m_nNodeType = rNd.GetNodeType();
+ m_bHasSelection = ( *pCursor->GetPoint() != *pCursor->GetMark() );
+
+ if( rNd.IsTextNode() )
+ m_nLeftFramePos = SwCallLink::getLayoutFrame( m_rShell.GetLayout(), *rNd.GetTextNode(), m_nContent,
+ !m_rShell.ActionPend() );
+ else
+ {
+ m_nLeftFramePos = 0;
+
+ // A special treatment for SwFeShell:
+ // When deleting the header/footer, footnotes SwFeShell sets the
+ // Cursor to NULL (Node + Content).
+ // If the Cursor is not on a ContentNode (ContentNode) this fact gets
+ // saved in nNdType.
+ if( SwNodeType::ContentMask & m_nNodeType )
+ m_nNodeType = SwNodeType::NONE;
+ }
+}
+
+namespace sw {
+
+/**
+ An empty paragraph inside a table with a nested table preceding it
+ should be hidden, unless the cursor is positioned in the paragraph.
+
+ If the cursor is now (or was previously) inside such a paragraph,
+ send a size change notification on the row frame to force reformatting.
+ */
+void NotifyTableCollapsedParagraph(const SwContentNode *const pNode, SwCursorShell *const pShell)
+{
+ if ( !pNode )
+ return;
+
+ SwFrame *const pMyFrame = pNode->getLayoutFrame(pShell ? pShell->GetLayout() : nullptr);
+ if ( !pMyFrame )
+ return;
+
+ // important: only invalidate layout if something is actually hidden or
+ // shown! Otherwise performance is going to suffer with "difficult" tables.
+ if (!pMyFrame->IsCollapse())
+ return;
+
+ SwRowFrame *const pRow = pMyFrame->FindRowFrame();
+ if ( !pRow )
+ return;
+
+ const SwTableLine* pLine = pRow->GetTabLine( );
+
+ if (pShell && (pShell->IsTableMode() || (pShell->StartsWith_() != SwCursorShell::StartsWith::None && pShell->ExtendedSelectedAll())))
+ {
+ // If we have a table selection, then avoid the notification: it's not necessary (the text
+ // cursor needs no updating) and the notification may kill the selection overlay, leading to
+ // flicker.
+ // Same for whole-document selection when it starts with a table.
+ return;
+ }
+
+ // notify a change in frame size to force reformatting of the row
+ const SwFormatFrameSize aSize = pLine->GetFrameFormat()->GetFrameSize();
+ pRow->OnFrameSize(aSize);
+}
+
+} // namespace sw
+
+void SwCallLink::ImplDestroy()
+{
+ if( m_nNodeType == SwNodeType::NONE || !m_rShell.m_bCallChgLnk ) // see ctor
+ return ;
+
+ // If travelling over Nodes check formats and register them anew at the
+ // new Node.
+ SwPaM* pCurrentCursor = m_rShell.IsTableMode() ? m_rShell.GetTableCrs() : m_rShell.GetCursor();
+ SwContentNode * pCNd = pCurrentCursor->GetPointContentNode();
+ if( !pCNd )
+ return;
+
+ if (pCNd->GetIndex() != m_nNode) // only if moved to different node
+ {
+ ::sw::NotifyTableCollapsedParagraph(pCNd, &m_rShell);
+
+ const SwDoc *pDoc=m_rShell.GetDoc();
+ if (sal_Int32(m_nNode) < sal_Int32(pDoc->GetNodes().Count()))
+ {
+ const SwContentNode *const pNode = pDoc->GetNodes()[m_nNode]->GetContentNode();
+ ::sw::NotifyTableCollapsedParagraph(pNode, &m_rShell);
+ }
+ }
+
+ sal_Int32 nCmp, nCurrentContent = pCurrentCursor->GetPoint()->GetContentIndex();
+ SwNodeType nNdWhich = pCNd->GetNodeType();
+ SwNodeOffset nCurrentNode = pCurrentCursor->GetPoint()->GetNodeIndex();
+
+ // Register the Shell as dependent at the current Node. By doing this all
+ // attribute changes can be signaled over the link.
+ pCNd->Add( &m_rShell );
+
+ const bool bCurrentHasSelection = (*pCurrentCursor->GetPoint() != *pCurrentCursor->GetMark());
+
+ if( m_nNodeType != nNdWhich || m_nNode != nCurrentNode )
+ {
+ // Every time a switch between nodes occurs, there is a chance that
+ // new attributes do apply - meaning text-attributes.
+ // So the currently applying attributes would have to be determined.
+ // That can be done in one go by the handler.
+ m_rShell.CallChgLnk();
+ }
+ else if (m_bHasSelection != bCurrentHasSelection)
+ {
+ // always call change link when selection changes
+ m_rShell.CallChgLnk();
+ }
+ else if( m_rShell.m_aChgLnk.IsSet() && SwNodeType::Text == nNdWhich &&
+ m_nContent != nCurrentContent )
+ {
+ // If travelling with left/right only and the frame is
+ // unchanged (columns!) then check text hints.
+ if( m_nLeftFramePos == SwCallLink::getLayoutFrame( m_rShell.GetLayout(), *pCNd->GetTextNode(), nCurrentContent,
+ !m_rShell.ActionPend() ) &&
+ (( nCmp = m_nContent ) + 1 == nCurrentContent || // Right
+ m_nContent -1 == ( nCmp = nCurrentContent )) ) // Left
+ {
+ if( nCmp == nCurrentContent && pCurrentCursor->HasMark() ) // left & select
+ ++nCmp;
+
+ if ( pCNd->GetTextNode()->HasHints() )
+ {
+ const SwpHints &rHts = pCNd->GetTextNode()->GetSwpHints();
+
+ for( size_t n = 0; n < rHts.Count(); ++n )
+ {
+ const SwTextAttr* pHt = rHts.Get( n );
+ const sal_Int32 *pEnd = pHt->End();
+ const sal_Int32 nStart = pHt->GetStart();
+
+ // If "only start" or "start and end equal" then call on
+ // every overflow of start.
+ if( ( !pEnd || ( nStart == *pEnd ) ) &&
+ ( nStart == m_nContent || nStart == nCurrentContent) )
+ {
+ m_rShell.CallChgLnk();
+ return;
+ }
+
+ // If the attribute has an area and that area is not empty ...
+ else if( pEnd && nStart < *pEnd &&
+ // ... then test if travelling occurred via start/end.
+ ( nStart == nCmp ||
+ ( pHt->DontExpand() ? nCmp == *pEnd-1
+ : nCmp == *pEnd ) ))
+ {
+ m_rShell.CallChgLnk();
+ return;
+ }
+ }
+ }
+
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+ const OUString rText = pCNd->GetTextNode()->GetText();
+ if( !nCmp ||
+ g_pBreakIt->GetBreakIter()->getScriptType( rText, m_nContent )
+ != g_pBreakIt->GetBreakIter()->getScriptType(rText, nCurrentContent))
+ {
+ m_rShell.CallChgLnk();
+ return;
+ }
+ }
+ else
+ // If travelling more than one character with home/end/.. then
+ // always call ChgLnk, because it can not be determined here what
+ // has changed. Something may have changed.
+ m_rShell.CallChgLnk();
+ }
+
+ const SwFrame* pFrame;
+ const SwFlyFrame *pFlyFrame;
+ if (m_rShell.ActionPend())
+ return;
+ pFrame = pCNd->getLayoutFrame(m_rShell.GetLayout(), nullptr, nullptr);
+ if (!pFrame)
+ return;
+ pFlyFrame = pFrame->FindFlyFrame();
+ if ( !pFlyFrame || m_rShell.IsTableMode() )
+ return;
+
+ const SwNodeIndex* pIndex = pFlyFrame->GetFormat()->GetContent().GetContentIdx();
+ OSL_ENSURE( pIndex, "Fly without Content" );
+
+ if (!pIndex)
+ return;
+
+ const SwNode& rStNd = pIndex->GetNode();
+
+ if( rStNd.EndOfSectionNode()->StartOfSectionIndex() > m_nNode ||
+ m_nNode > rStNd.EndOfSectionIndex() )
+ m_rShell.GetFlyMacroLnk().Call( pFlyFrame->GetFormat() );
+}
+
+SwCallLink::~SwCallLink()
+{
+ suppress_fun_call_w_exception(ImplDestroy());
+}
+
+tools::Long SwCallLink::getLayoutFrame(const SwRootFrame* pRoot,
+ SwTextNode const & rNd, sal_Int32 nCntPos, bool /*bCalcFrame*/)
+{
+ SwTextFrame* pFrame = static_cast<SwTextFrame*>(rNd.getLayoutFrame(pRoot, nullptr, nullptr));
+ SwTextFrame* pNext;
+ if ( pFrame && !pFrame->IsHiddenNow() )
+ {
+ if( pFrame->HasFollow() )
+ {
+ TextFrameIndex const nPos(pFrame->MapModelToView(&rNd, nCntPos));
+ for (;;)
+ {
+ pNext = pFrame->GetFollow();
+ if(!pNext || nPos < pNext->GetOffset())
+ break;
+ pFrame = pNext;
+ }
+ }
+
+ return pFrame->getFrameArea().Left();
+ }
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/callnk.hxx b/sw/source/core/crsr/callnk.hxx
new file mode 100644
index 0000000000..3614816749
--- /dev/null
+++ b/sw/source/core/crsr/callnk.hxx
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SW_SOURCE_CORE_CRSR_CALLNK_HXX
+#define INCLUDED_SW_SOURCE_CORE_CRSR_CALLNK_HXX
+
+#include <tools/long.hxx>
+#include <ndtyp.hxx>
+#include <nodeoffset.hxx>
+
+class SwCursorShell;
+class SwTextNode;
+class SwRootFrame;
+
+class SwCallLink
+{
+public:
+ SwCursorShell & m_rShell;
+ SwNodeOffset m_nNode;
+ tools::Long m_nLeftFramePos;
+ sal_Int32 m_nContent;
+ SwNodeType m_nNodeType;
+ bool m_bHasSelection;
+
+ explicit SwCallLink( SwCursorShell & rSh );
+ ~SwCallLink();
+
+ static tools::Long getLayoutFrame( const SwRootFrame*, SwTextNode const & rNd, sal_Int32 nCntPos, bool bCalcFrame );
+private:
+ void ImplDestroy();
+};
+
+#endif // INCLUDED_SW_SOURCE_CORE_CRSR_CALLNK_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/contentcontrolbutton.cxx b/sw/source/core/crsr/contentcontrolbutton.cxx
new file mode 100644
index 0000000000..0d805cb492
--- /dev/null
+++ b/sw/source/core/crsr/contentcontrolbutton.cxx
@@ -0,0 +1,179 @@
+/* -*- 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 <contentcontrolbutton.hxx>
+
+#include <utility>
+#include <vcl/weldutils.hxx>
+#include <vcl/event.hxx>
+#include <vcl/decoview.hxx>
+
+#include <edtwin.hxx>
+#include <dview.hxx>
+
+SwContentControlButton::SwContentControlButton(SwEditWin* pEditWin,
+ std::shared_ptr<SwContentControl> pContentControl)
+ : Control(pEditWin, WB_DIALOGCONTROL)
+ , m_pContentControl(std::move(pContentControl))
+{
+ assert(GetParent());
+ assert(dynamic_cast<SwEditWin*>(GetParent()));
+
+ SetBackground();
+ EnableChildTransparentMode();
+ SetParentClipMode(ParentClipMode::NoClip);
+ SetPaintTransparent(true);
+}
+
+SwContentControlButton::~SwContentControlButton() { disposeOnce(); }
+
+void SwContentControlButton::LaunchPopup()
+{
+ m_xPopup->connect_closed(LINK(this, SwContentControlButton, PopupModeEndHdl));
+
+ tools::Rectangle aRect(Point(0, 0), GetSizePixel());
+ weld::Window* pParent = weld::GetPopupParent(*this, aRect);
+ m_xPopup->popup_at_rect(pParent, aRect);
+}
+
+void SwContentControlButton::DestroyPopup()
+{
+ m_xPopup.reset();
+ m_xPopupBuilder.reset();
+}
+
+void SwContentControlButton::dispose()
+{
+ DestroyPopup();
+ Control::dispose();
+}
+
+void SwContentControlButton::CalcPosAndSize(const SwRect& rPortionPaintArea)
+{
+ assert(GetParent());
+
+ Point aBoxPos = GetParent()->LogicToPixel(rPortionPaintArea.Pos());
+ Size aBoxSize = GetParent()->LogicToPixel(rPortionPaintArea.SSize());
+
+ // First calculate the size of the frame around the content control's last portion
+ int nPadding = aBoxSize.Height() / 4;
+ aBoxPos.AdjustX(-nPadding / 2);
+ aBoxPos.AdjustY(-1);
+ aBoxSize.AdjustWidth(nPadding);
+ aBoxSize.AdjustHeight(2);
+
+ m_aFramePixel = tools::Rectangle(aBoxPos, aBoxSize);
+
+ // Then extend the size with the button area
+ if (m_bRTL)
+ {
+ aBoxPos.AdjustX(-GetParent()->LogicToPixel(rPortionPaintArea.SSize()).Height());
+ }
+ aBoxSize.AdjustWidth(GetParent()->LogicToPixel(rPortionPaintArea.SSize()).Height());
+
+ if (aBoxPos != GetPosPixel() || aBoxSize != GetSizePixel())
+ {
+ SetPosSizePixel(aBoxPos, aBoxSize);
+ Invalidate();
+ }
+}
+
+void SwContentControlButton::MouseButtonDown(const MouseEvent&) { StartPopup(); }
+
+void SwContentControlButton::StartPopup()
+{
+ if (m_xPopup) // tdf#152257 already launched, don't relaunch
+ return;
+ LaunchPopup();
+ Invalidate();
+}
+
+IMPL_LINK_NOARG(SwContentControlButton, PopupModeEndHdl, weld::Popover&, void)
+{
+ DestroyPopup();
+ Show(false);
+ Invalidate();
+}
+
+void SwContentControlButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ SetMapMode(MapMode(MapUnit::MapPixel));
+
+ Color aLineColor = COL_BLACK;
+ Color aFillColor = aLineColor;
+ aFillColor.IncreaseLuminance(255 * (m_xPopup ? 0.5 : 0.75));
+
+ // Calc the frame around the content control's last portion
+ int nPadding = 1;
+ Point aPos(nPadding, nPadding);
+ Size aSize(m_aFramePixel.GetSize().Width() - nPadding,
+ m_aFramePixel.GetSize().Height() - nPadding);
+ const tools::Rectangle aFrameRect(tools::Rectangle(aPos, aSize));
+
+ // Draw the button next to the frame
+ Point aButtonPos(aFrameRect.TopLeft());
+ if (m_bRTL)
+ {
+ aButtonPos.AdjustX(nPadding * 2);
+ }
+ else
+ {
+ aButtonPos.AdjustX(aFrameRect.GetSize().getWidth() - nPadding * 2);
+ }
+ Size aButtonSize(aFrameRect.GetSize());
+ aButtonSize.setWidth(GetSizePixel().getWidth() - aFrameRect.getOpenWidth() - nPadding);
+ const tools::Rectangle aButtonRect(tools::Rectangle(aButtonPos, aButtonSize));
+
+ // Background & border
+ rRenderContext.SetLineColor(aLineColor);
+ rRenderContext.SetFillColor(aFillColor);
+ rRenderContext.DrawRect(aButtonRect);
+
+ // the arrowhead
+ DecorationView aDecoView(&rRenderContext);
+ tools::Rectangle aSymbolRect(aButtonRect);
+ // 20% distance to the left and right button border
+ const tools::Long nBorderDistanceLeftAndRight = aSymbolRect.GetWidth() / 4;
+ aSymbolRect.AdjustLeft(nBorderDistanceLeftAndRight);
+ aSymbolRect.AdjustRight(-nBorderDistanceLeftAndRight);
+ // 20% distance to the top and bottom button border
+ const tools::Long nBorderDistanceTopAndBottom = aSymbolRect.GetHeight() / 4;
+ aSymbolRect.AdjustTop(nBorderDistanceTopAndBottom);
+ aSymbolRect.AdjustBottom(-nBorderDistanceTopAndBottom);
+ AntialiasingFlags eAntialiasing = rRenderContext.GetAntialiasing();
+ if (SwDrawView::IsAntiAliasing())
+ {
+ rRenderContext.SetAntialiasing(eAntialiasing | AntialiasingFlags::Enable);
+ }
+ aDecoView.DrawSymbol(aSymbolRect, SymbolType::SPIN_DOWN, GetTextColor(), DrawSymbolFlags::NONE);
+ if (SwDrawView::IsAntiAliasing())
+ {
+ rRenderContext.SetAntialiasing(eAntialiasing);
+ }
+}
+
+WindowHitTest SwContentControlButton::ImplHitTest(const Point& rFramePos)
+{
+ // We need to check whether the position hits the button (the frame should be mouse transparent)
+ WindowHitTest aResult = Control::ImplHitTest(rFramePos);
+ if (aResult != WindowHitTest::Inside)
+ return aResult;
+ else
+ {
+ if (m_bRTL)
+ {
+ return rFramePos.X() <= m_aFramePixel.Left() ? WindowHitTest::Inside
+ : WindowHitTest::Transparent;
+ }
+ return rFramePos.X() >= m_aFramePixel.Right() ? WindowHitTest::Inside
+ : WindowHitTest::Transparent;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/crbm.cxx b/sw/source/core/crsr/crbm.cxx
new file mode 100644
index 0000000000..e296bd50e8
--- /dev/null
+++ b/sw/source/core/crsr/crbm.cxx
@@ -0,0 +1,321 @@
+/* -*- 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 <crsrsh.hxx>
+#include <ndtxt.hxx>
+#include <rootfrm.hxx>
+#include <txtfrm.hxx>
+#include <IMark.hxx>
+#include <swcrsr.hxx>
+#include <IDocumentMarkAccess.hxx>
+#include <IDocumentSettingAccess.hxx>
+
+namespace
+{
+ struct CursorStateHelper
+ {
+ explicit CursorStateHelper(SwCursorShell const & rShell)
+ : m_pCursor(rShell.GetCursor())
+ , m_aSaveState(*m_pCursor)
+ { }
+
+ void SetCursorToMark(::sw::mark::IMark const * const pMark)
+ {
+ *(m_pCursor->GetPoint()) = pMark->GetMarkStart();
+ if(pMark->IsExpanded())
+ {
+ m_pCursor->SetMark();
+ *(m_pCursor->GetMark()) = pMark->GetMarkEnd();
+ }
+ }
+
+ /// returns true if the Cursor had been rolled back
+ bool RollbackIfIllegal()
+ {
+ if(m_pCursor->IsSelOvr(SwCursorSelOverFlags::CheckNodeSection
+ | SwCursorSelOverFlags::Toggle))
+ {
+ m_pCursor->DeleteMark();
+ m_pCursor->RestoreSavePos();
+ return true;
+ }
+ return false;
+ }
+
+ SwCursor* m_pCursor;
+ SwCursorSaveState m_aSaveState;
+ };
+
+ bool lcl_ReverseMarkOrderingByEnd(const ::sw::mark::IMark* pFirst,
+ const ::sw::mark::IMark* pSecond)
+ {
+ return pFirst->GetMarkEnd() > pSecond->GetMarkEnd();
+ }
+
+ bool lcl_IsInvisibleBookmark(const ::sw::mark::IMark* pMark)
+ {
+ return IDocumentMarkAccess::GetType(*pMark) != IDocumentMarkAccess::MarkType::BOOKMARK;
+ }
+}
+
+// at CurrentCursor.SPoint
+::sw::mark::IMark* SwCursorShell::SetBookmark(
+ const vcl::KeyCode& rCode,
+ const OUString& rName,
+ IDocumentMarkAccess::MarkType eMark)
+{
+ StartAction();
+ ::sw::mark::IMark* pMark = getIDocumentMarkAccess()->makeMark(
+ *GetCursor(),
+ rName,
+ eMark, sw::mark::InsertMode::New);
+ ::sw::mark::IBookmark* pBookmark = dynamic_cast< ::sw::mark::IBookmark* >(pMark);
+ if(pBookmark)
+ {
+ pBookmark->SetKeyCode(rCode);
+ pBookmark->SetShortName(OUString());
+ }
+ EndAction();
+ return pMark;
+}
+// set CurrentCursor.SPoint
+
+// at CurrentCursor.SPoint
+::sw::mark::IMark* SwCursorShell::SetBookmark2(
+ const vcl::KeyCode& rCode,
+ const OUString& rName,
+ bool bHide,
+ const OUString& rCondition)
+{
+ StartAction();
+ ::sw::mark::IMark* pMark = getIDocumentMarkAccess()->makeMark(
+ *GetCursor(),
+ rName,
+ IDocumentMarkAccess::MarkType::BOOKMARK, sw::mark::InsertMode::New);
+ ::sw::mark::IBookmark* pBookmark = dynamic_cast< ::sw::mark::IBookmark* >(pMark);
+ if (pBookmark)
+ {
+ pBookmark->SetKeyCode(rCode);
+ pBookmark->SetShortName(OUString());
+ pBookmark->Hide(bHide);
+ pBookmark->SetHideCondition(rCondition);
+ }
+ EndAction();
+ return pMark;
+}
+
+namespace sw {
+
+bool IsMarkHidden(SwRootFrame const& rLayout, ::sw::mark::IMark const& rMark)
+{
+ if (!rLayout.HasMergedParas())
+ {
+ return false;
+ }
+ SwNode const& rNode(rMark.GetMarkPos().GetNode());
+ SwTextNode const*const pTextNode(rNode.GetTextNode());
+ if (pTextNode == nullptr)
+ { // UNO_BOOKMARK may point to table node
+ return rNode.GetRedlineMergeFlag() == SwNode::Merge::Hidden;
+ }
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(
+ pTextNode->getLayoutFrame(&rLayout)));
+ if (!pFrame)
+ {
+ return true;
+ }
+ if (rMark.IsExpanded())
+ {
+ SwTextFrame const*const pOtherFrame(static_cast<SwTextFrame const*>(
+ rMark.GetOtherMarkPos().GetNode().GetTextNode()->getLayoutFrame(&rLayout)));
+ return pFrame == pOtherFrame
+ && pFrame->MapModelToViewPos(rMark.GetMarkPos())
+ == pFrame->MapModelToViewPos(rMark.GetOtherMarkPos());
+ }
+ else
+ {
+ if (rMark.GetMarkPos().GetContentIndex() == pTextNode->Len())
+ { // at end of node: never deleted (except if node deleted)
+ return pTextNode->GetRedlineMergeFlag() == SwNode::Merge::Hidden;
+ }
+ else
+ { // check character following mark pos
+ return pFrame->MapModelToViewPos(rMark.GetMarkPos())
+ == pFrame->MapModelToView(pTextNode, rMark.GetMarkPos().GetContentIndex() + 1);
+ }
+ }
+}
+
+} // namespace sw
+
+// set CurrentCursor.SPoint
+bool SwCursorShell::GotoMark(const ::sw::mark::IMark* const pMark, bool bAtStart)
+{
+ if (sw::IsMarkHidden(*GetLayout(), *pMark))
+ {
+ return false;
+ }
+ // watch Cursor-Moves
+ CursorStateHelper aCursorSt(*this);
+ if ( bAtStart )
+ *aCursorSt.m_pCursor->GetPoint() = pMark->GetMarkStart();
+ else
+ *aCursorSt.m_pCursor->GetPoint() = pMark->GetMarkEnd();
+
+ if(aCursorSt.RollbackIfIllegal()) return false;
+
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return true;
+}
+
+bool SwCursorShell::GotoMark(const ::sw::mark::IMark* const pMark)
+{
+ if (sw::IsMarkHidden(*GetLayout(), *pMark))
+ {
+ return false;
+ }
+ // watch Cursor-Moves
+ CursorStateHelper aCursorSt(*this);
+ aCursorSt.SetCursorToMark(pMark);
+
+ if(aCursorSt.RollbackIfIllegal()) return false;
+
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return true;
+}
+
+bool SwCursorShell::GoNextBookmark()
+{
+ IDocumentMarkAccess* pMarkAccess = getIDocumentMarkAccess();
+ std::vector<::sw::mark::IMark*> vCandidates;
+ remove_copy_if(
+ pMarkAccess->findFirstBookmarkStartsAfter(*GetCursor()->GetPoint()),
+ pMarkAccess->getBookmarksEnd(),
+ back_inserter(vCandidates),
+ &lcl_IsInvisibleBookmark);
+
+ // watch Cursor-Moves
+ CursorStateHelper aCursorSt(*this);
+ auto ppMark = vCandidates.begin();
+ for(; ppMark!=vCandidates.end(); ++ppMark)
+ {
+ if (sw::IsMarkHidden(*GetLayout(), **ppMark))
+ {
+ continue;
+ }
+ aCursorSt.SetCursorToMark(*ppMark);
+ if(!aCursorSt.RollbackIfIllegal())
+ break; // found legal move
+ }
+ if(ppMark==vCandidates.end())
+ {
+ SttEndDoc(false);
+ return false;
+ }
+
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return true;
+}
+
+bool SwCursorShell::GoPrevBookmark()
+{
+ IDocumentMarkAccess* pMarkAccess = getIDocumentMarkAccess();
+ // candidates from which to choose the mark before
+ // no need to consider marks starting after rPos
+ std::vector<::sw::mark::IMark*> vCandidates;
+ remove_copy_if(
+ pMarkAccess->getBookmarksBegin(),
+ pMarkAccess->findFirstBookmarkStartsAfter(*GetCursor()->GetPoint()),
+ back_inserter(vCandidates),
+ &lcl_IsInvisibleBookmark);
+ sort(
+ vCandidates.begin(),
+ vCandidates.end(),
+ &lcl_ReverseMarkOrderingByEnd);
+
+ // watch Cursor-Moves
+ CursorStateHelper aCursorSt(*this);
+ auto ppMark = vCandidates.begin();
+ for(; ppMark!=vCandidates.end(); ++ppMark)
+ {
+ // ignoring those not ending before the Cursor
+ // (we were only able to eliminate those starting
+ // behind the Cursor by the upper_bound(..)
+ // above)
+ if(!((**ppMark).GetMarkEnd() < *GetCursor()->GetPoint()))
+ continue;
+ if (sw::IsMarkHidden(*GetLayout(), **ppMark))
+ {
+ continue;
+ }
+ aCursorSt.SetCursorToMark(*ppMark);
+ if(!aCursorSt.RollbackIfIllegal())
+ break; // found legal move
+ }
+ if(ppMark==vCandidates.end())
+ {
+ SttEndDoc(true);
+ return false;
+ }
+
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return true;
+}
+
+bool SwCursorShell::IsFormProtected()
+{
+ return getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_FORM);
+}
+
+::sw::mark::IFieldmark* SwCursorShell::GetCurrentFieldmark()
+{
+ // TODO: Refactor
+ SwPosition pos(*GetCursor()->Start());
+ return getIDocumentMarkAccess()->getInnerFieldmarkFor(pos);
+}
+
+sw::mark::IFieldmark* SwCursorShell::GetFieldmarkAfter(bool bLoop)
+{
+ SwPosition pos(*GetCursor()->GetPoint());
+ return getIDocumentMarkAccess()->getFieldmarkAfter(pos, bLoop);
+}
+
+sw::mark::IFieldmark* SwCursorShell::GetFieldmarkBefore(bool bLoop)
+{
+ SwPosition pos(*GetCursor()->GetPoint());
+ return getIDocumentMarkAccess()->getFieldmarkBefore(pos, bLoop);
+}
+
+bool SwCursorShell::GotoFieldmark(::sw::mark::IFieldmark const * const pMark)
+{
+ if(pMark==nullptr) return false;
+
+ // watch Cursor-Moves
+ CursorStateHelper aCursorSt(*this);
+ aCursorSt.SetCursorToMark(pMark);
+ aCursorSt.m_pCursor->GetPoint()->AdjustContent(+1);
+ aCursorSt.m_pCursor->GetMark()->AdjustContent(-1);
+
+ if(aCursorSt.RollbackIfIllegal()) return false;
+
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/crossrefbookmark.cxx b/sw/source/core/crsr/crossrefbookmark.cxx
new file mode 100644
index 0000000000..8b61f709eb
--- /dev/null
+++ b/sw/source/core/crsr/crossrefbookmark.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 <sal/config.h>
+
+#include <cstdlib>
+
+#include <IDocumentMarkAccess.hxx>
+#include <crossrefbookmark.hxx>
+#include <ndtxt.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace
+{
+ constexpr OUString CrossRefNumItemBookmark_NamePrefix = u"__RefNumPara__"_ustr;
+}
+
+namespace sw::mark
+{
+ CrossRefBookmark::CrossRefBookmark(const SwPaM& rPaM,
+ const vcl::KeyCode& rCode,
+ const OUString& rName,
+ std::u16string_view rPrefix)
+ : Bookmark(
+ // ensure that m_pPos2 is null by only passing start to super
+ SwPaM(*rPaM.Start()), rCode, rName)
+ {
+ assert( IDocumentMarkAccess::IsLegalPaMForCrossRefHeadingBookmark(rPaM)
+ && "<CrossRefBookmark::CrossRefBookmark(..)>"
+ "- creation of cross-reference bookmark with an illegal PaM that does not expand over exactly one whole paragraph.");
+ if(rName.isEmpty())
+ m_aName = MarkBase::GenerateNewName(rPrefix);
+ assert(!m_oPos2);
+ }
+
+ void CrossRefBookmark::SetMarkPos(const SwPosition& rNewPos)
+ {
+ assert(rNewPos.GetNode().GetTextNode() &&
+ "<sw::mark::CrossRefBookmark::SetMarkPos(..)>"
+ " - new bookmark position for cross-reference bookmark doesn't mark text node");
+ assert(rNewPos.GetContentIndex() == 0 &&
+ "<sw::mark::CrossRefBookmark::SetMarkPos(..)>"
+ " - new bookmark position for cross-reference bookmark doesn't mark start of text node");
+ MarkBase::SetMarkPos(rNewPos);
+ }
+
+ SwPosition& CrossRefBookmark::GetOtherMarkPos() const
+ {
+ assert(false &&
+ "<sw::mark::CrossRefBookmark::GetOtherMarkPos(..)>"
+ " - this should never be called!");
+ for (;;) { std::abort(); } // avoid "must return a value" warnings
+ }
+
+ CrossRefHeadingBookmark::CrossRefHeadingBookmark(const SwPaM& rPaM,
+ const vcl::KeyCode& rCode,
+ const OUString& rName)
+ : CrossRefBookmark(rPaM, rCode, rName, Concat2View(IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix()+"_Toc"))
+ { }
+
+ bool CrossRefHeadingBookmark::IsLegalName(std::u16string_view rName)
+ {
+ return o3tl::starts_with(rName, IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix());
+ }
+
+ CrossRefNumItemBookmark::CrossRefNumItemBookmark(const SwPaM& rPaM,
+ const vcl::KeyCode& rCode,
+ const OUString& rName)
+ : CrossRefBookmark(rPaM, rCode, rName, CrossRefNumItemBookmark_NamePrefix)
+ { }
+
+ bool CrossRefNumItemBookmark::IsLegalName(std::u16string_view rName)
+ {
+ return o3tl::starts_with(rName, CrossRefNumItemBookmark_NamePrefix);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx
new file mode 100644
index 0000000000..04b263cda7
--- /dev/null
+++ b/sw/source/core/crsr/crsrsh.cxx
@@ -0,0 +1,4222 @@
+/* -*- 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 <com/sun/star/text/XTextRange.hpp>
+
+#include <hintids.hxx>
+#include <svx/srchdlg.hxx>
+#include <sfx2/viewsh.hxx>
+#include <SwSmartTagMgr.hxx>
+#include <doc.hxx>
+#include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <cntfrm.hxx>
+#include <viewimp.hxx>
+#include <pam.hxx>
+#include <swselectionlist.hxx>
+#include "BlockCursor.hxx"
+#include <ndtxt.hxx>
+#include <flyfrm.hxx>
+#include <dview.hxx>
+#include <viewopt.hxx>
+#include <crsrsh.hxx>
+#include <tabfrm.hxx>
+#include <txtfrm.hxx>
+#include <sectfrm.hxx>
+#include <swtable.hxx>
+#include "callnk.hxx"
+#include <viscrs.hxx>
+#include <section.hxx>
+#include <docsh.hxx>
+#include <scriptinfo.hxx>
+#include <globdoc.hxx>
+#include <pamtyp.hxx>
+#include <mdiexp.hxx>
+#include <fmteiro.hxx>
+#include <wrong.hxx>
+#include <unotextrange.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <GrammarContact.hxx>
+#include <OnlineAccessibilityCheck.hxx>
+#include <comphelper/flagguard.hxx>
+#include <strings.hrc>
+#include <IDocumentLayoutAccess.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <comphelper/lok.hxx>
+#include <comphelper/sequence.hxx>
+#include <sfx2/lokhelper.hxx>
+#include <editeng/editview.hxx>
+#include <editeng/frmdir.hxx>
+#include <sal/log.hxx>
+#include <PostItMgr.hxx>
+#include <DocumentSettingManager.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+#include <tabcol.hxx>
+#include <wrtsh.hxx>
+#include <undobj.hxx>
+#include <view.hxx>
+#include <hints.hxx>
+#include <tools/json_writer.hxx>
+#include <redline.hxx>
+
+using namespace com::sun::star;
+using namespace util;
+
+/**
+ * Check if pCurrentCursor points into already existing ranges and delete those.
+ * @param Pointer to SwCursor object
+ */
+static void CheckRange( SwCursor* pCurrentCursor )
+{
+ auto [pStt, pEnd] = pCurrentCursor->StartEnd(); // SwPosition*
+
+ SwPaM *pTmpDel = nullptr,
+ *pTmp = pCurrentCursor->GetNext();
+
+ // Search the complete ring
+ while( pTmp != pCurrentCursor )
+ {
+ auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition*
+ if( *pStt <= *pTmpStt )
+ {
+ if( *pEnd > *pTmpStt ||
+ ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd ))
+ pTmpDel = pTmp;
+ }
+ else
+ if( *pStt < *pTmpEnd )
+ pTmpDel = pTmp;
+
+ // If Point or Mark is within the Cursor range, we need to remove the old
+ // range. Take note that Point does not belong to the range anymore.
+ pTmp = pTmp->GetNext();
+ delete pTmpDel; // Remove old range
+ pTmpDel = nullptr;
+ }
+}
+
+// SwCursorShell
+
+/**
+ * Add a copy of current cursor, append it after current, and collapse current cursor.
+ * @return - Returns a newly created copy of current cursor.
+ */
+SwPaM * SwCursorShell::CreateCursor()
+{
+ // don't create new Cursor with active table Selection
+ assert(!IsTableMode());
+
+ // ensure that m_pCurrentCursor is valid; if it's invalid it would be
+ // copied to pNew and then pNew would be deleted in UpdateCursor() below
+ ClearUpCursors();
+
+ // New cursor as copy of current one. Add to the ring.
+ // Links point to previously created one, ie forward.
+ SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor );
+
+ // Hide PaM logically, to avoid undoing the inverting from
+ // copied PaM (#i75172#)
+ pNew->swapContent(*m_pCurrentCursor);
+
+ m_pCurrentCursor->DeleteMark();
+
+ UpdateCursor( SwCursorShell::SCROLLWIN );
+ return pNew;
+}
+
+/**
+ * Delete current Cursor, making the following one the current.
+ * Note, this function does not delete anything if there is no other cursor.
+ * @return - returns true if there was another cursor and we deleted one.
+ */
+void SwCursorShell::DestroyCursor()
+{
+ // don't delete Cursor with active table Selection
+ assert(!IsTableMode());
+
+ // Is there a next one? Don't do anything if not.
+ if(!m_pCurrentCursor->IsMultiSelection())
+ return;
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursor* pNextCursor = static_cast<SwCursor*>(m_pCurrentCursor->GetNext());
+ delete m_pCurrentCursor;
+ m_pCurrentCursor = dynamic_cast<SwShellCursor*>(pNextCursor);
+ UpdateCursor();
+}
+
+/**
+ * Create and return a new shell cursor.
+ * Simply returns the current shell cursor if there is no selection
+ * (HasSelection()).
+ */
+SwCursor & SwCursorShell::CreateNewShellCursor()
+{
+ if (HasSelection())
+ {
+ (void) CreateCursor(); // n.b. returns old cursor
+ }
+ return *GetCursor();
+}
+
+/**
+ * Return the current shell cursor
+ * @return - returns current `SwPaM` shell cursor
+ */
+SwCursor & SwCursorShell::GetCurrentShellCursor()
+{
+ return *GetCursor();
+}
+
+/**
+ * Return pointer to the current shell cursor
+ * @return - returns pointer to current `SwCursor` shell cursor
+ */
+SwCursor* SwCursorShell::GetCursor( bool bMakeTableCursor ) const
+{
+ if( m_pTableCursor )
+ {
+ if( bMakeTableCursor && m_pTableCursor->IsCursorMovedUpdate() )
+ {
+ //don't re-create 'parked' cursors
+ if( m_pTableCursor->GetPoint()->GetNodeIndex() &&
+ m_pTableCursor->GetMark()->GetNodeIndex() )
+ {
+ const SwContentNode* pCNd = m_pTableCursor->GetPointContentNode();
+ if( pCNd && pCNd->getLayoutFrame( GetLayout() ) )
+ {
+ pCNd = m_pTableCursor->GetMarkContentNode();
+ if( pCNd && pCNd->getLayoutFrame( GetLayout() ) )
+ {
+ SwShellTableCursor* pTC = m_pTableCursor;
+ GetLayout()->MakeTableCursors( *pTC );
+ }
+ }
+ }
+ }
+
+ if( m_pTableCursor->IsChgd() )
+ {
+ const_cast<SwCursorShell*>(this)->m_pCurrentCursor =
+ dynamic_cast<SwShellCursor*>(m_pTableCursor->MakeBoxSels( m_pCurrentCursor ));
+ }
+ }
+ return m_pCurrentCursor;
+}
+
+void SwCursorShell::StartAction()
+{
+ if( !ActionPend() )
+ {
+ // save for update of the ribbon bar
+ const SwNode& rNd = m_pCurrentCursor->GetPoint()->GetNode();
+ m_nCurrentNode = rNd.GetIndex();
+ m_nCurrentContent = m_pCurrentCursor->GetPoint()->GetContentIndex();
+ m_nCurrentNdTyp = rNd.GetNodeType();
+ if( rNd.IsTextNode() )
+ m_nLeftFramePos = SwCallLink::getLayoutFrame( GetLayout(), *rNd.GetTextNode(), m_nCurrentContent, true );
+ else
+ m_nLeftFramePos = 0;
+ }
+ SwViewShell::StartAction(); // to the SwViewShell
+}
+
+void SwCursorShell::EndAction( const bool bIdleEnd )
+{
+ comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
+ bool bVis = m_bSVCursorVis;
+
+ // Idle-formatting?
+ if( bIdleEnd && Imp()->HasPaintRegion() )
+ {
+ m_pCurrentCursor->Hide();
+ }
+
+ // Update all invalid numberings before the last action
+ if( 1 == mnStartAction )
+ GetDoc()->UpdateNumRule();
+
+ // #i76923#: Don't show the cursor in the SwViewShell::EndAction() - call.
+ // Only the UpdateCursor shows the cursor.
+ bool bSavSVCursorVis = m_bSVCursorVis;
+ m_bSVCursorVis = false;
+
+ SwViewShell::EndAction( bIdleEnd ); // have SwViewShell go first
+
+ m_bSVCursorVis = bSavSVCursorVis;
+
+ if( ActionPend() )
+ {
+ if( bVis ) // display SV-Cursor again
+ m_pVisibleCursor->Show();
+
+ return;
+ }
+
+ sal_uInt16 eFlags = SwCursorShell::CHKRANGE;
+ if ( !bIdleEnd )
+ eFlags |= SwCursorShell::SCROLLWIN;
+
+ UpdateCursor( eFlags, bIdleEnd ); // Show Cursor changes
+
+ {
+ SwCallLink aLk( *this ); // Watch cursor moves,
+ aLk.m_nNode = m_nCurrentNode; // possibly call the link
+ aLk.m_nNodeType = m_nCurrentNdTyp;
+ aLk.m_nContent = m_nCurrentContent;
+ aLk.m_nLeftFramePos = m_nLeftFramePos;
+
+ if( !m_nCursorMove ||
+ ( 1 == m_nCursorMove && m_bInCMvVisportChgd ) )
+ // display Cursor & Selections again
+ ShowCursors( m_bSVCursorVis );
+ }
+ // call ChgCall if there is still one
+ if( m_bCallChgLnk && m_bChgCallFlag && m_aChgLnk.IsSet() )
+ {
+ m_aChgLnk.Call(nullptr);
+ m_bChgCallFlag = false; // reset flag
+ }
+}
+
+void SwCursorShell::SttCursorMove()
+{
+#ifdef DBG_UTIL
+ OSL_ENSURE( m_nCursorMove < USHRT_MAX, "Too many nested CursorMoves." );
+#endif
+ ++m_nCursorMove;
+ StartAction();
+}
+
+void SwCursorShell::EndCursorMove( const bool bIdleEnd )
+{
+#ifdef DBG_UTIL
+ OSL_ENSURE( m_nCursorMove, "EndCursorMove() without SttCursorMove()." );
+#endif
+ EndAction( bIdleEnd );
+ --m_nCursorMove;
+#ifdef DBG_UTIL
+ if( !m_nCursorMove )
+ m_bInCMvVisportChgd = false;
+#endif
+}
+
+bool SwCursorShell::LeftRight( bool bLeft, sal_uInt16 nCnt, SwCursorSkipMode nMode,
+ bool bVisualAllowed )
+{
+ if( IsTableMode() )
+ return bLeft ? GoPrevCell() : GoNextCell();
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ bool bRet = false;
+
+ // #i27615# Handle cursor in front of label.
+ const SwTextNode* pTextNd = nullptr;
+
+ if( m_pBlockCursor )
+ m_pBlockCursor->clearPoints();
+
+ // 1. CASE: Cursor is in front of label. A move to the right
+ // will simply reset the bInFrontOfLabel flag:
+ SwShellCursor* pShellCursor = getShellCursor( true );
+ if ( !bLeft && pShellCursor->IsInFrontOfLabel() )
+ {
+ SetInFrontOfLabel( false );
+ bRet = true;
+ }
+ // 2. CASE: Cursor is at beginning of numbered paragraph. A move
+ // to the left will simply set the bInFrontOfLabel flag:
+ else if (bLeft
+ && pShellCursor->GetPoint()->GetNode().IsTextNode()
+ && static_cast<SwTextFrame const*>(
+ pShellCursor->GetPoint()->GetNode().GetTextNode()->getLayoutFrame(GetLayout())
+ )->MapModelToViewPos(*pShellCursor->GetPoint()) == TextFrameIndex(0)
+ && !pShellCursor->IsInFrontOfLabel()
+ && !pShellCursor->HasMark()
+ && nullptr != (pTextNd = sw::GetParaPropsNode(*GetLayout(), pShellCursor->GetPoint()->GetNode()))
+ && pTextNd->HasVisibleNumberingOrBullet())
+ {
+ SetInFrontOfLabel( true );
+ bRet = true;
+ }
+ // 3. CASE: Regular cursor move. Reset the bInFrontOfLabel flag:
+ else
+ {
+ const bool bSkipHidden = !GetViewOptions()->IsShowHiddenChar();
+ // #i107447#
+ // To avoid loop the reset of <bInFrontOfLabel> flag is no longer
+ // reflected in the return value <bRet>.
+ const bool bResetOfInFrontOfLabel = SetInFrontOfLabel( false );
+ bRet = pShellCursor->LeftRight( bLeft, nCnt, nMode, bVisualAllowed,
+ bSkipHidden, !IsOverwriteCursor(),
+ GetLayout(),
+ GetViewOptions()->IsFieldName());
+ if ( !bRet && bLeft && bResetOfInFrontOfLabel )
+ {
+ // undo reset of <bInFrontOfLabel> flag
+ SetInFrontOfLabel( true );
+ }
+ }
+
+ if( bRet )
+ {
+ UpdateCursor();
+ }
+
+ return bRet;
+}
+
+void SwCursorShell::MarkListLevel( const OUString& sListId,
+ const int nListLevel )
+{
+ if (sListId == m_sMarkedListId && nListLevel == m_nMarkedListLevel)
+ return;
+
+ // Writer redraws the "marked" list with the field shading, if there
+ // is no field shading then the marked list would be redrawn for no
+ // visually identifiable reason, so skip the mark if field shadings
+ // are disabled.
+ const bool bVisuallyMarked(GetViewOptions()->IsFieldShadings());
+ if (bVisuallyMarked)
+ {
+ if ( !m_sMarkedListId.isEmpty() )
+ mxDoc->MarkListLevel( m_sMarkedListId, m_nMarkedListLevel, false );
+
+ if ( !sListId.isEmpty() )
+ mxDoc->MarkListLevel( sListId, nListLevel, true );
+ }
+
+ m_sMarkedListId = sListId;
+ m_nMarkedListLevel = nListLevel;
+}
+
+void SwCursorShell::UpdateMarkedListLevel()
+{
+ SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(),
+ GetCursor_()->GetPoint()->GetNode());
+
+ if ( !pTextNd )
+ return;
+
+ if (!pTextNd->IsNumbered(GetLayout()))
+ {
+ m_pCurrentCursor->SetInFrontOfLabel_( false );
+ MarkListLevel( OUString(), 0 );
+ }
+ else if ( m_pCurrentCursor->IsInFrontOfLabel() )
+ {
+ if ( pTextNd->IsInList() )
+ {
+ assert(pTextNd->GetActualListLevel() >= 0 &&
+ pTextNd->GetActualListLevel() < MAXLEVEL);
+ MarkListLevel( pTextNd->GetListId(),
+ pTextNd->GetActualListLevel() );
+ }
+ }
+ else
+ {
+ MarkListLevel( OUString(), 0 );
+ }
+}
+
+void SwCursorShell::FirePageChangeEvent(sal_uInt16 nOldPage, sal_uInt16 nNewPage)
+{
+#ifdef ACCESSIBLE_LAYOUT
+ if( Imp()->IsAccessible() )
+ Imp()->FirePageChangeEvent( nOldPage, nNewPage );
+#else
+ (void)nOldPage;
+ (void)nNewPage;
+#endif
+}
+
+void SwCursorShell::FireColumnChangeEvent(sal_uInt16 nOldColumn, sal_uInt16 nNewColumn)
+{
+#ifdef ACCESSIBLE_LAYOUT
+ if( Imp()->IsAccessible() )
+ Imp()->FireColumnChangeEvent( nOldColumn, nNewColumn);
+#else
+ (void)nOldColumn;
+ (void)nNewColumn;
+#endif
+}
+
+void SwCursorShell::FireSectionChangeEvent(sal_uInt16 nOldSection, sal_uInt16 nNewSection)
+{
+#ifdef ACCESSIBLE_LAYOUT
+ if( Imp()->IsAccessible() )
+ Imp()->FireSectionChangeEvent( nOldSection, nNewSection );
+#else
+ (void)nOldSection;
+ (void)nNewSection;
+#endif
+}
+
+bool SwCursorShell::bColumnChange()
+{
+ SwFrame* pCurrFrame = GetCurrFrame(false);
+
+ if (pCurrFrame == nullptr)
+ {
+ return false;
+ }
+
+ SwFrame* pCurrCol=pCurrFrame->FindColFrame();
+
+ while(pCurrCol== nullptr && pCurrFrame!=nullptr )
+ {
+ SwLayoutFrame* pParent = pCurrFrame->GetUpper();
+ if(pParent!=nullptr)
+ {
+ pCurrCol=static_cast<SwFrame*>(pParent)->FindColFrame();
+ pCurrFrame = pParent;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if(m_oldColFrame == pCurrCol)
+ return false;
+ else
+ {
+ m_oldColFrame = pCurrCol;
+ return true;
+ }
+}
+
+bool SwCursorShell::UpDown( bool bUp, sal_uInt16 nCnt )
+{
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+
+ bool bTableMode = IsTableMode();
+ SwShellCursor* pTmpCursor = getShellCursor( true );
+
+ bool bRet = pTmpCursor->UpDown( bUp, nCnt );
+ // #i40019# UpDown should always reset the bInFrontOfLabel flag:
+ bRet |= SetInFrontOfLabel(false);
+
+ if( m_pBlockCursor )
+ m_pBlockCursor->clearPoints();
+
+ if( bRet )
+ {
+ m_eMvState = CursorMoveState::UpDown; // status for Cursor travelling - GetModelPositionForViewPoint
+ if( !ActionPend() )
+ {
+ CursorFlag eUpdateMode = SwCursorShell::SCROLLWIN;
+ if( !bTableMode )
+ eUpdateMode = static_cast<CursorFlag>(eUpdateMode
+ | SwCursorShell::UPDOWN | SwCursorShell::CHKRANGE);
+ UpdateCursor( o3tl::narrowing<sal_uInt16>(eUpdateMode) );
+ }
+ }
+ return bRet;
+}
+
+bool SwCursorShell::LRMargin( bool bLeft, bool bAPI)
+{
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ CurrShell aCurr( this );
+ m_eMvState = CursorMoveState::LeftMargin; // status for Cursor travelling - GetModelPositionForViewPoint
+
+ const bool bTableMode = IsTableMode();
+ SwShellCursor* pTmpCursor = getShellCursor( true );
+
+ if( m_pBlockCursor )
+ m_pBlockCursor->clearPoints();
+
+ const bool bWasAtLM = GetCursor_()->IsAtLeftRightMargin(*GetLayout(), true, bAPI);
+
+ bool bRet = pTmpCursor->LeftRightMargin(*GetLayout(), bLeft, bAPI);
+
+ if ( bLeft && !bTableMode && bRet && bWasAtLM && !GetCursor_()->HasMark() )
+ {
+ const SwTextNode * pTextNd = GetCursor_()->GetPointNode().GetTextNode();
+ assert(sw::GetParaPropsNode(*GetLayout(), GetCursor_()->GetPoint()->GetNode()) == pTextNd);
+ if ( pTextNd && pTextNd->HasVisibleNumberingOrBullet() )
+ SetInFrontOfLabel( true );
+ }
+ else if ( !bLeft )
+ {
+ bRet = SetInFrontOfLabel( false ) || bRet;
+ }
+
+ if( bRet )
+ {
+ UpdateCursor();
+ }
+ return bRet;
+}
+
+bool SwCursorShell::IsAtLRMargin( bool bLeft, bool bAPI ) const
+{
+ const SwShellCursor* pTmpCursor = getShellCursor( true );
+ return pTmpCursor->IsAtLeftRightMargin(*GetLayout(), bLeft, bAPI);
+}
+
+bool SwCursorShell::SttEndDoc( bool bStt )
+{
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+
+ SwShellCursor* pTmpCursor = m_pBlockCursor ? &m_pBlockCursor->getShellCursor() : m_pCurrentCursor;
+ bool bRet = pTmpCursor->SttEndDoc( bStt );
+ if( bRet )
+ {
+ if( bStt )
+ pTmpCursor->GetPtPos().setY( 0 ); // set to 0 explicitly (table header)
+ if( m_pBlockCursor )
+ {
+ m_pBlockCursor->clearPoints();
+ RefreshBlockCursor();
+ }
+
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ }
+ return bRet;
+}
+
+const SwTableNode* SwCursorShell::IsCursorInTable() const
+{
+ if (m_pTableCursor && m_pTableCursor->GetSelectedBoxesCount())
+ { // find the table that has the selected boxes
+ return m_pTableCursor->GetSelectedBoxes()[0]->GetSttNd()->FindTableNode();
+ }
+ return m_pCurrentCursor->GetPointNode().FindTableNode();
+}
+
+// fun cases to consider:
+// * outermost table
+// - into para => SA/ESA
+// - into prev/next table => continue...
+// - no prev/next => done
+// * inner table
+// - into containing cell => SA/ESA
+// - into prev/next of containing cell
+// + into para
+// + into table nested in prev/next cell
+// - out of table -> as above
+// => iterate in one direction until a node is reached that is a parent or a sibling of a parent of the current table
+// - parent reached => SA/ESA depending
+// - not in parent but in *prev/next* sibling of outer cell => TrySelectOuterTable
+// - not in parent but in *prev/next* sibling of outer table => TrySelectOuterTable
+// => select-all cannot select a sequence of table with no para at same level; only 1 table
+// - no parent, no prev/next => TrySelectOuterTable
+
+bool SwCursorShell::MoveOutOfTable()
+{
+ SwPosition const point(*getShellCursor(false)->GetPoint());
+ SwPosition const mark(*getShellCursor(false)->GetMark());
+
+ for (auto const fnMove : {&fnMoveBackward, &fnMoveForward})
+ {
+ Push();
+ SwCursor *const pCursor(getShellCursor(false));
+
+ pCursor->Normalize(fnMove == &fnMoveBackward);
+ pCursor->DeleteMark();
+ SwTableNode const*const pTable(pCursor->GetPoint()->GetNode().FindTableNode());
+ assert(pTable);
+ while (MovePara(GoInContent, *fnMove))
+ {
+ SwStartNode const*const pBox(pCursor->GetPoint()->GetNode().FindTableBoxStartNode());
+ if (!pBox)
+ {
+ Pop(SwCursorShell::PopMode::DeleteStack);
+ return true; // moved to paragraph at top-level of text
+ }
+ if (pBox->GetIndex() < pTable->GetIndex()
+ && pTable->EndOfSectionIndex() < pBox->EndOfSectionIndex())
+ {
+ Pop(SwCursorShell::PopMode::DeleteStack);
+ return true; // pBox contains start position (pTable)
+ }
+ }
+
+ Pop(SwCursorShell::PopMode::DeleteCurrent);
+ // FIXME: Pop doesn't restore original cursor if nested tables
+ *getShellCursor(false)->GetPoint() = point;
+ getShellCursor(false)->SetMark();
+ *getShellCursor(false)->GetMark() = mark;
+ }
+ return false;
+}
+
+bool SwCursorShell::TrySelectOuterTable()
+{
+ assert(m_pTableCursor);
+ SwTableNode const& rInnerTable(*m_pTableCursor->GetPoint()->GetNode().FindTableNode());
+ SwNodes const& rNodes(rInnerTable.GetNodes());
+ SwTableNode const*const pOuterTable(rInnerTable.GetNodes()[rInnerTable.GetIndex()-1]->FindTableNode());
+ if (!pOuterTable)
+ {
+ return false;
+ }
+
+ // manually select boxes of pOuterTable
+ SwNodeIndex firstCell(*pOuterTable, +1);
+ SwNodeIndex lastCell(*rNodes[pOuterTable->EndOfSectionIndex()-1]->StartOfSectionNode());
+ SwSelBoxes aNew;
+ pOuterTable->GetTable().CreateSelection(&firstCell.GetNode(), &lastCell.GetNode(),
+ aNew, SwTable::SEARCH_NONE, false);
+ // set table cursor to 1st / last content which may be in inner table
+ SwContentNode *const pStart = rNodes.GoNext(&firstCell);
+ assert(pStart); // must at least find the previous point node
+ lastCell = *lastCell.GetNode().EndOfSectionNode();
+ SwContentNode *const pEnd = SwNodes::GoPrevious(&lastCell);
+ assert(pEnd); // must at least find the previous point node
+ delete m_pTableCursor;
+ m_pTableCursor = new SwShellTableCursor(*this, SwPosition(*pStart, 0), Point(),
+ SwPosition(*pEnd, 0), Point());
+ m_pTableCursor->ActualizeSelection( aNew );
+ m_pTableCursor->IsCursorMovedUpdate(); // clear this so GetCursor() doesn't recreate our SwSelBoxes
+
+ // this will update m_pCurrentCursor based on m_pTableCursor
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+
+ return true;
+}
+
+/// find XText start node
+static SwStartNode const* FindTextStart(SwPosition const& rPos)
+{
+ SwStartNode const* pStartNode(rPos.GetNode().StartOfSectionNode());
+ while (pStartNode && (pStartNode->IsSectionNode() || pStartNode->IsTableNode()))
+ {
+ pStartNode = pStartNode->StartOfSectionNode();
+ }
+ return pStartNode;
+}
+
+static SwStartNode const* FindParentText(SwShellCursor const& rCursor)
+{
+ // find closest section containing both start and end - ignore Sections
+ SwStartNode const* pStartNode(FindTextStart(*rCursor.Start()));
+ SwEndNode const* pEndNode(FindTextStart(*rCursor.End())->EndOfSectionNode());
+ while (pStartNode->EndOfSectionNode()->GetIndex() < pEndNode->GetIndex())
+ {
+ pStartNode = pStartNode->StartOfSectionNode();
+ }
+ while (pStartNode->GetIndex() < pEndNode->StartOfSectionNode()->GetIndex())
+ {
+ pEndNode = pEndNode->StartOfSectionNode()->StartOfSectionNode()->EndOfSectionNode();
+ }
+ assert(pStartNode->EndOfSectionNode() == pEndNode);
+
+ return (pStartNode->IsSectionNode() || pStartNode->IsTableNode())
+ ? FindTextStart(SwPosition(*pStartNode))
+ : pStartNode;
+}
+
+bool SwCursorShell::MoveStartText()
+{
+ SwPosition const old(*m_pCurrentCursor->GetPoint());
+ SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false)));
+ assert(pStartNode);
+ SwTableNode const*const pTable(pStartNode->FindTableNode());
+ m_pCurrentCursor->GetPoint()->Assign(*pStartNode);
+ GetDoc()->GetNodes().GoNext(m_pCurrentCursor->GetPoint());
+ while (m_pCurrentCursor->GetPoint()->GetNode().FindTableNode() != pTable
+ && (!pTable || pTable->GetIndex() < m_pCurrentCursor->GetPoint()->GetNode().FindTableNode()->GetIndex())
+ && MoveOutOfTable());
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return old != *m_pCurrentCursor->GetPoint();
+}
+
+// select all inside the current XText, with table or hidden para at start/end
+void SwCursorShell::ExtendedSelectAll(bool bFootnotes)
+{
+ // find common ancestor node of both ends of cursor
+ SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false)));
+ assert(pStartNode);
+ if (IsTableMode())
+ { // convert m_pTableCursor to m_pCurrentCursor after determining pStartNode
+ TableCursorToCursor();
+ }
+ SwNodes& rNodes = GetDoc()->GetNodes();
+ m_pCurrentCursor->Normalize(true);
+ SwPosition* pPos = m_pCurrentCursor->GetPoint();
+ pPos->Assign(bFootnotes ? rNodes.GetEndOfPostIts() : static_cast<SwNode const&>(*pStartNode));
+ rNodes.GoNext( pPos );
+ pPos = m_pCurrentCursor->GetMark();
+ pPos->Assign(bFootnotes ? rNodes.GetEndOfContent() : static_cast<SwNode const&>(*pStartNode->EndOfSectionNode()));
+ SwContentNode* pCNd = SwNodes::GoPrevious( pPos );
+ if (pCNd)
+ pPos->AssignEndIndex(*pCNd);
+}
+
+static typename SwCursorShell::StartsWith StartsWith(SwStartNode const& rStart)
+{
+ for (auto i = rStart.GetIndex() + 1; i < rStart.EndOfSectionIndex(); ++i)
+ {
+ SwNode const& rNode(*rStart.GetNodes()[i]);
+ switch (rNode.GetNodeType())
+ {
+ case SwNodeType::Section:
+ continue;
+ case SwNodeType::Table:
+ return SwCursorShell::StartsWith::Table;
+ case SwNodeType::Text:
+ if (rNode.GetTextNode()->IsHidden())
+ {
+ return SwCursorShell::StartsWith::HiddenPara;
+ }
+ return SwCursorShell::StartsWith::None;
+ default:
+ return SwCursorShell::StartsWith::None;
+ }
+ }
+ return SwCursorShell::StartsWith::None;
+}
+
+static typename SwCursorShell::StartsWith EndsWith(SwStartNode const& rStart)
+{
+ for (auto i = rStart.EndOfSectionIndex() - 1; rStart.GetIndex() < i; --i)
+ {
+ SwNode const& rNode(*rStart.GetNodes()[i]);
+ switch (rNode.GetNodeType())
+ {
+ case SwNodeType::End:
+ if (rNode.StartOfSectionNode()->IsTableNode())
+ {
+ return SwCursorShell::StartsWith::Table;
+ }
+//TODO buggy SwUndoRedline in testTdf137503? assert(rNode.StartOfSectionNode()->IsSectionNode());
+ break;
+ case SwNodeType::Text:
+ if (rNode.GetTextNode()->IsHidden())
+ {
+ return SwCursorShell::StartsWith::HiddenPara;
+ }
+ return SwCursorShell::StartsWith::None;
+ default:
+ return SwCursorShell::StartsWith::None;
+ }
+ }
+ return SwCursorShell::StartsWith::None;
+}
+
+// return the node that is the start of the extended selection (to include table
+// or section start nodes; looks like extending for end nodes is not required)
+::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>>
+SwCursorShell::ExtendedSelectedAll() const
+{
+ if (m_pTableCursor)
+ {
+ return {};
+ }
+
+ SwNodes& rNodes = GetDoc()->GetNodes();
+ SwShellCursor const*const pShellCursor = getShellCursor(false);
+ SwStartNode const* pStartNode(FindParentText(*pShellCursor));
+
+ SwNodeIndex nNode(*pStartNode);
+ SwContentNode* pStart = rNodes.GoNext(&nNode);
+ if (!pStart)
+ {
+ return {};
+ }
+
+ nNode = *pStartNode->EndOfSectionNode();
+ SwContentNode* pEnd = SwNodes::GoPrevious(&nNode);
+ if (!pEnd)
+ {
+ return {};
+ }
+
+ SwPosition aStart(*pStart, 0);
+ SwPosition aEnd(*pEnd, pEnd->Len());
+ if (!(aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End()))
+ {
+ return {};
+ }
+
+ auto const ends(::EndsWith(*pStartNode));
+ if (::StartsWith(*pStartNode) == StartsWith::None
+ && ends == StartsWith::None)
+ {
+ return {}; // "ordinary" selection will work
+ }
+
+ ::std::vector<SwTableNode*> tablesAtEnd;
+ if (ends == StartsWith::Table)
+ {
+ SwNode * pLastNode(rNodes[pStartNode->EndOfSectionIndex() - 1]);
+ while (pLastNode->IsEndNode())
+ {
+ SwNode *const pNode(pLastNode->StartOfSectionNode());
+ if (pNode->IsTableNode())
+ {
+ tablesAtEnd.push_back(pNode->GetTableNode());
+ pLastNode = rNodes[pNode->GetIndex() - 1];
+ }
+ else if (pNode->IsSectionNode())
+ {
+ pLastNode = rNodes[pLastNode->GetIndex() - 1];
+ }
+ }
+ assert(!tablesAtEnd.empty());
+ }
+
+ // tdf#133990 ensure directly containing section is included in SwUndoDelete
+ while (pStartNode->IsSectionNode()
+ && pStartNode->GetIndex() == pStartNode->StartOfSectionNode()->GetIndex() + 1
+ && pStartNode->EndOfSectionNode()->GetIndex() + 1 == pStartNode->StartOfSectionNode()->EndOfSectionNode()->GetIndex())
+ {
+ pStartNode = pStartNode->StartOfSectionNode();
+ }
+
+ // pStartNode is the node that fully contains the selection - the first
+ // node of the selection is the first node inside pStartNode
+ return ::std::make_pair(rNodes[pStartNode->GetIndex() + 1], tablesAtEnd);
+}
+
+typename SwCursorShell::StartsWith SwCursorShell::StartsWith_()
+{
+ SwShellCursor const*const pShellCursor = getShellCursor(false);
+ // first, check if this is invalid; ExtendedSelectAll(true) may result in
+ // a) an ordinary selection that is valid
+ // b) a selection that is extended
+ // c) a selection that is invalid and will cause FindParentText to loop
+ SwNode const& rEndOfExtras(GetDoc()->GetNodes().GetEndOfExtras());
+ if (pShellCursor->Start()->nNode.GetIndex() <= rEndOfExtras.GetIndex()
+ && rEndOfExtras.GetIndex() < pShellCursor->End()->nNode.GetIndex())
+ {
+ return StartsWith::None; // *very* extended, no ExtendedSelectedAll handling!
+ }
+ SwStartNode const*const pStartNode(FindParentText(*pShellCursor));
+ if (auto const ret = ::StartsWith(*pStartNode); ret != StartsWith::None)
+ {
+ return ret;
+ }
+ if (auto const ret = ::EndsWith(*pStartNode); ret != StartsWith::None)
+ {
+ return ret;
+ }
+ return StartsWith::None;
+}
+
+bool SwCursorShell::MovePage( SwWhichPage fnWhichPage, SwPosPage fnPosPage )
+{
+ bool bRet = false;
+
+ // never jump of section borders at selection
+ if( !m_pCurrentCursor->HasMark() || !m_pCurrentCursor->IsNoContent() )
+ {
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ CurrShell aCurr( this );
+
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ Point& rPt = m_pCurrentCursor->GetPtPos();
+ std::pair<Point, bool> tmp(rPt, false);
+ SwContentFrame * pFrame = m_pCurrentCursor->GetPointContentNode()->
+ getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
+ if( pFrame && GetFrameInPage( pFrame, fnWhichPage, fnPosPage, m_pCurrentCursor ) &&
+ !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos ))
+ {
+ UpdateCursor();
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+bool SwCursorShell::isInHiddenTextFrame(SwShellCursor* pShellCursor)
+{
+ SwContentNode *pCNode = pShellCursor->GetPointContentNode();
+ std::pair<Point, bool> tmp(pShellCursor->GetPtPos(), false);
+ SwContentFrame *const pFrame = pCNode
+ ? pCNode->getLayoutFrame(GetLayout(), pShellCursor->GetPoint(), &tmp)
+ : nullptr;
+ return !pFrame || (pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsHiddenNow());
+}
+
+// sw_redlinehide: this should work for all cases: GoCurrPara, GoNextPara, GoPrevPara
+static bool IsAtStartOrEndOfFrame(SwCursorShell const*const pShell,
+ SwShellCursor const*const pShellCursor, SwMoveFnCollection const& fnPosPara)
+{
+ SwContentNode *const pCNode = pShellCursor->GetPointContentNode();
+ assert(pCNode); // surely can't have moved otherwise?
+ std::pair<Point, bool> tmp(pShellCursor->GetPtPos(), false);
+ SwContentFrame const*const pFrame = pCNode->getLayoutFrame(
+ pShell->GetLayout(), pShellCursor->GetPoint(), &tmp);
+ if (!pFrame || !pFrame->IsTextFrame())
+ {
+ return false;
+ }
+ SwTextFrame const& rTextFrame(static_cast<SwTextFrame const&>(*pFrame));
+ TextFrameIndex const ix(rTextFrame.MapModelToViewPos(*pShellCursor->GetPoint()));
+ if (&fnParaStart == &fnPosPara)
+ {
+ return ix == TextFrameIndex(0);
+ }
+ else
+ {
+ assert(&fnParaEnd == &fnPosPara);
+ return ix == TextFrameIndex(rTextFrame.GetText().getLength());
+ }
+}
+
+bool SwCursorShell::MovePara(SwWhichPara fnWhichPara, SwMoveFnCollection const & fnPosPara )
+{
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ SwShellCursor* pTmpCursor = getShellCursor( true );
+ bool bRet = pTmpCursor->MovePara( fnWhichPara, fnPosPara );
+ if( bRet )
+ {
+ //keep going until we get something visible, i.e. skip
+ //over hidden paragraphs, don't get stuck at the start
+ //which is what SwCursorShell::UpdateCursorPos will reset
+ //the position to if we pass it a position in an
+ //invisible hidden paragraph field
+ while (isInHiddenTextFrame(pTmpCursor)
+ || !IsAtStartOrEndOfFrame(this, pTmpCursor, fnPosPara))
+ {
+ if (!pTmpCursor->MovePara(fnWhichPara, fnPosPara))
+ break;
+ }
+
+ UpdateCursor();
+ }
+ return bRet;
+}
+
+bool SwCursorShell::MoveSection( SwWhichSection fnWhichSect,
+ SwMoveFnCollection const & fnPosSect)
+{
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ SwCursor* pTmpCursor = getShellCursor( true );
+ bool bRet = pTmpCursor->MoveSection( fnWhichSect, fnPosSect );
+ if( bRet )
+ UpdateCursor();
+ return bRet;
+
+}
+
+// position cursor
+
+static SwFrame* lcl_IsInHeaderFooter( SwNode& rNd, Point& rPt )
+{
+ SwFrame* pFrame = nullptr;
+ SwContentNode* pCNd = rNd.GetContentNode();
+ if( pCNd )
+ {
+ std::pair<Point, bool> tmp(rPt, false);
+ SwContentFrame *pContentFrame = pCNd->getLayoutFrame(
+ pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
+ nullptr, &tmp);
+ pFrame = pContentFrame ? pContentFrame->GetUpper() : nullptr;
+ while( pFrame && !pFrame->IsHeaderFrame() && !pFrame->IsFooterFrame() )
+ pFrame = pFrame->IsFlyFrame() ? static_cast<SwFlyFrame*>(pFrame)->AnchorFrame()
+ : pFrame->GetUpper();
+ }
+ return pFrame;
+}
+
+bool SwCursorShell::IsInHeaderFooter( bool* pbInHeader ) const
+{
+ Point aPt;
+ SwFrame* pFrame = ::lcl_IsInHeaderFooter( m_pCurrentCursor->GetPoint()->GetNode(), aPt );
+ if( pFrame && pbInHeader )
+ *pbInHeader = pFrame->IsHeaderFrame();
+ return nullptr != pFrame;
+}
+
+int SwCursorShell::SetCursor( const Point &rLPt, bool bOnlyText, bool bBlock )
+{
+ CurrShell aCurr( this );
+
+ SwShellCursor* pCursor = getShellCursor( bBlock );
+ SwPosition aPos( *pCursor->GetPoint() );
+ Point aPt( rLPt );
+ Point & rCurrentCursorPt = pCursor->GetPtPos();
+ SwCursorMoveState aTmpState( IsTableMode() ? CursorMoveState::TableSel :
+ bOnlyText ? CursorMoveState::SetOnlyText : CursorMoveState::NONE );
+ aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
+
+ SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
+
+ if ( pTextNd && !IsTableMode() &&
+ // #i37515# No bInFrontOfLabel during selection
+ !pCursor->HasMark() &&
+ pTextNd->HasVisibleNumberingOrBullet() )
+ {
+ aTmpState.m_bInFrontOfLabel = true; // #i27615#
+ }
+ else
+ {
+ aTmpState.m_bInFrontOfLabel = false;
+ }
+
+ int bRet = CRSR_POSOLD |
+ ( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState )
+ ? 0 : CRSR_POSCHG );
+
+ const bool bOldInFrontOfLabel = IsInFrontOfLabel();
+ const bool bNewInFrontOfLabel = aTmpState.m_bInFrontOfLabel;
+
+ pCursor->SetCursorBidiLevel( aTmpState.m_nCursorBidiLevel );
+
+ if( CursorMoveState::RightMargin == aTmpState.m_eState )
+ m_eMvState = CursorMoveState::RightMargin;
+ // is the new position in header or footer?
+ SwFrame* pFrame = lcl_IsInHeaderFooter( aPos.GetNode(), aPt );
+ if( IsTableMode() && !pFrame && aPos.GetNode().StartOfSectionNode() ==
+ pCursor->GetPoint()->GetNode().StartOfSectionNode() )
+ // same table column and not in header/footer -> back
+ return bRet;
+
+ if( m_pBlockCursor && bBlock )
+ {
+ m_pBlockCursor->setEndPoint( rLPt );
+ if( !pCursor->HasMark() )
+ m_pBlockCursor->setStartPoint( rLPt );
+ else if( !m_pBlockCursor->getStartPoint() )
+ m_pBlockCursor->setStartPoint( pCursor->GetMkPos() );
+ }
+ if( !pCursor->HasMark() )
+ {
+ // is at the same position and if in header/footer -> in the same
+ if( aPos == *pCursor->GetPoint() &&
+ bOldInFrontOfLabel == bNewInFrontOfLabel )
+ {
+ if( pFrame )
+ {
+ if( pFrame->getFrameArea().Contains( rCurrentCursorPt ))
+ return bRet;
+ }
+ else if( aPos.GetNode().IsContentNode() )
+ {
+ // in the same frame?
+ std::pair<Point, bool> tmp(m_aCharRect.Pos(), false);
+ SwFrame* pOld = static_cast<SwContentNode&>(aPos.GetNode()).getLayoutFrame(
+ GetLayout(), nullptr, &tmp);
+ tmp.first = aPt;
+ SwFrame* pNew = static_cast<SwContentNode&>(aPos.GetNode()).getLayoutFrame(
+ GetLayout(), nullptr, &tmp);
+ if( pNew == pOld )
+ return bRet;
+ }
+ }
+ }
+ else
+ {
+ // SSelection over not allowed sections or if in header/footer -> different
+ if( !CheckNodesRange( aPos.GetNode(), pCursor->GetMark()->GetNode(), true )
+ || ( pFrame && !pFrame->getFrameArea().Contains( pCursor->GetMkPos() ) ))
+ return bRet;
+
+ // is at same position but not in header/footer
+ if( aPos == *pCursor->GetPoint() )
+ return bRet;
+ }
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ SwCursorSaveState aSaveState( *pCursor );
+
+ *pCursor->GetPoint() = aPos;
+ rCurrentCursorPt = aPt;
+
+ // #i41424# Only update the marked number levels if necessary
+ // Force update of marked number levels if necessary.
+ if ( bNewInFrontOfLabel || bOldInFrontOfLabel )
+ m_pCurrentCursor->SetInFrontOfLabel_( !bNewInFrontOfLabel );
+ SetInFrontOfLabel( bNewInFrontOfLabel );
+
+ if( !pCursor->IsSelOvr( SwCursorSelOverFlags::ChangePos ) )
+ {
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE );
+ bRet &= ~CRSR_POSOLD;
+ }
+ else if( bOnlyText && !m_pCurrentCursor->HasMark() )
+ {
+ if( FindValidContentNode( bOnlyText ) )
+ {
+ // position cursor in a valid content
+ if( aPos == *pCursor->GetPoint() )
+ bRet = CRSR_POSOLD;
+ else
+ {
+ UpdateCursor();
+ bRet &= ~CRSR_POSOLD;
+ }
+ }
+ else
+ {
+ // there is no valid content -> hide cursor
+ m_pVisibleCursor->Hide(); // always hide visible cursor
+ m_eMvState = CursorMoveState::NONE; // status for Cursor travelling
+ m_bAllProtect = true;
+ if( GetDoc()->GetDocShell() )
+ {
+ GetDoc()->GetDocShell()->SetReadOnlyUI();
+ CallChgLnk(); // notify UI
+ }
+ }
+ }
+ return bRet;
+}
+
+void SwCursorShell::TableCursorToCursor()
+{
+ assert(m_pTableCursor);
+ delete m_pTableCursor;
+ m_pTableCursor = nullptr;
+}
+
+void SwCursorShell::BlockCursorToCursor()
+{
+ assert(m_pBlockCursor);
+ if( m_pBlockCursor && !HasSelection() )
+ {
+ SwPaM& rPam = m_pBlockCursor->getShellCursor();
+ m_pCurrentCursor->SetMark();
+ *m_pCurrentCursor->GetPoint() = *rPam.GetPoint();
+ if( rPam.HasMark() )
+ *m_pCurrentCursor->GetMark() = *rPam.GetMark();
+ else
+ m_pCurrentCursor->DeleteMark();
+ }
+ delete m_pBlockCursor;
+ m_pBlockCursor = nullptr;
+}
+
+void SwCursorShell::CursorToBlockCursor()
+{
+ if( !m_pBlockCursor )
+ {
+ SwPosition aPos( *m_pCurrentCursor->GetPoint() );
+ m_pBlockCursor = new SwBlockCursor( *this, aPos );
+ SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
+ rBlock.GetPtPos() = m_pCurrentCursor->GetPtPos();
+ if( m_pCurrentCursor->HasMark() )
+ {
+ rBlock.SetMark();
+ *rBlock.GetMark() = *m_pCurrentCursor->GetMark();
+ rBlock.GetMkPos() = m_pCurrentCursor->GetMkPos();
+ }
+ }
+ m_pBlockCursor->clearPoints();
+ RefreshBlockCursor();
+}
+
+void SwCursorShell::ClearMark()
+{
+ // is there any GetMark?
+ if( m_pTableCursor )
+ {
+ std::vector<SwPaM*> vCursors;
+ for(auto& rCursor : m_pCurrentCursor->GetRingContainer())
+ if(&rCursor != m_pCurrentCursor)
+ vCursors.push_back(&rCursor);
+ for(auto pCursor : vCursors)
+ delete pCursor;
+ m_pTableCursor->DeleteMark();
+
+ m_pCurrentCursor->DeleteMark();
+
+ *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
+ m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
+ delete m_pTableCursor;
+ m_pTableCursor = nullptr;
+ m_pCurrentCursor->SwSelPaintRects::Show();
+ }
+ else
+ {
+ if( !m_pCurrentCursor->HasMark() )
+ return;
+ m_pCurrentCursor->DeleteMark();
+ if( !m_nCursorMove )
+ m_pCurrentCursor->SwSelPaintRects::Show();
+ }
+}
+
+void SwCursorShell::NormalizePam(bool bPointFirst)
+{
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ m_pCurrentCursor->Normalize(bPointFirst);
+}
+
+void SwCursorShell::SwapPam()
+{
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ m_pCurrentCursor->Exchange();
+}
+
+//TODO: provide documentation
+/** Search in the selected area for a Selection that covers the given point.
+
+ It checks if a Selection exists but does
+ not move the current cursor.
+
+ @param rPt The point to search at.
+ @param bTstHit ???
+*/
+bool SwCursorShell::TestCurrPam(
+ const Point & rPt,
+ bool bTstHit )
+{
+ CurrShell aCurr( this );
+
+ // check if the SPoint is in a table selection
+ if( m_pTableCursor )
+ return m_pTableCursor->Contains( rPt );
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ // search position <rPt> in document
+ SwPosition aPtPos( *m_pCurrentCursor->GetPoint() );
+ Point aPt( rPt );
+
+ SwCursorMoveState aTmpState( CursorMoveState::NONE );
+ aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
+ if ( !GetLayout()->GetModelPositionForViewPoint( &aPtPos, aPt, &aTmpState ) && bTstHit )
+ return false;
+
+ // search in all selections for this position
+ SwShellCursor* pCmp = m_pCurrentCursor; // keep the pointer on cursor
+ do
+ {
+ if (pCmp->HasMark() && *pCmp->Start() <= aPtPos && *pCmp->End() > aPtPos)
+ return true; // return without update
+ pCmp = pCmp->GetNext();
+ } while (m_pCurrentCursor != pCmp);
+ return false;
+}
+
+void SwCursorShell::KillPams()
+{
+ // Does any exist for deletion?
+ if( !m_pTableCursor && !m_pBlockCursor && !m_pCurrentCursor->IsMultiSelection() )
+ return;
+
+ while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
+ delete m_pCurrentCursor->GetNext();
+ m_pCurrentCursor->SetColumnSelection( false );
+
+ if( m_pTableCursor )
+ {
+ // delete the ring of cursors
+ m_pCurrentCursor->DeleteMark();
+ *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
+ m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
+ delete m_pTableCursor;
+ m_pTableCursor = nullptr;
+ }
+ else if( m_pBlockCursor )
+ {
+ // delete the ring of cursors
+ m_pCurrentCursor->DeleteMark();
+ SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
+ *m_pCurrentCursor->GetPoint() = *rBlock.GetPoint();
+ m_pCurrentCursor->GetPtPos() = rBlock.GetPtPos();
+ rBlock.DeleteMark();
+ m_pBlockCursor->clearPoints();
+ }
+ UpdateCursor( SwCursorShell::SCROLLWIN );
+}
+
+int SwCursorShell::CompareCursorStackMkCurrPt() const
+{
+ int nRet = 0;
+ const SwPosition *pFirst = nullptr, *pSecond = nullptr;
+ const SwCursor *pCur = GetCursor(), *pStack = m_pStackCursor;
+ // cursor on stack is needed if we compare against stack
+ if( pStack )
+ {
+ pFirst = pStack->GetMark();
+ pSecond = pCur->GetPoint();
+ }
+ if( !pFirst || !pSecond )
+ nRet = INT_MAX;
+ else if( *pFirst < *pSecond )
+ nRet = -1;
+ else if( *pFirst == *pSecond )
+ nRet = 0;
+ else
+ nRet = 1;
+ return nRet;
+}
+
+bool SwCursorShell::IsSelOnePara() const
+{
+ if (m_pCurrentCursor->IsMultiSelection())
+ {
+ return false;
+ }
+ if (m_pCurrentCursor->GetPoint()->GetNode() == m_pCurrentCursor->GetMark()->GetNode())
+ {
+ return true;
+ }
+ if (GetLayout()->HasMergedParas())
+ {
+ SwContentFrame const*const pFrame(GetCurrFrame(false));
+ auto const n(m_pCurrentCursor->GetMark()->GetNodeIndex());
+ return FrameContainsNode(*pFrame, n);
+ }
+ return false;
+}
+
+bool SwCursorShell::IsSttPara() const
+{
+ if (GetLayout()->HasMergedParas())
+ {
+ SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode());
+ if (pNode)
+ {
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
+ pNode->getLayoutFrame(GetLayout())));
+ if (pFrame)
+ {
+ return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint())
+ == TextFrameIndex(0);
+ }
+ }
+ }
+ return m_pCurrentCursor->GetPoint()->GetContentIndex() == 0;
+}
+
+bool SwCursorShell::IsEndPara() const
+{
+ if (GetLayout()->HasMergedParas())
+ {
+ SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode());
+ if (pNode)
+ {
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(
+ pNode->getLayoutFrame(GetLayout())));
+ if (pFrame)
+ {
+ return pFrame->MapModelToViewPos(*m_pCurrentCursor->GetPoint())
+ == TextFrameIndex(pFrame->GetText().getLength());
+ }
+ }
+ }
+ return m_pCurrentCursor->GetPoint()->GetContentIndex() == m_pCurrentCursor->GetPointContentNode()->Len();
+}
+
+bool SwCursorShell::IsEndOfTable() const
+{
+ if (IsTableMode() || IsBlockMode() || !IsEndPara())
+ {
+ return false;
+ }
+ SwTableNode const*const pTableNode( IsCursorInTable() );
+ if (!pTableNode)
+ {
+ return false;
+ }
+ SwEndNode const*const pEndTableNode(pTableNode->EndOfSectionNode());
+ SwNodeIndex const lastNode(*pEndTableNode, -2);
+ SAL_WARN_IF(!lastNode.GetNode().GetTextNode(), "sw.core",
+ "text node expected");
+ return (lastNode == m_pCurrentCursor->GetPoint()->GetNode());
+}
+
+bool SwCursorShell::IsCursorInFootnote() const
+{
+ SwStartNodeType aStartNodeType = m_pCurrentCursor->GetPointNode().StartOfSectionNode()->GetStartNodeType();
+ return aStartNodeType == SwStartNodeType::SwFootnoteStartNode;
+}
+
+Point SwCursorShell::GetCursorPagePos() const
+{
+ Point aRet(-1, -1);
+ if (SwFrame *pFrame = GetCurrFrame())
+ {
+ if (SwPageFrame* pCurrentPage = pFrame->FindPageFrame())
+ {
+ const Point& rDocPos = GetCursorDocPos();
+ aRet = rDocPos - pCurrentPage->getFrameArea().TopLeft();
+ }
+ }
+ return aRet;
+}
+
+bool SwCursorShell::IsInFrontOfLabel() const
+{
+ return m_pCurrentCursor->IsInFrontOfLabel();
+}
+
+bool SwCursorShell::SetInFrontOfLabel( bool bNew )
+{
+ if ( bNew != IsInFrontOfLabel() )
+ {
+ m_pCurrentCursor->SetInFrontOfLabel_( bNew );
+ UpdateMarkedListLevel();
+ return true;
+ }
+ return false;
+}
+
+namespace {
+
+void collectUIInformation(const OUString& aPage)
+{
+ EventDescription aDescription;
+ aDescription.aAction = "GOTO";
+ aDescription.aParameters = {{"PAGE", aPage}};
+ aDescription.aID = "writer_edit";
+ aDescription.aKeyWord = "SwEditWinUIObject";
+ aDescription.aParent = "MainWindow";
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+
+}
+
+bool SwCursorShell::GotoPage( sal_uInt16 nPage )
+{
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ bool bRet = GetLayout()->SetCurrPage( m_pCurrentCursor, nPage ) &&
+ !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos );
+ if( bRet )
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+
+ collectUIInformation(OUString::number(nPage));
+ return bRet;
+}
+
+void SwCursorShell::GetCharRectAt(SwRect& rRect, const SwPosition* pPos)
+{
+ SwContentFrame* pFrame = GetCurrFrame();
+ pFrame->GetCharRect( rRect, *pPos );
+}
+
+void SwCursorShell::GetPageNum( sal_uInt16 &rnPhyNum, sal_uInt16 &rnVirtNum,
+ bool bAtCursorPos, const bool bCalcFrame )
+{
+ CurrShell aCurr( this );
+ // page number: first visible page or the one at the cursor
+ const SwContentFrame* pCFrame;
+ const SwPageFrame *pPg = nullptr;
+
+ if( !bAtCursorPos || nullptr == (pCFrame = GetCurrFrame( bCalcFrame )) ||
+ nullptr == (pPg = pCFrame->FindPageFrame()) )
+ {
+ pPg = Imp()->GetFirstVisPage(GetOut());
+ while( pPg && pPg->IsEmptyPage() )
+ pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
+ }
+ // pPg has to exist with a default of 1 for the special case "Writerstart"
+ rnPhyNum = pPg? pPg->GetPhyPageNum() : 1;
+ rnVirtNum = pPg? pPg->GetVirtPageNum() : 1;
+}
+
+sal_uInt16 SwCursorShell::GetPageNumSeqNonEmpty()
+{
+ CurrShell aCurr(this);
+ // page number: first visible page or the one at the cursor
+ const SwContentFrame* pCFrame = GetCurrFrame(/*bCalcFrame*/true);
+ const SwPageFrame* pPg = nullptr;
+
+ if (pCFrame == nullptr || nullptr == (pPg = pCFrame->FindPageFrame()))
+ {
+ pPg = Imp()->GetFirstVisPage(GetOut());
+ while (pPg && pPg->IsEmptyPage())
+ pPg = static_cast<const SwPageFrame*>(pPg->GetNext());
+ }
+
+ sal_uInt16 nPageNo = 0;
+ while (pPg)
+ {
+ if (!pPg->IsEmptyPage())
+ ++nPageNo;
+ pPg = static_cast<const SwPageFrame*>(pPg->GetPrev());
+ }
+ return nPageNo;
+}
+
+sal_uInt16 SwCursorShell::GetNextPrevPageNum( bool bNext )
+{
+ CurrShell aCurr( this );
+ // page number: first visible page or the one at the cursor
+ const SwPageFrame *pPg = Imp()->GetFirstVisPage(GetOut());
+ if( pPg )
+ {
+ const SwTwips nPageTop = pPg->getFrameArea().Top();
+
+ if( bNext )
+ {
+ // go to next view layout row:
+ do
+ {
+ pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
+ }
+ while( pPg && pPg->getFrameArea().Top() == nPageTop );
+
+ while( pPg && pPg->IsEmptyPage() )
+ pPg = static_cast<const SwPageFrame *>(pPg->GetNext());
+ }
+ else
+ {
+ // go to previous view layout row:
+ do
+ {
+ pPg = static_cast<const SwPageFrame *>(pPg->GetPrev());
+ }
+ while( pPg && pPg->getFrameArea().Top() == nPageTop );
+
+ while( pPg && pPg->IsEmptyPage() )
+ pPg = static_cast<const SwPageFrame *>(pPg->GetPrev());
+ }
+ }
+ // pPg has to exist with a default of 1 for the special case "Writerstart"
+ return pPg ? pPg->GetPhyPageNum() : USHRT_MAX;
+}
+
+sal_uInt16 SwCursorShell::GetPageCnt()
+{
+ CurrShell aCurr( this );
+ // return number of pages
+ return GetLayout()->GetPageNum();
+}
+
+OUString SwCursorShell::getPageRectangles()
+{
+ CurrShell aCurr(this);
+ SwRootFrame* pLayout = GetLayout();
+ OUStringBuffer aBuf;
+ for (const SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext())
+ {
+ aBuf.append(OUString::number(pFrame->getFrameArea().Left())
+ + ", "
+ + OUString::number(pFrame->getFrameArea().Top())
+ + ", "
+ + OUString::number(pFrame->getFrameArea().Width())
+ + ", "
+ + OUString::number(pFrame->getFrameArea().Height())
+ + "; ");
+ }
+ if (!aBuf.isEmpty())
+ aBuf.setLength( aBuf.getLength() - 2); // remove the last "; "
+ return aBuf.makeStringAndClear();
+}
+
+void SwCursorShell::NotifyCursor(SfxViewShell* pOtherShell) const
+{
+ auto pView = const_cast<SdrView*>(GetDrawView());
+ if (pView->GetTextEditObject())
+ {
+ // Blinking cursor.
+ EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView();
+ rEditView.RegisterOtherShell(pOtherShell);
+ rEditView.ShowCursor();
+ rEditView.RegisterOtherShell(nullptr);
+ // Text selection, if any.
+ rEditView.DrawSelectionXOR(pOtherShell);
+
+ // Shape text lock.
+ if (OutlinerView* pOutlinerView = pView->GetTextEditOutlinerView())
+ {
+ OString sRect = pOutlinerView->GetOutputArea().toString();
+ SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_LOCK, "rectangle", sRect);
+ }
+ }
+ else
+ {
+ // Cursor position.
+ m_pVisibleCursor->SetPosAndShow(pOtherShell);
+ // Cursor visibility.
+ if (GetSfxViewShell() != pOtherShell)
+ {
+ OString aPayload = OString::boolean(m_bSVCursorVis);
+ SfxLokHelper::notifyOtherView(GetSfxViewShell(), pOtherShell, LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
+ }
+ // Text selection.
+ m_pCurrentCursor->Show(pOtherShell);
+ // Graphic selection.
+ pView->AdjustMarkHdl(pOtherShell);
+ }
+}
+
+/// go to the next SSelection
+bool SwCursorShell::GoNextCursor()
+{
+ if( !m_pCurrentCursor->IsMultiSelection() )
+ return false;
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ m_pCurrentCursor = m_pCurrentCursor->GetNext();
+
+ // #i24086#: show also all others
+ if( !ActionPend() )
+ {
+ UpdateCursor();
+ m_pCurrentCursor->Show(nullptr);
+ }
+ return true;
+}
+
+/// go to the previous SSelection
+bool SwCursorShell::GoPrevCursor()
+{
+ if( !m_pCurrentCursor->IsMultiSelection() )
+ return false;
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ m_pCurrentCursor = m_pCurrentCursor->GetPrev();
+
+ // #i24086#: show also all others
+ if( !ActionPend() )
+ {
+ UpdateCursor();
+ m_pCurrentCursor->Show(nullptr);
+ }
+ return true;
+}
+
+void SwCursorShell::GoNextPrevCursorSetSearchLabel(const bool bNext)
+{
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
+
+ if( !m_pCurrentCursor->IsMultiSelection() )
+ {
+ if( !m_pCurrentCursor->HasMark() )
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return;
+ }
+
+ if (bNext)
+ GoNextCursor();
+ else
+ GoPrevCursor();
+}
+
+void SwCursorShell::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect)
+{
+ comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
+ CurrShell aCurr( this );
+
+ // always switch off all cursors when painting
+ SwRect aRect( rRect );
+
+ bool bVis = false;
+ // if a cursor is visible then hide the SV cursor
+ if( m_pVisibleCursor->IsVisible() && !aRect.Overlaps( m_aCharRect ) )
+ {
+ bVis = true;
+ m_pVisibleCursor->Hide();
+ }
+
+ // re-paint area
+ SwViewShell::Paint(rRenderContext, rRect);
+
+ if( m_bHasFocus && !m_bBasicHideCursor )
+ {
+ SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
+
+ if( !ActionPend() )
+ {
+ // so that right/bottom borders will not be cropped
+ pCurrentCursor->Invalidate( VisArea() );
+ pCurrentCursor->Show(nullptr);
+ }
+ else
+ pCurrentCursor->Invalidate( aRect );
+
+ }
+
+ if (SwPostItMgr* pPostItMgr = GetPostItMgr())
+ {
+ // No point in showing the cursor for Writer text when there is an
+ // active annotation edit.
+ if (bVis)
+ bVis = !pPostItMgr->HasActiveSidebarWin();
+ }
+
+ if( m_bSVCursorVis && bVis ) // also show SV cursor again
+ m_pVisibleCursor->Show();
+}
+
+void SwCursorShell::VisPortChgd( const SwRect & rRect )
+{
+ CurrShell aCurr( this );
+ bool bVis; // switch off all cursors when scrolling
+
+ // if a cursor is visible then hide the SV cursor
+ bVis = m_pVisibleCursor->IsVisible();
+ if( bVis )
+ m_pVisibleCursor->Hide();
+
+ m_bVisPortChgd = true;
+ m_aOldRBPos.setX(VisArea().Right());
+ m_aOldRBPos.setY(VisArea().Bottom());
+
+ // For not having problems with the SV cursor, Update() is called for the
+ // Window in SwViewShell::VisPo...
+ // During painting no selections should be shown, thus the call is encapsulated. <- TODO: old artefact?
+ SwViewShell::VisPortChgd( rRect ); // move area
+
+ if( m_bSVCursorVis && bVis ) // show SV cursor again
+ m_pVisibleCursor->Show();
+
+ if( m_nCursorMove )
+ m_bInCMvVisportChgd = true;
+
+ m_bVisPortChgd = false;
+}
+
+/** Set the cursor back into content.
+
+ This should only be called if the cursor was moved (e.g. when deleting a
+ text frame). The new position is calculated from its current position
+ in the layout.
+*/
+void SwCursorShell::UpdateCursorPos()
+{
+ CurrShell aCurr( this );
+ ++mnStartAction;
+ SwShellCursor* pShellCursor = getShellCursor( true );
+ Size aOldSz( GetDocSize() );
+
+ if (isInHiddenTextFrame(pShellCursor) && !ExtendedSelectedAll())
+ {
+ SwCursorMoveState aTmpState(CursorMoveState::SetOnlyText);
+ aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
+ GetLayout()->GetModelPositionForViewPoint( pShellCursor->GetPoint(), pShellCursor->GetPtPos(),
+ &aTmpState );
+ pShellCursor->DeleteMark();
+ // kde45196-1.html: try to get to a non-hidden paragraph, there must
+ // be one in the document body
+ while (isInHiddenTextFrame(pShellCursor))
+ {
+ if (!pShellCursor->MovePara(GoNextPara, fnParaStart))
+ {
+ break;
+ }
+ }
+ while (isInHiddenTextFrame(pShellCursor))
+ {
+ if (!pShellCursor->MovePara(GoPrevPara, fnParaStart))
+ {
+ break;
+ }
+ }
+ }
+ auto* pDoc = GetDoc();
+ if (pDoc)
+ {
+ pDoc->getGrammarContact()->updateCursorPosition(*m_pCurrentCursor->GetPoint());
+ pDoc->getOnlineAccessibilityCheck()->update(*m_pCurrentCursor->GetPoint());
+ }
+
+ --mnStartAction;
+ if( aOldSz != GetDocSize() )
+ SizeChgNotify();
+}
+
+// #i65475# - if Point/Mark in hidden sections, move them out
+static bool lcl_CheckHiddenSection( SwPosition& rPos )
+{
+ bool bOk = true;
+ const SwSectionNode* pSectNd = rPos.GetNode().FindSectionNode();
+ if( pSectNd && pSectNd->GetSection().IsHiddenFlag() )
+ {
+ const SwNode* pFrameNd =
+ rPos.GetNodes().FindPrvNxtFrameNode( *pSectNd, pSectNd->EndOfSectionNode() );
+ bOk = pFrameNd != nullptr;
+ SAL_WARN_IF(!bOk, "sw.core", "found no Node with Frames");
+ rPos.Assign( *(bOk ? pFrameNd : pSectNd) );
+ }
+ return bOk;
+}
+
+/// Try to set the cursor to the next visible content node.
+static void lcl_CheckHiddenPara( SwPosition& rPos )
+{
+ SwNodeIndex aTmp( rPos.GetNode() );
+ SwTextNode* pTextNd = aTmp.GetNode().GetTextNode();
+ while( pTextNd && pTextNd->HasHiddenCharAttribute( true ) )
+ {
+ SwContentNode* pContent = aTmp.GetNodes().GoNext( &aTmp );
+ if ( pContent && pContent->IsTextNode() )
+ pTextNd = pContent->GetTextNode();
+ else
+ pTextNd = nullptr;
+ }
+
+ if ( pTextNd )
+ rPos.Assign( *pTextNd, 0 );
+}
+
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+namespace {
+
+// #i27301# - helper class that notifies the accessibility about invalid text
+// selections in its destructor
+class SwNotifyAccAboutInvalidTextSelections
+{
+ private:
+ SwCursorShell& mrCursorSh;
+
+ public:
+ explicit SwNotifyAccAboutInvalidTextSelections( SwCursorShell& _rCursorSh )
+ : mrCursorSh( _rCursorSh )
+ {}
+
+ ~SwNotifyAccAboutInvalidTextSelections() COVERITY_NOEXCEPT_FALSE
+ {
+ mrCursorSh.InvalidateAccessibleParaTextSelection();
+ }
+};
+
+}
+#endif
+
+void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd )
+{
+ CurrShell aCurr( this );
+ ClearUpCursors();
+
+ if (ActionPend())
+ {
+ if ( eFlags & SwCursorShell::READONLY )
+ m_bIgnoreReadonly = true;
+ return; // if not then no update
+ }
+
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ SwNotifyAccAboutInvalidTextSelections aInvalidateTextSelections( *this );
+#endif
+
+ if ( m_bIgnoreReadonly )
+ {
+ m_bIgnoreReadonly = false;
+ eFlags |= SwCursorShell::READONLY;
+ }
+
+ if( eFlags & SwCursorShell::CHKRANGE ) // check all cursor moves for
+ CheckRange( m_pCurrentCursor ); // overlapping ranges
+
+ if( !bIdleEnd )
+ CheckTableBoxContent();
+
+ // If the current cursor is in a table and point/mark in different boxes,
+ // then the table mode is active (also if it is already active: m_pTableCursor)
+ SwPaM* pTstCursor = getShellCursor( true );
+ if( pTstCursor->HasMark() && !m_pBlockCursor &&
+ SwDoc::IsInTable( pTstCursor->GetPoint()->GetNode() ) &&
+ ( m_pTableCursor ||
+ pTstCursor->GetPointNode().StartOfSectionNode() !=
+ pTstCursor->GetMarkNode().StartOfSectionNode() ) && !mbSelectAll)
+ {
+ SwShellCursor* pITmpCursor = getShellCursor( true );
+ Point aTmpPt( pITmpCursor->GetPtPos() );
+ Point aTmpMk( pITmpCursor->GetMkPos() );
+ SwPosition* pPos = pITmpCursor->GetPoint();
+
+ // Bug 65475 (1999) - if Point/Mark in hidden sections, move them out
+ lcl_CheckHiddenSection( *pPos );
+ lcl_CheckHiddenSection( *pITmpCursor->GetMark() );
+
+ // Move cursor out of hidden paragraphs
+ if ( !GetViewOptions()->IsShowHiddenChar() )
+ {
+ lcl_CheckHiddenPara( *pPos );
+ lcl_CheckHiddenPara( *pITmpCursor->GetMark() );
+ }
+
+ std::pair<Point, bool> const tmp(aTmpPt, false);
+ SwContentFrame *pTableFrame = pPos->GetNode().GetContentNode()->
+ getLayoutFrame( GetLayout(), pPos, &tmp);
+
+ OSL_ENSURE( pTableFrame, "Table Cursor not in Content ??" );
+
+ // --> Make code robust. The table cursor may point
+ // to a table in a currently inactive header.
+ SwTabFrame *pTab = pTableFrame ? pTableFrame->FindTabFrame() : nullptr;
+
+ if ( pTab && pTab->GetTable()->GetRowsToRepeat() > 0 )
+ {
+ // First check if point is in repeated headline:
+ bool bInRepeatedHeadline = pTab->IsFollow() && pTab->IsInHeadline( *pTableFrame );
+
+ // Second check if mark is in repeated headline:
+ if ( !bInRepeatedHeadline )
+ {
+ std::pair<Point, bool> const tmp1(aTmpMk, false);
+ SwContentFrame* pMarkTableFrame = pITmpCursor->GetMarkContentNode()->
+ getLayoutFrame(GetLayout(), pITmpCursor->GetMark(), &tmp1);
+ OSL_ENSURE( pMarkTableFrame, "Table Cursor not in Content ??" );
+
+ if ( pMarkTableFrame )
+ {
+ SwTabFrame* pMarkTab = pMarkTableFrame->FindTabFrame();
+ OSL_ENSURE( pMarkTab, "Table Cursor not in Content ??" );
+
+ // Make code robust:
+ if ( pMarkTab )
+ {
+ bInRepeatedHeadline = pMarkTab->IsFollow() && pMarkTab->IsInHeadline( *pMarkTableFrame );
+ }
+ }
+ }
+
+ // No table cursor in repeated headlines:
+ if ( bInRepeatedHeadline )
+ {
+ pTableFrame = nullptr;
+
+ SwMoveFnCollection const & fnPosSect = *pPos < *pITmpCursor->GetMark()
+ ? fnSectionStart
+ : fnSectionEnd;
+
+ // then only select inside the Box
+ if( m_pTableCursor )
+ {
+ m_pCurrentCursor->SetMark();
+ *m_pCurrentCursor->GetMark() = *m_pTableCursor->GetMark();
+ m_pCurrentCursor->GetMkPos() = m_pTableCursor->GetMkPos();
+ m_pTableCursor->DeleteMark();
+ m_pTableCursor->SwSelPaintRects::Hide();
+ }
+
+ *m_pCurrentCursor->GetPoint() = *m_pCurrentCursor->GetMark();
+ GoCurrSection( *m_pCurrentCursor, fnPosSect );
+ }
+ }
+
+ // we really want a table selection
+ if( pTab && pTableFrame )
+ {
+ if( !m_pTableCursor )
+ {
+ m_pTableCursor = new SwShellTableCursor( *this,
+ *m_pCurrentCursor->GetMark(), m_pCurrentCursor->GetMkPos(),
+ *pPos, aTmpPt );
+ m_pCurrentCursor->DeleteMark();
+ m_pCurrentCursor->SwSelPaintRects::Hide();
+
+ CheckTableBoxContent();
+ if(!m_pTableCursor)
+ {
+ SAL_WARN("sw.core", "fdo#74854: "
+ "this should not happen, but better lose the selection "
+ "rather than crashing");
+ return;
+ }
+ }
+
+ SwCursorMoveState aTmpState( CursorMoveState::NONE );
+ aTmpState.m_bRealHeight = true;
+ {
+ DisableCallbackAction a(*GetLayout());
+ if (!pTableFrame->GetCharRect( m_aCharRect, *m_pTableCursor->GetPoint(), &aTmpState))
+ {
+ Point aCentrPt( m_aCharRect.Center() );
+ aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
+ pTableFrame->GetModelPositionForViewPoint(m_pTableCursor->GetPoint(), aCentrPt, &aTmpState);
+ bool const bResult =
+ pTableFrame->GetCharRect(m_aCharRect, *m_pTableCursor->GetPoint());
+ OSL_ENSURE( bResult, "GetCharRect failed." );
+ }
+ }
+
+ m_pVisibleCursor->Hide(); // always hide visible Cursor
+ // scroll Cursor to visible area
+ if( eFlags & SwCursorShell::SCROLLWIN &&
+ (HasSelection() || eFlags & SwCursorShell::READONLY ||
+ !IsCursorReadonly()) )
+ {
+ SwFrame* pBoxFrame = pTableFrame;
+ while( pBoxFrame && !pBoxFrame->IsCellFrame() )
+ pBoxFrame = pBoxFrame->GetUpper();
+ if( pBoxFrame && pBoxFrame->getFrameArea().HasArea() )
+ MakeVisible( pBoxFrame->getFrameArea() );
+ else
+ MakeVisible( m_aCharRect );
+ }
+
+ // let Layout create the Cursors in the Boxes
+ if( m_pTableCursor->IsCursorMovedUpdate() )
+ GetLayout()->MakeTableCursors( *m_pTableCursor );
+ if( m_bHasFocus && !m_bBasicHideCursor )
+ m_pTableCursor->Show(nullptr);
+
+ // set Cursor-Points to the new Positions
+ m_pTableCursor->GetPtPos().setX(m_aCharRect.Left());
+ m_pTableCursor->GetPtPos().setY(m_aCharRect.Top());
+
+ if( m_bSVCursorVis )
+ {
+ m_aCursorHeight.setX(0);
+ m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ?
+ -m_aCharRect.Width() : m_aCharRect.Height());
+ m_pVisibleCursor->Show(); // show again
+ }
+ m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents)
+ Imp()->InvalidateAccessibleCursorPosition( pTableFrame );
+#endif
+ return;
+ }
+ }
+
+ if( m_pTableCursor )
+ {
+ // delete Ring
+ while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
+ delete m_pCurrentCursor->GetNext();
+ m_pCurrentCursor->DeleteMark();
+ *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
+ m_pCurrentCursor->GetPtPos() = m_pTableCursor->GetPtPos();
+ delete m_pTableCursor;
+ m_pTableCursor = nullptr;
+ }
+
+ m_pVisibleCursor->Hide(); // always hide visible Cursor
+
+ // are we perhaps in a protected / hidden Section ?
+ {
+ SwShellCursor* pShellCursor = getShellCursor( true );
+ bool bChgState = true;
+ const SwSectionNode* pSectNd = pShellCursor->GetPointNode().FindSectionNode();
+ if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() ||
+ ( !IsReadOnlyAvailable() &&
+ pSectNd->GetSection().IsProtectFlag() &&
+ ( !mxDoc->GetDocShell() ||
+ !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect )) ) )
+ {
+ if( !FindValidContentNode( !HasDrawView() ||
+ 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount()))
+ {
+ // everything protected/hidden -> special mode
+ if( m_bAllProtect && !IsReadOnlyAvailable() &&
+ pSectNd->GetSection().IsProtectFlag() )
+ bChgState = false;
+ else
+ {
+ m_eMvState = CursorMoveState::NONE; // state for cursor travelling
+ m_bAllProtect = true;
+ if( GetDoc()->GetDocShell() )
+ {
+ GetDoc()->GetDocShell()->SetReadOnlyUI();
+ CallChgLnk(); // notify UI!
+ }
+ return;
+ }
+ }
+ }
+ if( bChgState )
+ {
+ bool bWasAllProtect = m_bAllProtect;
+ m_bAllProtect = false;
+ if( bWasAllProtect && GetDoc()->GetDocShell() &&
+ GetDoc()->GetDocShell()->IsReadOnlyUI() )
+ {
+ GetDoc()->GetDocShell()->SetReadOnlyUI( false );
+ CallChgLnk(); // notify UI!
+ }
+ }
+ }
+
+ UpdateCursorPos();
+
+ // The cursor must always point into content; there's some code
+ // that relies on this. (E.g. in SwEditShell::GetScriptType, which always
+ // loops _behind_ the last node in the selection, which always works if you
+ // are in content.) To achieve this, we'll force cursor(s) to point into
+ // content, if UpdateCursorPos() hasn't already done so.
+ for(SwPaM& rCmp : m_pCurrentCursor->GetRingContainer())
+ {
+ // start will move forwards, end will move backwards
+ bool bPointIsStart = ( rCmp.Start() == rCmp.GetPoint() );
+
+ // move point; forward if it's the start, backwards if it's the end
+ if( ! rCmp.GetPoint()->GetNode().IsContentNode() )
+ rCmp.Move( bPointIsStart ? fnMoveForward : fnMoveBackward,
+ GoInContent );
+
+ // move mark (if exists); forward if it's the start, else backwards
+ if( rCmp.HasMark() )
+ {
+ if( ! rCmp.GetMark()->GetNode().IsContentNode() )
+ {
+ rCmp.Exchange();
+ rCmp.Move( !bPointIsStart ? fnMoveForward : fnMoveBackward,
+ GoInContent );
+ rCmp.Exchange();
+ }
+ }
+ }
+
+ SwRect aOld( m_aCharRect );
+ bool bFirst = true;
+ SwContentFrame *pFrame;
+ int nLoopCnt = 100;
+ SwShellCursor* pShellCursor = getShellCursor( true );
+
+ do {
+ bool bAgainst;
+ do {
+ bAgainst = false;
+ std::pair<Point, bool> const tmp1(pShellCursor->GetPtPos(), false);
+ pFrame = pShellCursor->GetPointContentNode()->getLayoutFrame(GetLayout(),
+ pShellCursor->GetPoint(), &tmp1);
+ // if the Frame doesn't exist anymore, the complete Layout has to be
+ // created, because there used to be a Frame here!
+ if ( !pFrame )
+ {
+ // skip, if it is a hidden deleted cell without frame
+ if ( GetLayout()->IsHideRedlines() )
+ {
+ const SwStartNode* pNd = pShellCursor->GetPointNode().FindTableBoxStartNode();
+ if ( pNd && pNd->GetTableBox()->GetRedlineType() == RedlineType::Delete )
+ return;
+ }
+
+ do
+ {
+ CalcLayout();
+ std::pair<Point, bool> const tmp(pShellCursor->GetPtPos(), false);
+ pFrame = pShellCursor->GetPointContentNode()->getLayoutFrame(
+ GetLayout(), pShellCursor->GetPoint(), &tmp);
+ } while( !pFrame );
+ }
+ else if ( Imp()->IsIdleAction() )
+ // Guarantee everything's properly formatted
+ pFrame->PrepareCursor();
+
+ // In protected Fly? but ignore in case of frame selection
+ if( !IsReadOnlyAvailable() && pFrame->IsProtected() &&
+ ( !Imp()->GetDrawView() ||
+ !Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() ) &&
+ (!mxDoc->GetDocShell() ||
+ !mxDoc->GetDocShell()->IsReadOnly() || m_bAllProtect ) )
+ {
+ // look for a valid position
+ bool bChgState = true;
+ if( !FindValidContentNode(!HasDrawView() ||
+ 0 == Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount()))
+ {
+ // everything is protected / hidden -> special Mode
+ if( m_bAllProtect )
+ bChgState = false;
+ else
+ {
+ m_eMvState = CursorMoveState::NONE; // state for cursor travelling
+ m_bAllProtect = true;
+ if( GetDoc()->GetDocShell() )
+ {
+ GetDoc()->GetDocShell()->SetReadOnlyUI();
+ CallChgLnk(); // notify UI!
+ }
+ return;
+ }
+ }
+
+ if( bChgState )
+ {
+ bool bWasAllProtect = m_bAllProtect;
+ m_bAllProtect = false;
+ if( bWasAllProtect && GetDoc()->GetDocShell() &&
+ GetDoc()->GetDocShell()->IsReadOnlyUI() )
+ {
+ GetDoc()->GetDocShell()->SetReadOnlyUI( false );
+ CallChgLnk(); // notify UI!
+ }
+ m_bAllProtect = false;
+ bAgainst = true; // look for the right Frame again
+ }
+ }
+ } while( bAgainst );
+
+ SwCursorMoveState aTmpState( m_eMvState );
+ aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
+ aTmpState.m_bRealHeight = true;
+ aTmpState.m_bRealWidth = IsOverwriteCursor();
+ aTmpState.m_nCursorBidiLevel = pShellCursor->GetCursorBidiLevel();
+
+ // #i27615#,#i30453#
+ SwSpecialPos aSpecialPos;
+ aSpecialPos.nExtendRange = SwSPExtendRange::BEFORE;
+ if (pShellCursor->IsInFrontOfLabel())
+ {
+ aTmpState.m_pSpecialPos = &aSpecialPos;
+ }
+
+ {
+ DisableCallbackAction a(*GetLayout()); // tdf#91602 prevent recursive Action
+ if (!pFrame->GetCharRect(m_aCharRect, *pShellCursor->GetPoint(), &aTmpState))
+ {
+ Point& rPt = pShellCursor->GetPtPos();
+ rPt = m_aCharRect.Center();
+ pFrame->GetModelPositionForViewPoint( pShellCursor->GetPoint(), rPt, &aTmpState );
+ }
+ }
+ UISizeNotify(); // tdf#96256 update view size
+
+ if( !pShellCursor->HasMark() )
+ m_aCursorHeight = aTmpState.m_aRealHeight;
+ else
+ {
+ m_aCursorHeight.setX(0);
+ m_aCursorHeight.setY(aTmpState.m_aRealHeight.getY() < 0 ?
+ -m_aCharRect.Width() : m_aCharRect.Height());
+ }
+
+ if( !bFirst && aOld == m_aCharRect )
+ break;
+
+ // if the layout says that we are after the 100th iteration still in
+ // flow then we should always take the current position for granted.
+ // (see bug: 29658)
+ if( !--nLoopCnt )
+ {
+ OSL_ENSURE( false, "endless loop? CharRect != OldCharRect ");
+ break;
+ }
+ aOld = m_aCharRect;
+ bFirst = false;
+
+ // update cursor Points to the new Positions
+ pShellCursor->GetPtPos().setX(m_aCharRect.Left());
+ pShellCursor->GetPtPos().setY(m_aCharRect.Top());
+
+ if( !(eFlags & SwCursorShell::UPDOWN )) // delete old Pos. of Up/Down
+ {
+ DisableCallbackAction a(*GetLayout());
+ pFrame->Calc(GetOut());
+ m_nUpDownX = pFrame->IsVertical() ?
+ m_aCharRect.Top() - pFrame->getFrameArea().Top() :
+ m_aCharRect.Left() - pFrame->getFrameArea().Left();
+ }
+
+ // scroll Cursor to visible area
+ if( m_bHasFocus && eFlags & SwCursorShell::SCROLLWIN &&
+ (HasSelection() || eFlags & SwCursorShell::READONLY ||
+ !IsCursorReadonly() || GetViewOptions()->IsSelectionInReadonly()) )
+ {
+ // in case of scrolling this EndAction doesn't show the SV cursor
+ // again, thus save and reset the flag here
+ bool bSav = m_bSVCursorVis;
+ m_bSVCursorVis = false;
+ MakeSelVisible();
+ m_bSVCursorVis = bSav;
+ }
+
+ } while( eFlags & SwCursorShell::SCROLLWIN );
+
+ assert(pFrame);
+
+ if( m_pBlockCursor )
+ RefreshBlockCursor();
+
+ // We should not restrict cursor update to the active view when using LOK
+ bool bCheckFocus = m_bHasFocus || comphelper::LibreOfficeKit::isActive();
+
+ if( !bIdleEnd && bCheckFocus && !m_bBasicHideCursor )
+ {
+ if( m_pTableCursor )
+ m_pTableCursor->SwSelPaintRects::Show();
+ else
+ {
+ m_pCurrentCursor->SwSelPaintRects::Show();
+ if( m_pBlockCursor )
+ {
+ SwShellCursor* pNxt = m_pCurrentCursor->GetNext();
+ while( pNxt && pNxt != m_pCurrentCursor )
+ {
+ pNxt->SwSelPaintRects::Show();
+ pNxt = pNxt->GetNext();
+ }
+ }
+ }
+ }
+
+ m_eMvState = CursorMoveState::NONE; // state for cursor travelling - GetModelPositionForViewPoint
+
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ if (Imp()->IsAccessible() && m_bSendAccessibleCursorEvents)
+ Imp()->InvalidateAccessibleCursorPosition( pFrame );
+#endif
+
+ // switch from blinking cursor to read-only-text-selection cursor
+ const sal_uInt64 nBlinkTime = GetOut()->GetSettings().GetStyleSettings().
+ GetCursorBlinkTime();
+
+ if ( (IsCursorReadonly() && GetViewOptions()->IsSelectionInReadonly()) ==
+ ( nBlinkTime != STYLE_CURSOR_NOBLINKTIME ) )
+ {
+ // non blinking cursor in read only - text selection mode
+ AllSettings aSettings = GetOut()->GetSettings();
+ StyleSettings aStyleSettings = aSettings.GetStyleSettings();
+ const sal_uInt64 nNewBlinkTime = nBlinkTime == STYLE_CURSOR_NOBLINKTIME ?
+ Application::GetSettings().GetStyleSettings().GetCursorBlinkTime() :
+ STYLE_CURSOR_NOBLINKTIME;
+ aStyleSettings.SetCursorBlinkTime( nNewBlinkTime );
+ aSettings.SetStyleSettings( aStyleSettings );
+ GetOut()->SetSettings( aSettings );
+ }
+
+ if( m_bSVCursorVis )
+ m_pVisibleCursor->Show(); // show again
+
+ if (comphelper::LibreOfficeKit::isActive())
+ sendLOKCursorUpdates();
+
+ getIDocumentMarkAccess()->NotifyCursorUpdate(*this);
+}
+
+void SwCursorShell::sendLOKCursorUpdates()
+{
+ SwView* pView = static_cast<SwView*>(GetSfxViewShell());
+ if (!pView || !pView->GetWrtShellPtr())
+ return;
+
+ SwWrtShell* pShell = &pView->GetWrtShell();
+
+ SwFrame* pCurrentFrame = GetCurrFrame();
+ SelectionType eType = pShell->GetSelectionType();
+
+ tools::JsonWriter aJsonWriter;
+
+ if (pCurrentFrame && (eType & SelectionType::Table) && pCurrentFrame->IsInTab())
+ {
+ const SwRect& rPageRect = pShell->GetAnyCurRect(CurRectType::Page, nullptr);
+
+ {
+ auto columnsNode = aJsonWriter.startNode("columns");
+ SwTabCols aTabCols;
+ pShell->GetTabCols(aTabCols);
+
+ const int nColumnOffset = aTabCols.GetLeftMin() + rPageRect.Left();
+
+ aJsonWriter.put("left", aTabCols.GetLeft());
+ aJsonWriter.put("right", aTabCols.GetRight());
+ aJsonWriter.put("tableOffset", static_cast<sal_Int64>(nColumnOffset));
+
+ {
+ auto entriesNode = aJsonWriter.startArray("entries");
+ for (size_t i = 0; i < aTabCols.Count(); ++i)
+ {
+ auto entryNode = aJsonWriter.startStruct();
+ auto const & rEntry = aTabCols.GetEntry(i);
+ aJsonWriter.put("position", rEntry.nPos);
+ aJsonWriter.put("min", rEntry.nMin);
+ aJsonWriter.put("max", rEntry.nMax);
+ aJsonWriter.put("hidden", rEntry.bHidden);
+ }
+ }
+ }
+
+ {
+ auto rowsNode = aJsonWriter.startNode("rows");
+ SwTabCols aTabRows;
+ pShell->GetTabRows(aTabRows);
+
+ const int nRowOffset = aTabRows.GetLeftMin() + rPageRect.Top();
+
+ aJsonWriter.put("left", aTabRows.GetLeft());
+ aJsonWriter.put("right", aTabRows.GetRight());
+ aJsonWriter.put("tableOffset", static_cast<sal_Int64>(nRowOffset));
+
+ {
+ auto entriesNode = aJsonWriter.startArray("entries");
+ for (size_t i = 0; i < aTabRows.Count(); ++i)
+ {
+ auto entryNode = aJsonWriter.startStruct();
+ auto const & rEntry = aTabRows.GetEntry(i);
+ aJsonWriter.put("position", rEntry.nPos);
+ aJsonWriter.put("min", rEntry.nMin);
+ aJsonWriter.put("max", rEntry.nMax);
+ aJsonWriter.put("hidden", rEntry.bHidden);
+ }
+ }
+ }
+ }
+
+ OString pChar = aJsonWriter.finishAndGetAsOString();
+ GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, pChar);
+}
+
+void SwCursorShell::RefreshBlockCursor()
+{
+ assert(m_pBlockCursor);
+ SwShellCursor &rBlock = m_pBlockCursor->getShellCursor();
+ Point aPt = rBlock.GetPtPos();
+ std::pair<Point, bool> const tmp(aPt, false);
+ SwContentFrame* pFrame = rBlock.GetPointContentNode()->getLayoutFrame(
+ GetLayout(), rBlock.GetPoint(), &tmp);
+ Point aMk;
+ if( m_pBlockCursor->getEndPoint() && m_pBlockCursor->getStartPoint() )
+ {
+ aPt = *m_pBlockCursor->getStartPoint();
+ aMk = *m_pBlockCursor->getEndPoint();
+ }
+ else
+ {
+ aPt = rBlock.GetPtPos();
+ if( pFrame )
+ {
+ if( pFrame->IsVertical() )
+ aPt.setY(pFrame->getFrameArea().Top() + GetUpDownX());
+ else
+ aPt.setX(pFrame->getFrameArea().Left() + GetUpDownX());
+ }
+ aMk = rBlock.GetMkPos();
+ }
+ SwRect aRect( aMk, aPt );
+ aRect.Justify();
+ SwSelectionList aSelList( pFrame );
+
+ if( !GetLayout()->FillSelection( aSelList, aRect ) )
+ return;
+
+ SwCursor* pNxt = static_cast<SwCursor*>(m_pCurrentCursor->GetNext());
+ while( pNxt != m_pCurrentCursor )
+ {
+ delete pNxt;
+ pNxt = static_cast<SwCursor*>(m_pCurrentCursor->GetNext());
+ }
+
+ std::list<SwPaM*>::iterator pStart = aSelList.getStart();
+ std::list<SwPaM*>::iterator pPam = aSelList.getEnd();
+ OSL_ENSURE( pPam != pStart, "FillSelection should deliver at least one PaM" );
+ m_pCurrentCursor->SetMark();
+ --pPam;
+ // If there is only one text portion inside the rectangle, a simple
+ // selection is created
+ if( pPam == pStart )
+ {
+ *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint();
+ if( (*pPam)->HasMark() )
+ *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
+ else
+ m_pCurrentCursor->DeleteMark();
+ delete *pPam;
+ m_pCurrentCursor->SetColumnSelection( false );
+ }
+ else
+ {
+ // The order of the SwSelectionList has to be preserved but
+ // the order inside the ring created by CreateCursor() is not like
+ // expected => First create the selections before the last one
+ // downto the first selection.
+ // At least create the cursor for the last selection
+ --pPam;
+ *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-1 (if n == number of selections)
+ if( (*pPam)->HasMark() )
+ *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
+ else
+ m_pCurrentCursor->DeleteMark();
+ delete *pPam;
+ m_pCurrentCursor->SetColumnSelection( true );
+ while( pPam != pStart )
+ {
+ --pPam;
+
+ SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor );
+ pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end());
+ m_pCurrentCursor->clear();
+ m_pCurrentCursor->DeleteMark();
+
+ *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n-2, n-3, .., 2, 1
+ if( (*pPam)->HasMark() )
+ {
+ m_pCurrentCursor->SetMark();
+ *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
+ }
+ else
+ m_pCurrentCursor->DeleteMark();
+ m_pCurrentCursor->SetColumnSelection( true );
+ delete *pPam;
+ }
+ {
+ SwShellCursor* pNew = new SwShellCursor( *m_pCurrentCursor );
+ pNew->insert( pNew->begin(), m_pCurrentCursor->begin(), m_pCurrentCursor->end() );
+ m_pCurrentCursor->clear();
+ m_pCurrentCursor->DeleteMark();
+ }
+ pPam = aSelList.getEnd();
+ --pPam;
+ *m_pCurrentCursor->GetPoint() = *(*pPam)->GetPoint(); // n, the last selection
+ if( (*pPam)->HasMark() )
+ {
+ m_pCurrentCursor->SetMark();
+ *m_pCurrentCursor->GetMark() = *(*pPam)->GetMark();
+ }
+ else
+ m_pCurrentCursor->DeleteMark();
+ m_pCurrentCursor->SetColumnSelection( true );
+ delete *pPam;
+ }
+}
+
+/// create a copy of the cursor and save it in the stack
+void SwCursorShell::Push()
+{
+ // fdo#60513: if we have a table cursor, copy that; else copy current.
+ // This seems to work because UpdateCursor() will fix this up on Pop(),
+ // then MakeBoxSels() will re-create the current m_pCurrentCursor cell ring.
+ SwShellCursor *const pCurrent(m_pTableCursor ? m_pTableCursor : m_pCurrentCursor);
+ m_pStackCursor = new SwShellCursor( *this, *pCurrent->GetPoint(),
+ pCurrent->GetPtPos(), m_pStackCursor );
+
+ if (pCurrent->HasMark())
+ {
+ m_pStackCursor->SetMark();
+ *m_pStackCursor->GetMark() = *pCurrent->GetMark();
+ }
+}
+
+/** delete cursor
+
+ @param eDelete delete from stack, or delete current
+ and assign the one from stack as the new current cursor.
+ @return <true> if there was one on the stack, <false> otherwise
+*/
+bool SwCursorShell::Pop(PopMode const eDelete)
+{
+ std::optional<SwCallLink> aLink(std::in_place, *this); // watch Cursor-Moves; call Link if needed
+ return Pop(eDelete, aLink);
+}
+
+bool SwCursorShell::Pop(PopMode const eDelete,
+ [[maybe_unused]] std::optional<SwCallLink>& roLink)
+{
+ // parameter exists only to be deleted before return
+ assert(roLink);
+ comphelper::ScopeGuard aGuard( [&]() { roLink.reset(); } );
+
+ // are there any left?
+ if (nullptr == m_pStackCursor)
+ return false;
+
+ SwShellCursor *pTmp = nullptr, *pOldStack = m_pStackCursor;
+
+ // the successor becomes the current one
+ if (m_pStackCursor->GetNext() != m_pStackCursor)
+ {
+ pTmp = m_pStackCursor->GetNext();
+ }
+
+ if (PopMode::DeleteStack == eDelete)
+ delete m_pStackCursor;
+
+ m_pStackCursor = pTmp; // assign new one
+
+ if (PopMode::DeleteCurrent == eDelete)
+ {
+ ::std::optional<SwCursorSaveState> oSaveState( *m_pCurrentCursor );
+
+ // If the visible SSelection was not changed
+ const Point& rPoint = pOldStack->GetPtPos();
+ if (rPoint == m_pCurrentCursor->GetPtPos() || rPoint == m_pCurrentCursor->GetMkPos())
+ {
+ // move "Selections Rectangles"
+ m_pCurrentCursor->insert( m_pCurrentCursor->begin(), pOldStack->begin(), pOldStack->end() );
+ pOldStack->clear();
+ }
+
+ if( pOldStack->HasMark() )
+ {
+ m_pCurrentCursor->SetMark();
+ *m_pCurrentCursor->GetMark() = *pOldStack->GetMark();
+ m_pCurrentCursor->GetMkPos() = pOldStack->GetMkPos();
+ }
+ else
+ // no selection so revoke old one and set to old position
+ m_pCurrentCursor->DeleteMark();
+ *m_pCurrentCursor->GetPoint() = *pOldStack->GetPoint();
+ m_pCurrentCursor->GetPtPos() = pOldStack->GetPtPos();
+ delete pOldStack;
+
+ if( !m_pCurrentCursor->IsInProtectTable( true ) &&
+ !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos ) )
+ {
+ oSaveState.reset(); // prevent UAF
+ UpdateCursor(); // update current cursor
+ if (m_pTableCursor)
+ { // tdf#106929 ensure m_pCurrentCursor ring is recreated from table
+ m_pTableCursor->SetChgd();
+ }
+ }
+ }
+ return true;
+}
+
+/** Combine two cursors
+
+ Delete topmost from stack and use its GetMark in the current.
+*/
+void SwCursorShell::Combine()
+{
+ // any others left?
+ if (nullptr == m_pStackCursor)
+ return;
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ // rhbz#689053: IsSelOvr must restore the saved stack position, not the
+ // current one, because current point + stack mark may be invalid PaM
+ SwCursorSaveState aSaveState(*m_pStackCursor);
+ // stack cursor & current cursor in same Section?
+ assert(!m_pStackCursor->HasMark() ||
+ CheckNodesRange(m_pStackCursor->GetMark()->GetNode(),
+ m_pCurrentCursor->GetPoint()->GetNode(), true));
+ *m_pStackCursor->GetPoint() = *m_pCurrentCursor->GetPoint();
+ m_pStackCursor->GetPtPos() = m_pCurrentCursor->GetPtPos();
+
+ SwShellCursor * pTmp = nullptr;
+ if (m_pStackCursor->GetNext() != m_pStackCursor)
+ {
+ pTmp = m_pStackCursor->GetNext();
+ }
+ delete m_pCurrentCursor;
+ m_pCurrentCursor = m_pStackCursor;
+ m_pStackCursor->MoveTo(nullptr); // remove from ring
+ m_pStackCursor = pTmp;
+ if( !m_pCurrentCursor->IsInProtectTable( true ) &&
+ !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos ) )
+ {
+ UpdateCursor(); // update current cursor
+ }
+}
+
+void SwCursorShell::HideCursors()
+{
+ if( !m_bHasFocus || m_bBasicHideCursor )
+ return;
+
+ // if cursor is visible then hide SV cursor
+ if( m_pVisibleCursor->IsVisible() )
+ {
+ CurrShell aCurr( this );
+ m_pVisibleCursor->Hide();
+ }
+ // revoke inversion of SSelection
+ SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
+ pCurrentCursor->Hide();
+}
+
+void SwCursorShell::ShowCursors( bool bCursorVis )
+{
+ if( !m_bHasFocus || m_bAllProtect || m_bBasicHideCursor )
+ return;
+
+ CurrShell aCurr( this );
+ SwShellCursor* pCurrentCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
+ pCurrentCursor->Show(nullptr);
+
+ if( m_bSVCursorVis && bCursorVis ) // also show SV cursor again
+ m_pVisibleCursor->Show();
+}
+
+void SwCursorShell::ShowCursor()
+{
+ if( m_bBasicHideCursor )
+ return;
+
+ comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
+
+ m_bSVCursorVis = true;
+ m_pCurrentCursor->SetShowTextInputFieldOverlay( true );
+ m_pCurrentCursor->SetShowContentControlOverlay(true);
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ const OString aPayload = OString::boolean(m_bSVCursorVis);
+ GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload);
+ SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
+ }
+
+ UpdateCursor();
+}
+
+void SwCursorShell::HideCursor()
+{
+ if( m_bBasicHideCursor )
+ return;
+
+ m_bSVCursorVis = false;
+ // possibly reverse selected areas!!
+ CurrShell aCurr( this );
+ m_pCurrentCursor->SetShowTextInputFieldOverlay( false );
+ m_pCurrentCursor->SetShowContentControlOverlay(false);
+ m_pVisibleCursor->Hide();
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ OString aPayload = OString::boolean(m_bSVCursorVis);
+ GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload);
+ SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload);
+ }
+}
+
+void SwCursorShell::ShellLoseFocus()
+{
+ if( !m_bBasicHideCursor )
+ HideCursors();
+ m_bHasFocus = false;
+}
+
+void SwCursorShell::ShellGetFocus()
+{
+ comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll());
+
+ m_bHasFocus = true;
+ if( !m_bBasicHideCursor && VisArea().Width() )
+ {
+ UpdateCursor( o3tl::narrowing<sal_uInt16>( SwCursorShell::CHKRANGE ) );
+ ShowCursors( m_bSVCursorVis );
+ }
+}
+
+/** Get current frame in which the cursor is positioned. */
+SwContentFrame *SwCursorShell::GetCurrFrame( const bool bCalcFrame ) const
+{
+ CurrShell aCurr( const_cast<SwCursorShell*>(this) );
+ SwContentFrame *pRet = nullptr;
+ SwContentNode *pNd = m_pCurrentCursor->GetPointContentNode();
+ if ( pNd )
+ {
+ if ( bCalcFrame )
+ {
+ sal_uInt16* pST = const_cast<sal_uInt16*>(&mnStartAction);
+ ++(*pST);
+ const Size aOldSz( GetDocSize() );
+ std::pair<Point, bool> const tmp(m_pCurrentCursor->GetPtPos(), true);
+ pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
+ --(*pST);
+ if( aOldSz != GetDocSize() )
+ const_cast<SwCursorShell*>(this)->SizeChgNotify();
+ }
+ else
+ {
+ std::pair<Point, bool> const tmp(m_pCurrentCursor->GetPtPos(), false);
+ pRet = pNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
+ }
+ }
+ return pRet;
+}
+
+//TODO: provide documentation
+/** forward all attribute/format changes at the current node to the Link
+
+ @param pOld ???
+ @param pNew ???
+*/
+void SwCursorShell::SwClientNotify(const SwModify&, const SfxHint& rHint)
+{
+ if(dynamic_cast<const sw::PostGraphicArrivedHint*>(&rHint) && m_aGrfArrivedLnk.IsSet())
+ {
+ m_aGrfArrivedLnk.Call(*this);
+ return;
+ }
+ if (rHint.GetId() != SfxHintId::SwLegacyModify)
+ return;
+ auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ auto nWhich = pLegacy->GetWhich();
+ if(!nWhich)
+ nWhich = RES_OBJECTDYING;
+ if( m_bCallChgLnk &&
+ ( !isFormatMessage(nWhich)
+ || nWhich == RES_FMT_CHG
+ || nWhich == RES_UPDATE_ATTR
+ || nWhich == RES_ATTRSET_CHG ))
+ // messages are not forwarded
+ // #i6681#: RES_UPDATE_ATTR is implicitly unset in
+ // SwTextNode::Insert(SwTextHint*, sal_uInt16); we react here and thus do
+ // not need to send the expensive RES_FMT_CHG in Insert.
+ CallChgLnk();
+ if( nWhich == RES_OBJECTDYING )
+ {
+ EndListeningAll();
+ }
+
+}
+
+/** Does the current cursor create a selection?
+
+ This means checking if GetMark is set and if SPoint and GetMark differ.
+*/
+bool SwCursorShell::HasSelection() const
+{
+ const SwPaM* pCursor = getShellCursor( true );
+ return IsTableMode()
+ || (pCursor->HasMark() &&
+ (*pCursor->GetPoint() != *pCursor->GetMark()
+ || IsFlySelectedByCursor(*GetDoc(), *pCursor->Start(), *pCursor->End())));
+}
+
+void SwCursorShell::CallChgLnk()
+{
+ // Do not make any call in StartAction/EndAction but just set the flag.
+ // This will be handled in EndAction.
+ if (ActionPend())
+ m_bChgCallFlag = true; // remember change
+ else if( m_aChgLnk.IsSet() )
+ {
+ if( m_bCallChgLnk )
+ m_aChgLnk.Call(nullptr);
+ m_bChgCallFlag = false; // reset flag
+ }
+}
+
+/// get selected text of a node at current cursor
+OUString SwCursorShell::GetSelText() const
+{
+ OUString aText;
+ if (GetLayout()->HasMergedParas())
+ {
+ SwContentFrame const*const pFrame(GetCurrFrame(false));
+ if (pFrame && FrameContainsNode(*pFrame, m_pCurrentCursor->GetMark()->GetNodeIndex()))
+ {
+ OUStringBuffer buf;
+ SwPosition const*const pStart(m_pCurrentCursor->Start());
+ SwPosition const*const pEnd(m_pCurrentCursor->End());
+ for (SwNodeOffset i = pStart->GetNodeIndex(); i <= pEnd->GetNodeIndex(); ++i)
+ {
+ SwNode const& rNode(*pStart->GetNodes()[i]);
+ assert(!rNode.IsEndNode());
+ if (rNode.IsStartNode())
+ {
+ i = rNode.EndOfSectionIndex();
+ }
+ else if (rNode.IsTextNode())
+ {
+ sal_Int32 const nStart(i == pStart->GetNodeIndex()
+ ? pStart->GetContentIndex()
+ : 0);
+ sal_Int32 const nEnd(i == pEnd->GetNodeIndex()
+ ? pEnd->GetContentIndex()
+ : rNode.GetTextNode()->Len());
+ buf.append(rNode.GetTextNode()->GetExpandText(
+ GetLayout(),
+ nStart, nEnd - nStart, false, false, false,
+ ExpandMode::HideDeletions));
+
+ }
+ }
+ aText = buf.makeStringAndClear();
+ }
+ }
+ else if( m_pCurrentCursor->GetPoint()->GetNodeIndex() ==
+ m_pCurrentCursor->GetMark()->GetNodeIndex() )
+ {
+ SwTextNode* pTextNd = m_pCurrentCursor->GetPointNode().GetTextNode();
+ if( pTextNd )
+ {
+ const sal_Int32 nStt = m_pCurrentCursor->Start()->GetContentIndex();
+ aText = pTextNd->GetExpandText(GetLayout(), nStt,
+ m_pCurrentCursor->End()->GetContentIndex() - nStt );
+ }
+ }
+ return aText;
+}
+
+/** get the nth character of the current SSelection
+
+ @param bEnd Start counting from the end? From start otherwise.
+ @param nOffset position of the character
+*/
+sal_Unicode SwCursorShell::GetChar( bool bEnd, tools::Long nOffset )
+{
+ if( IsTableMode() ) // not possible in table mode
+ return 0;
+
+ const SwPosition* pPos = !m_pCurrentCursor->HasMark() ? m_pCurrentCursor->GetPoint()
+ : bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start();
+ SwTextNode* pTextNd = pPos->GetNode().GetTextNode();
+ if( !pTextNd )
+ return 0;
+
+ const sal_Int32 nPos = pPos->GetContentIndex();
+ const OUString& rStr = pTextNd->GetText();
+ sal_Unicode cCh = 0;
+
+ if (((nPos+nOffset) >= 0 ) && (nPos+nOffset) < rStr.getLength())
+ cCh = rStr[nPos + nOffset];
+
+ return cCh;
+}
+
+/** extend current SSelection by n characters
+
+ @param bEnd Start counting from the end? From start otherwise.
+ @param nCount Number of characters.
+*/
+bool SwCursorShell::ExtendSelection( bool bEnd, sal_Int32 nCount )
+{
+ if( !m_pCurrentCursor->HasMark() || IsTableMode() )
+ return false; // no selection
+
+ SwPosition* pPos = bEnd ? m_pCurrentCursor->End() : m_pCurrentCursor->Start();
+ SwTextNode* pTextNd = pPos->GetNode().GetTextNode();
+ assert(pTextNd);
+
+ sal_Int32 nPos = pPos->GetContentIndex();
+ if( bEnd )
+ {
+ if ((nPos + nCount) <= pTextNd->GetText().getLength())
+ nPos = nPos + nCount;
+ else
+ return false; // not possible
+ }
+ else if( nPos >= nCount )
+ nPos = nPos - nCount;
+ else
+ return false; // not possible anymore
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+
+ pPos->SetContent(nPos) ;
+ UpdateCursor();
+
+ return true;
+}
+
+/** Move visible cursor to given position in document.
+
+ @param rPt The position to move the visible cursor to.
+ @return <false> if SPoint was corrected by the layout.
+*/
+bool SwCursorShell::SetVisibleCursor( const Point &rPt )
+{
+ CurrShell aCurr( this );
+ Point aPt( rPt );
+ SwPosition aPos( *m_pCurrentCursor->GetPoint() );
+ SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText );
+ aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
+ aTmpState.m_bRealHeight = true;
+
+ const bool bRet = GetLayout()->GetModelPositionForViewPoint( &aPos, aPt /*, &aTmpState*/ );
+
+ SetInFrontOfLabel( false ); // #i27615#
+
+ // show only in TextNodes
+ SwTextNode* pTextNd = aPos.GetNode().GetTextNode();
+ if( !pTextNd )
+ return false;
+
+ const SwSectionNode* pSectNd = pTextNd->FindSectionNode();
+ if( pSectNd && (pSectNd->GetSection().IsHiddenFlag() ||
+ ( !IsReadOnlyAvailable() &&
+ pSectNd->GetSection().IsProtectFlag())) )
+ return false;
+
+ std::pair<Point, bool> const tmp(aPt, true);
+ SwContentFrame *pFrame = pTextNd->getLayoutFrame(GetLayout(), &aPos, &tmp);
+ if ( Imp()->IsIdleAction() )
+ pFrame->PrepareCursor();
+ SwRect aTmp( m_aCharRect );
+
+ pFrame->GetCharRect( m_aCharRect, aPos, &aTmpState );
+
+ // #i10137#
+ if( aTmp == m_aCharRect && m_pVisibleCursor->IsVisible() )
+ return true;
+
+ m_pVisibleCursor->Hide(); // always hide visible cursor
+ if( IsScrollMDI( this, m_aCharRect ))
+ {
+ MakeVisible( m_aCharRect );
+ m_pCurrentCursor->Show(nullptr);
+ }
+
+ {
+ if( aTmpState.m_bRealHeight )
+ m_aCursorHeight = aTmpState.m_aRealHeight;
+ else
+ {
+ m_aCursorHeight.setX(0);
+ m_aCursorHeight.setY(m_aCharRect.Height());
+ }
+
+ m_pVisibleCursor->SetDragCursor();
+ m_pVisibleCursor->Show(); // show again
+ }
+ return bRet;
+}
+
+SwVisibleCursor* SwCursorShell::GetVisibleCursor() const
+{
+ return m_pVisibleCursor;
+}
+
+bool SwCursorShell::IsOverReadOnlyPos( const Point& rPt ) const
+{
+ Point aPt( rPt );
+ SwPaM aPam( *m_pCurrentCursor->GetPoint() );
+ GetLayout()->GetModelPositionForViewPoint( aPam.GetPoint(), aPt );
+ // form view
+ return aPam.HasReadonlySel(GetViewOptions()->IsFormView(), false);
+}
+
+/** Get the number of elements in the ring of cursors
+
+ @param bAll If <false> get only spanned ones (= with selections) (Basic).
+*/
+sal_uInt16 SwCursorShell::GetCursorCnt( bool bAll ) const
+{
+ SwPaM* pTmp = GetCursor()->GetNext();
+ sal_uInt16 n = (bAll || ( m_pCurrentCursor->HasMark() &&
+ *m_pCurrentCursor->GetPoint() != *m_pCurrentCursor->GetMark())) ? 1 : 0;
+ while( pTmp != m_pCurrentCursor )
+ {
+ if( bAll || ( pTmp->HasMark() &&
+ *pTmp->GetPoint() != *pTmp->GetMark()))
+ ++n;
+ pTmp = pTmp->GetNext();
+ }
+ return n;
+}
+
+bool SwCursorShell::IsStartOfDoc() const
+{
+ if( m_pCurrentCursor->GetPoint()->GetContentIndex() )
+ return false;
+
+ // after EndOfIcons comes the content selection (EndNd+StNd+ContentNd)
+ SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfExtras(), 2 );
+ if( !aIdx.GetNode().IsContentNode() )
+ GetDoc()->GetNodes().GoNext( &aIdx );
+ return aIdx == m_pCurrentCursor->GetPoint()->GetNode();
+}
+
+bool SwCursorShell::IsEndOfDoc() const
+{
+ SwNodeIndex aIdx( GetDoc()->GetNodes().GetEndOfContent(), -1 );
+ SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
+ if( !pCNd )
+ pCNd = SwNodes::GoPrevious( &aIdx );
+
+ return aIdx == m_pCurrentCursor->GetPoint()->GetNode() && pCNd &&
+ pCNd->Len() == m_pCurrentCursor->GetPoint()->GetContentIndex();
+}
+
+/** Invalidate cursors
+
+ Delete all created cursors, set table crsr and last crsr to their TextNode
+ (or StartNode?). They will then all re-created at the next ::GetCursor() call.
+
+ This is needed for Drag&Drop/ Clipboard-paste in tables.
+*/
+bool SwCursorShell::ParkTableCursor()
+{
+ if( !m_pTableCursor )
+ return false;
+
+ m_pTableCursor->ParkCursor();
+
+ while( m_pCurrentCursor->GetNext() != m_pCurrentCursor )
+ delete m_pCurrentCursor->GetNext();
+
+ // *always* move cursor's Point and Mark
+ m_pCurrentCursor->DeleteMark();
+ *m_pCurrentCursor->GetPoint() = *m_pTableCursor->GetPoint();
+
+ return true;
+}
+
+void SwCursorShell::ParkPams( SwPaM* pDelRg, SwShellCursor** ppDelRing )
+{
+ auto [pStt, pEnd] = pDelRg->StartEnd(); // SwPosition*
+
+ SwPaM *pTmpDel = nullptr, *pTmp = *ppDelRing;
+
+ // search over the whole ring
+ bool bGoNext;
+ do {
+
+ if (!pTmp)
+ break;
+
+ auto [pTmpStt, pTmpEnd] = pTmp->StartEnd(); // SwPosition*
+ // If a SPoint or GetMark are in a cursor area then cancel the old area.
+ // During comparison keep in mind that End() is outside the area.
+ if( *pStt <= *pTmpStt )
+ {
+ if( *pEnd > *pTmpStt ||
+ ( *pEnd == *pTmpStt && *pEnd == *pTmpEnd ))
+ pTmpDel = pTmp;
+ }
+ else
+ if( *pStt < *pTmpEnd )
+ pTmpDel = pTmp;
+
+ bGoNext = true;
+ if (pTmpDel) // is the pam in the range -> delete
+ {
+ bool bDelete = true;
+ if( *ppDelRing == pTmpDel )
+ {
+ if( *ppDelRing == m_pCurrentCursor )
+ {
+ bDelete = GoNextCursor();
+ if( bDelete )
+ {
+ bGoNext = false;
+ pTmp = pTmp->GetNext();
+ }
+ }
+ else
+ bDelete = false; // never delete the StackCursor
+ }
+
+ if( bDelete )
+ {
+ if (pTmp == pTmpDel)
+ pTmp = nullptr;
+ delete pTmpDel; // invalidate old area
+ }
+ else
+ {
+ pTmpDel->GetPoint()->Assign(SwNodeOffset(0));
+ pTmpDel->DeleteMark();
+ }
+ pTmpDel = nullptr;
+ }
+ if( bGoNext && pTmp )
+ pTmp = pTmp->GetNext();
+
+ } while( !bGoNext || *ppDelRing != pTmp );
+}
+
+//TODO: provide documentation
+/** Remove selections and additional cursors of all shells.
+
+ The remaining cursor of the shell is parked.
+
+ @param rIdx ???
+*/
+void SwCursorShell::ParkCursor( const SwNode &rIdx )
+{
+ const SwNode *pNode = &rIdx;
+
+ // create a new PaM
+ SwPaM aNew( *GetCursor()->GetPoint() );
+ if( pNode->GetStartNode() )
+ {
+ pNode = pNode->StartOfSectionNode();
+ if( pNode->IsTableNode() )
+ {
+ // the given node is in a table, thus park cursor to table node
+ // (outside of the table)
+ aNew.GetPoint()->Assign( *pNode->StartOfSectionNode() );
+ }
+ else
+ // Also on the start node itself. Then we need to request the start
+ // node always via its end node! (StartOfSelection of StartNode is
+ // the parent)
+ aNew.GetPoint()->Assign( *pNode->EndOfSectionNode()->StartOfSectionNode() );
+ }
+ else
+ aNew.GetPoint()->Assign( *pNode->StartOfSectionNode() );
+ aNew.SetMark();
+ aNew.GetPoint()->Assign(*pNode->EndOfSectionNode());
+
+ // take care of all shells
+ for(SwViewShell& rTmp : GetRingContainer())
+ {
+ if( auto pSh = dynamic_cast<SwCursorShell *>(&rTmp))
+ {
+ if (pSh->m_pStackCursor)
+ pSh->ParkPams(&aNew, &pSh->m_pStackCursor);
+
+ pSh->ParkPams( &aNew, &pSh->m_pCurrentCursor );
+ if( pSh->m_pTableCursor )
+ {
+ // set table cursor always to 0 and the current one always to
+ // the beginning of the table
+ SwPaM* pTCursor = pSh->GetTableCrs();
+ SwNode* pTableNd = pTCursor->GetPoint()->GetNode().FindTableNode();
+ if ( pTableNd )
+ {
+ pTCursor->GetPoint()->Assign(SwNodeOffset(0));
+ pTCursor->DeleteMark();
+ pSh->m_pCurrentCursor->GetPoint()->Assign( *pTableNd );
+ }
+ }
+ }
+ }
+}
+
+/** Copy constructor
+
+ Copy cursor position and add it to the ring.
+ All views of a document are in the ring of the shell.
+*/
+SwCursorShell::SwCursorShell( SwCursorShell& rShell, vcl::Window *pInitWin )
+ : SwViewShell( rShell, pInitWin )
+ , sw::BroadcastingModify()
+ , m_pStackCursor( nullptr )
+ , m_pBlockCursor( nullptr )
+ , m_pTableCursor( nullptr )
+ , m_pBoxIdx( nullptr )
+ , m_pBoxPtr( nullptr )
+ , m_nUpDownX(0)
+ , m_nLeftFramePos(0)
+ , m_nCurrentNode(0)
+ , m_nCurrentContent(0)
+ , m_nCurrentNdTyp(SwNodeType::NONE)
+ , m_nCursorMove( 0 )
+ , m_eMvState( CursorMoveState::NONE )
+ , m_eEnhancedTableSel(SwTable::SEARCH_NONE)
+ , m_nMarkedListLevel( 0 )
+ , m_oldColFrame(nullptr)
+{
+ CurrShell aCurr( this );
+ // only keep the position of the current cursor of the copy shell
+ m_pCurrentCursor = new SwShellCursor( *this, *(rShell.m_pCurrentCursor->GetPoint()) );
+ m_pCurrentCursor->GetPointContentNode()->Add( this );
+
+ m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd =
+ m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor =
+ m_bOverwriteCursor = false;
+ m_bSendAccessibleCursorEvents = true;
+ m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true;
+ m_bSVCursorVis = true;
+ m_bSetCursorInReadOnly = true;
+ m_pVisibleCursor = new SwVisibleCursor( this );
+ m_bMacroExecAllowed = rShell.IsMacroExecAllowed();
+}
+
+/// default constructor
+SwCursorShell::SwCursorShell( SwDoc& rDoc, vcl::Window *pInitWin,
+ const SwViewOption *pInitOpt )
+ : SwViewShell( rDoc, pInitWin, pInitOpt )
+ , sw::BroadcastingModify()
+ , m_pStackCursor( nullptr )
+ , m_pBlockCursor( nullptr )
+ , m_pTableCursor( nullptr )
+ , m_pBoxIdx( nullptr )
+ , m_pBoxPtr( nullptr )
+ , m_nUpDownX(0)
+ , m_nLeftFramePos(0)
+ , m_nCurrentNode(0)
+ , m_nCurrentContent(0)
+ , m_nCurrentNdTyp(SwNodeType::NONE)
+ , m_nCursorMove( 0 )
+ , m_eMvState( CursorMoveState::NONE ) // state for crsr-travelling - GetModelPositionForViewPoint
+ , m_eEnhancedTableSel(SwTable::SEARCH_NONE)
+ , m_nMarkedListLevel( 0 )
+ , m_oldColFrame(nullptr)
+{
+ CurrShell aCurr( this );
+ // create initial cursor and set it to first content position
+ SwNodes& rNds = rDoc.GetNodes();
+
+ SwNodeIndex aNodeIdx( *rNds.GetEndOfContent().StartOfSectionNode() );
+ SwContentNode* pCNd = rNds.GoNext( &aNodeIdx ); // go to the first ContentNode
+
+ m_pCurrentCursor = new SwShellCursor( *this, SwPosition( aNodeIdx, pCNd, 0 ) );
+
+ // Register shell as dependent at current node. As a result all attribute
+ // changes can be forwarded via the Link.
+ pCNd->Add( this );
+
+ m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd =
+ m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor =
+ m_bOverwriteCursor = false;
+ m_bSendAccessibleCursorEvents = true;
+ m_bCallChgLnk = m_bHasFocus = m_bAutoUpdateCells = true;
+ m_bSVCursorVis = true;
+ m_bSetCursorInReadOnly = true;
+
+ m_pVisibleCursor = new SwVisibleCursor( this );
+ m_bMacroExecAllowed = true;
+}
+
+SwCursorShell::~SwCursorShell()
+{
+ // if it is not the last view then at least the field should be updated
+ if( !unique() )
+ CheckTableBoxContent( m_pCurrentCursor->GetPoint() );
+ else
+ ClearTableBoxContent();
+
+ delete m_pVisibleCursor;
+ delete m_pBlockCursor;
+ delete m_pTableCursor;
+
+ // release cursors
+ while(m_pCurrentCursor->GetNext() != m_pCurrentCursor)
+ delete m_pCurrentCursor->GetNext();
+ delete m_pCurrentCursor;
+
+ // free stack
+ if (m_pStackCursor)
+ {
+ while (m_pStackCursor->GetNext() != m_pStackCursor)
+ delete m_pStackCursor->GetNext();
+ delete m_pStackCursor;
+ }
+
+ // #i54025# - do not give a HTML parser that might potentially hang as
+ // a client at the cursor shell the chance to hang itself on a TextNode
+ EndListeningAll();
+}
+
+SwShellCursor* SwCursorShell::getShellCursor( bool bBlock )
+{
+ if( m_pTableCursor )
+ return m_pTableCursor;
+ if( m_pBlockCursor && bBlock )
+ return &m_pBlockCursor->getShellCursor();
+ return m_pCurrentCursor;
+}
+
+/** Should WaitPtr be switched on for the clipboard?
+
+ Wait for TableMode, multiple selections and more than x selected paragraphs.
+*/
+bool SwCursorShell::ShouldWait() const
+{
+ if ( IsTableMode() || GetCursorCnt() > 1 )
+ return true;
+
+ if( HasDrawView() && GetDrawView()->GetMarkedObjectList().GetMarkCount() )
+ return true;
+
+ SwPaM* pPam = GetCursor();
+ return pPam->Start()->GetNodeIndex() + SwNodeOffset(10) <
+ pPam->End()->GetNodeIndex();
+}
+
+size_t SwCursorShell::UpdateTableSelBoxes()
+{
+ if (m_pTableCursor && (m_pTableCursor->IsChgd() || !m_pTableCursor->GetSelectedBoxesCount()))
+ {
+ GetLayout()->MakeTableCursors( *m_pTableCursor );
+ }
+ return m_pTableCursor ? m_pTableCursor->GetSelectedBoxesCount() : 0;
+}
+
+/// show the current selected "object"
+void SwCursorShell::MakeSelVisible()
+{
+ OSL_ENSURE( m_bHasFocus, "no focus but cursor should be made visible?" );
+ if( m_aCursorHeight.Y() < m_aCharRect.Height() && m_aCharRect.Height() > VisArea().Height() )
+ {
+ SwRect aTmp( m_aCharRect );
+ tools::Long nDiff = m_aCharRect.Height() - VisArea().Height();
+ if( nDiff < m_aCursorHeight.getX() )
+ aTmp.Top( nDiff + m_aCharRect.Top() );
+ else
+ {
+ aTmp.Top( m_aCursorHeight.getX() + m_aCharRect.Top() );
+ aTmp.Height( m_aCursorHeight.getY() );
+ }
+ if( !aTmp.HasArea() )
+ {
+ aTmp.AddHeight(1 );
+ aTmp.AddWidth(1 );
+ }
+ MakeVisible( aTmp );
+ }
+ else
+ {
+ if( m_aCharRect.HasArea() )
+ MakeVisible( m_aCharRect );
+ else
+ {
+ SwRect aTmp( m_aCharRect );
+ aTmp.AddHeight(1 );
+ aTmp.AddWidth(1 );
+ MakeVisible( aTmp );
+ }
+ }
+}
+
+/// search a valid content position (not protected/hidden)
+bool SwCursorShell::FindValidContentNode( bool bOnlyText )
+{
+ if( m_pTableCursor )
+ {
+ assert(!"Did not remove table selection!");
+ return false;
+ }
+
+ // #i45129# - everything is allowed in UI-readonly
+ if( !m_bAllProtect && GetDoc()->GetDocShell() &&
+ GetDoc()->GetDocShell()->IsReadOnlyUI() )
+ return true;
+
+ if( m_pCurrentCursor->HasMark() )
+ ClearMark();
+
+ // first check for frames
+ SwPosition& rNdPos = *m_pCurrentCursor->GetPoint();
+ SwNodeOffset nNdIdx = rNdPos.GetNodeIndex(); // keep backup
+ SwNodes& rNds = mxDoc->GetNodes();
+ SwContentNode* pCNd = rNdPos.GetNode().GetContentNode();
+ const SwContentFrame * pFrame;
+
+ if (pCNd && nullptr != (pFrame = pCNd->getLayoutFrame(GetLayout(), m_pCurrentCursor->GetPoint())) &&
+ !IsReadOnlyAvailable() && pFrame->IsProtected() &&
+ nNdIdx < rNds.GetEndOfExtras().GetIndex() )
+ {
+ // skip protected frame
+ SwPaM aPam( *m_pCurrentCursor->GetPoint() );
+ aPam.SetMark();
+ aPam.GetMark()->Assign( rNds.GetEndOfContent() );
+ aPam.GetPoint()->Assign( *pCNd->EndOfSectionNode() );
+
+ bool bFirst = false;
+ if( nullptr == (pCNd = ::GetNode( aPam, bFirst, fnMoveForward )))
+ {
+ aPam.GetMark()->Assign( *rNds.GetEndOfPostIts().StartOfSectionNode() );
+ pCNd = ::GetNode( aPam, bFirst, fnMoveBackward );
+ }
+
+ if( !pCNd ) // should *never* happen
+ {
+ rNdPos.Assign(nNdIdx); // back to old node
+ return false;
+ }
+ *m_pCurrentCursor->GetPoint() = *aPam.GetPoint();
+ }
+ else if( bOnlyText && pCNd && pCNd->IsNoTextNode() )
+ {
+ // set to beginning of document
+ rNdPos.Assign( mxDoc->GetNodes().GetEndOfExtras() );
+ mxDoc->GetNodes().GoNext( &rNdPos );
+ nNdIdx = rNdPos.GetNodeIndex();
+ }
+
+ bool bOk = true;
+
+ // #i9059# cursor may not stand in protected cells
+ // (unless cursor in protected areas is OK.)
+ const SwTableNode* pTableNode = rNdPos.GetNode().FindTableNode();
+ if( !IsReadOnlyAvailable() &&
+ pTableNode != nullptr && rNdPos.GetNode().IsProtect() )
+ {
+ // we're in a table, and we're in a protected area, so we're
+ // probably in a protected cell.
+
+ // move forward into non-protected area.
+ SwPaM aPam( rNdPos.GetNode(), 0 );
+ while( aPam.GetPointNode().IsProtect() &&
+ aPam.Move( fnMoveForward, GoInContent ) )
+ ; // nothing to do in the loop; the aPam.Move does the moving!
+
+ // didn't work? then go backwards!
+ if( aPam.GetPointNode().IsProtect() )
+ {
+ SwPaM aTmpPaM( rNdPos.GetNode(), 0 );
+ aPam = aTmpPaM;
+ while( aPam.GetPointNode().IsProtect() &&
+ aPam.Move( fnMoveBackward, GoInContent ) )
+ ; // nothing to do in the loop; the aPam.Move does the moving!
+ }
+
+ // if we're successful, set the new position
+ if( ! aPam.GetPointNode().IsProtect() )
+ {
+ *m_pCurrentCursor->GetPoint() = *aPam.GetPoint();
+ }
+ }
+
+ // in a protected frame
+ const SwSectionNode* pSectNd = rNdPos.GetNode().FindSectionNode();
+ if( pSectNd && ( pSectNd->GetSection().IsHiddenFlag() ||
+ ( !IsReadOnlyAvailable() &&
+ pSectNd->GetSection().IsProtectFlag() )) )
+ {
+ bOk = false;
+ bool bGoNextSection = true;
+ for( int nLoopCnt = 0; !bOk && nLoopCnt < 2; ++nLoopCnt )
+ {
+ bool bContinue;
+ do {
+ bContinue = false;
+ for (;;)
+ {
+ if (bGoNextSection)
+ pCNd = rNds.GoNextSection( &rNdPos,
+ true, !IsReadOnlyAvailable() );
+ else
+ pCNd = SwNodes::GoPrevSection( &rNdPos,
+ true, !IsReadOnlyAvailable() );
+ if ( pCNd == nullptr) break;
+ // moved inside a table -> check if it is protected
+ if( pCNd->FindTableNode() )
+ {
+ SwCallLink aTmp( *this );
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ aTmp.m_nNodeType = SwNodeType::NONE; // don't do anything in DTOR
+ if( !m_pCurrentCursor->IsInProtectTable( true ) )
+ {
+ const SwSectionNode* pSNd = pCNd->FindSectionNode();
+ if( !pSNd || !pSNd->GetSection().IsHiddenFlag()
+ || (!IsReadOnlyAvailable() &&
+ pSNd->GetSection().IsProtectFlag() ))
+ {
+ bOk = true;
+ break; // found non-protected cell
+ }
+ continue; // continue search
+ }
+ }
+ else
+ {
+ bOk = true;
+ break; // found non-protected cell
+ }
+ }
+
+ if( bOk && rNdPos.GetNodeIndex() < rNds.GetEndOfExtras().GetIndex() )
+ {
+ // also check for Fly - might be protected as well
+ pFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr);
+ if (nullptr == pFrame ||
+ ( !IsReadOnlyAvailable() && pFrame->IsProtected() ) ||
+ ( bOnlyText && pCNd->IsNoTextNode() ) )
+ {
+ // continue search
+ bOk = false;
+ bContinue = true;
+ }
+ }
+ } while( bContinue );
+
+ if( !bOk )
+ {
+ if( !nLoopCnt )
+ bGoNextSection = false;
+ rNdPos.Assign( nNdIdx );
+ }
+ }
+ }
+ if( bOk )
+ {
+ pCNd = rNdPos.GetNode().GetContentNode();
+ const sal_Int32 nContent = rNdPos.GetNodeIndex() < nNdIdx ? pCNd->Len() : 0;
+ m_pCurrentCursor->GetPoint()->SetContent( nContent );
+ }
+ else
+ {
+ pCNd = rNdPos.GetNode().GetContentNode();
+ // if cursor in hidden frame, always move it
+ if (!pCNd || !pCNd->getLayoutFrame(GetLayout(), nullptr, nullptr))
+ {
+ SwCursorMoveState aTmpState( CursorMoveState::NONE );
+ aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
+ GetLayout()->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), m_pCurrentCursor->GetPtPos(),
+ &aTmpState );
+ }
+ }
+ return bOk;
+}
+
+bool SwCursorShell::IsCursorReadonly() const
+{
+ if ( GetViewOptions()->IsReadonly() ||
+ GetViewOptions()->IsFormView() /* Formula view */ )
+ {
+ SwFrame *pFrame = GetCurrFrame( false );
+ const SwFlyFrame* pFly;
+ const SwSection* pSection;
+
+ if( pFrame && pFrame->IsInFly() &&
+ (pFly = pFrame->FindFlyFrame())->GetFormat()->GetEditInReadonly().GetValue() &&
+ pFly->Lower() &&
+ !pFly->Lower()->IsNoTextFrame() &&
+ !GetDrawView()->GetMarkedObjectList().GetMarkCount() )
+ {
+ return false;
+ }
+ // edit in readonly sections
+ else if ( pFrame && pFrame->IsInSct() &&
+ nullptr != ( pSection = pFrame->FindSctFrame()->GetSection() ) &&
+ pSection->IsEditInReadonlyFlag() )
+ {
+ return false;
+ }
+ else if ( !IsMultiSelection() && CursorInsideInputField() )
+ {
+ return false;
+ }
+
+ return true;
+ }
+ return false;
+}
+
+/// is the cursor allowed to enter ReadOnly sections?
+void SwCursorShell::SetReadOnlyAvailable( bool bFlag )
+{
+ // *never* switch in GlobalDoc
+ if( (!GetDoc()->GetDocShell() ||
+ dynamic_cast<const SwGlobalDocShell*>(GetDoc()->GetDocShell()) == nullptr ) &&
+ bFlag != m_bSetCursorInReadOnly )
+ {
+ // If the flag is switched off then all selections need to be
+ // invalidated. Otherwise we would trust that nothing protected is selected.
+ if( !bFlag )
+ {
+ ClearMark();
+ }
+ m_bSetCursorInReadOnly = bFlag;
+ UpdateCursor();
+ }
+}
+
+bool SwCursorShell::HasReadonlySel(bool const isReplace) const
+{
+ // Treat selections that span over start or end of paragraph of an outline node
+ // with folded outline content as read-only.
+ if (GetViewOptions()->IsShowOutlineContentVisibilityButton())
+ {
+ SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell();
+ if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected())
+ return true;
+ }
+ bool bRet = false;
+ // If protected area is to be ignored, then selections are never read-only.
+ if ((IsReadOnlyAvailable() || GetViewOptions()->IsFormView() ||
+ GetDoc()->GetDocumentSettingManager().get( DocumentSettingId::PROTECT_FORM )) &&
+ !SwViewOption::IsIgnoreProtectedArea())
+ {
+ if ( m_pTableCursor != nullptr )
+ {
+ // TODO: handling when a table cell (cells) is selected
+ bRet = m_pTableCursor->HasReadOnlyBoxSel()
+ || m_pTableCursor->HasReadonlySel(GetViewOptions()->IsFormView(), isReplace);
+ }
+ else
+ {
+ for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer())
+ {
+ if (rCursor.HasReadonlySel(GetViewOptions()->IsFormView(), isReplace))
+ {
+ bRet = true;
+ break;
+ }
+ }
+ }
+ }
+ return bRet;
+}
+
+bool SwCursorShell::HasHiddenSections() const
+{
+ // Treat selections that span over start or end of paragraph of an outline node
+ // with folded outline content as read-only.
+ if (GetViewOptions()->IsShowOutlineContentVisibilityButton())
+ {
+ SwWrtShell* pWrtSh = GetDoc()->GetDocShell()->GetWrtShell();
+ if (pWrtSh && pWrtSh->HasFoldedOutlineContentSelected())
+ return true;
+ }
+ bool bRet = false;
+
+ if ( m_pTableCursor != nullptr )
+ {
+ bRet = m_pTableCursor->HasHiddenBoxSel()
+ || m_pTableCursor->HasHiddenSections();
+ }
+ else
+ {
+ for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer())
+ {
+ if (rCursor.HasHiddenSections())
+ {
+ bRet = true;
+ break;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool SwCursorShell::IsSelFullPara() const
+{
+ bool bRet = false;
+
+ if( m_pCurrentCursor->GetPoint()->GetNodeIndex() ==
+ m_pCurrentCursor->GetMark()->GetNodeIndex() && !m_pCurrentCursor->IsMultiSelection() )
+ {
+ sal_Int32 nStt = m_pCurrentCursor->GetPoint()->GetContentIndex();
+ sal_Int32 nEnd = m_pCurrentCursor->GetMark()->GetContentIndex();
+ if( nStt > nEnd )
+ std::swap( nStt, nEnd );
+ const SwContentNode* pCNd = m_pCurrentCursor->GetPointContentNode();
+ bRet = pCNd && !nStt && nEnd == pCNd->Len();
+ }
+ return bRet;
+}
+
+SvxFrameDirection SwCursorShell::GetTextDirection( const Point* pPt ) const
+{
+ SwPosition aPos( *m_pCurrentCursor->GetPoint() );
+ Point aPt( pPt ? *pPt : m_pCurrentCursor->GetPtPos() );
+ if( pPt )
+ {
+ SwCursorMoveState aTmpState( CursorMoveState::NONE );
+ aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
+
+ GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState );
+ }
+
+ return mxDoc->GetTextDirection( aPos, &aPt );
+}
+
+bool SwCursorShell::IsInVerticalText( const Point* pPt ) const
+{
+ const SvxFrameDirection nDir = GetTextDirection( pPt );
+ return SvxFrameDirection::Vertical_RL_TB == nDir || SvxFrameDirection::Vertical_LR_TB == nDir
+ || nDir == SvxFrameDirection::Vertical_LR_BT;
+}
+
+bool SwCursorShell::IsInRightToLeftText() const
+{
+ const SvxFrameDirection nDir = GetTextDirection();
+ // GetTextDirection uses SvxFrameDirection::Vertical_LR_TB to indicate RTL in
+ // vertical environment
+ return SvxFrameDirection::Vertical_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir;
+}
+
+/// If the current cursor position is inside a hidden range true is returned. If bSelect is
+/// true, the hidden range is selected. If bSelect is false, the hidden range is not selected.
+bool SwCursorShell::IsInHiddenRange(const bool bSelect)
+{
+ bool bRet = false;
+ if ( !GetViewOptions()->IsShowHiddenChar() && !m_pCurrentCursor->HasMark() )
+ {
+ SwPosition& rPt = *m_pCurrentCursor->GetPoint();
+ const SwTextNode* pNode = rPt.GetNode().GetTextNode();
+ if ( pNode )
+ {
+ const sal_Int32 nPos = rPt.GetContentIndex();
+
+ // check if nPos is in hidden range
+ sal_Int32 nHiddenStart;
+ sal_Int32 nHiddenEnd;
+ SwScriptInfo::GetBoundsOfHiddenRange( *pNode, nPos, nHiddenStart, nHiddenEnd );
+ if ( COMPLETE_STRING != nHiddenStart )
+ {
+ if (bSelect)
+ {
+ // make selection:
+ m_pCurrentCursor->SetMark();
+ m_pCurrentCursor->GetMark()->SetContent(nHiddenEnd);
+ }
+ bRet = true;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+sal_Int32 SwCursorShell::Find_Text( const i18nutil::SearchOptions2& rSearchOpt,
+ bool bSearchInNotes,
+ SwDocPositions eStart, SwDocPositions eEnd,
+ bool& bCancel,
+ FindRanges eRng,
+ bool bReplace )
+{
+ if( m_pTableCursor )
+ GetCursor();
+ delete m_pTableCursor;
+ m_pTableCursor = nullptr;
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ sal_Int32 nRet = m_pCurrentCursor->Find_Text(rSearchOpt, bSearchInNotes, eStart, eEnd,
+ bCancel, eRng, bReplace, GetLayout());
+ if( nRet || bCancel )
+ UpdateCursor();
+ return nRet;
+}
+
+sal_Int32 SwCursorShell::FindFormat( const SwTextFormatColl& rFormatColl,
+ SwDocPositions eStart, SwDocPositions eEnd,
+ bool& bCancel,
+ FindRanges eRng,
+ const SwTextFormatColl* pReplFormat )
+{
+ if( m_pTableCursor )
+ GetCursor();
+ delete m_pTableCursor;
+ m_pTableCursor = nullptr;
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ sal_Int32 nRet = m_pCurrentCursor->FindFormat(rFormatColl, eStart, eEnd, bCancel, eRng,
+ pReplFormat );
+ if( nRet )
+ UpdateCursor();
+ return nRet;
+}
+
+sal_Int32 SwCursorShell::FindAttrs( const SfxItemSet& rSet,
+ bool bNoCollections,
+ SwDocPositions eStart, SwDocPositions eEnd,
+ bool& bCancel,
+ FindRanges eRng,
+ const i18nutil::SearchOptions2* pSearchOpt,
+ const SfxItemSet* rReplSet )
+{
+ if( m_pTableCursor )
+ GetCursor();
+ delete m_pTableCursor;
+ m_pTableCursor = nullptr;
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ sal_Int32 nRet = m_pCurrentCursor->FindAttrs(rSet, bNoCollections, eStart, eEnd,
+ bCancel, eRng, pSearchOpt, rReplSet, GetLayout());
+ if( nRet )
+ UpdateCursor();
+ return nRet;
+}
+
+void SwCursorShell::SetSelection( const SwPaM& rCursor )
+{
+ StartAction();
+ SwCursor* pCursor = GetCursor();
+ *pCursor->GetPoint() = *rCursor.GetPoint();
+ if(rCursor.GetNext() != &rCursor)
+ {
+ const SwPaM *_pStartCursor = rCursor.GetNext();
+ do
+ {
+ SwPaM* pCurrentCursor = CreateCursor();
+ *pCurrentCursor->GetPoint() = *_pStartCursor->GetPoint();
+ if(_pStartCursor->HasMark())
+ {
+ pCurrentCursor->SetMark();
+ *pCurrentCursor->GetMark() = *_pStartCursor->GetMark();
+ }
+ } while( (_pStartCursor = _pStartCursor->GetNext()) != &rCursor );
+ }
+ // CreateCursor() adds a copy of current cursor after current, and then deletes mark of current
+ // cursor; therefore set current cursor's mark only after creating all other cursors
+ if (rCursor.HasMark())
+ {
+ pCursor->SetMark();
+ *pCursor->GetMark() = *rCursor.GetMark();
+ }
+ EndAction();
+}
+
+static const SwStartNode* lcl_NodeContext( const SwNode& rNode )
+{
+ const SwStartNode *pRet = rNode.StartOfSectionNode();
+ while( pRet->IsSectionNode() || pRet->IsTableNode() ||
+ pRet->GetStartNodeType() == SwTableBoxStartNode )
+ {
+ pRet = pRet->StartOfSectionNode();
+ }
+ return pRet;
+}
+
+/**
+ Checks if a position is valid. To be valid the position's node must
+ be a content node and the content must not be unregistered.
+
+ @param aPos the position to check.
+*/
+static bool sw_PosOk(const SwPosition & aPos)
+{
+ return nullptr != aPos.GetNode().GetContentNode() &&
+ aPos.GetContentNode();
+}
+
+/**
+ Checks if a PaM is valid. For a PaM to be valid its point must be
+ valid. Additionally if the PaM has a mark this has to be valid, too.
+
+ @param aPam the PaM to check
+*/
+static bool lcl_CursorOk(SwPaM & aPam)
+{
+ return sw_PosOk(*aPam.GetPoint()) && (! aPam.HasMark()
+ || sw_PosOk(*aPam.GetMark()));
+}
+
+void SwCursorShell::ClearUpCursors()
+{
+ // start of the ring
+ SwPaM * pStartCursor = GetCursor();
+ // start loop with second entry of the ring
+ SwPaM * pCursor = pStartCursor->GetNext();
+ SwPaM * pTmpCursor;
+ bool bChanged = false;
+
+ // For all entries in the ring except the start entry delete the entry if
+ // it is invalid.
+ while (pCursor != pStartCursor)
+ {
+ pTmpCursor = pCursor->GetNext();
+ if ( ! lcl_CursorOk(*pCursor))
+ {
+ delete pCursor;
+ bChanged = true;
+ }
+ pCursor = pTmpCursor;
+ }
+
+ if( pStartCursor->HasMark() && !sw_PosOk( *pStartCursor->GetMark() ) )
+ {
+ pStartCursor->DeleteMark();
+ bChanged = true;
+ }
+ if (pStartCursor->GetPoint()->GetNode().IsTableNode())
+ {
+ // tdf#106959: When cursor points to start of a table, the proper content
+ // node is the first one inside the table, not the previous one
+ SwNodes& aNodes = GetDoc()->GetNodes();
+ SwNodeIndex aIdx(pStartCursor->GetPoint()->GetNode());
+ if (SwNode* pNode = aNodes.GoNext(&aIdx))
+ {
+ SwPaM aTmpPam(*pNode);
+ *pStartCursor = aTmpPam;
+ bChanged = true;
+ }
+ }
+ if( !sw_PosOk( *pStartCursor->GetPoint() ) )
+ {
+ SwNodes & aNodes = GetDoc()->GetNodes();
+ const SwNode* pStart = lcl_NodeContext( pStartCursor->GetPoint()->GetNode() );
+ SwNodeIndex aIdx( pStartCursor->GetPoint()->GetNode() );
+ SwNode * pNode = SwNodes::GoPrevious(&aIdx);
+ if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart )
+ {
+ pNode = aNodes.GoNext( &aIdx );
+ if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart )
+ {
+ // If the start entry of the ring is invalid replace it with a
+ // cursor pointing to the beginning of the first content node in the
+ // document.
+ aIdx = *(aNodes.GetEndOfContent().StartOfSectionNode());
+ pNode = aNodes.GoNext( &aIdx );
+ }
+ }
+ bool bFound = (pNode != nullptr);
+
+ assert(bFound);
+
+ if (bFound)
+ {
+ SwPaM aTmpPam(*pNode);
+ *pStartCursor = aTmpPam;
+ }
+
+ bChanged = true;
+ }
+
+ // If at least one of the cursors in the ring have been deleted or replaced,
+ // remove the table cursor.
+ if (m_pTableCursor != nullptr && bChanged)
+ TableCursorToCursor();
+}
+
+OUString SwCursorShell::GetCursorDescr() const
+{
+ OUString aResult;
+
+ if (IsMultiSelection())
+ aResult += SwResId(STR_MULTISEL);
+ else
+ aResult = SwDoc::GetPaMDescr(*GetCursor());
+
+ return aResult;
+}
+
+void SwCursorShell::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwCursorShell"));
+
+ SwViewShell::dumpAsXml(pWriter);
+
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pCurrentCursor"));
+ for (const SwPaM& rPaM : m_pCurrentCursor->GetRingContainer())
+ rPaM.dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+static void lcl_FillRecognizerData( std::vector< OUString >& rSmartTagTypes,
+ uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps,
+ const SwWrongList& rSmartTagList, sal_Int32 nCurrent )
+{
+ // Insert smart tag information
+ std::vector< uno::Reference< container::XStringKeyMap > > aStringKeyMaps;
+
+ for ( sal_uInt16 i = 0; i < rSmartTagList.Count(); ++i )
+ {
+ const sal_Int32 nSTPos = rSmartTagList.Pos( i );
+ const sal_Int32 nSTLen = rSmartTagList.Len( i );
+
+ if ( nSTPos <= nCurrent && nCurrent < nSTPos + nSTLen )
+ {
+ const SwWrongArea* pArea = rSmartTagList.GetElement( i );
+ if ( pArea )
+ {
+ rSmartTagTypes.push_back( pArea->maType );
+ aStringKeyMaps.push_back( pArea->mxPropertyBag );
+ }
+ }
+ }
+
+ if ( !rSmartTagTypes.empty() )
+ {
+ rStringKeyMaps = comphelper::containerToSequence(aStringKeyMaps);
+ }
+}
+
+static void lcl_FillTextRange( uno::Reference<text::XTextRange>& rRange,
+ SwTextNode& rNode, sal_Int32 nBegin, sal_Int32 nLen )
+{
+ // create SwPosition for nStartIndex
+ SwPosition aStartPos( rNode, nBegin );
+ // create SwPosition for nEndIndex
+ SwPosition aEndPos( rNode, nBegin + nLen );
+
+ const rtl::Reference<SwXTextRange> xRange =
+ SwXTextRange::CreateXTextRange(rNode.GetDoc(), aStartPos, &aEndPos);
+
+ rRange = xRange;
+}
+
+void SwCursorShell::GetSmartTagTerm( std::vector< OUString >& rSmartTagTypes,
+ uno::Sequence< uno::Reference< container::XStringKeyMap > >& rStringKeyMaps,
+ uno::Reference< text::XTextRange>& rRange ) const
+{
+ if ( !SwSmartTagMgr::Get().IsSmartTagsEnabled() )
+ return;
+
+ SwPaM* pCursor = GetCursor();
+ SwPosition aPos( *pCursor->GetPoint() );
+ SwTextNode *pNode = aPos.GetNode().GetTextNode();
+ if ( !pNode || pNode->IsInProtectSect() )
+ return;
+
+ const SwWrongList *pSmartTagList = pNode->GetSmartTags();
+ if ( !pSmartTagList )
+ return;
+
+ sal_Int32 nCurrent = aPos.GetContentIndex();
+ sal_Int32 nBegin = nCurrent;
+ sal_Int32 nLen = 1;
+
+ if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin))
+ return;
+
+ const sal_uInt16 nIndex = pSmartTagList->GetWrongPos( nBegin );
+ const SwWrongList* pSubList = pSmartTagList->SubList( nIndex );
+ if ( pSubList )
+ {
+ pSmartTagList = pSubList;
+ nCurrent = 0;
+ }
+
+ lcl_FillRecognizerData( rSmartTagTypes, rStringKeyMaps, *pSmartTagList, nCurrent );
+ lcl_FillTextRange( rRange, *pNode, nBegin, nLen );
+}
+
+// see also SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect )
+void SwCursorShell::GetSmartTagRect( const Point& rPt, SwRect& rSelectRect )
+{
+ SwPaM* pCursor = GetCursor();
+ SwPosition aPos( *pCursor->GetPoint() );
+ Point aPt( rPt );
+ SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText );
+ SwSpecialPos aSpecialPos;
+ eTmpState.m_pSpecialPos = &aSpecialPos;
+ SwTextNode *pNode;
+ const SwWrongList *pSmartTagList;
+
+ if( !GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &eTmpState ) )
+ return;
+ pNode = aPos.GetNode().GetTextNode();
+ if( !pNode )
+ return;
+ pSmartTagList = pNode->GetSmartTags();
+ if( !pSmartTagList )
+ return;
+ if( pNode->IsInProtectSect() )
+ return;
+
+ sal_Int32 nBegin = aPos.GetContentIndex();
+ sal_Int32 nLen = 1;
+
+ if (!pSmartTagList->InWrongWord(nBegin, nLen) || pNode->IsSymbolAt(nBegin))
+ return;
+
+ // get smarttag word
+ OUString aText( pNode->GetText().copy(nBegin, nLen) );
+
+ //save the start and end positions of the line and the starting point
+ Push();
+ LeftMargin();
+ const sal_Int32 nLineStart = GetCursor()->GetPoint()->GetContentIndex();
+ RightMargin();
+ const sal_Int32 nLineEnd = GetCursor()->GetPoint()->GetContentIndex();
+ Pop(PopMode::DeleteCurrent);
+
+ // make sure the selection build later from the data below does not
+ // include "in word" character to the left and right in order to
+ // preserve those. Therefore count those "in words" in order to
+ // modify the selection accordingly.
+ const sal_Unicode* pChar = aText.getStr();
+ sal_Int32 nLeft = 0;
+ while (*pChar++ == CH_TXTATR_INWORD)
+ ++nLeft;
+ pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr;
+ sal_Int32 nRight = 0;
+ while (pChar && *pChar-- == CH_TXTATR_INWORD)
+ ++nRight;
+
+ aPos.SetContent( nBegin + nLeft );
+ pCursor = GetCursor();
+ *pCursor->GetPoint() = aPos;
+ pCursor->SetMark();
+ ExtendSelection( true, nLen - nLeft - nRight );
+ // do not determine the rectangle in the current line
+ const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft;
+ // take one less than the line end - otherwise the next line would
+ // be calculated
+ const sal_Int32 nWordEnd = std::min(nBegin + nLen - nLeft - nRight, nLineEnd);
+ Push();
+ pCursor->DeleteMark();
+ SwPosition& rPos = *GetCursor()->GetPoint();
+ rPos.SetContent( nWordStart );
+ SwRect aStartRect;
+ SwCursorMoveState aState;
+ aState.m_bRealWidth = true;
+ SwContentNode* pContentNode = pCursor->GetPointContentNode();
+ std::pair<Point, bool> const tmp(rPt, false);
+ SwContentFrame *pContentFrame = pContentNode->getLayoutFrame(
+ GetLayout(), pCursor->GetPoint(), &tmp);
+
+ pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState );
+ rPos.SetContent( nWordEnd - 1 );
+ SwRect aEndRect;
+ pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState );
+ rSelectRect = aStartRect.Union( aEndRect );
+ Pop(PopMode::DeleteCurrent);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/crstrvl.cxx b/sw/source/core/crsr/crstrvl.cxx
new file mode 100644
index 0000000000..db11d4ea33
--- /dev/null
+++ b/sw/source/core/crsr/crstrvl.cxx
@@ -0,0 +1,2860 @@
+/* -*- 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 <hintids.hxx>
+#include <comphelper/string.hxx>
+#include <svl/itemiter.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/formatbreakitem.hxx>
+#include <svx/svdobj.hxx>
+#include <osl/diagnose.h>
+#include <crsrsh.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <pagefrm.hxx>
+#include <cntfrm.hxx>
+#include <rootfrm.hxx>
+#include <pam.hxx>
+#include <ndtxt.hxx>
+#include <fldbas.hxx>
+#include <swtable.hxx>
+#include <docary.hxx>
+#include <txtfld.hxx>
+#include <fmtfld.hxx>
+#include <txtftn.hxx>
+#include <txtinet.hxx>
+#include <fmtinfmt.hxx>
+#include <txttxmrk.hxx>
+#include <frmfmt.hxx>
+#include <flyfrm.hxx>
+#include <viscrs.hxx>
+#include "callnk.hxx"
+#include <doctxm.hxx>
+#include <docfld.hxx>
+#include <expfld.hxx>
+#include <reffld.hxx>
+#include <flddat.hxx>
+#include <cellatr.hxx>
+#include <swundo.hxx>
+#include <redline.hxx>
+#include <fmtcntnt.hxx>
+#include <fmthdft.hxx>
+#include <pagedesc.hxx>
+#include <fesh.hxx>
+#include <charfmt.hxx>
+#include <fmturl.hxx>
+#include <txtfrm.hxx>
+#include <wrong.hxx>
+#include <calbck.hxx>
+#include <unotools/intlwrapper.hxx>
+#include <docufld.hxx>
+#include <svx/srchdlg.hxx>
+#include <frameformats.hxx>
+#include <docsh.hxx>
+#include <wrtsh.hxx>
+#include <textcontentcontrol.hxx>
+
+using namespace ::com::sun::star;
+
+void SwCursorShell::MoveCursorToNum()
+{
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ if( ActionPend() )
+ return;
+ CurrShell aCurr( this );
+ // try to set cursor onto this position, at half of the char-
+ // SRectangle's height
+ Point aPt( m_pCurrentCursor->GetPtPos() );
+ std::pair<Point, bool> const tmp(aPt, true);
+ SwContentFrame * pFrame = m_pCurrentCursor->GetPointContentNode()->getLayoutFrame(
+ GetLayout(), m_pCurrentCursor->GetPoint(), &tmp);
+ pFrame->GetCharRect( m_aCharRect, *m_pCurrentCursor->GetPoint() );
+ pFrame->Calc(GetOut());
+ if( pFrame->IsVertical() )
+ {
+ aPt.setX(m_aCharRect.Center().getX());
+ aPt.setY(pFrame->getFrameArea().Top() + GetUpDownX());
+ }
+ else
+ {
+ aPt.setY(m_aCharRect.Center().getY());
+ aPt.setX(pFrame->getFrameArea().Left() + GetUpDownX());
+ }
+ pFrame->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), aPt );
+ if ( !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos ))
+ {
+ UpdateCursor(SwCursorShell::UPDOWN |
+ SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
+ SwCursorShell::READONLY );
+ }
+}
+
+/// go to next/previous point on the same level
+void SwCursorShell::GotoNextNum()
+{
+ if (!SwDoc::GotoNextNum(*m_pCurrentCursor->GetPoint(), GetLayout()))
+ return;
+ MoveCursorToNum();
+}
+
+void SwCursorShell::GotoPrevNum()
+{
+ if (!SwDoc::GotoPrevNum(*m_pCurrentCursor->GetPoint(), GetLayout()))
+ return;
+ MoveCursorToNum();
+}
+
+/// jump from content to header
+bool SwCursorShell::GotoHeaderText()
+{
+ const SwFrame* pFrame = GetCurrFrame()->FindPageFrame();
+ while( pFrame && !pFrame->IsHeaderFrame() )
+ pFrame = pFrame->GetLower();
+ // found header, search 1. content frame
+ while( pFrame && !pFrame->IsContentFrame() )
+ pFrame = pFrame->GetLower();
+
+ if( !pFrame )
+ return false;
+
+ CurrShell aCurr( this );
+ // get header frame
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursor *pTmpCursor = getShellCursor( true );
+ SwCursorSaveState aSaveState( *pTmpCursor );
+ pFrame->Calc(GetOut());
+ Point aPt( pFrame->getFrameArea().Pos() + pFrame->getFramePrintArea().Pos() );
+ pFrame->GetModelPositionForViewPoint( pTmpCursor->GetPoint(), aPt );
+ if( !pTmpCursor->IsSelOvr() )
+ UpdateCursor();
+ else
+ pFrame = nullptr;
+ return nullptr != pFrame;
+}
+
+/// jump from content to footer
+bool SwCursorShell::GotoFooterText()
+{
+ const SwPageFrame* pFrame = GetCurrFrame()->FindPageFrame();
+ if( !pFrame )
+ return false;
+
+ const SwFrame* pLower = pFrame->GetLastLower();
+
+ while( pLower && !pLower->IsFooterFrame() )
+ pLower = pLower->GetLower();
+ // found footer, search 1. content frame
+ while( pLower && !pLower->IsContentFrame() )
+ pLower = pLower->GetLower();
+
+ if( !pLower )
+ return false;
+
+ SwCursor *pTmpCursor = getShellCursor( true );
+ CurrShell aCurr( this );
+ // get position in footer
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *pTmpCursor );
+ pLower->Calc(GetOut());
+ Point aPt( pLower->getFrameArea().Pos() + pLower->getFramePrintArea().Pos() );
+ pLower->GetModelPositionForViewPoint( pTmpCursor->GetPoint(), aPt );
+ if( !pTmpCursor->IsSelOvr() )
+ UpdateCursor();
+ else
+ pFrame = nullptr;
+ return nullptr != pFrame;
+}
+
+bool SwCursorShell::SetCursorInHdFt(size_t nDescNo, bool bInHeader, bool bEven, bool bFirst)
+{
+ SwDoc *pMyDoc = GetDoc();
+ const SwPageDesc* pDesc = nullptr;
+
+ CurrShell aCurr( this );
+
+ if( SIZE_MAX == nDescNo )
+ {
+ // take the current one
+ const SwContentFrame *pCurrFrame = GetCurrFrame();
+ const SwPageFrame* pPage = (pCurrFrame == nullptr) ? nullptr : pCurrFrame->FindPageFrame();
+ if( pPage && pMyDoc->ContainsPageDesc(
+ pPage->GetPageDesc(), &nDescNo) )
+ pDesc = pPage->GetPageDesc();
+ }
+ else
+ if (nDescNo < pMyDoc->GetPageDescCnt())
+ pDesc = &pMyDoc->GetPageDesc( nDescNo );
+
+ if( !pDesc )
+ return false;
+
+ // check if the attribute exists
+ const SwFormatContent* pCnt = nullptr;
+ if( bInHeader )
+ {
+ const SwFormatHeader& rHd
+ = bEven ? bFirst ? pDesc->GetFirstLeft().GetHeader() : pDesc->GetLeft().GetHeader()
+ : bFirst ? pDesc->GetFirstMaster().GetHeader() : pDesc->GetMaster().GetHeader();
+ if( rHd.GetHeaderFormat() )
+ pCnt = &rHd.GetHeaderFormat()->GetContent();
+ }
+ else
+ {
+ const SwFormatFooter& rFt
+ = bEven ? bFirst ? pDesc->GetFirstLeft().GetFooter() : pDesc->GetLeft().GetFooter()
+ : bFirst ? pDesc->GetFirstMaster().GetFooter() : pDesc->GetMaster().GetFooter();
+ if( rFt.GetFooterFormat() )
+ pCnt = &rFt.GetFooterFormat()->GetContent();
+ }
+
+ if( !pCnt || !pCnt->GetContentIdx() )
+ return false;
+
+ SwNodeIndex aIdx( *pCnt->GetContentIdx(), 1 );
+ SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
+ if( !pCNd )
+ pCNd = pMyDoc->GetNodes().GoNext( &aIdx );
+
+ Point aPt( m_pCurrentCursor->GetPtPos() );
+
+ std::pair<Point, bool> const tmp(aPt, false);
+ if (!pCNd || nullptr == pCNd->getLayoutFrame(GetLayout(), nullptr, &tmp))
+ return false;
+
+ // then we can set the cursor in here
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ ClearMark();
+
+ SwPosition& rPos = *m_pCurrentCursor->GetPoint();
+ rPos.Assign( *pCNd );
+
+ if (m_pCurrentCursor->IsSelOvr())
+ return false;
+
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
+ SwCursorShell::READONLY );
+ return true;
+}
+
+/// jump to the next index
+bool SwCursorShell::GotoNextTOXBase( const OUString* pName )
+{
+ const SwSectionFormats& rFormats = GetDoc()->GetSections();
+ SwContentNode* pFnd = nullptr;
+ for( SwSectionFormats::size_type n = rFormats.size(); n; )
+ {
+ const SwSection* pSect = rFormats[ --n ]->GetSection();
+ if (SectionType::ToxContent == pSect->GetType())
+ {
+ SwSectionNode const*const pSectNd(
+ pSect->GetFormat()->GetSectionNode());
+ if ( pSectNd
+ && m_pCurrentCursor->GetPoint()->GetNode() < *pSectNd
+ && (!pFnd || pFnd->GetIndex() > pSectNd->GetIndex())
+ && (!pName || *pName ==
+ static_cast<SwTOXBaseSection const*>(pSect)->GetTOXName()))
+ {
+ SwNodeIndex aIdx(*pSectNd, 1);
+ SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
+ if (!pCNd)
+ pCNd = GetDoc()->GetNodes().GoNext( &aIdx );
+ if (pCNd &&
+ pCNd->EndOfSectionIndex() <= pSectNd->EndOfSectionIndex())
+ {
+ SwContentFrame const*const pCFrame(
+ pCNd->getLayoutFrame(GetLayout()));
+ if (pCFrame &&
+ (IsReadOnlyAvailable() || !pCFrame->IsProtected()))
+ {
+ pFnd = pCNd;
+ }
+ }
+ }
+ }
+ }
+ if( !pFnd )
+ return false;
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ m_pCurrentCursor->GetPoint()->Assign( *pFnd );
+ bool bRet = !m_pCurrentCursor->IsSelOvr();
+ if( bRet )
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return bRet;
+}
+
+/// jump to previous index
+bool SwCursorShell::GotoPrevTOXBase( const OUString* pName )
+{
+ const SwSectionFormats& rFormats = GetDoc()->GetSections();
+ SwContentNode* pFnd = nullptr;
+ for( SwSectionFormats::size_type n = rFormats.size(); n; )
+ {
+ const SwSection* pSect = rFormats[ --n ]->GetSection();
+ if (SectionType::ToxContent == pSect->GetType())
+ {
+ SwSectionNode const*const pSectNd(
+ pSect->GetFormat()->GetSectionNode());
+ if ( pSectNd
+ && m_pCurrentCursor->GetPoint()->GetNode() > *pSectNd->EndOfSectionNode()
+ && (!pFnd || *pFnd < *pSectNd)
+ && (!pName || *pName ==
+ static_cast<SwTOXBaseSection const*>(pSect)->GetTOXName()))
+ {
+ SwNodeIndex aIdx(*pSectNd, 1);
+ SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
+ if (!pCNd)
+ pCNd = GetDoc()->GetNodes().GoNext( &aIdx );
+ if (pCNd &&
+ pCNd->EndOfSectionIndex() <= pSectNd->EndOfSectionIndex())
+ {
+ SwContentFrame const*const pCFrame(
+ pCNd->getLayoutFrame(GetLayout()));
+ if (pCFrame &&
+ (IsReadOnlyAvailable() || !pCFrame->IsProtected()))
+ {
+ pFnd = pCNd;
+ }
+ }
+ }
+ }
+ }
+
+ if( !pFnd )
+ return false;
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ m_pCurrentCursor->GetPoint()->Assign(*pFnd);
+ bool bRet = !m_pCurrentCursor->IsSelOvr();
+ if( bRet )
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return bRet;
+}
+
+/// jump to index of TOXMark
+void SwCursorShell::GotoTOXMarkBase()
+{
+ SwTOXMarks aMarks;
+ sal_uInt16 nCnt = SwDoc::GetCurTOXMark(*m_pCurrentCursor->GetPoint(), aMarks);
+ if(!nCnt)
+ return;
+ // Take the 1. and get the index type. Ask it for the actual index.
+ const SwTOXType* pType = aMarks[0]->GetTOXType();
+ auto pContentFrame = pType->FindContentFrame(*GetDoc(), *GetLayout());
+ if(!pContentFrame)
+ return;
+ SwCallLink aLk(*this); // watch Cursor-Moves
+ SwCursorSaveState aSaveState(*m_pCurrentCursor);
+ assert(pContentFrame->IsTextFrame());
+ *m_pCurrentCursor->GetPoint() = static_cast<SwTextFrame const*>(pContentFrame)->MapViewToModelPos(TextFrameIndex(0));
+ if(!m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr())
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+}
+
+/// Jump to next/previous table formula
+/// Optionally it is possible to also jump to broken formulas
+bool SwCursorShell::GotoNxtPrvTableFormula( bool bNext, bool bOnlyErrors )
+{
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
+
+ if( IsTableMode() )
+ return false;
+
+ bool bFnd = false;
+ SwPosition aOldPos = *m_pCurrentCursor->GetPoint();
+ SwPosition& rPos = *m_pCurrentCursor->GetPoint();
+
+ Point aPt;
+ SwPosition aFndPos( GetDoc()->GetNodes().GetEndOfContent() );
+ if( !bNext )
+ aFndPos.Assign(SwNodeOffset(0));
+ SetGetExpField aFndGEF( aFndPos ), aCurGEF( rPos );
+
+ {
+ const SwNode* pSttNd = rPos.GetNode().FindTableBoxStartNode();
+ if( pSttNd )
+ {
+ const SwTableBox* pTBox = pSttNd->FindTableNode()->GetTable().
+ GetTableBox( pSttNd->GetIndex() );
+ if( pTBox )
+ aCurGEF = SetGetExpField( *pTBox );
+ }
+ }
+
+ if( rPos.GetNode() < GetDoc()->GetNodes().GetEndOfExtras() )
+ {
+ // also at collection use only the first frame
+ std::pair<Point, bool> const tmp(aPt, false);
+ aCurGEF.SetBodyPos( *rPos.GetNode().GetContentNode()->getLayoutFrame( GetLayout(),
+ &rPos, &tmp) );
+ }
+
+ const registeredSfxPoolItems& rSurrogates(GetDoc()->GetAttrPool().GetItemSurrogates(RES_BOXATR_FORMULA));
+ const sal_uInt32 nMaxItems(rSurrogates.size());
+ if( nMaxItems > 0 )
+ {
+ sal_uInt8 nMaxDo = 2;
+ do {
+ for (const SfxPoolItem* pItem : rSurrogates)
+ {
+ const SwTableBox* pTBox;
+ auto pFormulaItem = dynamic_cast<const SwTableBoxFormula*>(pItem);
+ if( !pFormulaItem )
+ continue;
+ pTBox = pFormulaItem->GetTableBox();
+ if( pTBox &&
+ pTBox->GetSttNd() &&
+ pTBox->GetSttNd()->GetNodes().IsDocNodes() &&
+ ( !bOnlyErrors ||
+ !pFormulaItem->HasValidBoxes() ) )
+ {
+ SwNodeIndex aIdx( *pTBox->GetSttNd() );
+ const SwContentNode* pCNd = GetDoc()->GetNodes().GoNext( &aIdx );
+ std::pair<Point, bool> const tmp(aPt, false);
+ if (pCNd)
+ {
+ const SwContentFrame* pCFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ if (pCFrame && (IsReadOnlyAvailable() || !pCFrame->IsProtected() ))
+ {
+ SetGetExpField aCmp( *pTBox );
+ aCmp.SetBodyPos( *pCFrame );
+
+ if( bNext ? ( aCurGEF < aCmp && aCmp < aFndGEF )
+ : ( aCmp < aCurGEF && aFndGEF < aCmp ))
+ {
+ aFndGEF = aCmp;
+ bFnd = true;
+ }
+ }
+ }
+ }
+ }
+ if( !bFnd )
+ {
+ if( bNext )
+ {
+ rPos.Assign(SwNodeOffset(0), 0);
+ aCurGEF = SetGetExpField( rPos );
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped );
+ }
+ else
+ {
+ aCurGEF = SetGetExpField( SwPosition( GetDoc()->GetNodes().GetEndOfContent() ) );
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped );
+ }
+ }
+ } while( !bFnd && --nMaxDo );
+ }
+
+ if( !bFnd )
+ {
+ rPos = aOldPos;
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ aFndGEF.GetPosOfContent( rPos );
+ m_pCurrentCursor->DeleteMark();
+
+ bFnd = !m_pCurrentCursor->IsSelOvr();
+ if( bFnd )
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
+ SwCursorShell::READONLY );
+
+ return bFnd;
+}
+
+/// jump to next/previous index marker
+bool SwCursorShell::GotoNxtPrvTOXMark( bool bNext )
+{
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
+
+ if( IsTableMode() )
+ return false;
+
+ bool bFnd = false;
+ SwPosition& rPos = *m_pCurrentCursor->GetPoint();
+
+ Point aPt;
+ SwPosition aFndPos( GetDoc()->GetNodes().GetEndOfContent() );
+ if( !bNext )
+ aFndPos.Assign(SwNodeOffset(0));
+ SetGetExpField aFndGEF( aFndPos ), aCurGEF( rPos );
+
+ if( rPos.GetNodeIndex() < GetDoc()->GetNodes().GetEndOfExtras().GetIndex() )
+ {
+ // also at collection use only the first frame
+ std::pair<Point, bool> const tmp(aPt, false);
+ aCurGEF.SetBodyPos( *rPos.GetNode().
+ GetContentNode()->getLayoutFrame(GetLayout(), &rPos, &tmp));
+ }
+
+ const SwTextNode* pTextNd;
+ const SwTextTOXMark* pTextTOX;
+ const registeredSfxPoolItems& rSurrogates(GetDoc()->GetAttrPool().GetItemSurrogates(RES_TXTATR_TOXMARK));
+ const sal_uInt32 nMaxItems(rSurrogates.size());
+ if( nMaxItems == 0 )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+
+ do {
+ for (const SfxPoolItem* pItem : rSurrogates)
+ {
+ auto pToxMarkItem = dynamic_cast<const SwTOXMark*>(pItem);
+ if( !pToxMarkItem )
+ continue;
+ pTextTOX = pToxMarkItem->GetTextTOXMark();
+ if( !pTextTOX )
+ continue;
+ pTextNd = &pTextTOX->GetTextNode();
+ if( !pTextNd->GetNodes().IsDocNodes() )
+ continue;
+ std::pair<Point, bool> const tmp(aPt, false);
+ const SwContentFrame* pCFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ if( pCFrame && ( IsReadOnlyAvailable() || !pCFrame->IsProtected() ))
+ {
+ SetGetExpField aCmp( *pTextNd, *pTextTOX );
+ aCmp.SetBodyPos( *pCFrame );
+
+ if( bNext ? ( aCurGEF < aCmp && aCmp < aFndGEF )
+ : ( aCmp < aCurGEF && aFndGEF < aCmp ))
+ {
+ aFndGEF = aCmp;
+ bFnd = true;
+ }
+ }
+ }
+ if( !bFnd )
+ {
+ if ( bNext )
+ {
+ rPos.Assign(SwNodeOffset(0), 0);
+ aCurGEF = SetGetExpField( rPos );
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped );
+ }
+ else
+ {
+ aCurGEF = SetGetExpField( SwPosition( GetDoc()->GetNodes().GetEndOfContent() ) );
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped );
+ }
+ }
+ } while ( !bFnd );
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ aFndGEF.GetPosOfContent( rPos );
+
+ bFnd = !m_pCurrentCursor->IsSelOvr();
+ if( bFnd )
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
+ SwCursorShell::READONLY );
+ return bFnd;
+}
+
+/// traveling between marks
+const SwTOXMark& SwCursorShell::GotoTOXMark( const SwTOXMark& rStart,
+ SwTOXSearch eDir )
+{
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ const SwTOXMark& rNewMark = GetDoc()->GotoTOXMark( rStart, eDir,
+ IsReadOnlyAvailable() );
+ // set position
+ SwPosition& rPos = *GetCursor()->GetPoint();
+ rPos.Assign(rNewMark.GetTextTOXMark()->GetTextNode(),
+ rNewMark.GetTextTOXMark()->GetStart() );
+
+ if( !m_pCurrentCursor->IsSelOvr() )
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
+ SwCursorShell::READONLY );
+
+ return rNewMark;
+}
+
+/// jump to next/previous field type
+static void lcl_MakeFieldLst(
+ SetGetExpFields& rLst,
+ const SwFieldType& rFieldType,
+ const bool bInReadOnly,
+ const bool bChkInpFlag = false )
+{
+ // always search the 1. frame
+ Point aPt;
+ std::vector<SwFormatField*> vFields;
+ rFieldType.GatherFields(vFields, false);
+ for(SwFormatField* pFormatField: vFields)
+ {
+ SwTextField* pTextField = pFormatField->GetTextField();
+ if ( pTextField != nullptr
+ && ( !bChkInpFlag
+ || static_cast<const SwSetExpField*>(pTextField->GetFormatField().GetField())->GetInputFlag() ) )
+ {
+ const SwTextNode& rTextNode = pTextField->GetTextNode();
+ std::pair<Point, bool> const tmp(aPt, false);
+ const SwContentFrame* pCFrame =
+ rTextNode.getLayoutFrame(
+ rTextNode.GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
+ nullptr, &tmp);
+ if ( pCFrame != nullptr
+ && ( bInReadOnly || !pCFrame->IsProtected() ) )
+ {
+ std::unique_ptr<SetGetExpField> pNew(new SetGetExpField( rTextNode, pTextField ));
+ pNew->SetBodyPos( *pCFrame );
+ rLst.insert( std::move(pNew) );
+ }
+ }
+ }
+}
+
+static SetGetExpFields::const_iterator
+lcl_FindField(bool & o_rFound, SetGetExpFields const& rSrtLst,
+ SwRootFrame const *const pLayout, SwTextNode *const pTextNode,
+ SwTextField const *const pTextField, SwPosition const& rPos,
+ sal_Int32 const nContentOffset)
+{
+ std::optional<SetGetExpField> oSrch;
+ if (-1 == nContentOffset)
+ {
+ oSrch.emplace(rPos.GetNode(), pTextField, rPos.GetContentIndex());
+ }
+ else
+ {
+ oSrch.emplace(rPos.GetNode(), pTextField, nContentOffset);
+ }
+
+ if (rPos.GetNodeIndex() < pTextNode->GetNodes().GetEndOfExtras().GetIndex())
+ {
+ // also at collection use only the first frame
+ Point aPt;
+ std::pair<Point, bool> const tmp(aPt, false);
+ oSrch->SetBodyPos(*pTextNode->getLayoutFrame(pLayout, &rPos, &tmp));
+ }
+
+ SetGetExpFields::const_iterator it = rSrtLst.lower_bound(&*oSrch);
+
+ o_rFound = (it != rSrtLst.end()) && (**it == *oSrch);
+ return it;
+}
+
+bool SwCursorShell::MoveFieldType(
+ const SwFieldType* pFieldType,
+ const bool bNext,
+ const SwFieldIds nResType,
+ const bool bAddSetExpressionFieldsToInputFields )
+{
+ // sorted list of all fields
+ SetGetExpFields aSrtLst;
+
+ if ( pFieldType )
+ {
+ if( SwFieldIds::Input != pFieldType->Which() && !pFieldType->HasWriterListeners() )
+ {
+ return false;
+ }
+
+ // found Modify object, add all fields to array
+ ::lcl_MakeFieldLst( aSrtLst, *pFieldType, IsReadOnlyAvailable() );
+
+ if( SwFieldIds::Input == pFieldType->Which() && bAddSetExpressionFieldsToInputFields )
+ {
+ // there are hidden input fields in the set exp. fields
+ const SwFieldTypes& rFieldTypes = *mxDoc->getIDocumentFieldsAccess().GetFieldTypes();
+ const size_t nSize = rFieldTypes.size();
+ for( size_t i=0; i < nSize; ++i )
+ {
+ pFieldType = rFieldTypes[ i ].get();
+ if ( SwFieldIds::SetExp == pFieldType->Which() )
+ {
+ ::lcl_MakeFieldLst( aSrtLst, *pFieldType, IsReadOnlyAvailable(), true );
+ }
+ }
+ }
+ }
+ else
+ {
+ const SwFieldTypes& rFieldTypes = *mxDoc->getIDocumentFieldsAccess().GetFieldTypes();
+ const size_t nSize = rFieldTypes.size();
+ const bool bAllFieldTypes = nResType == SwFieldIds::Unknown;
+ for( size_t i=0; i < nSize; ++i )
+ {
+ pFieldType = rFieldTypes[ i ].get();
+ if (bAllFieldTypes || nResType == pFieldType->Which())
+ {
+ ::lcl_MakeFieldLst( aSrtLst, *pFieldType, IsReadOnlyAvailable() );
+ }
+ }
+ }
+
+ // found no fields?
+ if( aSrtLst.empty() )
+ return false;
+
+ SetGetExpFields::const_iterator it;
+ SwCursor* pCursor = getShellCursor( true );
+ {
+ // (1998): Always use field for search so that the right one is found as
+ // well some are in frames that are anchored to a paragraph that has a
+ // field
+ const SwPosition& rPos = *pCursor->GetPoint();
+
+ SwTextNode* pTNd = rPos.GetNode().GetTextNode();
+ OSL_ENSURE( pTNd, "No ContentNode" );
+
+ SwTextField * pTextField = pTNd->GetFieldTextAttrAt(rPos.GetContentIndex(), ::sw::GetTextAttrMode::Default);
+ const bool bDelField = ( pTextField == nullptr );
+ sal_Int32 nContentOffset = -1;
+
+ if( bDelField )
+ {
+ // create dummy for the search
+ SwFormatField* pFormatField = new SwFormatField( SwDateTimeField(
+ static_cast<SwDateTimeFieldType*>(mxDoc->getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::DateTime ) ) ) );
+
+ pTextField = new SwTextField( *pFormatField, rPos.GetContentIndex(),
+ mxDoc->IsClipBoard() );
+ pTextField->ChgTextNode( pTNd );
+ }
+ else
+ {
+ // the cursor might be anywhere inside the input field,
+ // but we will be searching for the field start
+ if (pTextField->Which() == RES_TXTATR_INPUTFIELD
+ && rPos.GetContentIndex() != pTextField->GetStart())
+ nContentOffset = pTextField->GetStart();
+ }
+ bool isSrch;
+ it = lcl_FindField(isSrch, aSrtLst,
+ GetLayout(), pTNd, pTextField, rPos, nContentOffset);
+
+ if( bDelField )
+ {
+ auto const pFormat(static_cast<SwFormatField*>(&pTextField->GetAttr()));
+ delete pTextField;
+ delete pFormat;
+ }
+
+ if( it != aSrtLst.end() && isSrch ) // found
+ {
+ if( bNext )
+ {
+ if( ++it == aSrtLst.end() )
+ return false; // already at the end
+ }
+ else
+ {
+ if( it == aSrtLst.begin() )
+ return false; // no more steps backward possible
+ --it;
+ }
+ }
+ else // not found
+ {
+ if( bNext )
+ {
+ if( it == aSrtLst.end() )
+ return false;
+ }
+ else
+ {
+ if( it == aSrtLst.begin() )
+ return false; // no more steps backward possible
+ --it;
+ }
+ }
+ }
+ const SetGetExpField& rFnd = **it;
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *pCursor );
+
+ rFnd.GetPosOfContent( *pCursor->GetPoint() );
+ bool bRet = !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection |
+ SwCursorSelOverFlags::Toggle );
+ if( bRet )
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return bRet;
+}
+
+bool SwCursorShell::GotoFootnoteAnchor(const SwTextFootnote& rTextFootnote)
+{
+ if (SwWrtShell* pWrtSh = dynamic_cast<SwWrtShell*>(this))
+ pWrtSh->addCurrentPosition();
+
+ bool bRet = false;
+ SwCursor* pCursor = getShellCursor(true);
+
+ CurrShell aCurr(this);
+ SwCallLink aLk(*this); // watch Cursor-Moves
+ SwCursorSaveState aSaveState(*pCursor);
+
+ pCursor->GetPoint()->Assign(rTextFootnote.GetTextNode(),
+ rTextFootnote.GetStart());
+ bRet = !pCursor->IsSelOvr();
+ if (bRet)
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return bRet;
+}
+
+bool SwCursorShell::GotoFormatContentControl(const SwFormatContentControl& rContentControl)
+{
+ std::shared_ptr<SwContentControl> pContentControl = rContentControl.GetContentControl();
+ const SwTextContentControl* pTextContentControl = pContentControl->GetTextAttr();
+ if (!pTextContentControl)
+ return false;
+
+ CurrShell aCurr(this);
+ SwCallLink aLink(*this);
+
+ SwCursor* pCursor = getShellCursor(true);
+ SwCursorSaveState aSaveState(*pCursor);
+
+ SwTextNode* pTextNode = pContentControl->GetTextNode();
+ // Don't select the text attribute itself at the start.
+ sal_Int32 nStart = pTextContentControl->GetStart() + 1;
+ pCursor->GetPoint()->Assign(*pTextNode, nStart);
+
+ bool bRet = true;
+ // select contents for certain controls or conditions
+ if (pContentControl->GetShowingPlaceHolder() || pContentControl->GetCheckbox()
+ || pContentControl->GetSelectedListItem() || pContentControl->GetSelectedDate())
+ {
+ pCursor->SetMark();
+ // Don't select the CH_TXTATR_BREAKWORD itself at the end.
+ sal_Int32 nEnd = *pTextContentControl->End() - 1;
+ pCursor->GetMark()->Assign(*pTextNode, nEnd);
+ bRet = !pCursor->IsSelOvr();
+ }
+ else
+ ClearMark();
+
+ if (bRet)
+ {
+ UpdateCursor(SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE
+ | SwCursorShell::READONLY);
+ }
+
+ return bRet;
+}
+
+/**
+ * Go to the next (or previous) form control, based first on tabIndex and then paragraph position,
+ * where a tabIndex of 1 is first, 0 is last, and -1 is excluded.
+ */
+void SwCursorShell::GotoFormControl(bool bNext)
+{
+ // (note: this only applies to modern content controls and legacy fieldmarks,
+ // since activeX richText controls aren't exposed to SW keystrokes)
+
+ struct FormControlSort
+ {
+ bool operator()(std::pair<const SwPosition&, sal_uInt32> rLHS,
+ std::pair<const SwPosition&, sal_uInt32> rRHS) const
+ {
+ assert(rLHS.second && rRHS.second && "tabIndex zero must be changed to SAL_MAX_UINT32");
+ //first compare tabIndexes where 1 has the priority.
+ if (rLHS.second < rRHS.second)
+ return true;
+ if (rLHS.second > rRHS.second)
+ return false;
+
+ // when tabIndexes are equal (and they usually are) then sort by paragraph position
+ return rLHS.first < rRHS.first;
+ }
+ };
+ std::map<std::pair<SwPosition, sal_uInt32>,
+ std::pair<SwTextContentControl*, sw::mark::IFieldmark*>, FormControlSort> aFormMap;
+
+ // add all of the eligible modern Content Controls into a sorted map
+ SwContentControlManager& rManager = GetDoc()->GetContentControlManager();
+ for (size_t i = 0; i < rManager.GetCount(); ++i)
+ {
+ SwTextContentControl* pTCC = rManager.UnsortedGet(i);
+ if (!pTCC || !pTCC->GetTextNode())
+ continue;
+ auto pCC = pTCC->GetContentControl().GetContentControl();
+
+ // -1 indicates the control should not participate in keyboard tab navigation
+ if (pCC && pCC->GetTabIndex() == SAL_MAX_UINT32)
+ continue;
+
+ const SwPosition nPos(*pTCC->GetTextNode(), pTCC->GetStart());
+
+ // since 0 is the lowest priority (1 is the highest), and -1 has already been excluded,
+ // use SAL_MAX_UINT32 as zero's tabIndex so that automatic sorting is correct.
+ sal_uInt32 nTabIndex = pCC && pCC->GetTabIndex() ? pCC->GetTabIndex() : SAL_MAX_UINT32;
+
+ const std::pair<SwTextContentControl*, sw::mark::IFieldmark*> pFormControl(pTCC, nullptr);
+ aFormMap[std::make_pair(nPos, nTabIndex)] = pFormControl;
+ }
+
+ if (aFormMap.begin() == aFormMap.end())
+ {
+ // only legacy fields exist. Avoid reprocessing everything and use legacy code path.
+ GotoFieldmark(bNext ? GetFieldmarkAfter(/*Loop=*/true) : GetFieldmarkBefore(/*Loop=*/true));
+ return;
+ }
+
+ // add all of the legacy form field controls into the sorted map
+ IDocumentMarkAccess* pMarkAccess = GetDoc()->getIDocumentMarkAccess();
+ for (auto it = pMarkAccess->getFieldmarksBegin(); it != pMarkAccess->getFieldmarksEnd(); ++it)
+ {
+ auto pFieldMark = dynamic_cast<sw::mark::IFieldmark*>(*it);
+ assert(pFieldMark);
+ std::pair<SwTextContentControl*, sw::mark::IFieldmark*> pFormControl(nullptr, pFieldMark);
+ // legacy form fields do not have (functional) tabIndexes - use lowest priority for them
+ aFormMap[std::make_pair((*it)->GetMarkStart(), SAL_MAX_UINT32)] = pFormControl;
+ }
+
+ if (aFormMap.begin() == aFormMap.end())
+ return;
+
+ // Identify the current location in the document, and the current tab index priority
+
+ // A content control could contain a Fieldmark, so check for legacy fieldmarks first
+ sw::mark::IFieldmark* pFieldMark = GetCurrentFieldmark();
+ SwTextContentControl* pTCC = !pFieldMark ? CursorInsideContentControl() : nullptr;
+
+ auto pCC = pTCC ? pTCC->GetContentControl().GetContentControl() : nullptr;
+ const sal_Int32 nCurTabIndex = pCC && pCC->GetTabIndex() ? pCC->GetTabIndex() : SAL_MAX_UINT32;
+
+ SwPosition nCurPos(*GetCursor()->GetPoint());
+ if (pFieldMark)
+ nCurPos = pFieldMark->GetMarkStart();
+ else if (pTCC && pTCC->GetTextNode())
+ nCurPos = SwPosition(*pTCC->GetTextNode(), pTCC->GetStart());
+
+ // Find the previous (or next) tab control and navigate to it
+ const std::pair<SwPosition, sal_uInt32> nOldPos(nCurPos, nCurTabIndex);
+
+ // lower_bound acts like find, and returns a pointer to nFindPos if it exists,
+ // otherwise it will point to the previous entry.
+ auto aNewPos = aFormMap.lower_bound(nOldPos);
+ if (bNext && aNewPos != aFormMap.end())
+ ++aNewPos;
+ else if (!bNext && aNewPos != aFormMap.end() && aNewPos->first == nOldPos)
+ {
+ // Found the current position - need to return previous
+ if (aNewPos == aFormMap.begin())
+ aNewPos = aFormMap.end(); // prepare to loop around
+ else
+ --aNewPos;
+ }
+
+ if (aNewPos == aFormMap.end())
+ {
+ // Loop around to the other side
+ if (bNext)
+ aNewPos = aFormMap.begin();
+ else
+ --aNewPos;
+ }
+
+ // the entry contains a pointer to either a Content Control (first) or Fieldmark (second)
+ if (aNewPos->second.first)
+ {
+ auto& rFCC = static_cast<SwFormatContentControl&>(aNewPos->second.first->GetAttr());
+ GotoFormatContentControl(rFCC);
+ }
+ else
+ {
+ assert(aNewPos->second.second);
+ GotoFieldmark(aNewPos->second.second);
+ }
+}
+
+bool SwCursorShell::GotoFormatField( const SwFormatField& rField )
+{
+ SwTextField const*const pTextField(rField.GetTextField());
+ if (!pTextField
+ || (GetLayout()->IsHideRedlines()
+ && sw::IsFieldDeletedInModel(
+ GetDoc()->getIDocumentRedlineAccess(), *pTextField)))
+ return false;
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+
+ SwCursor* pCursor = getShellCursor( true );
+ SwCursorSaveState aSaveState( *pCursor );
+
+ SwTextNode* pTNd = pTextField->GetpTextNode();
+ pCursor->GetPoint()->Assign(*pTNd, pTextField->GetStart() );
+
+ bool bRet = !pCursor->IsSelOvr();
+ if( bRet )
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return bRet;
+}
+
+SwTextField * SwCursorShell::GetTextFieldAtPos(
+ const SwPosition* pPos,
+ ::sw::GetTextAttrMode const eMode)
+{
+ SwTextField* pTextField = nullptr;
+
+ SwTextNode * const pNode = pPos->GetNode().GetTextNode();
+ if ( pNode != nullptr )
+ {
+ pTextField = pNode->GetFieldTextAttrAt(pPos->GetContentIndex(), eMode);
+ }
+
+ return pTextField;
+}
+
+SwTextField* SwCursorShell::GetTextFieldAtCursor(
+ const SwPaM* pCursor,
+ ::sw::GetTextAttrMode const eMode)
+{
+ SwTextField* pTextField = GetTextFieldAtPos(pCursor->Start(), eMode);
+ if ( !pTextField
+ || pCursor->Start()->GetNode() != pCursor->End()->GetNode() )
+ return nullptr;
+
+ SwTextField* pFieldAtCursor = nullptr;
+ const sal_Int32 nTextFieldLength =
+ pTextField->End() != nullptr
+ ? *(pTextField->End()) - pTextField->GetStart()
+ : 1;
+ if ( ( pCursor->End()->GetContentIndex() - pCursor->Start()->GetContentIndex() ) <= nTextFieldLength )
+ {
+ pFieldAtCursor = pTextField;
+ }
+
+ return pFieldAtCursor;
+}
+
+SwField* SwCursorShell::GetFieldAtCursor(
+ const SwPaM *const pCursor,
+ const bool bIncludeInputFieldAtStart)
+{
+ SwTextField *const pField(GetTextFieldAtCursor(pCursor,
+ bIncludeInputFieldAtStart ? ::sw::GetTextAttrMode::Default : ::sw::GetTextAttrMode::Expand));
+ return pField
+ ? const_cast<SwField*>(pField->GetFormatField().GetField())
+ : nullptr;
+}
+
+SwField* SwCursorShell::GetCurField( const bool bIncludeInputFieldAtStart ) const
+{
+ SwPaM* pCursor = GetCursor();
+ if ( pCursor->IsMultiSelection() )
+ {
+ // multi selection not handled.
+ return nullptr;
+ }
+
+ SwField* pCurField = GetFieldAtCursor( pCursor, bIncludeInputFieldAtStart );
+ if ( pCurField != nullptr
+ && SwFieldIds::Table == pCurField->GetTyp()->Which() )
+ {
+ // table formula? convert internal name into external
+ const SwTableNode* pTableNd = IsCursorInTable();
+ static_cast<SwTableField*>(pCurField)->PtrToBoxNm( pTableNd ? &pTableNd->GetTable() : nullptr );
+ }
+
+ return pCurField;
+}
+
+bool SwCursorShell::CursorInsideInputField() const
+{
+ for(SwPaM& rCursor : GetCursor()->GetRingContainer())
+ {
+ if (dynamic_cast<const SwTextInputField*>(GetTextFieldAtCursor(&rCursor, ::sw::GetTextAttrMode::Parent)))
+ return true;
+ }
+ return false;
+}
+
+SwTextContentControl* SwCursorShell::CursorInsideContentControl() const
+{
+ for (SwPaM& rCursor : GetCursor()->GetRingContainer())
+ {
+ const SwPosition* pStart = rCursor.Start();
+ SwTextNode* pTextNode = pStart->GetNode().GetTextNode();
+ if (!pTextNode)
+ {
+ continue;
+ }
+
+ sal_Int32 nIndex = pStart->GetContentIndex();
+ if (SwTextAttr* pAttr = pTextNode->GetTextAttrAt(nIndex, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent))
+ {
+ return static_txtattr_cast<SwTextContentControl*>(pAttr);
+ }
+ }
+
+ return nullptr;
+}
+
+bool SwCursorShell::PosInsideInputField( const SwPosition& rPos )
+{
+ return dynamic_cast<const SwTextInputField*>(GetTextFieldAtPos(&rPos, ::sw::GetTextAttrMode::Parent)) != nullptr;
+}
+
+bool SwCursorShell::DocPtInsideInputField( const Point& rDocPt ) const
+{
+ SwPosition aPos( *(GetCursor()->Start()) );
+ Point aDocPt( rDocPt );
+ if ( GetLayout()->GetModelPositionForViewPoint( &aPos, aDocPt ) )
+ {
+ return PosInsideInputField( aPos );
+ }
+ return false;
+}
+
+sal_Int32 SwCursorShell::StartOfInputFieldAtPos( const SwPosition& rPos )
+{
+ const SwTextInputField* pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextFieldAtPos(&rPos, ::sw::GetTextAttrMode::Default));
+ assert(pTextInputField != nullptr
+ && "<SwEditShell::StartOfInputFieldAtPos(..)> - no Input Field at given position");
+ return pTextInputField->GetStart();
+}
+
+sal_Int32 SwCursorShell::EndOfInputFieldAtPos( const SwPosition& rPos )
+{
+ const SwTextInputField* pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextFieldAtPos(&rPos, ::sw::GetTextAttrMode::Default));
+ assert(pTextInputField != nullptr
+ && "<SwEditShell::EndOfInputFieldAtPos(..)> - no Input Field at given position");
+ return *(pTextInputField->End());
+}
+
+void SwCursorShell::GotoOutline( SwOutlineNodes::size_type nIdx )
+{
+ SwCursor* pCursor = getShellCursor( true );
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *pCursor );
+
+ const SwNodes& rNds = GetDoc()->GetNodes();
+ SwTextNode* pTextNd = rNds.GetOutLineNds()[ nIdx ]->GetTextNode();
+ pCursor->GetPoint()->Assign(*pTextNd);
+
+ if( !pCursor->IsSelOvr() )
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+}
+
+bool SwCursorShell::GotoOutline( const OUString& rName )
+{
+ SwCursor* pCursor = getShellCursor( true );
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *pCursor );
+
+ bool bRet = false;
+ if (mxDoc->GotoOutline(*pCursor->GetPoint(), rName, GetLayout())
+ && !pCursor->IsSelOvr())
+ {
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ bRet = true;
+ }
+ return bRet;
+}
+
+/// jump to next node with outline num.
+bool SwCursorShell::GotoNextOutline()
+{
+ const SwNodes& rNds = GetDoc()->GetNodes();
+
+ if ( rNds.GetOutLineNds().empty() )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+
+ SwCursor* pCursor = getShellCursor( true );
+ SwNode* pNd = &(pCursor->GetPointNode());
+ SwOutlineNodes::size_type nPos;
+ bool bUseFirst = !rNds.GetOutLineNds().Seek_Entry( pNd, &nPos );
+ SwOutlineNodes::size_type const nStartPos(nPos);
+
+ do
+ {
+ if (!bUseFirst)
+ {
+ ++nPos;
+ }
+ if (rNds.GetOutLineNds().size() <= nPos)
+ {
+ nPos = 0;
+ }
+
+ if (bUseFirst)
+ {
+ bUseFirst = false;
+ }
+ else
+ {
+ if (nPos == nStartPos)
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+ }
+
+ pNd = rNds.GetOutLineNds()[ nPos ];
+ }
+ while (!sw::IsParaPropsNode(*GetLayout(), *pNd->GetTextNode()));
+
+ if (nPos < nStartPos)
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped );
+ }
+ else
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
+ }
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *pCursor );
+ pCursor->GetPoint()->Assign(*pNd);
+
+ bool bRet = !pCursor->IsSelOvr();
+ if( bRet )
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return bRet;
+}
+
+/// jump to previous node with outline num.
+bool SwCursorShell::GotoPrevOutline()
+{
+ const SwNodes& rNds = GetDoc()->GetNodes();
+
+ if ( rNds.GetOutLineNds().empty() )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+
+ SwCursor* pCursor = getShellCursor( true );
+ SwNode* pNd = &(pCursor->GetPointNode());
+ SwOutlineNodes::size_type nPos;
+ (void)rNds.GetOutLineNds().Seek_Entry(pNd, &nPos);
+ SwOutlineNodes::size_type const nStartPos(nPos);
+
+ do
+ {
+ if (nPos == 0)
+ {
+ nPos = rNds.GetOutLineNds().size() - 1;
+ }
+ else
+ {
+ --nPos; // before
+ }
+ if (nPos == nStartPos)
+ {
+ pNd = nullptr;
+ break;
+ }
+
+ pNd = rNds.GetOutLineNds()[ nPos ];
+ }
+ while (!sw::IsParaPropsNode(*GetLayout(), *pNd->GetTextNode()));
+
+ if (!pNd)
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+
+ if (nStartPos < nPos)
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped );
+ }
+ else
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
+ }
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *pCursor );
+ pCursor->GetPoint()->Assign(*pNd);
+
+ bool bRet = !pCursor->IsSelOvr();
+ if( bRet )
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return bRet;
+}
+
+/// search "outline position" before previous outline node at given level
+SwOutlineNodes::size_type SwCursorShell::GetOutlinePos(sal_uInt8 nLevel, SwPaM* pPaM)
+{
+ SwPaM* pCursor = pPaM ? pPaM : getShellCursor(true);
+ const SwNodes& rNds = GetDoc()->GetNodes();
+
+ SwNode* pNd = &(pCursor->GetPointNode());
+ SwOutlineNodes::size_type nPos;
+ if( rNds.GetOutLineNds().Seek_Entry( pNd, &nPos ))
+ nPos++; // is at correct position; take next for while
+
+ while( nPos-- ) // check the one in front of the current
+ {
+ pNd = rNds.GetOutLineNds()[ nPos ];
+
+ if (sw::IsParaPropsNode(*GetLayout(), *pNd->GetTextNode())
+ && pNd->GetTextNode()->GetAttrOutlineLevel()-1 <= nLevel)
+ {
+ if (pNd->GetIndex() < rNds.GetEndOfExtras().GetIndex()
+ && pCursor->GetPointNode().GetIndex() > rNds.GetEndOfExtras().GetIndex())
+ {
+ // node found in extras but cursor position is not in extras
+ return SwOutlineNodes::npos;
+ }
+ return nPos;
+ }
+ }
+ return SwOutlineNodes::npos; // no more left
+}
+
+void SwCursorShell::MakeOutlineSel(SwOutlineNodes::size_type nSttPos, SwOutlineNodes::size_type nEndPos,
+ bool bWithChildren , bool bKillPams)
+{
+ const SwNodes& rNds = GetDoc()->GetNodes();
+ const SwOutlineNodes& rOutlNds = rNds.GetOutLineNds();
+ if( rOutlNds.empty() )
+ return;
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+
+ if( nSttPos > nEndPos ) // parameters switched?
+ {
+ OSL_ENSURE( false, "Start > End for array access" );
+ std::swap(nSttPos, nEndPos);
+ }
+
+ SwNode* pSttNd = rOutlNds[ nSttPos ];
+ SwNode* pEndNd = rOutlNds[ nEndPos ];
+
+ if( bWithChildren )
+ {
+ const int nLevel = pEndNd->GetTextNode()->GetAttrOutlineLevel()-1;
+ for( ++nEndPos; nEndPos < rOutlNds.size(); ++nEndPos )
+ {
+ pEndNd = rOutlNds[ nEndPos ];
+ const int nNxtLevel = pEndNd->GetTextNode()->GetAttrOutlineLevel()-1;
+ if( nNxtLevel <= nLevel )
+ break; // EndPos is now on the next one
+ }
+ }
+ // if without children then set onto next one
+ else if( ++nEndPos < rOutlNds.size() )
+ pEndNd = rOutlNds[ nEndPos ];
+
+ if( nEndPos == rOutlNds.size() ) // no end found
+ pEndNd = &rNds.GetEndOfContent();
+
+ if( bKillPams )
+ KillPams();
+
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ // set end to the end of the previous content node
+ m_pCurrentCursor->GetPoint()->Assign(*pSttNd);
+ m_pCurrentCursor->SetMark();
+ m_pCurrentCursor->GetPoint()->Assign(*pEndNd);
+ m_pCurrentCursor->Move( fnMoveBackward, GoInNode ); // end of predecessor
+
+ // and everything is already selected
+ bool bRet = !m_pCurrentCursor->IsSelOvr();
+ if( bRet )
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+}
+
+/// jump to reference marker
+bool SwCursorShell::GotoRefMark( const OUString& rRefMark, sal_uInt16 nSubType,
+ sal_uInt16 nSeqNo, sal_uInt16 nFlags )
+{
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ sal_Int32 nPos = -1;
+
+ SwPaM* pCursor = GetCursor();
+ SwPosition* pPos = pCursor->GetPoint();
+ SwTextNode* pRefTextNd = pPos->GetNode().GetTextNode();
+ SwContentFrame* pRefFrame = GetCurrFrame();
+
+ SwTextNode* pTextNd = SwGetRefFieldType::FindAnchor(GetDoc(), rRefMark,
+ nSubType, nSeqNo, nFlags, &nPos, nullptr, GetLayout(), pRefTextNd, pRefFrame);
+ if( !pTextNd || !pTextNd->GetNodes().IsDocNodes() )
+ return false;
+
+ m_pCurrentCursor->GetPoint()->Assign(*pTextNd, nPos );
+
+ if( m_pCurrentCursor->IsSelOvr() )
+ return false;
+
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return true;
+}
+
+bool SwCursorShell::IsPageAtPos( const Point &rPt ) const
+{
+ if( GetLayout() )
+ return nullptr != GetLayout()->GetPageAtPos( rPt );
+ return false;
+}
+
+bool SwCursorShell::GetContentAtPos( const Point& rPt,
+ SwContentAtPos& rContentAtPos,
+ bool bSetCursor,
+ SwRect* pFieldRect )
+{
+ CurrShell aCurr( this );
+ bool bRet = false;
+
+ if( IsTableMode() )
+ {
+ rContentAtPos.eContentAtPos = IsAttrAtPos::NONE;
+ rContentAtPos.aFnd.pField = nullptr;
+ return false;
+ }
+
+ Point aPt( rPt );
+ SwPosition aPos( *m_pCurrentCursor->GetPoint() );
+
+ SwTextNode* pTextNd;
+ SwCursorMoveState aTmpState;
+ aTmpState.m_bFieldInfo = true;
+ aTmpState.m_bExactOnly = !( IsAttrAtPos::Outline & rContentAtPos.eContentAtPos );
+ aTmpState.m_bContentCheck = bool(IsAttrAtPos::ContentCheck & rContentAtPos.eContentAtPos);
+ aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable();
+
+ SwSpecialPos aSpecialPos;
+ aTmpState.m_pSpecialPos = ( IsAttrAtPos::SmartTag & rContentAtPos.eContentAtPos ) ?
+ &aSpecialPos : nullptr;
+
+ const bool bCursorFoundExact = GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState );
+ pTextNd = aPos.GetNode().GetTextNode();
+
+ const SwNodes& rNds = GetDoc()->GetNodes();
+ if( pTextNd
+ && IsAttrAtPos::Outline & rContentAtPos.eContentAtPos
+ && !rNds.GetOutLineNds().empty() )
+ {
+ // only for nodes in outline nodes
+ SwOutlineNodes::size_type nPos = 0;
+ bool bFoundOutline = rNds.GetOutLineNds().Seek_Entry(pTextNd, &nPos);
+ if (!bFoundOutline && nPos && (IsAttrAtPos::AllowContaining & rContentAtPos.eContentAtPos))
+ {
+ // nPos points to the first found outline node not before pTextNd, or to end();
+ // when bFoundOutline is false, and nPos is not 0, it means that there were
+ // outline nodes before pTextNd, and nPos-1 points to the last of those.
+ pTextNd = rNds.GetOutLineNds()[nPos - 1]->GetTextNode();
+ bFoundOutline = true;
+ }
+ if (bFoundOutline)
+ {
+ rContentAtPos.eContentAtPos = IsAttrAtPos::Outline;
+ rContentAtPos.sStr = sw::GetExpandTextMerged(GetLayout(), *pTextNd, true, false, ExpandMode::ExpandFootnote);
+ rContentAtPos.aFnd.pNode = pTextNd;
+ bRet = true;
+ }
+ }
+ else if ( IsAttrAtPos::ContentCheck & rContentAtPos.eContentAtPos
+ && bCursorFoundExact )
+ {
+ bRet = true;
+ }
+ else if( pTextNd
+ && IsAttrAtPos::NumLabel & rContentAtPos.eContentAtPos)
+ {
+ bRet = aTmpState.m_bInNumPortion;
+ rContentAtPos.aFnd.pNode = sw::GetParaPropsNode(*GetLayout(), aPos.GetNode());
+
+ Size aSizeLogic(aTmpState.m_nInNumPortionOffset, 0);
+ Size aSizePixel = GetWin()->LogicToPixel(aSizeLogic);
+ rContentAtPos.nDist = aSizePixel.Width();
+ }
+ else if( bCursorFoundExact && pTextNd )
+ {
+ SwContentFrame *pFrame(nullptr);
+ if( !aTmpState.m_bPosCorr )
+ {
+ SwTextAttr* pTextAttr;
+ if ( IsAttrAtPos::SmartTag & rContentAtPos.eContentAtPos
+ && !aTmpState.m_bFootnoteNoInfo )
+ {
+ const SwWrongList* pSmartTagList = pTextNd->GetSmartTags();
+ sal_Int32 nCurrent = aPos.GetContentIndex();
+ const sal_Int32 nBegin = nCurrent;
+ sal_Int32 nLen = 1;
+
+ if (pSmartTagList && pSmartTagList->InWrongWord(nCurrent, nLen) && !pTextNd->IsSymbolAt(nBegin))
+ {
+ const sal_uInt16 nIndex = pSmartTagList->GetWrongPos( nBegin );
+ const SwWrongList* pSubList = pSmartTagList->SubList( nIndex );
+ if ( pSubList )
+ {
+ nCurrent = aTmpState.m_pSpecialPos->nCharOfst;
+
+ if ( pSubList->InWrongWord( nCurrent, nLen ) )
+ bRet = true;
+ }
+ else
+ bRet = true;
+
+ if( bRet && bSetCursor )
+ {
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ m_pCurrentCursor->DeleteMark();
+ *m_pCurrentCursor->GetPoint() = aPos;
+ if( m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | SwCursorSelOverFlags::Toggle) )
+ bRet = false;
+ else
+ UpdateCursor();
+ }
+ if( bRet )
+ {
+ rContentAtPos.eContentAtPos = IsAttrAtPos::SmartTag;
+
+ std::pair<Point, bool> tmp(aPt, true);
+ if (pFieldRect)
+ {
+ pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ if (pFrame)
+ pFrame->GetCharRect( *pFieldRect, aPos, &aTmpState );
+ }
+ }
+ }
+ }
+
+ if ( !bRet
+ && ( IsAttrAtPos::Field | IsAttrAtPos::ClickField ) & rContentAtPos.eContentAtPos
+ && !aTmpState.m_bFootnoteNoInfo )
+ {
+ pTextAttr = pTextNd->GetFieldTextAttrAt( aPos.GetContentIndex() );
+ const SwField* pField = pTextAttr != nullptr
+ ? pTextAttr->GetFormatField().GetField()
+ : nullptr;
+ if ( IsAttrAtPos::ClickField & rContentAtPos.eContentAtPos
+ && pField && !pField->HasClickHdl() )
+ {
+ pField = nullptr;
+ }
+
+ if ( pField )
+ {
+ if (pFieldRect)
+ {
+ std::pair<Point, bool> tmp(aPt, true);
+ pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ if (pFrame)
+ {
+ //tdf#116397 now that we looking for the bounds of the field drop the SmartTag
+ //index within field setting so we don't the bounds of the char within the field
+ SwSpecialPos* pSpecialPos = aTmpState.m_pSpecialPos;
+ aTmpState.m_pSpecialPos = nullptr;
+ pFrame->GetCharRect( *pFieldRect, aPos, &aTmpState );
+ aTmpState.m_pSpecialPos = pSpecialPos;
+ }
+ }
+
+ if( bSetCursor )
+ {
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ m_pCurrentCursor->DeleteMark();
+ *m_pCurrentCursor->GetPoint() = aPos;
+ if( m_pCurrentCursor->IsSelOvr() )
+ {
+ // allow click fields in protected sections
+ // only placeholder is not possible
+ if( IsAttrAtPos::Field & rContentAtPos.eContentAtPos
+ || SwFieldIds::JumpEdit == pField->Which() )
+ pField = nullptr;
+ }
+ else
+ UpdateCursor();
+ }
+ else if( SwFieldIds::Table == pField->Which() &&
+ static_cast<const SwTableField*>(pField)->IsIntrnlName() )
+ {
+ // create from internal (for CORE) the external
+ // (for UI) formula
+ const SwTableNode* pTableNd = pTextNd->FindTableNode();
+ if( pTableNd ) // is in a table
+ const_cast<SwTableField*>(static_cast<const SwTableField*>(pField))->PtrToBoxNm( &pTableNd->GetTable() );
+ }
+ }
+
+ if( pField )
+ {
+ rContentAtPos.aFnd.pField = pField;
+ rContentAtPos.pFndTextAttr = pTextAttr;
+ rContentAtPos.eContentAtPos = IsAttrAtPos::Field;
+ bRet = true;
+ }
+ }
+
+ if( !bRet && IsAttrAtPos::FormControl & rContentAtPos.eContentAtPos )
+ {
+ IDocumentMarkAccess* pMarksAccess = GetDoc()->getIDocumentMarkAccess( );
+ sw::mark::IFieldmark* pFieldBookmark = pMarksAccess->getInnerFieldmarkFor(aPos);
+ if (bCursorFoundExact && pFieldBookmark)
+ {
+ rContentAtPos.eContentAtPos = IsAttrAtPos::FormControl;
+ rContentAtPos.aFnd.pFieldmark = pFieldBookmark;
+ bRet=true;
+ }
+ }
+
+ if (!bRet && rContentAtPos.eContentAtPos & IsAttrAtPos::ContentControl)
+ {
+ SwTextAttr* pAttr = pTextNd->GetTextAttrAt(
+ aPos.GetContentIndex(), RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent);
+ if (pAttr)
+ {
+ rContentAtPos.eContentAtPos = IsAttrAtPos::ContentControl;
+ rContentAtPos.pFndTextAttr = pAttr;
+ bRet = true;
+ }
+ }
+
+ if( !bRet && IsAttrAtPos::Ftn & rContentAtPos.eContentAtPos )
+ {
+ if( aTmpState.m_bFootnoteNoInfo )
+ {
+ // over the footnote's char
+ bRet = true;
+ if( bSetCursor )
+ {
+ *m_pCurrentCursor->GetPoint() = aPos;
+ if( !GotoFootnoteAnchor() )
+ bRet = false;
+ }
+ if( bRet )
+ rContentAtPos.eContentAtPos = IsAttrAtPos::Ftn;
+ }
+ else if ( nullptr != ( pTextAttr = pTextNd->GetTextAttrForCharAt(
+ aPos.GetContentIndex(), RES_TXTATR_FTN )) )
+ {
+ bRet = true;
+ if( bSetCursor )
+ {
+ if (SwWrtShell* pWrtSh = dynamic_cast<SwWrtShell*>(this))
+ pWrtSh->addCurrentPosition();
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ m_pCurrentCursor->GetPoint()->Assign( *static_cast<SwTextFootnote*>(pTextAttr)->GetStartNode() );
+ SwContentNode* pCNd = GetDoc()->GetNodes().GoNextSection(
+ m_pCurrentCursor->GetPoint(),
+ true, !IsReadOnlyAvailable() );
+
+ if( pCNd )
+ {
+ if( m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection |
+ SwCursorSelOverFlags::Toggle ))
+ bRet = false;
+ else
+ UpdateCursor();
+ }
+ else
+ bRet = false;
+ }
+
+ if( bRet )
+ {
+ rContentAtPos.eContentAtPos = IsAttrAtPos::Ftn;
+ rContentAtPos.pFndTextAttr = pTextAttr;
+ rContentAtPos.aFnd.pAttr = &pTextAttr->GetAttr();
+
+ if (pFieldRect)
+ {
+ std::pair<Point, bool> tmp(aPt, true);
+ pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ if (pFrame)
+ pFrame->GetCharRect( *pFieldRect, aPos, &aTmpState );
+ }
+ }
+ }
+ }
+
+ if( !bRet
+ && ( IsAttrAtPos::ToxMark | IsAttrAtPos::RefMark ) & rContentAtPos.eContentAtPos
+ && !aTmpState.m_bFootnoteNoInfo )
+ {
+ pTextAttr = nullptr;
+ if( IsAttrAtPos::ToxMark & rContentAtPos.eContentAtPos )
+ {
+ std::vector<SwTextAttr *> const marks(
+ pTextNd->GetTextAttrsAt(
+ aPos.GetContentIndex(), RES_TXTATR_TOXMARK));
+ if (!marks.empty())
+ { // hmm... can only return 1 here
+ pTextAttr = *marks.begin();
+ }
+ }
+
+ if( !pTextAttr &&
+ IsAttrAtPos::RefMark & rContentAtPos.eContentAtPos )
+ {
+ std::vector<SwTextAttr *> const marks(
+ pTextNd->GetTextAttrsAt(
+ aPos.GetContentIndex(), RES_TXTATR_REFMARK));
+ if (!marks.empty())
+ { // hmm... can only return 1 here
+ pTextAttr = *marks.begin();
+ }
+ }
+
+ if( pTextAttr )
+ {
+ bRet = true;
+ if( bSetCursor )
+ {
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ m_pCurrentCursor->DeleteMark();
+ *m_pCurrentCursor->GetPoint() = aPos;
+ if( m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | SwCursorSelOverFlags::Toggle ) )
+ bRet = false;
+ else
+ UpdateCursor();
+ }
+
+ if( bRet )
+ {
+ const sal_Int32* pEnd = pTextAttr->GetEnd();
+ if( pEnd )
+ rContentAtPos.sStr =
+ pTextNd->GetExpandText(GetLayout(), pTextAttr->GetStart(), *pEnd - pTextAttr->GetStart());
+ else if( RES_TXTATR_TOXMARK == pTextAttr->Which())
+ rContentAtPos.sStr =
+ pTextAttr->GetTOXMark().GetAlternativeText();
+
+ rContentAtPos.eContentAtPos =
+ RES_TXTATR_TOXMARK == pTextAttr->Which()
+ ? IsAttrAtPos::ToxMark
+ : IsAttrAtPos::RefMark;
+ rContentAtPos.pFndTextAttr = pTextAttr;
+ rContentAtPos.aFnd.pAttr = &pTextAttr->GetAttr();
+
+ std::pair<Point, bool> tmp(aPt, true);
+ if (pFieldRect)
+ {
+ pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ if (pFrame)
+ pFrame->GetCharRect( *pFieldRect, aPos, &aTmpState );
+ }
+ }
+ }
+ }
+
+ if ( !bRet
+ && IsAttrAtPos::InetAttr & rContentAtPos.eContentAtPos
+ && !aTmpState.m_bFootnoteNoInfo )
+ {
+ sal_Int32 index = aPos.GetContentIndex();
+ pTextAttr = pTextNd->GetTextAttrAt(index, RES_TXTATR_INETFMT);
+
+ // "detect" only INetAttrs with URLs
+ if( pTextAttr && !pTextAttr->GetINetFormat().GetValue().isEmpty() )
+ {
+ bRet = true;
+ if( bSetCursor )
+ {
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ m_pCurrentCursor->DeleteMark();
+ *m_pCurrentCursor->GetPoint() = aPos;
+ if( m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection |
+ SwCursorSelOverFlags::Toggle) )
+ bRet = false;
+ else
+ UpdateCursor();
+ }
+ if( bRet )
+ {
+ const sal_Int32 nSt = pTextAttr->GetStart();
+ const sal_Int32 nEnd = *pTextAttr->End();
+
+ rContentAtPos.sStr = pTextNd->GetExpandText(GetLayout(), nSt, nEnd-nSt);
+
+ rContentAtPos.aFnd.pAttr = &pTextAttr->GetAttr();
+ rContentAtPos.eContentAtPos = IsAttrAtPos::InetAttr;
+ rContentAtPos.pFndTextAttr = pTextAttr;
+
+ if (pFieldRect)
+ {
+ std::pair<Point, bool> tmp(aPt, true);
+ pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ if (pFrame)
+ {
+ //get bounding box of range
+ SwRect aStart;
+ SwPosition aStartPos(*pTextNd, nSt);
+ pFrame->GetCharRect(aStart, aStartPos, &aTmpState);
+ SwRect aEnd;
+ SwPosition aEndPos(*pTextNd, nEnd);
+ pFrame->GetCharRect(aEnd, aEndPos, &aTmpState);
+ if (aStart.Top() != aEnd.Top() || aStart.Bottom() != aEnd.Bottom())
+ {
+ aStart.Left(pFrame->getFrameArea().Left());
+ aEnd.Right(pFrame->getFrameArea().Right());
+ }
+ *pFieldRect = aStart.Union(aEnd);
+ }
+ }
+ }
+ }
+ }
+
+ if( !bRet && IsAttrAtPos::Redline & rContentAtPos.eContentAtPos )
+ {
+ const SwRangeRedline* pRedl = GetDoc()->getIDocumentRedlineAccess().GetRedline(aPos, nullptr);
+
+ if( pRedl )
+ {
+ rContentAtPos.aFnd.pRedl = pRedl;
+ rContentAtPos.eContentAtPos = IsAttrAtPos::Redline;
+ rContentAtPos.pFndTextAttr = nullptr;
+ bRet = true;
+
+ if (pFieldRect)
+ {
+ std::pair<Point, bool> tmp(aPt, true);
+ pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ if( pFrame )
+ {
+ // not sure if this should be limited to one
+ // paragraph, or mark the entire redline; let's
+ // leave it limited to one for now...
+ sal_Int32 nStart;
+ sal_Int32 nEnd;
+ pRedl->CalcStartEnd(pTextNd->GetIndex(), nStart, nEnd);
+ if (nStart == COMPLETE_STRING)
+ {
+ // consistency: found pRedl, so there must be
+ // something in pTextNd
+ assert(nEnd != COMPLETE_STRING);
+ nStart = 0;
+ }
+ if (nEnd == COMPLETE_STRING)
+ {
+ nEnd = pTextNd->Len();
+ }
+ //get bounding box of range
+ SwRect aStart;
+ pFrame->GetCharRect(aStart, SwPosition(*pTextNd, nStart), &aTmpState);
+ SwRect aEnd;
+ pFrame->GetCharRect(aEnd, SwPosition(*pTextNd, nEnd), &aTmpState);
+ if (aStart.Top() != aEnd.Top() || aStart.Bottom() != aEnd.Bottom())
+ {
+ aStart.Left(pFrame->getFrameArea().Left());
+ aEnd.Right(pFrame->getFrameArea().Right());
+ }
+ *pFieldRect = aStart.Union(aEnd);
+ }
+ }
+ }
+ }
+ }
+
+ if( !bRet && ( ( IsAttrAtPos::TableRedline & rContentAtPos.eContentAtPos ) ||
+ ( IsAttrAtPos::TableColRedline & rContentAtPos.eContentAtPos ) ) )
+ {
+ const SwTableNode* pTableNd;
+ const SwTableBox* pBox;
+ const SwTableLine* pTableLine;
+ const SwStartNode* pSttNd = pTextNd->FindTableBoxStartNode();
+ if( pSttNd && nullptr != ( pTableNd = pTextNd->FindTableNode()) &&
+ nullptr != ( pBox = pTableNd->GetTable().GetTableBox(
+ pSttNd->GetIndex() )) &&
+ nullptr != ( pTableLine = pBox->GetUpper() ) &&
+ ( RedlineType::None != pBox->GetRedlineType() ||
+ RedlineType::None != pTableLine->GetRedlineType() ) )
+ {
+ const SwRedlineTable& aRedlineTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable();
+ if ( RedlineType::None != pTableLine->GetRedlineType() )
+ {
+ SwRedlineTable::size_type nPos = 0;
+ nPos = pTableLine->UpdateTextChangesOnly(nPos);
+ if ( nPos != SwRedlineTable::npos )
+ {
+ rContentAtPos.aFnd.pRedl = aRedlineTable[nPos];
+ rContentAtPos.eContentAtPos = IsAttrAtPos::TableRedline;
+ bRet = true;
+ }
+ }
+ else
+ {
+ SwRedlineTable::size_type n = 0;
+ SwNodeIndex aIdx( *pSttNd, 1 );
+ const SwPosition aBoxStart(aIdx);
+ const SwRangeRedline* pFnd = aRedlineTable.FindAtPosition( aBoxStart, n, /*next=*/true );
+ if( pFnd && RedlineType::Delete == pFnd->GetType() )
+ {
+ rContentAtPos.aFnd.pRedl = aRedlineTable[n];
+ rContentAtPos.eContentAtPos = IsAttrAtPos::TableColRedline;
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ if( !bRet
+ && ( IsAttrAtPos::TableBoxFml & rContentAtPos.eContentAtPos
+#ifdef DBG_UTIL
+ || IsAttrAtPos::TableBoxValue & rContentAtPos.eContentAtPos
+#endif
+ ) )
+ {
+ const SwTableNode* pTableNd;
+ const SwTableBox* pBox;
+ const SwStartNode* pSttNd = pTextNd->FindTableBoxStartNode();
+ const SwTableBoxFormula* pItem;
+#ifdef DBG_UTIL
+ const SwTableBoxValue* pItem2 = nullptr;
+#endif
+ if( pSttNd && nullptr != ( pTableNd = pTextNd->FindTableNode()) &&
+ nullptr != ( pBox = pTableNd->GetTable().GetTableBox(
+ pSttNd->GetIndex() )) &&
+#ifdef DBG_UTIL
+ ( (pItem = pBox->GetFrameFormat()->GetItemIfSet( RES_BOXATR_FORMULA, false )) ||
+ (pItem2 = pBox->GetFrameFormat()->GetItemIfSet( RES_BOXATR_VALUE, false )) )
+#else
+ (pItem = pBox->GetFrameFormat()->GetItemIfSet( RES_BOXATR_FORMULA, false ))
+#endif
+ )
+ {
+ std::pair<Point, bool> tmp(aPt, true);
+ SwFrame* pF = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ if( pF )
+ {
+ // then the CellFrame
+ pFrame = static_cast<SwContentFrame*>(pF);
+ while( pF && !pF->IsCellFrame() )
+ pF = pF->GetUpper();
+ }
+
+ if( aTmpState.m_bPosCorr )
+ {
+ if( pF && !pF->getFrameArea().Contains( aPt ))
+ pF = nullptr;
+ }
+ else if( !pF )
+ pF = pFrame;
+
+ if( pF ) // only then it is valid
+ {
+ // create from internal (for CORE) the external
+ // (for UI) formula
+ rContentAtPos.eContentAtPos = IsAttrAtPos::TableBoxFml;
+#ifdef DBG_UTIL
+ if( pItem2 )
+ rContentAtPos.eContentAtPos = IsAttrAtPos::TableBoxValue;
+ else
+#endif
+ const_cast<SwTableBoxFormula&>(*pItem).PtrToBoxNm( &pTableNd->GetTable() );
+
+ bRet = true;
+ if( bSetCursor )
+ {
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+ *m_pCurrentCursor->GetPoint() = aPos;
+ if( m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::CheckNodeSection |
+ SwCursorSelOverFlags::Toggle) )
+ bRet = false;
+ else
+ UpdateCursor();
+ }
+
+ if( bRet )
+ {
+ if( pFieldRect )
+ {
+ *pFieldRect = pF->getFramePrintArea();
+ *pFieldRect += pF->getFrameArea().Pos();
+ }
+ rContentAtPos.pFndTextAttr = nullptr;
+ rContentAtPos.aFnd.pAttr = pItem;
+ }
+ }
+ }
+ }
+
+#ifdef DBG_UTIL
+ if( !bRet && IsAttrAtPos::CurrAttrs & rContentAtPos.eContentAtPos )
+ {
+ const sal_Int32 n = aPos.GetContentIndex();
+ SfxItemSetFixed<POOLATTR_BEGIN, POOLATTR_END - 1> aSet( GetDoc()->GetAttrPool() );
+ if( pTextNd->GetpSwpHints() )
+ {
+ for( size_t i = 0; i < pTextNd->GetSwpHints().Count(); ++i )
+ {
+ const SwTextAttr* pHt = pTextNd->GetSwpHints().Get(i);
+ const sal_Int32 nAttrStart = pHt->GetStart();
+ if( nAttrStart > n ) // over the section
+ break;
+
+ if( nullptr != pHt->End() && (
+ ( nAttrStart < n &&
+ ( pHt->DontExpand() ? n < *pHt->End()
+ : n <= *pHt->End() )) ||
+ ( n == nAttrStart &&
+ ( nAttrStart == *pHt->End() || !n ))) )
+ {
+ aSet.Put( pHt->GetAttr() );
+ }
+ }
+ if( pTextNd->HasSwAttrSet() &&
+ pTextNd->GetpSwAttrSet()->Count() )
+ {
+ SfxItemSet aFormatSet( pTextNd->GetSwAttrSet() );
+ // remove all from format set that are also in TextSet
+ aFormatSet.Differentiate( aSet );
+ // now merge all together
+ aSet.Put( aFormatSet );
+ }
+ }
+ else
+ pTextNd->SwContentNode::GetAttr( aSet );
+
+ rContentAtPos.sStr = "Pos: (";
+ rContentAtPos.sStr += OUString::number( sal_Int32(aPos.GetNodeIndex()));
+ rContentAtPos.sStr += ":";
+ rContentAtPos.sStr += OUString::number( aPos.GetContentIndex());
+ rContentAtPos.sStr += ")";
+ rContentAtPos.sStr += "\nParagraph Style: ";
+ rContentAtPos.sStr += pTextNd->GetFormatColl()->GetName();
+ if( pTextNd->GetCondFormatColl() )
+ {
+ rContentAtPos.sStr += "\nConditional Style: " + pTextNd->GetCondFormatColl()->GetName();
+ }
+
+ if( aSet.Count() )
+ {
+ OUStringBuffer sAttrs;
+ SfxItemIter aIter( aSet );
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+ const IntlWrapper aInt(SvtSysLocale().GetUILanguageTag());
+ do
+ {
+ if( !IsInvalidItem( pItem ))
+ {
+ OUString aStr;
+ GetDoc()->GetAttrPool().GetPresentation(*pItem,
+ MapUnit::MapCM, aStr, aInt);
+ if (!sAttrs.isEmpty())
+ sAttrs.append(", ");
+ sAttrs.append(aStr);
+ }
+ pItem = aIter.NextItem();
+ } while (pItem);
+ if (!sAttrs.isEmpty())
+ {
+ if( !rContentAtPos.sStr.isEmpty() )
+ rContentAtPos.sStr += "\n";
+ rContentAtPos.sStr += "Attr: " + sAttrs;
+ }
+ }
+ bRet = true;
+ rContentAtPos.eContentAtPos = IsAttrAtPos::CurrAttrs;
+ }
+#endif
+ }
+
+ if( !bRet )
+ {
+ rContentAtPos.eContentAtPos = IsAttrAtPos::NONE;
+ rContentAtPos.aFnd.pField = nullptr;
+ }
+ return bRet;
+}
+
+// #i90516#
+const SwPostItField* SwCursorShell::GetPostItFieldAtCursor() const
+{
+ if ( IsTableMode() )
+ return nullptr;
+
+ const SwPosition* pCursorPos = GetCursor_()->GetPoint();
+ const SwTextNode* pTextNd = pCursorPos->GetNode().GetTextNode();
+ if ( !pTextNd )
+ return nullptr;
+
+ const SwPostItField* pPostItField = nullptr;
+ SwTextAttr* pTextAttr = pTextNd->GetFieldTextAttrAt( pCursorPos->GetContentIndex() );
+ const SwField* pField = pTextAttr != nullptr ? pTextAttr->GetFormatField().GetField() : nullptr;
+ if ( pField && pField->Which()== SwFieldIds::Postit )
+ {
+ pPostItField = static_cast<const SwPostItField*>(pField);
+ }
+
+ return pPostItField;
+}
+
+/// is the node in a protected section?
+bool SwContentAtPos::IsInProtectSect() const
+{
+ const SwTextNode* pNd = nullptr;
+ if( pFndTextAttr )
+ {
+ switch( eContentAtPos )
+ {
+ case IsAttrAtPos::Field:
+ case IsAttrAtPos::ClickField:
+ pNd = static_txtattr_cast<SwTextField const*>(pFndTextAttr)->GetpTextNode();
+ break;
+
+ case IsAttrAtPos::Ftn:
+ pNd = &static_cast<const SwTextFootnote*>(pFndTextAttr)->GetTextNode();
+ break;
+
+ case IsAttrAtPos::InetAttr:
+ pNd = static_txtattr_cast<SwTextINetFormat const*>(pFndTextAttr)->GetpTextNode();
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if( !pNd )
+ return false;
+ if( pNd->IsInProtectSect() )
+ return true;
+
+ const SwContentFrame* pFrame = pNd->getLayoutFrame(pNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr);
+ return pFrame && pFrame->IsProtected() ;
+}
+
+bool SwContentAtPos::IsInRTLText()const
+{
+ const SwTextNode* pNd = nullptr;
+ if (!pFndTextAttr || (eContentAtPos != IsAttrAtPos::Ftn))
+ return false;
+
+ const SwTextFootnote* pTextFootnote = static_cast<const SwTextFootnote*>(pFndTextAttr);
+ if(!pTextFootnote->GetStartNode())
+ return false;
+
+ SwStartNode* pSttNd = pTextFootnote->GetStartNode()->GetNode().GetStartNode();
+ SwPaM aTemp( *pSttNd );
+ aTemp.Move(fnMoveForward, GoInNode);
+ SwContentNode* pContentNode = aTemp.GetPointContentNode();
+ if(pContentNode && pContentNode->IsTextNode())
+ pNd = pContentNode->GetTextNode();
+ if(!pNd)
+ return false;
+
+ bool bRet = false;
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pNd);
+ SwTextFrame* pTmpFrame = aIter.First();
+ while( pTmpFrame )
+ {
+ if ( !pTmpFrame->IsFollow())
+ {
+ bRet = pTmpFrame->IsRightToLeft();
+ break;
+ }
+ pTmpFrame = aIter.Next();
+ }
+ return bRet;
+}
+
+bool SwCursorShell::SelectTextModel( const sal_Int32 nStart,
+ const sal_Int32 nEnd )
+{
+ CurrShell aCurr( this );
+ bool bRet = false;
+
+ SwCallLink aLk( *this );
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ SwPosition& rPos = *m_pCurrentCursor->GetPoint();
+ m_pCurrentCursor->DeleteMark();
+ rPos.SetContent(nStart);
+ m_pCurrentCursor->SetMark();
+ rPos.SetContent(nEnd);
+
+ if( !m_pCurrentCursor->IsSelOvr() )
+ {
+ UpdateCursor();
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+TextFrameIndex SwCursorShell::GetCursorPointAsViewIndex() const
+{
+ SwPosition const*const pPos(GetCursor()->GetPoint());
+ SwTextNode const*const pTextNode(pPos->GetNode().GetTextNode());
+ assert(pTextNode);
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTextNode->getLayoutFrame(GetLayout())));
+ assert(pFrame);
+ return pFrame->MapModelToViewPos(*pPos);
+}
+
+bool SwCursorShell::SelectTextView(TextFrameIndex const nStart,
+ TextFrameIndex const nEnd)
+{
+ CurrShell aCurr( this );
+ bool bRet = false;
+
+ SwCallLink aLk( *this );
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ SwPosition& rPos = *m_pCurrentCursor->GetPoint();
+ m_pCurrentCursor->DeleteMark();
+ // indexes must correspond to cursor point!
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(m_pCurrentCursor->GetPoint()->GetNode().GetTextNode()->getLayoutFrame(GetLayout())));
+ assert(pFrame);
+ rPos = pFrame->MapViewToModelPos(nStart);
+ m_pCurrentCursor->SetMark();
+ rPos = pFrame->MapViewToModelPos(nEnd);
+
+ if (!m_pCurrentCursor->IsSelOvr())
+ {
+ UpdateCursor();
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool SwCursorShell::SelectTextAttr( sal_uInt16 nWhich,
+ bool bExpand,
+ const SwTextAttr* pTextAttr )
+{
+ CurrShell aCurr( this );
+
+ if( IsTableMode() )
+ return false;
+
+ if( !pTextAttr )
+ {
+ SwPosition& rPos = *m_pCurrentCursor->GetPoint();
+ SwTextNode* pTextNd = rPos.GetNode().GetTextNode();
+ pTextAttr = pTextNd
+ ? pTextNd->GetTextAttrAt(rPos.GetContentIndex(),
+ nWhich,
+ bExpand ? ::sw::GetTextAttrMode::Expand : ::sw::GetTextAttrMode::Default)
+ : nullptr;
+ }
+ if( !pTextAttr )
+ return false;
+
+ const sal_Int32* pEnd = pTextAttr->End();
+ bool bRet = SelectTextModel(pTextAttr->GetStart(), (pEnd ? *pEnd : pTextAttr->GetStart() + 1));
+ return bRet;
+}
+
+bool SwCursorShell::GotoINetAttr( const SwTextINetFormat& rAttr )
+{
+ if( !rAttr.GetpTextNode() )
+ return false;
+ SwCursor* pCursor = getShellCursor( true );
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *pCursor );
+
+ pCursor->GetPoint()->Assign(*rAttr.GetpTextNode(), rAttr.GetStart() );
+ bool bRet = !pCursor->IsSelOvr();
+ if( bRet )
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ return bRet;
+}
+
+const SwFormatINetFormat* SwCursorShell::FindINetAttr( std::u16string_view rName ) const
+{
+ return mxDoc->FindINetAttr( rName );
+}
+
+bool SwCursorShell::GetShadowCursorPos( const Point& rPt, SwFillMode eFillMode,
+ SwRect& rRect, sal_Int16& rOrient )
+{
+
+ CurrShell aCurr( this );
+
+ if (IsTableMode() || HasSelection()
+ || !GetDoc()->GetIDocumentUndoRedo().DoesUndo())
+ return false;
+
+ Point aPt( rPt );
+ SwPosition aPos( *m_pCurrentCursor->GetPoint() );
+
+ SwFillCursorPos aFPos( eFillMode );
+ SwCursorMoveState aTmpState( &aFPos );
+
+ bool bRet = false;
+ if( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ) &&
+ !aPos.GetNode().IsProtect())
+ {
+ // start position in protected section?
+ rRect = aFPos.aCursor;
+ rOrient = aFPos.eOrient;
+ bRet = true;
+ }
+ return bRet;
+}
+
+bool SwCursorShell::SetShadowCursorPos( const Point& rPt, SwFillMode eFillMode )
+{
+ CurrShell aCurr( this );
+
+ if (IsTableMode() || HasSelection()
+ || !GetDoc()->GetIDocumentUndoRedo().DoesUndo())
+ return false;
+
+ Point aPt( rPt );
+ SwPosition aPos( *m_pCurrentCursor->GetPoint() );
+
+ SwFillCursorPos aFPos( eFillMode );
+ SwCursorMoveState aTmpState( &aFPos );
+
+ if( !GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ) )
+ return false;
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ StartAction();
+
+ SwContentNode* pCNd = aPos.GetNode().GetContentNode();
+ SwUndoId nUndoId = SwUndoId::INS_FROM_SHADOWCRSR;
+ // If only the paragraph attributes "Adjust" or "LRSpace" are set,
+ // then the following should not delete those again.
+ if( 0 == aFPos.nParaCnt + aFPos.nColumnCnt &&
+ ( SwFillMode::Indent == aFPos.eMode ||
+ ( text::HoriOrientation::NONE != aFPos.eOrient &&
+ 0 == aFPos.nTabCnt + aFPos.nSpaceCnt )) &&
+ pCNd && pCNd->Len() )
+ nUndoId = SwUndoId::EMPTY;
+
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( nUndoId, nullptr );
+
+ SwTextFormatColl* pNextFormat = nullptr;
+ SwTextNode* pTNd = pCNd ? pCNd->GetTextNode() : nullptr;
+ if( pTNd )
+ pNextFormat = &pTNd->GetTextColl()->GetNextTextFormatColl();
+
+ const SwSectionNode* pSectNd = pCNd ? pCNd->FindSectionNode() : nullptr;
+ if( pSectNd && aFPos.nParaCnt )
+ {
+ SwNodeIndex aEnd( aPos.GetNode(), 1 );
+ while( aEnd.GetNode().IsEndNode() &&
+ &aEnd.GetNode() !=
+ pSectNd->EndOfSectionNode() )
+ ++aEnd;
+
+ if( aEnd.GetNode().IsEndNode() &&
+ pCNd->Len() == aPos.GetContentIndex() )
+ aPos.Assign( *pSectNd->EndOfSectionNode() );
+ }
+
+ for( sal_uInt16 n = 0; n < aFPos.nParaCnt + aFPos.nColumnCnt; ++n )
+ {
+ GetDoc()->getIDocumentContentOperations().AppendTextNode( aPos );
+ if( !n && pNextFormat )
+ {
+ *m_pCurrentCursor->GetPoint() = aPos;
+ GetDoc()->SetTextFormatColl( *m_pCurrentCursor, pNextFormat, false );
+ }
+ if( n < aFPos.nColumnCnt )
+ {
+ *m_pCurrentCursor->GetPoint() = aPos;
+ GetDoc()->getIDocumentContentOperations().InsertPoolItem( *m_pCurrentCursor,
+ SvxFormatBreakItem( SvxBreak::ColumnBefore, RES_BREAK ) );
+ }
+ }
+
+ *m_pCurrentCursor->GetPoint() = aPos;
+ switch( aFPos.eMode )
+ {
+ case SwFillMode::Indent:
+ if( nullptr != (pCNd = aPos.GetNode().GetContentNode() ))
+ {
+ assert(pCNd->IsTextNode()); // ???
+ SfxItemSetFixed<
+ RES_PARATR_ADJUST, RES_PARATR_ADJUST,
+ RES_MARGIN_FIRSTLINE, RES_MARGIN_TEXTLEFT> aSet(GetDoc()->GetAttrPool());
+ SvxFirstLineIndentItem firstLine(pCNd->GetAttr(RES_MARGIN_FIRSTLINE));
+ SvxTextLeftMarginItem leftMargin(pCNd->GetAttr(RES_MARGIN_TEXTLEFT));
+ firstLine.SetTextFirstLineOffset(0);
+ leftMargin.SetTextLeft(aFPos.nTabCnt);
+ aSet.Put(firstLine);
+ aSet.Put(leftMargin);
+
+ const SvxAdjustItem& rAdj = pCNd->GetAttr(RES_PARATR_ADJUST);
+ if( SvxAdjust::Left != rAdj.GetAdjust() )
+ aSet.Put( SvxAdjustItem( SvxAdjust::Left, RES_PARATR_ADJUST ) );
+
+ GetDoc()->getIDocumentContentOperations().InsertItemSet( *m_pCurrentCursor, aSet );
+ }
+ else {
+ OSL_ENSURE( false, "No ContentNode" );
+ }
+ break;
+
+ case SwFillMode::Tab:
+ case SwFillMode::TabSpace:
+ case SwFillMode::Space:
+ {
+ OUStringBuffer sInsert;
+ if (aFPos.eMode == SwFillMode::Space)
+ {
+ comphelper::string::padToLength(sInsert, sInsert.getLength() + aFPos.nSpaceOnlyCnt, ' ');
+ }
+ else
+ {
+ if (aFPos.nTabCnt)
+ comphelper::string::padToLength(sInsert, aFPos.nTabCnt, '\t');
+ if (aFPos.nSpaceCnt)
+ comphelper::string::padToLength(sInsert, sInsert.getLength() + aFPos.nSpaceCnt, ' ');
+ }
+ if (!sInsert.isEmpty())
+ GetDoc()->getIDocumentContentOperations().InsertString( *m_pCurrentCursor, sInsert.makeStringAndClear());
+ }
+ [[fallthrough]]; // still need to set orientation
+ case SwFillMode::Margin:
+ if( text::HoriOrientation::NONE != aFPos.eOrient )
+ {
+ SvxAdjustItem aAdj( SvxAdjust::Left, RES_PARATR_ADJUST );
+ switch( aFPos.eOrient )
+ {
+ case text::HoriOrientation::CENTER:
+ aAdj.SetAdjust( SvxAdjust::Center );
+ break;
+ case text::HoriOrientation::RIGHT:
+ aAdj.SetAdjust( SvxAdjust::Right );
+ break;
+ default:
+ break;
+ }
+ GetDoc()->getIDocumentContentOperations().InsertPoolItem( *m_pCurrentCursor, aAdj );
+ }
+ break;
+ }
+
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( nUndoId, nullptr );
+ EndAction();
+
+ return true;
+}
+
+const SwRangeRedline* SwCursorShell::SelNextRedline()
+{
+ if( IsTableMode() )
+ return nullptr;
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ // ensure point is at the end so alternating SelNext/SelPrev works
+ NormalizePam(false);
+ const SwRangeRedline* pFnd = GetDoc()->getIDocumentRedlineAccess().SelNextRedline( *m_pCurrentCursor );
+
+ // at the end of the document, go to the start of the document, and try again
+ if ( !pFnd )
+ {
+ GetDoc()->GetDocShell()->GetWrtShell()->StartOfSection();
+ pFnd = GetDoc()->getIDocumentRedlineAccess().SelNextRedline( *m_pCurrentCursor );
+ }
+
+ if( pFnd && !m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr() )
+ UpdateCursor( SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ else
+ pFnd = nullptr;
+ return pFnd;
+}
+
+const SwRangeRedline* SwCursorShell::SelPrevRedline()
+{
+ if( IsTableMode() )
+ return nullptr;
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ // ensure point is at the start so alternating SelNext/SelPrev works
+ NormalizePam(true);
+ const SwRangeRedline* pFnd = GetDoc()->getIDocumentRedlineAccess().SelPrevRedline( *m_pCurrentCursor );
+
+ // at the start of the document, go to the end of the document, and try again
+ if ( !pFnd )
+ {
+ GetDoc()->GetDocShell()->GetWrtShell()->EndOfSection();
+ pFnd = GetDoc()->getIDocumentRedlineAccess().SelPrevRedline( *m_pCurrentCursor );
+ }
+
+ if( pFnd && !m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr() )
+ UpdateCursor( SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+ else
+ pFnd = nullptr;
+ return pFnd;
+}
+
+const SwRangeRedline* SwCursorShell::GotoRedline_( SwRedlineTable::size_type nArrPos, bool bSelect )
+{
+ const SwRangeRedline* pFnd = nullptr;
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ pFnd = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable()[ nArrPos ];
+ if( !pFnd )
+ return nullptr;
+
+ *m_pCurrentCursor->GetPoint() = *pFnd->Start();
+
+ SwPosition* pPtPos = m_pCurrentCursor->GetPoint();
+ if( !pPtPos->GetNode().IsContentNode() )
+ {
+ SwContentNode* pCNd = GetDoc()->GetNodes().GoNextSection( pPtPos,
+ true, IsReadOnlyAvailable() );
+ if( pCNd )
+ {
+ if( pPtPos->GetNode() <= pFnd->End()->GetNode() )
+ pPtPos->SetContent( 0 );
+ else
+ pFnd = nullptr;
+ }
+ }
+
+ if( pFnd && bSelect )
+ {
+ m_pCurrentCursor->SetMark();
+ if( RedlineType::FmtColl == pFnd->GetType() )
+ {
+ SwContentNode* pCNd = pPtPos->GetNode().GetContentNode();
+ m_pCurrentCursor->GetPoint()->SetContent( pCNd->Len() );
+ m_pCurrentCursor->GetMark()->Assign( *pCNd, 0 );
+ }
+ else
+ *m_pCurrentCursor->GetPoint() = *pFnd->End();
+
+ pPtPos = m_pCurrentCursor->GetPoint();
+ if( !pPtPos->GetNode().IsContentNode() )
+ {
+ SwContentNode* pCNd = SwNodes::GoPrevSection( pPtPos,
+ true, IsReadOnlyAvailable() );
+ if( pCNd )
+ {
+ if( pPtPos->GetNode() >= m_pCurrentCursor->GetMark()->GetNode() )
+ pPtPos->SetContent( pCNd->Len() );
+ else
+ pFnd = nullptr;
+ }
+ }
+ }
+
+ if( !pFnd )
+ {
+ m_pCurrentCursor->DeleteMark();
+ m_pCurrentCursor->RestoreSavePos();
+ }
+ else if( bSelect && *m_pCurrentCursor->GetMark() == *m_pCurrentCursor->GetPoint() )
+ m_pCurrentCursor->DeleteMark();
+
+ if( pFnd && !m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr() )
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE
+ | SwCursorShell::READONLY );
+ else
+ {
+ pFnd = nullptr;
+ if( bSelect )
+ m_pCurrentCursor->DeleteMark();
+ }
+ return pFnd;
+}
+
+const SwRangeRedline* SwCursorShell::GotoRedline( SwRedlineTable::size_type nArrPos, bool bSelect )
+{
+ const SwRangeRedline* pFnd = nullptr;
+ if( IsTableMode() )
+ return nullptr;
+
+ CurrShell aCurr( this );
+
+ const SwRedlineTable& rTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable();
+ const SwRangeRedline* pTmp = rTable[ nArrPos ];
+ sal_uInt16 nSeqNo = pTmp->GetSeqNo();
+ if( !nSeqNo || !bSelect )
+ {
+ pFnd = GotoRedline_( nArrPos, bSelect );
+ return pFnd;
+ }
+
+ bool bCheck = false;
+ int nLoopCnt = 2;
+ SwRedlineTable::size_type nArrSavPos = nArrPos;
+
+ do {
+ pTmp = GotoRedline_( nArrPos, true );
+
+ if( !pFnd )
+ pFnd = pTmp;
+
+ if( pTmp && bCheck )
+ {
+ // Check for overlaps. These can happen when FormatColl-
+ // Redlines were stretched over a whole paragraph
+ SwPaM* pCur = m_pCurrentCursor;
+ SwPaM* pNextPam = pCur->GetNext();
+ auto [pCStt, pCEnd] = pCur->StartEnd(); // SwPosition*
+ while( pCur != pNextPam )
+ {
+ auto [pNStt, pNEnd] = pNextPam->StartEnd(); // SwPosition*
+
+ bool bDel = true;
+ switch( ::ComparePosition( *pCStt, *pCEnd,
+ *pNStt, *pNEnd ))
+ {
+ case SwComparePosition::Inside: // Pos1 is completely in Pos2
+ if( !pCur->HasMark() )
+ {
+ pCur->SetMark();
+ *pCur->GetMark() = *pNStt;
+ }
+ else
+ *pCStt = *pNStt;
+ *pCEnd = *pNEnd;
+ break;
+
+ case SwComparePosition::Outside: // Pos2 is completely in Pos1
+ case SwComparePosition::Equal: // Pos1 has same size as Pos2
+ break;
+
+ case SwComparePosition::OverlapBefore: // Pos1 overlaps Pos2 at beginning
+ if( !pCur->HasMark() )
+ pCur->SetMark();
+ *pCEnd = *pNEnd;
+ break;
+ case SwComparePosition::OverlapBehind: // Pos1 overlaps Pos2 at end
+ if( !pCur->HasMark() )
+ {
+ pCur->SetMark();
+ *pCur->GetMark() = *pNStt;
+ }
+ else
+ *pCStt = *pNStt;
+ break;
+
+ default:
+ bDel = false;
+ }
+
+ if( bDel )
+ {
+ // not needed anymore
+ SwPaM* pPrevPam = pNextPam->GetPrev();
+ delete pNextPam;
+ pNextPam = pPrevPam;
+ }
+ pNextPam = pNextPam->GetNext();
+ }
+ }
+
+ SwRedlineTable::size_type nFndPos = 2 == nLoopCnt
+ ? rTable.FindNextOfSeqNo( nArrPos )
+ : rTable.FindPrevOfSeqNo( nArrPos );
+ if( SwRedlineTable::npos != nFndPos ||
+ ( 0 != ( --nLoopCnt ) && SwRedlineTable::npos != (
+ nFndPos = rTable.FindPrevOfSeqNo( nArrSavPos ))) )
+ {
+ if( pTmp )
+ {
+ // create new cursor
+ CreateCursor();
+ bCheck = true;
+ }
+ nArrPos = nFndPos;
+ }
+ else
+ nLoopCnt = 0;
+
+ } while( nLoopCnt );
+ return pFnd;
+}
+
+bool SwCursorShell::SelectNxtPrvHyperlink( bool bNext )
+{
+ SwNodes& rNds = GetDoc()->GetNodes();
+ const SwNode* pBodyEndNd = &rNds.GetEndOfContent();
+ const SwNode* pBodySttNd = pBodyEndNd->StartOfSectionNode();
+ SwNodeOffset nBodySttNdIdx = pBodySttNd->GetIndex();
+ Point aPt;
+
+ SetGetExpField aCmpPos( SwPosition( bNext ? *pBodyEndNd : *pBodySttNd ) );
+ SetGetExpField aCurPos( bNext ? *m_pCurrentCursor->End() : *m_pCurrentCursor->Start() );
+ if( aCurPos.GetNode() < nBodySttNdIdx )
+ {
+ const SwContentNode* pCNd = aCurPos.GetNodeFromContent()->GetContentNode();
+ std::pair<Point, bool> tmp(aPt, true);
+ if (pCNd)
+ {
+ SwContentFrame* pFrame = pCNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ if( pFrame )
+ aCurPos.SetBodyPos( *pFrame );
+ }
+ }
+
+ // check first all the hyperlink fields
+ {
+ const SwTextNode* pTextNd;
+ const SwCharFormats* pFormats = GetDoc()->GetCharFormats();
+ for( SwCharFormats::size_type n = pFormats->size(); 1 < n; )
+ {
+ SwIterator<SwTextINetFormat,SwCharFormat> aIter(*(*pFormats)[--n]);
+
+ for( SwTextINetFormat* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() )
+ {
+ pTextNd = pFnd->GetpTextNode();
+ if( pTextNd && pTextNd->GetNodes().IsDocNodes() )
+ {
+ SwTextINetFormat& rAttr = *pFnd;
+ SetGetExpField aPos( *pTextNd, rAttr );
+ if (pTextNd->GetIndex() < nBodySttNdIdx)
+ {
+ std::pair<Point, bool> tmp(aPt, true);
+ SwContentFrame* pFrame = pTextNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ if (pFrame)
+ {
+ aPos.SetBodyPos( *pFrame );
+ }
+ }
+
+ if( bNext
+ ? ( aPos < aCmpPos && aCurPos < aPos )
+ : ( aCmpPos < aPos && aPos < aCurPos ))
+ {
+ OUString sText(pTextNd->GetExpandText(GetLayout(),
+ rAttr.GetStart(),
+ *rAttr.GetEnd() - rAttr.GetStart() ) );
+
+ sText = sText.replaceAll("\x0a", "");
+ sText = comphelper::string::strip(sText, ' ');
+
+ if( !sText.isEmpty() )
+ aCmpPos = aPos;
+ }
+ }
+ }
+ }
+ }
+
+ // then check all the Flys with a URL or image map
+ {
+ for(sw::SpzFrameFormat* pSpz: *GetDoc()->GetSpzFrameFormats())
+ {
+ auto pFormat = static_cast<SwFlyFrameFormat*>(pSpz);
+ const SwFormatURL& rURLItem = pFormat->GetURL();
+ if( rURLItem.GetMap() || !rURLItem.GetURL().isEmpty() )
+ {
+ SwFlyFrame* pFly = pFormat->GetFrame( &aPt );
+ SwPosition aTmpPos( *pBodySttNd );
+ if( pFly &&
+ GetBodyTextNode( *GetDoc(), aTmpPos, *pFly->GetLower() ) )
+ {
+ SetGetExpField aPos( *pFormat, &aTmpPos );
+
+ if( bNext
+ ? ( aPos < aCmpPos && aCurPos < aPos )
+ : ( aCmpPos < aPos && aPos < aCurPos ))
+ aCmpPos = aPos;
+ }
+ }
+ }
+ }
+
+ // found any URL ?
+ const SwTextINetFormat* pFndAttr = aCmpPos.GetINetFormat();
+ const SwFlyFrameFormat* pFndFormat = aCmpPos.GetFlyFormat();
+ if( !pFndAttr && !pFndFormat )
+ return false;
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this );
+
+ bool bRet = false;
+ // found a text attribute ?
+ if( pFndAttr )
+ {
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ aCmpPos.GetPosOfContent( *m_pCurrentCursor->GetPoint() );
+ m_pCurrentCursor->DeleteMark();
+ m_pCurrentCursor->SetMark();
+ m_pCurrentCursor->GetPoint()->SetContent( *pFndAttr->End() );
+
+ if( !m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr() )
+ {
+ UpdateCursor( SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|
+ SwCursorShell::READONLY );
+ bRet = true;
+ }
+ }
+ // found a draw object ?
+ else if( RES_DRAWFRMFMT == pFndFormat->Which() )
+ {
+ const SdrObject* pSObj = pFndFormat->FindSdrObject();
+ if (pSObj)
+ {
+ static_cast<SwFEShell*>(this)->SelectObj( pSObj->GetCurrentBoundRect().Center() );
+ MakeSelVisible();
+ bRet = true;
+ }
+ }
+ else // then is it a fly
+ {
+ SwFlyFrame* pFly = pFndFormat->GetFrame(&aPt);
+ if( pFly )
+ {
+ static_cast<SwFEShell*>(this)->SelectFlyFrame( *pFly );
+ MakeSelVisible();
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/crstrvl1.cxx b/sw/source/core/crsr/crstrvl1.cxx
new file mode 100644
index 0000000000..5654566769
--- /dev/null
+++ b/sw/source/core/crsr/crstrvl1.cxx
@@ -0,0 +1,126 @@
+/* -*- 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 <crsrsh.hxx>
+#include <viscrs.hxx>
+
+#include <com/sun/star/i18n/WordType.hpp>
+
+using namespace ::com::sun::star::i18n;
+
+bool SwCursorShell::IsStartWord( sal_Int16 nWordType ) const
+{
+ return m_pCurrentCursor->IsStartWordWT(nWordType, GetLayout());
+}
+bool SwCursorShell::IsEndWord( sal_Int16 nWordType ) const
+{
+ return m_pCurrentCursor->IsEndWordWT(nWordType, GetLayout());
+}
+
+bool SwCursorShell::IsInWord( sal_Int16 nWordType ) const
+{
+ return m_pCurrentCursor->IsInWordWT(nWordType, GetLayout());
+}
+
+bool SwCursorShell::IsStartSentence() const
+{
+ return m_pCurrentCursor->IsStartEndSentence(false, GetLayout());
+}
+bool SwCursorShell::IsEndSentence() const
+{
+ return m_pCurrentCursor->IsStartEndSentence(true, GetLayout());
+}
+
+bool SwCursorShell::GoStartWord()
+{
+ return CallCursorShellFN( &SwCursorShell::GoStartWordImpl );
+}
+bool SwCursorShell::GoEndWord()
+{
+ return CallCursorShellFN( &SwCursorShell::GoEndWordImpl );
+}
+
+bool SwCursorShell::GoNextWord()
+{
+ return CallCursorShellFN( &SwCursorShell::GoNextWordImpl );
+}
+bool SwCursorShell::GoPrevWord()
+{
+ return CallCursorShellFN( &SwCursorShell::GoPrevWordImpl );
+}
+
+bool SwCursorShell::GoNextSentence()
+{
+ return CallCursorShellFN( &SwCursorShell::GoNextSentenceImpl );
+}
+
+bool SwCursorShell::GoEndSentence()
+{
+ return CallCursorShellFN( &SwCursorShell::GoEndSentenceImpl );
+}
+
+bool SwCursorShell::GoStartSentence()
+{
+ return CallCursorShellFN( &SwCursorShell::GoStartSentenceImpl );
+}
+
+bool SwCursorShell::SelectWord( const Point* pPt )
+{
+ return m_pCurrentCursor->SelectWord( this, pPt );
+}
+
+void SwCursorShell::ExpandToSentenceBorders()
+{
+ m_pCurrentCursor->ExpandToSentenceBorders(GetLayout());
+}
+
+bool SwCursorShell::GoStartWordImpl()
+{
+ return getShellCursor(true)->GoStartWordWT(WordType::ANYWORD_IGNOREWHITESPACES, GetLayout());
+}
+
+bool SwCursorShell::GoEndWordImpl()
+{
+ return getShellCursor(true)->GoEndWordWT(WordType::ANYWORD_IGNOREWHITESPACES, GetLayout());
+}
+
+bool SwCursorShell::GoNextWordImpl()
+{
+ return getShellCursor(true)->GoNextWordWT(WordType::ANYWORD_IGNOREWHITESPACES, GetLayout());
+}
+
+bool SwCursorShell::GoPrevWordImpl()
+{
+ return getShellCursor(true)->GoPrevWordWT(WordType::ANYWORD_IGNOREWHITESPACES, GetLayout());
+}
+
+bool SwCursorShell::GoNextSentenceImpl()
+{
+ return getShellCursor(true)->GoSentence(SwCursor::NEXT_SENT, GetLayout());
+}
+bool SwCursorShell::GoEndSentenceImpl()
+{
+ return getShellCursor(true)->GoSentence(SwCursor::END_SENT, GetLayout());
+}
+bool SwCursorShell::GoStartSentenceImpl()
+{
+ return getShellCursor(true)->GoSentence(SwCursor::START_SENT, GetLayout());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/datecontentcontrolbutton.cxx b/sw/source/core/crsr/datecontentcontrolbutton.cxx
new file mode 100644
index 0000000000..9de971b6f9
--- /dev/null
+++ b/sw/source/core/crsr/datecontentcontrolbutton.cxx
@@ -0,0 +1,71 @@
+/* -*- 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 <datecontentcontrolbutton.hxx>
+
+#include <svl/numformat.hxx>
+#include <tools/date.hxx>
+#include <vcl/svapp.hxx>
+
+#include <edtwin.hxx>
+#include <formatcontentcontrol.hxx>
+#include <view.hxx>
+#include <wrtsh.hxx>
+
+IMPL_LINK(SwDateContentControlButton, SelectHandler, weld::Calendar&, rCalendar, void)
+{
+ const Date& rNullDate = m_pNumberFormatter->GetNullDate();
+ double fDate = rCalendar.get_date() - rNullDate;
+ m_xPopup->popdown();
+ m_pContentControl->SetSelectedDate(fDate);
+ SwView& rView = static_cast<SwEditWin*>(GetParent())->GetView();
+ SwWrtShell& rWrtShell = rView.GetWrtShell();
+ rWrtShell.GotoContentControl(*m_pContentControl->GetFormatContentControl());
+}
+
+SwDateContentControlButton::SwDateContentControlButton(
+ SwEditWin* pEditWin, const std::shared_ptr<SwContentControl>& pContentControl,
+ SvNumberFormatter* pNumberFormatter)
+ : SwContentControlButton(pEditWin, pContentControl)
+ , m_pNumberFormatter(pNumberFormatter)
+{
+}
+
+SwDateContentControlButton::~SwDateContentControlButton() { disposeOnce(); }
+
+void SwDateContentControlButton::LaunchPopup()
+{
+ m_xPopupBuilder = Application::CreateBuilder(GetFrameWeld(),
+ "modules/swriter/ui/contentcontrolcalendar.ui");
+ m_xPopup = m_xPopupBuilder->weld_popover("Calendar");
+ m_xCalendar = m_xPopupBuilder->weld_calendar("date");
+
+ // Read the doc model.
+ if (m_pContentControl)
+ {
+ const Date& rNullDate = m_pNumberFormatter->GetNullDate();
+ double fCurrentDate = m_pContentControl->GetCurrentDateValue();
+ if (fCurrentDate != 0)
+ {
+ m_xCalendar->set_date(rNullDate + sal_Int32(fCurrentDate));
+ }
+ }
+
+ m_xCalendar->connect_activated(LINK(this, SwDateContentControlButton, SelectHandler));
+ SwContentControlButton::LaunchPopup();
+ m_xCalendar->grab_focus();
+}
+
+void SwDateContentControlButton::DestroyPopup()
+{
+ m_xCalendar.reset();
+ SwContentControlButton::DestroyPopup();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/dropdowncontentcontrolbutton.cxx b/sw/source/core/crsr/dropdowncontentcontrolbutton.cxx
new file mode 100644
index 0000000000..ba47c33f26
--- /dev/null
+++ b/sw/source/core/crsr/dropdowncontentcontrolbutton.cxx
@@ -0,0 +1,96 @@
+/* -*- 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 <dropdowncontentcontrolbutton.hxx>
+
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+
+#include <edtwin.hxx>
+#include <view.hxx>
+#include <strings.hrc>
+#include <formatcontentcontrol.hxx>
+#include <wrtsh.hxx>
+
+void SwDropDownContentControlButton::InitDropdown()
+{
+ std::vector<SwContentControlListItem> aListItems = m_pContentControl->GetListItems();
+
+ for (const auto& rListItem : aListItems)
+ {
+ m_xTreeView->append_text(rListItem.ToString());
+ }
+
+ if (aListItems.empty())
+ {
+ m_xTreeView->append_text(SwResId(STR_DROP_DOWN_EMPTY_LIST));
+ }
+
+ int nHeight = m_xTreeView->get_height_rows(
+ std::min<int>(Application::GetSettings().GetStyleSettings().GetListBoxMaximumLineCount(),
+ m_xTreeView->n_children()));
+ m_xTreeView->set_size_request(-1, nHeight);
+ Size aSize(m_xTreeView->get_preferred_size());
+ aSize.AdjustWidth(4);
+ aSize.AdjustHeight(4);
+ tools::Long nMinListWidth = GetSizePixel().Width();
+ aSize.setWidth(std::max(aSize.Width(), nMinListWidth));
+ m_xTreeView->set_size_request(aSize.Width(), aSize.Height());
+ m_xTreeView->set_direction(m_bRTL);
+}
+
+IMPL_LINK(SwDropDownContentControlButton, ListBoxHandler, weld::TreeView&, rBox, bool)
+{
+ OUString sSelection = rBox.get_selected_text();
+ if (sSelection == SwResId(STR_DROP_DOWN_EMPTY_LIST))
+ {
+ m_xPopup->popdown();
+ return true;
+ }
+
+ sal_Int32 nSelection = rBox.get_selected_index();
+ m_xPopup->popdown();
+ if (nSelection >= 0)
+ {
+ SwView& rView = static_cast<SwEditWin*>(GetParent())->GetView();
+ SwWrtShell& rWrtShell = rView.GetWrtShell();
+ m_pContentControl->SetSelectedListItem(nSelection);
+ rWrtShell.GotoContentControl(*m_pContentControl->GetFormatContentControl());
+ }
+
+ return true;
+}
+
+SwDropDownContentControlButton::SwDropDownContentControlButton(
+ SwEditWin* pEditWin, const std::shared_ptr<SwContentControl>& pContentControl)
+ : SwContentControlButton(pEditWin, pContentControl)
+{
+}
+
+SwDropDownContentControlButton::~SwDropDownContentControlButton() { disposeOnce(); }
+
+void SwDropDownContentControlButton::LaunchPopup()
+{
+ m_xPopupBuilder = Application::CreateBuilder(GetFrameWeld(),
+ "modules/swriter/ui/contentcontroldropdown.ui");
+ m_xPopup = m_xPopupBuilder->weld_popover("ContentControlDropDown");
+ m_xTreeView = m_xPopupBuilder->weld_tree_view("list");
+ InitDropdown();
+ m_xTreeView->connect_row_activated(LINK(this, SwDropDownContentControlButton, ListBoxHandler));
+ SwContentControlButton::LaunchPopup();
+ m_xTreeView->grab_focus();
+}
+
+void SwDropDownContentControlButton::DestroyPopup()
+{
+ m_xTreeView.reset();
+ SwContentControlButton::DestroyPopup();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/findattr.cxx b/sw/source/core/crsr/findattr.cxx
new file mode 100644
index 0000000000..9492cfdd6c
--- /dev/null
+++ b/sw/source/core/crsr/findattr.cxx
@@ -0,0 +1,1446 @@
+/* -*- 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/lang/Locale.hpp>
+#include <com/sun/star/util/SearchAlgorithms2.hpp>
+#include <com/sun/star/util/SearchFlags.hpp>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nutil/searchopt.hxx>
+#include <osl/diagnose.h>
+#include <unotools/syslocale.hxx>
+#include <hintids.hxx>
+#include <svl/itemiter.hxx>
+#include <svl/srchitem.hxx>
+#include <svl/whiter.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/fontitem.hxx>
+#include <fmtpdsc.hxx>
+#include <txatbase.hxx>
+#include <charfmt.hxx>
+#include <crsrsh.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentState.hxx>
+#include <swcrsr.hxx>
+#include <ndtxt.hxx>
+#include <pamtyp.hxx>
+#include <txtfrm.hxx>
+#include <swundo.hxx>
+#include <optional>
+
+#include <algorithm>
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::util;
+
+// Special case for SvxFontItem: only compare the name
+static bool CmpAttr( const SfxPoolItem& rItem1, const SfxPoolItem& rItem2 )
+{
+ switch( rItem1.Which() )
+ {
+ case RES_CHRATR_FONT:
+ return rItem1.StaticWhichCast(RES_CHRATR_FONT).GetFamilyName() == rItem2.StaticWhichCast(RES_CHRATR_FONT).GetFamilyName();
+
+ case RES_CHRATR_COLOR:
+ return rItem1.StaticWhichCast(RES_CHRATR_COLOR).GetValue().IsRGBEqual(rItem2.StaticWhichCast(RES_CHRATR_COLOR).GetValue());
+ case RES_PAGEDESC:
+ ::std::optional<sal_uInt16> const oNumOffset1 = rItem1.StaticWhichCast(RES_PAGEDESC).GetNumOffset();
+ ::std::optional<sal_uInt16> const oNumOffset2 = rItem2.StaticWhichCast(RES_PAGEDESC).GetNumOffset();
+
+ if (oNumOffset1 != oNumOffset2)
+ return false;
+
+ return rItem1.StaticWhichCast(RES_PAGEDESC).GetPageDesc() == rItem2.StaticWhichCast(RES_PAGEDESC).GetPageDesc();
+ }
+ return rItem1 == rItem2;
+}
+
+const SwTextAttr* GetFrwrdTextHint( const SwpHints& rHtsArr, size_t& rPos,
+ sal_Int32 nContentPos )
+{
+ while( rPos < rHtsArr.Count() )
+ {
+ const SwTextAttr *pTextHt = rHtsArr.Get( rPos++ );
+ // the start of an attribute has to be in the section
+ if( pTextHt->GetStart() >= nContentPos )
+ return pTextHt; // valid text attribute
+ }
+ return nullptr; // invalid text attribute
+}
+
+const SwTextAttr* GetBkwrdTextHint( const SwpHints& rHtsArr, size_t& rPos,
+ sal_Int32 nContentPos )
+{
+ while( rPos > 0 )
+ {
+ const SwTextAttr *pTextHt = rHtsArr.Get( --rPos );
+ // the start of an attribute has to be in the section
+ if( pTextHt->GetStart() < nContentPos )
+ return pTextHt; // valid text attribute
+ }
+ return nullptr; // invalid text attribute
+}
+
+static void lcl_SetAttrPam( SwPaM& rPam, sal_Int32 nStart, const sal_Int32* pEnd,
+ const bool bSaveMark )
+{
+ sal_Int32 nContentPos;
+ if( bSaveMark )
+ nContentPos = rPam.GetMark()->GetContentIndex();
+ else
+ nContentPos = rPam.GetPoint()->GetContentIndex();
+ bool bTstEnd = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode();
+
+ rPam.GetPoint()->SetContent( nStart );
+ rPam.SetMark(); // Point == GetMark
+
+ // Point points to end of search area or end of attribute
+ if( pEnd )
+ {
+ if( bTstEnd && *pEnd > nContentPos )
+ rPam.GetPoint()->SetContent(nContentPos);
+ else
+ rPam.GetPoint()->SetContent(*pEnd);
+ }
+}
+
+// TODO: provide documentation
+/** search for a text attribute
+
+ This function searches in a text node for a given attribute.
+ If that is found then the SwPaM contains the section that surrounds the
+ attribute (w.r.t. the search area).
+
+ @param rTextNd Text node to search in.
+ @param rPam ???
+ @param rCmpItem ???
+ @param fnMove ???
+ @return Returns <true> if found, <false> otherwise.
+*/
+static bool lcl_SearchAttr( const SwTextNode& rTextNd, SwPaM& rPam,
+ const SfxPoolItem& rCmpItem,
+ SwMoveFnCollection const & fnMove)
+{
+ if ( !rTextNd.HasHints() )
+ return false;
+
+ const SwTextAttr *pTextHt = nullptr;
+ bool bForward = &fnMove == &fnMoveForward;
+ size_t nPos = bForward ? 0 : rTextNd.GetSwpHints().Count();
+ sal_Int32 nContentPos = rPam.GetPoint()->GetContentIndex();
+
+ while( nullptr != ( pTextHt=(*fnMove.fnGetHint)(rTextNd.GetSwpHints(),nPos,nContentPos)))
+ if (pTextHt->Which() == rCmpItem.Which())
+ {
+ lcl_SetAttrPam( rPam, pTextHt->GetStart(), pTextHt->End(), bForward );
+ return true;
+ }
+ return false;
+}
+
+namespace {
+
+/// search for multiple text attributes
+struct SwSrchChrAttr
+{
+ sal_uInt16 nWhich;
+ sal_Int32 nStt;
+ sal_Int32 nEnd;
+
+ SwSrchChrAttr(): nWhich(0), nStt(0), nEnd(0) {}
+
+ SwSrchChrAttr( const SfxPoolItem& rItem,
+ sal_Int32 nStart, sal_Int32 nAnyEnd )
+ : nWhich( rItem.Which() ), nStt( nStart ), nEnd( nAnyEnd )
+ {}
+};
+
+class SwAttrCheckArr
+{
+ SwSrchChrAttr *m_pFindArr, *m_pStackArr;
+ sal_Int32 m_nNodeStart;
+ sal_Int32 m_nNodeEnd;
+ sal_uInt16 m_nArrStart, m_nArrLen;
+ sal_uInt16 m_nFound, m_nStackCount;
+ SfxItemSet m_aComapeSet;
+ bool m_bNoColls;
+ bool m_bForward;
+
+public:
+ SwAttrCheckArr( const SfxItemSet& rSet, bool bForward, bool bNoCollections );
+ ~SwAttrCheckArr();
+
+ void SetNewSet( const SwTextNode& rTextNd, const SwPaM& rPam );
+
+ /// how many attributes are there in total?
+ sal_uInt16 Count() const { return m_aComapeSet.Count(); }
+ bool Found() const { return m_nFound == m_aComapeSet.Count(); }
+ bool CheckStack();
+
+ sal_Int32 Start() const;
+ sal_Int32 End() const;
+
+ sal_Int32 GetNdStt() const { return m_nNodeStart; }
+ sal_Int32 GetNdEnd() const { return m_nNodeEnd; }
+
+ bool SetAttrFwd( const SwTextAttr& rAttr );
+ bool SetAttrBwd( const SwTextAttr& rAttr );
+};
+
+}
+
+SwAttrCheckArr::SwAttrCheckArr( const SfxItemSet& rSet, bool bFwd,
+ bool bNoCollections )
+ : m_nNodeStart(0)
+ , m_nNodeEnd(0)
+ , m_nFound(0)
+ , m_nStackCount(0)
+ , m_aComapeSet( *rSet.GetPool(), svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_END-1> )
+ , m_bNoColls(bNoCollections)
+ , m_bForward(bFwd)
+{
+ m_aComapeSet.Put( rSet, false );
+
+ // determine area of Fnd/Stack array (Min/Max)
+ SfxItemIter aIter( m_aComapeSet );
+ m_nArrStart = m_aComapeSet.GetWhichByOffset( aIter.GetFirstPos() );
+ m_nArrLen = m_aComapeSet.GetWhichByOffset( aIter.GetLastPos() ) - m_nArrStart+1;
+
+ char* pFndChar = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ];
+ char* pStackChar = new char[ m_nArrLen * sizeof(SwSrchChrAttr) ];
+
+ m_pFindArr = reinterpret_cast<SwSrchChrAttr*>(pFndChar);
+ m_pStackArr = reinterpret_cast<SwSrchChrAttr*>(pStackChar);
+}
+
+SwAttrCheckArr::~SwAttrCheckArr()
+{
+ delete[] reinterpret_cast<char*>(m_pFindArr);
+ delete[] reinterpret_cast<char*>(m_pStackArr);
+}
+
+void SwAttrCheckArr::SetNewSet( const SwTextNode& rTextNd, const SwPaM& rPam )
+{
+ std::fill(m_pFindArr, m_pFindArr + m_nArrLen, SwSrchChrAttr());
+ std::fill(m_pStackArr, m_pStackArr + m_nArrLen, SwSrchChrAttr());
+ m_nFound = 0;
+ m_nStackCount = 0;
+
+ if( m_bForward )
+ {
+ m_nNodeStart = rPam.GetPoint()->GetContentIndex();
+ m_nNodeEnd = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode()
+ ? rPam.GetMark()->GetContentIndex()
+ : rTextNd.GetText().getLength();
+ }
+ else
+ {
+ m_nNodeEnd = rPam.GetPoint()->GetContentIndex();
+ m_nNodeStart = rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode()
+ ? rPam.GetMark()->GetContentIndex()
+ : 0;
+ }
+
+ if( m_bNoColls && !rTextNd.HasSwAttrSet() )
+ return ;
+
+ const SfxItemSet& rSet = rTextNd.GetSwAttrSet();
+
+ SfxItemIter aIter( m_aComapeSet );
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+ const SfxPoolItem* pFndItem;
+ sal_uInt16 nWhich;
+
+ do
+ {
+ if( IsInvalidItem( pItem ) )
+ {
+ nWhich = m_aComapeSet.GetWhichByOffset( aIter.GetCurPos() );
+ if( RES_TXTATR_END <= nWhich )
+ break; // end of text attributes
+
+ if( SfxItemState::SET == rSet.GetItemState( nWhich, !m_bNoColls, &pFndItem )
+ && !CmpAttr( *pFndItem, rSet.GetPool()->GetDefaultItem( nWhich ) ))
+ {
+ m_pFindArr[ nWhich - m_nArrStart ] =
+ SwSrchChrAttr( *pFndItem, m_nNodeStart, m_nNodeEnd );
+ m_nFound++;
+ }
+ }
+ else
+ {
+ nWhich = pItem->Which();
+ if( RES_TXTATR_END <= nWhich )
+ break; // end of text attributes
+
+ if( CmpAttr( rSet.Get( nWhich, !m_bNoColls ), *pItem ) )
+ {
+ m_pFindArr[ nWhich - m_nArrStart ] =
+ SwSrchChrAttr( *pItem, m_nNodeStart, m_nNodeEnd );
+ m_nFound++;
+ }
+ }
+
+ pItem = aIter.NextItem();
+ } while (pItem);
+}
+
+static bool
+lcl_IsAttributeIgnorable(sal_Int32 const nNdStart, sal_Int32 const nNdEnd,
+ SwSrchChrAttr const& rTmp)
+{
+ // #i115528#: if there is a paragraph attribute, it has been added by the
+ // SwAttrCheckArr ctor, and nFound is 1.
+ // if the paragraph is entirely covered by hints that override the paragraph
+ // attribute, then this function must find an attribute to decrement nFound!
+ // so check for an empty search range, let attributes that start/end there
+ // cover it, and hope for the best...
+ return ((nNdEnd == nNdStart)
+ ? ((rTmp.nEnd < nNdStart) || (nNdEnd < rTmp.nStt))
+ : ((rTmp.nEnd <= nNdStart) || (nNdEnd <= rTmp.nStt)));
+}
+
+bool SwAttrCheckArr::SetAttrFwd( const SwTextAttr& rAttr )
+{
+ SwSrchChrAttr aTmp( rAttr.GetAttr(), rAttr.GetStart(), rAttr.GetAnyEnd() );
+
+ // ignore all attributes not in search range
+ if (lcl_IsAttributeIgnorable(m_nNodeStart, m_nNodeEnd, aTmp))
+ {
+ return Found();
+ }
+
+ const SfxPoolItem* pItem;
+ // here we explicitly also search in character templates
+ sal_uInt16 nWhch = rAttr.Which();
+ std::optional<SfxWhichIter> oIter;
+ const SfxPoolItem* pTmpItem = nullptr;
+ const SfxItemSet* pSet = nullptr;
+ if( RES_TXTATR_CHARFMT == nWhch || RES_TXTATR_AUTOFMT == nWhch )
+ {
+ if( m_bNoColls && RES_TXTATR_CHARFMT == nWhch )
+ return Found();
+ pTmpItem = nullptr;
+ pSet = CharFormat::GetItemSet( rAttr.GetAttr() );
+ if ( pSet )
+ {
+ oIter.emplace( *pSet );
+ nWhch = oIter->FirstWhich();
+ while( nWhch &&
+ SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
+ nWhch = oIter->NextWhich();
+ if( !nWhch )
+ pTmpItem = nullptr;
+ }
+ }
+ else
+ pTmpItem = &rAttr.GetAttr();
+
+ while( pTmpItem )
+ {
+ SfxItemState eState = m_aComapeSet.GetItemState( nWhch, false, &pItem );
+ if( SfxItemState::DONTCARE == eState || SfxItemState::SET == eState )
+ {
+ sal_uInt16 n;
+ SwSrchChrAttr* pCmp;
+
+ // first delete all up to start position that are already invalid
+ SwSrchChrAttr* pArrPtr;
+ if( m_nFound )
+ for( pArrPtr = m_pFindArr, n = 0; n < m_nArrLen;
+ ++n, ++pArrPtr )
+ if( pArrPtr->nWhich && pArrPtr->nEnd <= aTmp.nStt )
+ {
+ pArrPtr->nWhich = 0; // deleted
+ m_nFound--;
+ }
+
+ // delete all up to start position that are already invalid and
+ // move all "open" ones (= stick out over start position) from stack
+ // into FndSet
+ if( m_nStackCount )
+ for( pArrPtr = m_pStackArr, n=0; n < m_nArrLen; ++n, ++pArrPtr )
+ {
+ if( !pArrPtr->nWhich )
+ continue;
+
+ if( pArrPtr->nEnd <= aTmp.nStt )
+ {
+ pArrPtr->nWhich = 0; // deleted
+ if( !--m_nStackCount )
+ break;
+ }
+ else if( pArrPtr->nStt <= aTmp.nStt )
+ {
+ pCmp = &m_pFindArr[ n ];
+ if( pCmp->nWhich )
+ {
+ if( pCmp->nEnd < pArrPtr->nEnd ) // extend
+ pCmp->nEnd = pArrPtr->nEnd;
+ }
+ else
+ {
+ *pCmp = *pArrPtr;
+ m_nFound++;
+ }
+ pArrPtr->nWhich = 0;
+ if( !--m_nStackCount )
+ break;
+ }
+ }
+
+ bool bContinue = false;
+
+ if( SfxItemState::DONTCARE == eState )
+ {
+ // Will the attribute become valid?
+ if( !CmpAttr( m_aComapeSet.GetPool()->GetDefaultItem( nWhch ),
+ *pTmpItem ))
+ {
+ // search attribute and extend if needed
+ pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
+ if( !pCmp->nWhich )
+ {
+ *pCmp = aTmp; // not found, insert
+ m_nFound++;
+ }
+ else if( pCmp->nEnd < aTmp.nEnd ) // extend?
+ pCmp->nEnd = aTmp.nEnd;
+
+ bContinue = true;
+ }
+ }
+ // Will the attribute become valid?
+ else if( CmpAttr( *pItem, *pTmpItem ) )
+ {
+ m_pFindArr[ nWhch - m_nArrStart ] = aTmp;
+ ++m_nFound;
+ bContinue = true;
+ }
+
+ // then is has to go on the stack
+ if( !bContinue )
+ {
+ pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
+ if (pCmp->nWhich )
+ {
+ // exists on stack, only if it is even bigger
+ if( pCmp->nEnd > aTmp.nEnd )
+ {
+ OSL_ENSURE( !m_pStackArr[ nWhch - m_nArrStart ].nWhich,
+ "slot on stack is still in use" );
+
+ if( aTmp.nStt <= pCmp->nStt )
+ pCmp->nStt = aTmp.nEnd;
+ else
+ pCmp->nEnd = aTmp.nStt;
+
+ m_pStackArr[ nWhch - m_nArrStart ] = *pCmp;
+ m_nStackCount++;
+ }
+ pCmp->nWhich = 0;
+ m_nFound--;
+ }
+ }
+ }
+ if( oIter )
+ {
+ assert(pSet && "otherwise no oIter");
+ nWhch = oIter->NextWhich();
+ while( nWhch &&
+ SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
+ nWhch = oIter->NextWhich();
+ if( !nWhch )
+ break;
+ }
+ else
+ break;
+ }
+ oIter.reset();
+ return Found();
+}
+
+bool SwAttrCheckArr::SetAttrBwd( const SwTextAttr& rAttr )
+{
+ SwSrchChrAttr aTmp( rAttr.GetAttr(), rAttr.GetStart(), rAttr.GetAnyEnd() );
+
+ // ignore all attributes not in search range
+ if (lcl_IsAttributeIgnorable(m_nNodeStart, m_nNodeEnd, aTmp))
+ {
+ return Found();
+ }
+
+ const SfxPoolItem* pItem;
+ // here we explicitly also search in character templates
+ sal_uInt16 nWhch = rAttr.Which();
+ std::optional<SfxWhichIter> oIter;
+ const SfxPoolItem* pTmpItem = nullptr;
+ const SfxItemSet* pSet = nullptr;
+ if( RES_TXTATR_CHARFMT == nWhch || RES_TXTATR_AUTOFMT == nWhch )
+ {
+ if( m_bNoColls && RES_TXTATR_CHARFMT == nWhch )
+ return Found();
+
+ pSet = CharFormat::GetItemSet( rAttr.GetAttr() );
+ if ( pSet )
+ {
+ oIter.emplace( *pSet );
+ nWhch = oIter->FirstWhich();
+ while( nWhch &&
+ SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
+ nWhch = oIter->NextWhich();
+ if( !nWhch )
+ pTmpItem = nullptr;
+ }
+ }
+ else
+ pTmpItem = &rAttr.GetAttr();
+
+ while( pTmpItem )
+ {
+ SfxItemState eState = m_aComapeSet.GetItemState( nWhch, false, &pItem );
+ if( SfxItemState::DONTCARE == eState || SfxItemState::SET == eState )
+ {
+ sal_uInt16 n;
+ SwSrchChrAttr* pCmp;
+
+ // first delete all up to start position that are already invalid
+ SwSrchChrAttr* pArrPtr;
+ if( m_nFound )
+ for( pArrPtr = m_pFindArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr )
+ if( pArrPtr->nWhich && pArrPtr->nStt >= aTmp.nEnd )
+ {
+ pArrPtr->nWhich = 0; // deleted
+ m_nFound--;
+ }
+
+ // delete all up to start position that are already invalid and
+ // move all "open" ones (= stick out over start position) from stack
+ // into FndSet
+ if( m_nStackCount )
+ for( pArrPtr = m_pStackArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr )
+ {
+ if( !pArrPtr->nWhich )
+ continue;
+
+ if( pArrPtr->nStt >= aTmp.nEnd )
+ {
+ pArrPtr->nWhich = 0; // deleted
+ if( !--m_nStackCount )
+ break;
+ }
+ else if( pArrPtr->nEnd >= aTmp.nEnd )
+ {
+ pCmp = &m_pFindArr[ n ];
+ if( pCmp->nWhich )
+ {
+ if( pCmp->nStt > pArrPtr->nStt ) // extend
+ pCmp->nStt = pArrPtr->nStt;
+ }
+ else
+ {
+ *pCmp = *pArrPtr;
+ m_nFound++;
+ }
+ pArrPtr->nWhich = 0;
+ if( !--m_nStackCount )
+ break;
+ }
+ }
+
+ bool bContinue = false;
+ if( SfxItemState::DONTCARE == eState )
+ {
+ // Will the attribute become valid?
+ if( !CmpAttr( m_aComapeSet.GetPool()->GetDefaultItem( nWhch ),
+ *pTmpItem ) )
+ {
+ // search attribute and extend if needed
+ pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
+ if( !pCmp->nWhich )
+ {
+ *pCmp = aTmp; // not found, insert
+ m_nFound++;
+ }
+ else if( pCmp->nStt > aTmp.nStt ) // extend?
+ pCmp->nStt = aTmp.nStt;
+
+ bContinue = true;
+ }
+ }
+ // Will the attribute become valid?
+ else if( CmpAttr( *pItem, *pTmpItem ))
+ {
+ m_pFindArr[ nWhch - m_nArrStart ] = aTmp;
+ ++m_nFound;
+ bContinue = true;
+ }
+
+ // then is has to go on the stack
+ if( !bContinue )
+ {
+ pCmp = &m_pFindArr[ nWhch - m_nArrStart ];
+ if( pCmp->nWhich )
+ {
+ // exists on stack, only if it is even bigger
+ if( pCmp->nStt < aTmp.nStt )
+ {
+ OSL_ENSURE( !m_pStackArr[ nWhch - m_nArrStart ].nWhich,
+ "slot on stack is still in use" );
+
+ if( aTmp.nEnd <= pCmp->nEnd )
+ pCmp->nEnd = aTmp.nStt;
+ else
+ pCmp->nStt = aTmp.nEnd;
+
+ m_pStackArr[ nWhch - m_nArrStart ] = *pCmp;
+ m_nStackCount++;
+ }
+ pCmp->nWhich = 0;
+ m_nFound--;
+ }
+ }
+ }
+ if( oIter )
+ {
+ assert(pSet && "otherwise no oIter");
+ nWhch = oIter->NextWhich();
+ while( nWhch &&
+ SfxItemState::SET != oIter->GetItemState( true, &pTmpItem ) )
+ nWhch = oIter->NextWhich();
+ if( !nWhch )
+ break;
+ }
+ else
+ break;
+ }
+ oIter.reset();
+ return Found();
+}
+
+sal_Int32 SwAttrCheckArr::Start() const
+{
+ sal_Int32 nStart = m_nNodeStart;
+ SwSrchChrAttr* pArrPtr = m_pFindArr;
+ for( sal_uInt16 n = 0; n < m_nArrLen; ++n, ++pArrPtr )
+ if( pArrPtr->nWhich && pArrPtr->nStt > nStart )
+ nStart = pArrPtr->nStt;
+
+ return nStart;
+}
+
+sal_Int32 SwAttrCheckArr::End() const
+{
+ SwSrchChrAttr* pArrPtr = m_pFindArr;
+ sal_Int32 nEnd = m_nNodeEnd;
+ for( sal_uInt16 n = 0; n < m_nArrLen; ++n, ++pArrPtr )
+ if( pArrPtr->nWhich && pArrPtr->nEnd < nEnd )
+ nEnd = pArrPtr->nEnd;
+
+ return nEnd;
+}
+
+bool SwAttrCheckArr::CheckStack()
+{
+ if( !m_nStackCount )
+ return false;
+
+ sal_uInt16 n;
+ const sal_Int32 nSttPos = Start();
+ const sal_Int32 nEndPos = End();
+ SwSrchChrAttr* pArrPtr;
+ for( pArrPtr = m_pStackArr, n = 0; n < m_nArrLen; ++n, ++pArrPtr )
+ {
+ if( !pArrPtr->nWhich )
+ continue;
+
+ if( m_bForward ? pArrPtr->nEnd <= nSttPos : pArrPtr->nStt >= nEndPos )
+ {
+ pArrPtr->nWhich = 0; // deleted
+ if( !--m_nStackCount )
+ return m_nFound == m_aComapeSet.Count();
+ }
+ else if( m_bForward ? pArrPtr->nStt < nEndPos : pArrPtr->nEnd > nSttPos )
+ {
+ // move all "open" ones (= stick out over start position) into FndSet
+ OSL_ENSURE( !m_pFindArr[ n ].nWhich, "slot in array is already in use" );
+ m_pFindArr[ n ] = *pArrPtr;
+ pArrPtr->nWhich = 0;
+ m_nFound++;
+ if( !--m_nStackCount )
+ return m_nFound == m_aComapeSet.Count();
+ }
+ }
+ return m_nFound == m_aComapeSet.Count();
+}
+
+static bool lcl_SearchForward( const SwTextNode& rTextNd, SwAttrCheckArr& rCmpArr,
+ SwPaM& rPam )
+{
+ sal_Int32 nEndPos;
+ rCmpArr.SetNewSet( rTextNd, rPam );
+ if( !rTextNd.HasHints() )
+ {
+ if( !rCmpArr.Found() )
+ return false;
+ nEndPos = rCmpArr.GetNdEnd();
+ lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, true );
+ return true;
+ }
+
+ const SwpHints& rHtArr = rTextNd.GetSwpHints();
+ const SwTextAttr* pAttr;
+ size_t nPos = 0;
+
+ // if everything is already there then check with which it will be ended
+ if( rCmpArr.Found() )
+ {
+ for( ; nPos < rHtArr.Count(); ++nPos )
+ {
+ pAttr = rHtArr.Get( nPos );
+ if( !rCmpArr.SetAttrFwd( *pAttr ) )
+ {
+ if( rCmpArr.GetNdStt() < pAttr->GetStart() )
+ {
+ // found end
+ auto nTmpStart = pAttr->GetStart();
+ lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(),
+ &nTmpStart, true );
+ return true;
+ }
+ // continue search
+ break;
+ }
+ }
+
+ if( nPos == rHtArr.Count() && rCmpArr.Found() )
+ {
+ // found
+ nEndPos = rCmpArr.GetNdEnd();
+ lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, true );
+ return true;
+ }
+ }
+
+ sal_Int32 nSttPos;
+ for( ; nPos < rHtArr.Count(); ++nPos )
+ {
+ pAttr = rHtArr.Get( nPos );
+ if( rCmpArr.SetAttrFwd( *pAttr ) )
+ {
+ // Do multiple start at that position? Do also check those:
+ nSttPos = pAttr->GetStart();
+ while( ++nPos < rHtArr.Count() )
+ {
+ pAttr = rHtArr.Get( nPos );
+ if( nSttPos != pAttr->GetStart() || !rCmpArr.SetAttrFwd( *pAttr ) )
+ break;
+ }
+
+ if( !rCmpArr.Found() )
+ continue;
+
+ // then we have our search area
+ nSttPos = rCmpArr.Start();
+ nEndPos = rCmpArr.End();
+ if( nSttPos > nEndPos )
+ return false;
+
+ lcl_SetAttrPam( rPam, nSttPos, &nEndPos, true );
+ return true;
+ }
+ }
+
+ if( !rCmpArr.CheckStack() )
+ return false;
+ nSttPos = rCmpArr.Start();
+ nEndPos = rCmpArr.End();
+ if( nSttPos > nEndPos )
+ return false;
+
+ lcl_SetAttrPam( rPam, nSttPos, &nEndPos, true );
+ return true;
+}
+
+static bool lcl_SearchBackward( const SwTextNode& rTextNd, SwAttrCheckArr& rCmpArr,
+ SwPaM& rPam )
+{
+ sal_Int32 nEndPos;
+ rCmpArr.SetNewSet( rTextNd, rPam );
+ if( !rTextNd.HasHints() )
+ {
+ if( !rCmpArr.Found() )
+ return false;
+ nEndPos = rCmpArr.GetNdEnd();
+ lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, false );
+ return true;
+ }
+
+ const SwpHints& rHtArr = rTextNd.GetSwpHints();
+ const SwTextAttr* pAttr;
+ size_t nPos = rHtArr.Count();
+ sal_Int32 nSttPos;
+
+ // if everything is already there then check with which it will be ended
+ if( rCmpArr.Found() )
+ {
+ while( nPos )
+ {
+ pAttr = rHtArr.GetSortedByEnd( --nPos );
+ if( !rCmpArr.SetAttrBwd( *pAttr ) )
+ {
+ nSttPos = pAttr->GetAnyEnd();
+ if( nSttPos < rCmpArr.GetNdEnd() )
+ {
+ // found end
+ nEndPos = rCmpArr.GetNdEnd();
+ lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false );
+ return true;
+ }
+
+ // continue search
+ break;
+ }
+ }
+
+ if( !nPos && rCmpArr.Found() )
+ {
+ // found
+ nEndPos = rCmpArr.GetNdEnd();
+ lcl_SetAttrPam( rPam, rCmpArr.GetNdStt(), &nEndPos, false );
+ return true;
+ }
+ }
+
+ while( nPos )
+ {
+ pAttr = rHtArr.GetSortedByEnd( --nPos );
+ if( rCmpArr.SetAttrBwd( *pAttr ) )
+ {
+ // Do multiple start at that position? Do also check those:
+ if( nPos )
+ {
+ nEndPos = pAttr->GetAnyEnd();
+ while( --nPos )
+ {
+ pAttr = rHtArr.GetSortedByEnd( nPos );
+ if( nEndPos != pAttr->GetAnyEnd() || !rCmpArr.SetAttrBwd( *pAttr ) )
+ break;
+ }
+ }
+ if( !rCmpArr.Found() )
+ continue;
+
+ // then we have our search area
+ nSttPos = rCmpArr.Start();
+ nEndPos = rCmpArr.End();
+ if( nSttPos > nEndPos )
+ return false;
+
+ lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false );
+ return true;
+ }
+ }
+
+ if( !rCmpArr.CheckStack() )
+ return false;
+ nSttPos = rCmpArr.Start();
+ nEndPos = rCmpArr.End();
+ if( nSttPos > nEndPos )
+ return false;
+
+ lcl_SetAttrPam( rPam, nSttPos, &nEndPos, false );
+ return true;
+}
+
+static bool lcl_Search( const SwContentNode& rCNd, const SfxItemSet& rCmpSet, bool bNoColls )
+{
+ // search only hard attribution?
+ if( bNoColls && !rCNd.HasSwAttrSet() )
+ return false;
+
+ const SfxItemSet& rNdSet = rCNd.GetSwAttrSet();
+ SfxItemIter aIter( rCmpSet );
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+ const SfxPoolItem* pNdItem;
+ sal_uInt16 nWhich;
+
+ do
+ {
+ if( IsInvalidItem( pItem ))
+ {
+ nWhich = rCmpSet.GetWhichByOffset( aIter.GetCurPos() );
+ if( SfxItemState::SET != rNdSet.GetItemState( nWhich, !bNoColls, &pNdItem )
+ || CmpAttr( *pNdItem, rNdSet.GetPool()->GetDefaultItem( nWhich ) ))
+ return false;
+ }
+ else
+ {
+ nWhich = pItem->Which();
+
+ if( !CmpAttr( rNdSet.Get( nWhich, !bNoColls ), *pItem ))
+ return false;
+ }
+
+ pItem = aIter.NextItem();
+ } while (pItem);
+ return true; // found
+}
+
+namespace sw {
+
+bool FindAttrImpl(SwPaM & rSearchPam,
+ const SfxPoolItem& rAttr, SwMoveFnCollection const & fnMove,
+ const SwPaM & rRegion, bool bInReadOnly,
+ SwRootFrame const*const pLayout)
+{
+ // determine which attribute is searched:
+ const sal_uInt16 nWhich = rAttr.Which();
+ bool bCharAttr = isCHRATR(nWhich) || isTXTATR(nWhich);
+ assert(isTXTATR(nWhich)); // sw_redlinehide: only works for non-formatting hints such as needed in UpdateFields; use FindAttrsImpl for others
+
+ std::optional<SwPaM> oPam;
+ sw::MakeRegion(fnMove, rRegion, oPam);
+
+ bool bFound = false;
+ bool bFirst = true;
+ const bool bSrchForward = &fnMove == &fnMoveForward;
+ SwContentNode * pNode;
+
+ // if at beginning/end then move it out of the node
+ if( bSrchForward
+ ? oPam->GetPoint()->GetContentIndex() == oPam->GetPointContentNode()->Len()
+ : !oPam->GetPoint()->GetContentIndex() )
+ {
+ if( !(*fnMove.fnPos)( oPam->GetPoint(), false ))
+ {
+ return false;
+ }
+ SwContentNode *pNd = oPam->GetPointContentNode();
+ oPam->GetPoint()->SetContent( bSrchForward ? 0 : pNd->Len() );
+ }
+
+ while (nullptr != (pNode = ::GetNode(*oPam, bFirst, fnMove, bInReadOnly, pLayout)))
+ {
+ if( bCharAttr )
+ {
+ if( !pNode->IsTextNode() ) // CharAttr are only in text nodes
+ continue;
+
+ SwTextFrame const*const pFrame(pLayout
+ ? static_cast<SwTextFrame const*>(pNode->getLayoutFrame(pLayout))
+ : nullptr);
+ if (pFrame)
+ {
+ SwTextNode const* pAttrNode(nullptr);
+ SwTextAttr const* pAttr(nullptr);
+ if (bSrchForward)
+ {
+ sw::MergedAttrIter iter(*pFrame);
+ do
+ {
+ pAttr = iter.NextAttr(&pAttrNode);
+ }
+ while (pAttr
+ && (pAttrNode->GetIndex() < oPam->GetPoint()->GetNodeIndex()
+ || (pAttrNode->GetIndex() == oPam->GetPoint()->GetNodeIndex()
+ && pAttr->GetStart() < oPam->GetPoint()->GetContentIndex())
+ || pAttr->Which() != nWhich));
+ }
+ else
+ {
+ sw::MergedAttrIterReverse iter(*pFrame);
+ do
+ {
+ pAttr = iter.PrevAttr(&pAttrNode);
+ }
+ while (pAttr
+ && (oPam->GetPoint()->GetNodeIndex() < pAttrNode->GetIndex()
+ || (oPam->GetPoint()->GetNodeIndex() == pAttrNode->GetIndex()
+ && oPam->GetPoint()->GetContentIndex() <= pAttr->GetStart())
+ || pAttr->Which() != nWhich));
+ }
+ if (pAttr)
+ {
+ assert(pAttrNode);
+ oPam->GetPoint()->Assign(*pAttrNode);
+ lcl_SetAttrPam(*oPam, pAttr->GetStart(), pAttr->End(), bSrchForward);
+ bFound = true;
+ break;
+ }
+ }
+ else if (!pLayout && pNode->GetTextNode()->HasHints() &&
+ lcl_SearchAttr(*pNode->GetTextNode(), *oPam, rAttr, fnMove))
+ {
+ bFound = true;
+ }
+ if (bFound)
+ {
+ // set to the values of the attribute
+ rSearchPam.SetMark();
+ *rSearchPam.GetPoint() = *oPam->GetPoint();
+ *rSearchPam.GetMark() = *oPam->GetMark();
+ break;
+ }
+ else if (isTXTATR(nWhich))
+ continue;
+ }
+
+#if 0
+ // no hard attribution, so check if node was asked for this attr before
+ if( !pNode->HasSwAttrSet() )
+ {
+ SwFormat* pTmpFormat = pNode->GetFormatColl();
+ if( !aFormatArr.insert( pTmpFormat ).second )
+ continue; // collection was requested earlier
+ }
+
+ if( SfxItemState::SET == pNode->GetSwAttrSet().GetItemState( nWhich,
+ true, &pItem ))
+ {
+ // FORWARD: SPoint at the end, GetMark at the beginning of the node
+ // BACKWARD: SPoint at the beginning, GetMark at the end of the node
+ // always: incl. start and incl. end
+ *rSearchPam.GetPoint() = *pPam->GetPoint();
+ rSearchPam.SetMark();
+ rSearchPam.GetPoint()->SetContent(pNode->Len());
+ bFound = true;
+ break;
+ }
+#endif
+ }
+
+ // if backward search, switch point and mark
+ if( bFound && !bSrchForward )
+ rSearchPam.Exchange();
+
+ return bFound;
+}
+
+} // namespace sw
+
+typedef bool (*FnSearchAttr)( const SwTextNode&, SwAttrCheckArr&, SwPaM& );
+
+static bool FindAttrsImpl(SwPaM & rSearchPam,
+ const SfxItemSet& rSet, bool bNoColls, SwMoveFnCollection const & fnMove,
+ const SwPaM & rRegion, bool bInReadOnly, bool bMoveFirst,
+ SwRootFrame const*const pLayout)
+{
+ std::optional<SwPaM> oPam;
+ sw::MakeRegion(fnMove, rRegion, oPam);
+
+ bool bFound = false;
+ bool bFirst = true;
+ const bool bSrchForward = &fnMove == &fnMoveForward;
+ SwContentNode * pNode;
+ o3tl::sorted_vector<SwFormat*> aFormatArr;
+
+ // check which text/char attributes are searched
+ SwAttrCheckArr aCmpArr( rSet, bSrchForward, bNoColls );
+ SfxItemSetFixed<RES_PARATR_BEGIN, RES_GRFATR_END-1> aOtherSet( rSearchPam.GetDoc().GetAttrPool() );
+ aOtherSet.Put( rSet, false ); // got all invalid items
+
+ FnSearchAttr fnSearch = bSrchForward
+ ? (&::lcl_SearchForward)
+ : (&::lcl_SearchBackward);
+
+ // if at beginning/end then move it out of the node
+ if( bMoveFirst &&
+ ( bSrchForward
+ ? oPam->GetPoint()->GetContentIndex() == oPam->GetPointContentNode()->Len()
+ : !oPam->GetPoint()->GetContentIndex() ) )
+ {
+ if( !(*fnMove.fnPos)( oPam->GetPoint(), false ))
+ {
+ return false;
+ }
+ SwContentNode *pNd = oPam->GetPointContentNode();
+ oPam->GetPoint()->SetContent( bSrchForward ? 0 : pNd->Len() );
+ }
+
+ while (nullptr != (pNode = ::GetNode(*oPam, bFirst, fnMove, bInReadOnly, pLayout)))
+ {
+ SwTextFrame const*const pFrame(pLayout && pNode->IsTextNode()
+ ? static_cast<SwTextFrame const*>(pNode->getLayoutFrame(pLayout))
+ : nullptr);
+ assert(!pLayout || !pNode->IsTextNode() || pFrame);
+ // sw_redlinehide: it's apparently not possible to find break items
+ // with the UI, so checking one node is enough
+ SwContentNode const& rPropsNode(*(pFrame
+ ? pFrame->GetTextNodeForParaProps()
+ : pNode));
+
+ if( aCmpArr.Count() )
+ {
+ if( !pNode->IsTextNode() ) // CharAttr are only in text nodes
+ continue;
+
+ if (aOtherSet.Count() &&
+ !lcl_Search(rPropsNode, aOtherSet, bNoColls))
+ {
+ continue;
+ }
+ sw::MergedPara const*const pMergedPara(pFrame ? pFrame->GetMergedPara() : nullptr);
+ if (pMergedPara)
+ {
+ SwPosition const& rStart(*oPam->Start());
+ SwPosition const& rEnd(*oPam->End());
+ // no extents? fall back to searching index 0 of propsnode
+ // to find its node items
+ if (pMergedPara->extents.empty())
+ {
+ if (rStart.GetNodeIndex() <= rPropsNode.GetIndex()
+ && rPropsNode.GetIndex() <= rEnd.GetNodeIndex())
+ {
+ SwPaM tmp(rPropsNode, 0, rPropsNode, 0);
+ bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, tmp);
+ if (bFound)
+ {
+ *oPam = tmp;
+ }
+ }
+ }
+ else
+ {
+ // iterate the extents, and intersect with input pPam:
+ // the found ranges should never include delete redlines
+ // so that subsequent Replace will not affect them
+ for (size_t i = 0; i < pMergedPara->extents.size(); ++i)
+ {
+ auto const rExtent(pMergedPara->extents[bSrchForward
+ ? i
+ : pMergedPara->extents.size() - i - 1]);
+ if (rExtent.pNode->GetIndex() < rStart.GetNodeIndex()
+ || rEnd.GetNodeIndex() < rExtent.pNode->GetIndex())
+ {
+ continue;
+ }
+ sal_Int32 const nStart(rExtent.pNode == &rStart.GetNode()
+ ? rStart.GetContentIndex()
+ : 0);
+ if (rExtent.nEnd <= nStart)
+ {
+ continue;
+ }
+ sal_Int32 const nEnd(rExtent.pNode == &rEnd.GetNode()
+ ? rEnd.GetContentIndex()
+ : rExtent.pNode->Len());
+ if (nEnd < rExtent.nStart
+ || (nStart != nEnd && nEnd == rExtent.nStart))
+ {
+ continue;
+ }
+ SwPaM tmp(*rExtent.pNode, std::max(nStart, rExtent.nStart),
+ *rExtent.pNode, std::min(nEnd, rExtent.nEnd));
+ tmp.Normalize(bSrchForward);
+ bFound = (*fnSearch)(*rExtent.pNode, aCmpArr, tmp);
+ if (bFound)
+ {
+ *oPam = tmp;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, *oPam);
+ }
+ if (bFound)
+ {
+ // set to the values of the attribute
+ rSearchPam.SetMark();
+ *rSearchPam.GetPoint() = *oPam->GetPoint();
+ *rSearchPam.GetMark() = *oPam->GetMark();
+ break;
+ }
+ continue; // text attribute
+ }
+
+ if( !aOtherSet.Count() )
+ continue;
+
+ // no hard attribution, so check if node was asked for this attr before
+ // (as an optimisation)
+ if (!rPropsNode.HasSwAttrSet())
+ {
+ SwFormat* pTmpFormat = rPropsNode.GetFormatColl();
+ if( !aFormatArr.insert( pTmpFormat ).second )
+ continue; // collection was requested earlier
+ }
+
+ if (lcl_Search(rPropsNode, aOtherSet, bNoColls))
+ {
+ // FORWARD: SPoint at the end, GetMark at the beginning of the node
+ // BACKWARD: SPoint at the beginning, GetMark at the end of the node
+ if (pFrame)
+ {
+ *rSearchPam.GetPoint() = *oPam->GetPoint();
+ rSearchPam.SetMark();
+ *rSearchPam.GetMark() = pFrame->MapViewToModelPos(
+ TextFrameIndex(bSrchForward ? pFrame->GetText().getLength() : 0));
+ }
+ else
+ {
+ *rSearchPam.GetPoint() = *oPam->GetPoint();
+ rSearchPam.SetMark();
+ if (bSrchForward)
+ {
+ rSearchPam.GetPoint()->SetContent(pNode->Len());
+ }
+ else
+ {
+ rSearchPam.GetPoint()->SetContent(0);
+ }
+ }
+ bFound = true;
+ break;
+ }
+ }
+
+ // in search direction, mark precedes point, because the next iteration
+ // starts at point
+ if (bFound)
+ {
+ rSearchPam.Normalize(!bSrchForward);
+ }
+
+ return bFound;
+}
+
+namespace {
+
+/// parameters for search for attributes
+struct SwFindParaAttr : public SwFindParas
+{
+ bool m_bNoCollection;
+ const SfxItemSet *pSet, *pReplSet;
+ const i18nutil::SearchOptions2 *pSearchOpt;
+ SwCursor& m_rCursor;
+ SwRootFrame const* m_pLayout;
+ std::unique_ptr<utl::TextSearch> pSText;
+
+ SwFindParaAttr( const SfxItemSet& rSet, bool bNoCollection,
+ const i18nutil::SearchOptions2* pOpt, const SfxItemSet* pRSet,
+ SwCursor& rCursor, SwRootFrame const*const pLayout)
+ : m_bNoCollection(bNoCollection)
+ , pSet( &rSet )
+ , pReplSet( pRSet )
+ , pSearchOpt( pOpt )
+ , m_rCursor(rCursor)
+ , m_pLayout(pLayout)
+ {}
+
+ virtual ~SwFindParaAttr() {}
+
+ virtual int DoFind(SwPaM &, SwMoveFnCollection const &, const SwPaM &, bool bInReadOnly,
+ std::unique_ptr<SvxSearchItem>& xSearchItem) override;
+ virtual bool IsReplaceMode() const override;
+};
+
+}
+
+int SwFindParaAttr::DoFind(SwPaM & rCursor, SwMoveFnCollection const & fnMove,
+ const SwPaM & rRegion, bool bInReadOnly,
+ std::unique_ptr<SvxSearchItem>& xSearchItem)
+{
+ // replace string (only if text given and search is not parameterized)?
+ bool bReplaceText = pSearchOpt && ( !pSearchOpt->replaceString.isEmpty() ||
+ !pSet->Count() );
+ bool bReplaceAttr = pReplSet && pReplSet->Count();
+ bool bMoveFirst = !bReplaceAttr;
+ if( bInReadOnly && (bReplaceAttr || bReplaceText ))
+ bInReadOnly = false;
+
+ // We search for attributes, should we search for text as well?
+ {
+ SwPaM aRegion( *rRegion.GetMark(), *rRegion.GetPoint() );
+ SwPaM* pTextRegion = &aRegion;
+ SwPaM aSrchPam( *rCursor.GetPoint() );
+
+ while( true )
+ {
+ if( pSet->Count() ) // any attributes?
+ {
+ // first attributes
+ if (!FindAttrsImpl(aSrchPam, *pSet, m_bNoCollection, fnMove, aRegion, bInReadOnly, bMoveFirst, m_pLayout))
+ return FIND_NOT_FOUND;
+ bMoveFirst = true;
+
+ if( !pSearchOpt )
+ break; // ok, only attributes, so found
+
+ pTextRegion = &aSrchPam;
+ }
+ else if( !pSearchOpt )
+ return FIND_NOT_FOUND;
+
+ // then search in text of it
+ if( !pSText )
+ {
+ i18nutil::SearchOptions2 aTmp( *pSearchOpt );
+
+ // search in selection
+ aTmp.searchFlag |= (SearchFlags::REG_NOT_BEGINOFLINE |
+ SearchFlags::REG_NOT_ENDOFLINE);
+
+ aTmp.Locale = SvtSysLocale().GetLanguageTag().getLocale();
+
+ pSText.reset( new utl::TextSearch( aTmp ) );
+ }
+
+ // TODO: searching for attributes in Outliner text?!
+
+ // continue search in correct section (pTextRegion)
+ if (sw::FindTextImpl(aSrchPam, *pSearchOpt, false/*bSearchInNotes*/, *pSText, fnMove, *pTextRegion, bInReadOnly, m_pLayout, xSearchItem) &&
+ *aSrchPam.GetMark() != *aSrchPam.GetPoint() )
+ break; // found
+ else if( !pSet->Count() )
+ return FIND_NOT_FOUND; // only text and nothing found
+
+ *aRegion.GetMark() = *aSrchPam.GetPoint();
+ }
+
+ *rCursor.GetPoint() = *aSrchPam.GetPoint();
+ rCursor.SetMark();
+ *rCursor.GetMark() = *aSrchPam.GetMark();
+ }
+
+ if( bReplaceText )
+ {
+ const bool bRegExp(
+ SearchAlgorithms2::REGEXP == pSearchOpt->AlgorithmType2);
+ SwPosition& rSttCntPos = *rCursor.Start();
+ const sal_Int32 nSttCnt = rSttCntPos.GetContentIndex();
+
+ // add to shell-cursor-ring so that the regions will be moved eventually
+ SwPaM* pPrevRing(nullptr);
+ if( bRegExp )
+ {
+ pPrevRing = const_cast<SwPaM &>(rRegion).GetPrev();
+ const_cast<SwPaM &>(rRegion).GetRingContainer().merge( m_rCursor.GetRingContainer() );
+ }
+
+ std::optional<OUString> xRepl;
+ if (bRegExp)
+ xRepl = sw::ReplaceBackReferences(*pSearchOpt, &rCursor, m_pLayout);
+ sw::ReplaceImpl(rCursor,
+ xRepl ? *xRepl : pSearchOpt->replaceString, bRegExp,
+ m_rCursor.GetDoc(), m_pLayout);
+
+ m_rCursor.SaveTableBoxContent( rCursor.GetPoint() );
+
+ if( bRegExp )
+ {
+ // and remove region again
+ SwPaM* p;
+ SwPaM* pNext = const_cast<SwPaM*>(&rRegion);
+ do {
+ p = pNext;
+ pNext = p->GetNext();
+ p->MoveTo(const_cast<SwPaM*>(&rRegion));
+ } while( p != pPrevRing );
+ }
+ rSttCntPos.SetContent(nSttCnt);
+ }
+
+ if( bReplaceAttr )
+ {
+ // is the selection still existent?
+ // all searched attributes are reset to default if
+ // they are not in ReplaceSet
+ if( !pSet->Count() )
+ {
+ rCursor.GetDoc().getIDocumentContentOperations().InsertItemSet(
+ rCursor, *pReplSet, SetAttrMode::DEFAULT, m_pLayout);
+ }
+ else
+ {
+ SfxItemPool* pPool = pReplSet->GetPool();
+ SfxItemSet aSet( *pPool, pReplSet->GetRanges() );
+
+ SfxItemIter aIter( *pSet );
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+ do
+ {
+ // reset all that are not set with pool defaults
+ if( !IsInvalidItem( pItem ) && SfxItemState::SET !=
+ pReplSet->GetItemState( pItem->Which(), false ))
+ aSet.Put( pPool->GetDefaultItem( pItem->Which() ));
+
+ pItem = aIter.NextItem();
+ } while (pItem);
+ aSet.Put( *pReplSet );
+ rCursor.GetDoc().getIDocumentContentOperations().InsertItemSet(
+ rCursor, aSet, SetAttrMode::DEFAULT, m_pLayout);
+ }
+
+ return FIND_NO_RING;
+ }
+ else
+ return FIND_FOUND;
+}
+
+bool SwFindParaAttr::IsReplaceMode() const
+{
+ return ( pSearchOpt && !pSearchOpt->replaceString.isEmpty() ) ||
+ ( pReplSet && pReplSet->Count() );
+}
+
+/// search for attributes
+sal_Int32 SwCursor::FindAttrs( const SfxItemSet& rSet, bool bNoCollections,
+ SwDocPositions nStart, SwDocPositions nEnd,
+ bool& bCancel, FindRanges eFndRngs,
+ const i18nutil::SearchOptions2* pSearchOpt,
+ const SfxItemSet* pReplSet,
+ SwRootFrame const*const pLayout)
+{
+ // switch off OLE-notifications
+ SwDoc& rDoc = GetDoc();
+ Link<bool,void> aLnk( rDoc.GetOle2Link() );
+ rDoc.SetOle2Link( Link<bool,void>() );
+
+ bool bReplace = ( pSearchOpt && ( !pSearchOpt->replaceString.isEmpty() ||
+ !rSet.Count() ) ) ||
+ (pReplSet && pReplSet->Count());
+ bool const bStartUndo = rDoc.GetIDocumentUndoRedo().DoesUndo() && bReplace;
+ if (bStartUndo)
+ {
+ rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE, nullptr );
+ }
+
+ SwFindParaAttr aSwFindParaAttr( rSet, bNoCollections, pSearchOpt,
+ pReplSet, *this, pLayout );
+
+ sal_Int32 nRet = FindAll( aSwFindParaAttr, nStart, nEnd, eFndRngs, bCancel );
+ rDoc.SetOle2Link( aLnk );
+ if( nRet && bReplace )
+ rDoc.getIDocumentState().SetModified();
+
+ if (bStartUndo)
+ {
+ rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE, nullptr );
+ }
+
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/findcoll.cxx b/sw/source/core/crsr/findcoll.cxx
new file mode 100644
index 0000000000..31a64aa331
--- /dev/null
+++ b/sw/source/core/crsr/findcoll.cxx
@@ -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 .
+ */
+
+#include <swcrsr.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentState.hxx>
+#include <pamtyp.hxx>
+#include <swundo.hxx>
+#include <SwRewriter.hxx>
+#include <strings.hrc>
+
+namespace {
+
+/// parameters for a search for FormatCollections
+struct SwFindParaFormatColl : public SwFindParas
+{
+ const SwTextFormatColl *pFormatColl, *pReplColl;
+ SwRootFrame const* m_pLayout;
+ SwFindParaFormatColl(const SwTextFormatColl& rFormatColl,
+ const SwTextFormatColl *const pRpColl,
+ SwRootFrame const*const pLayout)
+ : pFormatColl( &rFormatColl )
+ , pReplColl( pRpColl )
+ , m_pLayout(pLayout)
+ {}
+ virtual ~SwFindParaFormatColl() {}
+ virtual int DoFind(SwPaM &, SwMoveFnCollection const &, const SwPaM &, bool bInReadOnly, std::unique_ptr<SvxSearchItem>& xSearchItem) override;
+ virtual bool IsReplaceMode() const override;
+};
+
+}
+
+int SwFindParaFormatColl::DoFind(SwPaM & rCursor, SwMoveFnCollection const & fnMove,
+ const SwPaM & rRegion, bool bInReadOnly,
+ std::unique_ptr<SvxSearchItem>& /*xSearchItem*/)
+{
+ int nRet = FIND_FOUND;
+ if( bInReadOnly && pReplColl )
+ bInReadOnly = false;
+
+ if (!sw::FindFormatImpl(rCursor, *pFormatColl, fnMove, rRegion, bInReadOnly, m_pLayout))
+ nRet = FIND_NOT_FOUND;
+ else if( pReplColl )
+ {
+ rCursor.GetDoc().SetTextFormatColl(rCursor,
+ const_cast<SwTextFormatColl*>(pReplColl), true, false, m_pLayout);
+ nRet = FIND_NO_RING;
+ }
+ return nRet;
+}
+
+bool SwFindParaFormatColl::IsReplaceMode() const
+{
+ return nullptr != pReplColl;
+}
+
+/// search for Format-Collections
+sal_Int32 SwCursor::FindFormat( const SwTextFormatColl& rFormatColl, SwDocPositions nStart,
+ SwDocPositions nEnd, bool& bCancel,
+ FindRanges eFndRngs, const SwTextFormatColl* pReplFormatColl,
+ SwRootFrame const*const pLayout)
+{
+ // switch off OLE-notifications
+ SwDoc& rDoc = GetDoc();
+ Link<bool,void> aLnk( rDoc.GetOle2Link() );
+ rDoc.SetOle2Link( Link<bool,void>() );
+
+ bool const bStartUndo =
+ rDoc.GetIDocumentUndoRedo().DoesUndo() && pReplFormatColl;
+ if (bStartUndo)
+ {
+ SwRewriter aRewriter;
+ aRewriter.AddRule(UndoArg1, rFormatColl.GetName());
+ aRewriter.AddRule(UndoArg2, SwResId(STR_YIELDS));
+ aRewriter.AddRule(UndoArg3, pReplFormatColl->GetName());
+
+ rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_REPLACE_STYLE,
+ &aRewriter );
+ }
+
+ SwFindParaFormatColl aSwFindParaFormatColl(rFormatColl, pReplFormatColl, pLayout);
+
+ sal_Int32 nRet = FindAll( aSwFindParaFormatColl, nStart, nEnd, eFndRngs, bCancel );
+ rDoc.SetOle2Link( aLnk );
+
+ if( nRet && pReplFormatColl )
+ rDoc.getIDocumentState().SetModified();
+
+ if (bStartUndo)
+ {
+ rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr);
+ }
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/findfmt.cxx b/sw/source/core/crsr/findfmt.cxx
new file mode 100644
index 0000000000..ba8ab83828
--- /dev/null
+++ b/sw/source/core/crsr/findfmt.cxx
@@ -0,0 +1,98 @@
+/* -*- 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 <pamtyp.hxx>
+#include <pam.hxx>
+#include <txtfrm.hxx>
+#include <ndtxt.hxx>
+#include <memory>
+
+namespace sw {
+
+bool FindFormatImpl(SwPaM & rSearchPam,
+ const SwFormat& rFormat, SwMoveFnCollection const & fnMove,
+ const SwPaM &rRegion, bool bInReadOnly,
+ SwRootFrame const*const pLayout)
+{
+ bool bFound = false;
+ const bool bSrchForward = &fnMove == &fnMoveForward;
+ std::optional<SwPaM> oPam;
+ MakeRegion( fnMove, rRegion, oPam );
+
+ // if at beginning/end then move it out of the node
+ if( bSrchForward
+ ? oPam->GetPoint()->GetContentIndex() == oPam->GetPointContentNode()->Len()
+ : !oPam->GetPoint()->GetContentIndex() )
+ {
+ if( !(*fnMove.fnPos)( oPam->GetPoint(), false ))
+ {
+ return false;
+ }
+ SwContentNode *pNd = oPam->GetPoint()->GetNode().GetContentNode();
+ oPam->GetPoint()->SetContent( bSrchForward ? 0 : pNd->Len() );
+ }
+
+ bool bFirst = true;
+ SwContentNode* pNode;
+ while (nullptr != (pNode = ::GetNode(*oPam, bFirst, fnMove, bInReadOnly, pLayout)))
+ {
+ SwTextFrame const*const pFrame(pLayout && pNode->IsTextNode()
+ ? static_cast<SwTextFrame const*>(pNode->getLayoutFrame(pLayout))
+ : nullptr);
+ assert(!pLayout || !pNode->IsTextNode() || pFrame);
+ SwContentNode const& rPropsNode(*(pFrame
+ ? pFrame->GetTextNodeForParaProps()
+ : pNode));
+
+ if (rPropsNode.GetFormatColl() == &rFormat)
+ {
+ // if a FormatCollection is found then it is definitely a SwContentNode
+
+ // FORWARD: SPoint at the end, GetMark at the beginning of the node
+ // BACKWARD: SPoint at the beginning, GetMark at the end of the node
+ // always: incl. start and incl. end
+ if (pFrame)
+ {
+ *rSearchPam.GetPoint() = *oPam->GetPoint();
+ rSearchPam.SetMark();
+ *rSearchPam.GetMark() = pFrame->MapViewToModelPos(
+ TextFrameIndex(bSrchForward ? pFrame->GetText().getLength() : 0));
+ }
+ else
+ {
+ *rSearchPam.GetPoint() = *oPam->GetPoint();
+ rSearchPam.SetMark();
+ rSearchPam.GetPoint()->SetContent(pNode->Len());
+ rSearchPam.GetMark()->SetContent(0);
+ }
+
+ // if backward search, switch point and mark
+ if( !bSrchForward )
+ rSearchPam.Exchange();
+
+ bFound = true;
+ break;
+ }
+ }
+ return bFound;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/findtxt.cxx b/sw/source/core/crsr/findtxt.cxx
new file mode 100644
index 0000000000..5f63f8f136
--- /dev/null
+++ b/sw/source/core/crsr/findtxt.cxx
@@ -0,0 +1,1172 @@
+/* -*- 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/util/SearchFlags.hpp>
+#include <com/sun/star/util/SearchResult.hpp>
+#include <comphelper/lok.hxx>
+#include <o3tl/safeint.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <svx/svdview.hxx>
+#include <svl/srchitem.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <editeng/outliner.hxx>
+#include <osl/diagnose.h>
+
+#include <wrtsh.hxx>
+#include <txatritr.hxx>
+#include <fldbas.hxx>
+#include <fmtfld.hxx>
+#include <txtfld.hxx>
+#include <txtfrm.hxx>
+#include <rootfrm.hxx>
+#include <swcrsr.hxx>
+#include <redline.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentState.hxx>
+#include <IDocumentDrawModelAccess.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <dcontact.hxx>
+#include <pamtyp.hxx>
+#include <ndtxt.hxx>
+#include <swundo.hxx>
+#include <UndoInsert.hxx>
+#include <breakit.hxx>
+#include <docsh.hxx>
+#include <PostItMgr.hxx>
+#include <view.hxx>
+
+using namespace ::com::sun::star;
+using namespace util;
+
+namespace {
+
+/// because the Find may be called on the View or the Model, we need an index
+/// afflicted by multiple personality disorder
+struct AmbiguousIndex
+{
+private:
+ sal_Int32 m_value;
+
+#ifndef NDEBUG
+ enum class tags : char { Any, Frame, Model };
+ tags m_tag;
+#endif
+
+public:
+ AmbiguousIndex() : m_value(-1)
+#ifndef NDEBUG
+ , m_tag(tags::Any)
+#endif
+ {}
+ explicit AmbiguousIndex(sal_Int32 const value
+#ifndef NDEBUG
+ , tags const tag
+#endif
+ ) : m_value(value)
+#ifndef NDEBUG
+ , m_tag(tag)
+#endif
+ {}
+
+ sal_Int32 & GetAnyIndex() { return m_value; } ///< for arithmetic
+ sal_Int32 const& GetAnyIndex() const { return m_value; } ///< for arithmetic
+ TextFrameIndex GetFrameIndex() const
+ {
+ assert(m_tag != tags::Model);
+ return TextFrameIndex(m_value);
+ }
+ sal_Int32 GetModelIndex() const
+ {
+ assert(m_tag != tags::Frame);
+ return m_value;
+ }
+ void SetFrameIndex(TextFrameIndex const value)
+ {
+#ifndef NDEBUG
+ m_tag = tags::Frame;
+#endif
+ m_value = sal_Int32(value);
+ }
+ void SetModelIndex(sal_Int32 const value)
+ {
+#ifndef NDEBUG
+ m_tag = tags::Model;
+#endif
+ m_value = value;
+ }
+
+ bool operator ==(AmbiguousIndex const& rOther) const
+ {
+ assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag);
+ return m_value == rOther.m_value;
+ }
+ bool operator <=(AmbiguousIndex const& rOther) const
+ {
+ assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag);
+ return m_value <= rOther.m_value;
+ }
+ bool operator < (AmbiguousIndex const& rOther) const
+ {
+ assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag);
+ return m_value < rOther.m_value;
+ }
+ AmbiguousIndex operator - (AmbiguousIndex const& rOther) const
+ {
+ assert(m_tag == tags::Any || rOther.m_tag == tags::Any || m_tag == rOther.m_tag);
+ return AmbiguousIndex(m_value - rOther.m_value
+#ifndef NDEBUG
+ , std::max(m_tag, rOther.m_tag)
+#endif
+ );
+ }
+};
+
+class MaybeMergedIter
+{
+ std::optional<sw::MergedAttrIter> m_oMergedIter;
+ SwTextNode const*const m_pNode;
+ size_t m_HintIndex;
+
+public:
+ MaybeMergedIter(SwTextFrame const*const pFrame, SwTextNode const*const pNode)
+ : m_pNode(pNode)
+ , m_HintIndex(0)
+ {
+ if (pFrame)
+ {
+ m_oMergedIter.emplace(*pFrame);
+ }
+ }
+
+ SwTextAttr const* NextAttr(SwTextNode const*& rpNode)
+ {
+ if (m_oMergedIter)
+ {
+ return m_oMergedIter->NextAttr(&rpNode);
+ }
+ if (SwpHints const*const pHints = m_pNode->GetpSwpHints())
+ {
+ if (m_HintIndex < pHints->Count())
+ {
+ rpNode = m_pNode;
+ return pHints->Get(m_HintIndex++);
+ }
+ }
+ return nullptr;
+ }
+};
+
+}
+
+static OUString
+lcl_CleanStr(const SwTextNode& rNd,
+ SwTextFrame const*const pFrame,
+ SwRootFrame const*const pLayout,
+ AmbiguousIndex const nStart, AmbiguousIndex & rEnd,
+ std::vector<AmbiguousIndex> &rArr,
+ bool const bRemoveSoftHyphen, bool const bRemoveCommentAnchors)
+{
+ OUStringBuffer buf(pLayout ? pFrame->GetText() : rNd.GetText());
+ rArr.clear();
+
+ MaybeMergedIter iter(pLayout ? pFrame : nullptr, pLayout ? nullptr : &rNd);
+
+ AmbiguousIndex nSoftHyphen = nStart;
+ AmbiguousIndex nHintStart;
+ bool bNewHint = true;
+ bool bNewSoftHyphen = true;
+ const AmbiguousIndex nEnd = rEnd;
+ std::vector<AmbiguousIndex> aReplaced;
+ SwTextNode const* pNextHintNode(nullptr);
+ SwTextAttr const* pNextHint(iter.NextAttr(pNextHintNode));
+
+ do
+ {
+ if ( bNewHint )
+ {
+ if (pLayout)
+ {
+ nHintStart.SetFrameIndex(pNextHint
+ ? pFrame->MapModelToView(pNextHintNode, pNextHint->GetStart())
+ : TextFrameIndex(-1));
+ }
+ else
+ {
+ nHintStart.SetModelIndex(pNextHint ? pNextHint->GetStart() : -1);
+ }
+ }
+
+ if ( bNewSoftHyphen )
+ {
+ if (pLayout)
+ {
+ nSoftHyphen.SetFrameIndex(TextFrameIndex(bRemoveSoftHyphen
+ ? pFrame->GetText().indexOf(CHAR_SOFTHYPHEN, nSoftHyphen.GetAnyIndex())
+ : -1));
+ }
+ else
+ {
+ nSoftHyphen.SetModelIndex(bRemoveSoftHyphen
+ ? rNd.GetText().indexOf(CHAR_SOFTHYPHEN, nSoftHyphen.GetAnyIndex())
+ : -1);
+ }
+ }
+
+ bNewHint = false;
+ bNewSoftHyphen = false;
+ AmbiguousIndex nStt;
+
+ // Check if next stop is a hint.
+ if (0 <= nHintStart.GetAnyIndex()
+ && (-1 == nSoftHyphen.GetAnyIndex() || nHintStart < nSoftHyphen)
+ && nHintStart < nEnd )
+ {
+ nStt = nHintStart;
+ bNewHint = true;
+ }
+ // Check if next stop is a soft hyphen.
+ else if ( -1 != nSoftHyphen.GetAnyIndex()
+ && (-1 == nHintStart.GetAnyIndex() || nSoftHyphen < nHintStart)
+ && nSoftHyphen < nEnd)
+ {
+ nStt = nSoftHyphen;
+ bNewSoftHyphen = true;
+ }
+ // If nSoftHyphen == nHintStart, the current hint *must* be a hint with an end.
+ else if (-1 != nSoftHyphen.GetAnyIndex() && nSoftHyphen == nHintStart)
+ {
+ nStt = nSoftHyphen;
+ bNewHint = true;
+ bNewSoftHyphen = true;
+ }
+ else
+ break;
+
+ AmbiguousIndex nCurrent(nStt);
+ nCurrent.GetAnyIndex() -= rArr.size();
+
+ if ( bNewHint )
+ {
+ if (pNextHint && pNextHint->HasDummyChar() && (nStart <= nStt))
+ {
+ switch (pNextHint->Which())
+ {
+ case RES_TXTATR_FLYCNT:
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_REFMARK:
+ case RES_TXTATR_TOXMARK:
+ case RES_TXTATR_META:
+ case RES_TXTATR_METAFIELD:
+ {
+ // (1998) they are desired as separators and
+ // belong not any longer to a word.
+ // they should also be ignored at a
+ // beginning/end of a sentence if blank. Those are
+ // simply removed if first. If at the end, we keep the
+ // replacement and remove afterwards all at a string's
+ // end (might be normal 0x7f).
+ const bool bEmpty = pNextHint->Which() != RES_TXTATR_FIELD
+ || (static_txtattr_cast<SwTextField const*>(pNextHint)->GetFormatField().GetField()->ExpandField(true, pLayout).isEmpty());
+ if ( bEmpty && nStart == nCurrent )
+ {
+ rArr.push_back( nCurrent );
+ if (rEnd.GetAnyIndex() > nCurrent.GetAnyIndex())
+ {
+ --rEnd.GetAnyIndex();
+ }
+ buf.remove(nCurrent.GetAnyIndex(), 1);
+ }
+ else
+ {
+ if ( bEmpty )
+ aReplaced.push_back( nCurrent );
+ buf[nCurrent.GetAnyIndex()] = '\x7f';
+ }
+ }
+ break;
+ case RES_TXTATR_ANNOTATION:
+ {
+ if( bRemoveCommentAnchors )
+ {
+ rArr.push_back( nCurrent );
+ if (rEnd.GetAnyIndex() > nCurrent.GetAnyIndex())
+ {
+ --rEnd.GetAnyIndex();
+ }
+ buf.remove( nCurrent.GetAnyIndex(), 1 );
+ }
+ }
+ break;
+ default:
+ OSL_FAIL( "unknown case in lcl_CleanStr" );
+ break;
+ }
+ }
+ pNextHint = iter.NextAttr(pNextHintNode);
+ }
+
+ if ( bNewSoftHyphen )
+ {
+ rArr.push_back( nCurrent );
+
+ // If the soft hyphen to be removed is past the end of the range we're searching in,
+ // don't adjust the end.
+ if (rEnd.GetAnyIndex() > nCurrent.GetAnyIndex())
+ {
+ --rEnd.GetAnyIndex();
+ }
+
+ buf.remove(nCurrent.GetAnyIndex(), 1);
+ ++nSoftHyphen.GetAnyIndex();
+ }
+ }
+ while ( true );
+
+ for (auto i = aReplaced.size(); i; )
+ {
+ const AmbiguousIndex nTmp = aReplaced[ --i ];
+ if (nTmp.GetAnyIndex() == buf.getLength() - 1)
+ {
+ buf.truncate(nTmp.GetAnyIndex());
+ rArr.push_back( nTmp );
+ --rEnd.GetAnyIndex();
+ }
+ }
+
+ return buf.makeStringAndClear();
+}
+
+static bool DoSearch(SwPaM & rSearchPam,
+ const i18nutil::SearchOptions2& rSearchOpt, utl::TextSearch& rSText,
+ SwMoveFnCollection const & fnMove,
+ bool bSrchForward, bool bRegSearch, bool bChkEmptyPara, bool bChkParaEnd,
+ AmbiguousIndex & nStart, AmbiguousIndex & nEnd, AmbiguousIndex nTextLen,
+ SwTextNode const* pNode, SwTextFrame const* pTextFrame,
+ SwRootFrame const* pLayout, SwPaM* pPam);
+
+namespace sw {
+
+// @param xSearchItem allocate in parent so we can do so outside the calling loop
+bool FindTextImpl(SwPaM & rSearchPam,
+ const i18nutil::SearchOptions2& rSearchOpt, bool bSearchInNotes,
+ utl::TextSearch& rSText,
+ SwMoveFnCollection const & fnMove, const SwPaM & rRegion,
+ bool bInReadOnly, SwRootFrame const*const pLayout,
+ std::unique_ptr<SvxSearchItem>& xSearchItem)
+{
+ if( rSearchOpt.searchString.isEmpty() )
+ return false;
+
+ std::optional<SwPaM> oPam;
+ sw::MakeRegion(fnMove, rRegion, oPam);
+ const bool bSrchForward = &fnMove == &fnMoveForward;
+ SwPosition& rPtPos = *oPam->GetPoint();
+
+ // If bFound is true then the string was found and is between nStart and nEnd
+ bool bFound = false;
+ // start position in text or initial position
+ bool bFirst = true;
+ SwContentNode * pNode;
+
+ const bool bRegSearch = SearchAlgorithms2::REGEXP == rSearchOpt.AlgorithmType2;
+ const bool bChkEmptyPara = bRegSearch && 2 == rSearchOpt.searchString.getLength() &&
+ ( rSearchOpt.searchString == "^$" ||
+ rSearchOpt.searchString == "$^" );
+ const bool bChkParaEnd = bRegSearch && rSearchOpt.searchString == "$";
+
+ if (!xSearchItem)
+ {
+ xSearchItem.reset(new SvxSearchItem(SID_SEARCH_ITEM)); // this is a very expensive operation (calling configmgr etc.)
+ xSearchItem->SetSearchOptions(rSearchOpt);
+ xSearchItem->SetBackward(!bSrchForward);
+ }
+
+ // LanguageType eLastLang = 0;
+ while (nullptr != (pNode = ::GetNode(*oPam, bFirst, fnMove, bInReadOnly, pLayout)))
+ {
+ if( pNode->IsTextNode() )
+ {
+ SwTextNode& rTextNode = *pNode->GetTextNode();
+ SwTextFrame const*const pFrame(pLayout
+ ? static_cast<SwTextFrame const*>(rTextNode.getLayoutFrame(pLayout))
+ : nullptr);
+ assert(!pLayout || pFrame);
+ AmbiguousIndex nTextLen;
+ if (pLayout)
+ {
+ nTextLen.SetFrameIndex(TextFrameIndex(pFrame->GetText().getLength()));
+ }
+ else
+ {
+ nTextLen.SetModelIndex(rTextNode.GetText().getLength());
+ }
+ AmbiguousIndex nEnd;
+ if (pLayout
+ ? FrameContainsNode(*pFrame, oPam->GetMark()->GetNodeIndex())
+ : rPtPos.GetNode() == oPam->GetMark()->GetNode())
+ {
+ if (pLayout)
+ {
+ nEnd.SetFrameIndex(pFrame->MapModelToViewPos(*oPam->GetMark()));
+ }
+ else
+ {
+ nEnd.SetModelIndex(oPam->GetMark()->GetContentIndex());
+ }
+ }
+ else
+ {
+ if (bSrchForward)
+ {
+ nEnd = nTextLen;
+ }
+ else
+ {
+ if (pLayout)
+ {
+ nEnd.SetFrameIndex(TextFrameIndex(0));
+ }
+ else
+ {
+ nEnd.SetModelIndex(0);
+ }
+ }
+ }
+ AmbiguousIndex nStart;
+ if (pLayout)
+ {
+ nStart.SetFrameIndex(pFrame->MapModelToViewPos(*oPam->GetPoint()));
+ }
+ else
+ {
+ nStart.SetModelIndex(rPtPos.GetContentIndex());
+ }
+
+ /* #i80135# */
+ // if there are SwPostItFields inside our current node text, we
+ // split the text into separate pieces and search for text inside
+ // the pieces as well as inside the fields
+ MaybeMergedIter iter(pLayout ? pFrame : nullptr, pLayout ? nullptr : &rTextNode);
+
+ // count PostItFields by looping over all fields
+ std::vector<std::pair<SwTextAttr const*, AmbiguousIndex>> postits;
+ if (bSearchInNotes)
+ {
+ if (!bSrchForward)
+ {
+ std::swap(nStart, nEnd);
+ }
+
+ SwTextNode const* pTemp(nullptr);
+ while (SwTextAttr const*const pTextAttr = iter.NextAttr(pTemp))
+ {
+ if ( pTextAttr->Which()==RES_TXTATR_ANNOTATION )
+ {
+ AmbiguousIndex aPos;
+ aPos.SetModelIndex(pTextAttr->GetStart());
+ if (pLayout)
+ {
+ aPos.SetFrameIndex(pFrame->MapModelToView(pTemp, aPos.GetModelIndex()));
+ }
+ if ((nStart <= aPos) && (aPos <= nEnd))
+ {
+ postits.emplace_back(pTextAttr, aPos);
+ }
+ }
+ }
+
+ if (!bSrchForward)
+ {
+ std::swap(nStart, nEnd);
+ }
+
+ }
+
+ SwDocShell *const pDocShell = pNode->GetDoc().GetDocShell();
+ SwWrtShell *const pWrtShell = pDocShell ? pDocShell->GetWrtShell() : nullptr;
+ SwPostItMgr *const pPostItMgr = pWrtShell ? pWrtShell->GetPostItMgr() : nullptr;
+
+ // If there is an active text edit, then search there.
+ bool bEndedTextEdit = false;
+ SdrView* pSdrView = pWrtShell ? pWrtShell->GetDrawView() : nullptr;
+ if (pSdrView)
+ {
+ // If the edited object is not anchored to this node, then ignore it.
+ SdrObject* pObject = pSdrView->GetTextEditObject();
+ if (pObject)
+ {
+ if (SwFrameFormat* pFrameFormat = FindFrameFormat(pObject))
+ {
+ const SwNode* pAnchorNode = pFrameFormat->GetAnchor().GetAnchorNode();
+ if (!pAnchorNode || (pLayout
+ ? !FrameContainsNode(*pFrame, pAnchorNode->GetIndex())
+ : pAnchorNode->GetIndex() != pNode->GetIndex()))
+ pObject = nullptr;
+ }
+ }
+
+ if (pObject)
+ {
+ sal_uInt16 nResult = pSdrView->GetTextEditOutlinerView()->StartSearchAndReplace(*xSearchItem);
+ if (!nResult)
+ {
+ // If not found, end the text edit.
+ pSdrView->SdrEndTextEdit();
+ const Point aPoint(pSdrView->GetAllMarkedRect().TopLeft());
+ pSdrView->UnmarkAll();
+ pWrtShell->CallSetCursor(&aPoint, true);
+ pWrtShell->Edit();
+ bEndedTextEdit = true;
+ }
+ else
+ {
+ bFound = true;
+ break;
+ }
+ }
+ }
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // Writer and editeng selections are not supported in parallel.
+ SvxSearchItem* pSearchItem = SwView::GetSearchItem();
+ // If we just finished search in shape text, don't attempt to do that again.
+ if (!bEndedTextEdit && !(pSearchItem && pSearchItem->GetCommand() == SvxSearchCmd::FIND_ALL))
+ {
+ // If there are any shapes anchored to this node, search there.
+ SwPaM aPaM(pNode->GetDoc().GetNodes().GetEndOfContent());
+ if (pLayout)
+ {
+ *aPaM.GetPoint() = pFrame->MapViewToModelPos(nStart.GetFrameIndex());
+ }
+ else
+ {
+ aPaM.GetPoint()->Assign(rTextNode, nStart.GetModelIndex());
+ }
+ aPaM.SetMark();
+ if (pLayout)
+ {
+ aPaM.GetMark()->Assign( (pFrame->GetMergedPara()
+ ? *pFrame->GetMergedPara()->pLastNode
+ : rTextNode)
+ .GetIndex() + 1 );
+ }
+ else
+ {
+ aPaM.GetMark()->Assign( rTextNode.GetIndex() + 1 );
+ }
+ if (pNode->GetDoc().getIDocumentDrawModelAccess().Search(aPaM, *xSearchItem) && pSdrView)
+ {
+ if (SdrObject* pObject = pSdrView->GetTextEditObject())
+ {
+ if (SwFrameFormat* pFrameFormat = FindFrameFormat(pObject))
+ {
+ const SwNode* pAnchorNode = pFrameFormat->GetAnchor().GetAnchorNode();
+ if (pAnchorNode)
+ {
+ // Set search position to the shape's anchor point.
+ rSearchPam.GetPoint()->Assign(*pAnchorNode);
+ rSearchPam.SetMark();
+ bFound = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // do we need to finish a note?
+ if (pPostItMgr && pPostItMgr->HasActiveSidebarWin())
+ {
+ if (bSearchInNotes)
+ {
+ if (!postits.empty())
+ {
+ if (bSrchForward)
+ {
+ postits.erase(postits.begin());
+ }
+ else
+ {
+ postits.pop_back(); // hope that's the right one?
+ }
+ }
+ //search inside, finish and put focus back into the doc
+ if (pPostItMgr->FinishSearchReplace(rSearchOpt,bSrchForward))
+ {
+ bFound = true ;
+ break;
+ }
+ }
+ else
+ {
+ pPostItMgr->SetActiveSidebarWin(nullptr);
+ }
+ }
+
+ if (!postits.empty())
+ {
+ // now we have to split
+ AmbiguousIndex nStartInside;
+ AmbiguousIndex nEndInside;
+ sal_Int32 aLoop = bSrchForward ? 0 : postits.size();
+
+ while ((0 <= aLoop) && (o3tl::make_unsigned(aLoop) <= postits.size()))
+ {
+ if (bSrchForward)
+ {
+ if (aLoop == 0)
+ {
+ nStartInside = nStart;
+ }
+ else if (pLayout)
+ {
+ nStartInside.SetFrameIndex(postits[aLoop - 1].second.GetFrameIndex() + TextFrameIndex(1));
+ }
+ else
+ {
+ nStartInside.SetModelIndex(postits[aLoop - 1].second.GetModelIndex() + 1);
+ }
+ nEndInside = static_cast<size_t>(aLoop) == postits.size()
+ ? nEnd
+ : postits[aLoop].second;
+ nTextLen = nEndInside - nStartInside;
+ }
+ else
+ {
+ nStartInside = static_cast<size_t>(aLoop) == postits.size()
+ ? nStart
+ : postits[aLoop].second;
+ if (aLoop == 0)
+ {
+ nEndInside = nEnd;
+ }
+ else if (pLayout)
+ {
+ nEndInside.SetFrameIndex(postits[aLoop - 1].second.GetFrameIndex() + TextFrameIndex(1));
+ }
+ else
+ {
+ nEndInside.SetModelIndex(postits[aLoop - 1].second.GetModelIndex() + 1);
+ }
+ nTextLen = nStartInside - nEndInside;
+ }
+ // search inside the text between a note
+ bFound = DoSearch( rSearchPam,
+ rSearchOpt, rSText, fnMove, bSrchForward,
+ bRegSearch, bChkEmptyPara, bChkParaEnd,
+ nStartInside, nEndInside, nTextLen,
+ pNode->GetTextNode(), pFrame, pLayout,
+ oPam ? &*oPam : nullptr );
+ if ( bFound )
+ break;
+ else
+ {
+ // we should now be right in front of a note, search inside
+ if (bSrchForward
+ ? (static_cast<size_t>(aLoop) != postits.size())
+ : (aLoop != 0))
+ {
+ const SwTextAttr *const pTextAttr = bSrchForward
+ ? postits[aLoop].first
+ : postits[aLoop - 1].first;
+ if (pPostItMgr && pPostItMgr->SearchReplace(
+ static_txtattr_cast<SwTextField const*>(pTextAttr)->GetFormatField(),rSearchOpt,bSrchForward))
+ {
+ bFound = true ;
+ break;
+ }
+ }
+ }
+ aLoop = bSrchForward ? aLoop+1 : aLoop-1;
+ }
+ }
+ else
+ {
+ // if there is no SwPostItField inside or searching inside notes
+ // is disabled, we search the whole length just like before
+ bFound = DoSearch( rSearchPam,
+ rSearchOpt, rSText, fnMove, bSrchForward,
+ bRegSearch, bChkEmptyPara, bChkParaEnd,
+ nStart, nEnd, nTextLen,
+ pNode->GetTextNode(), pFrame, pLayout,
+ oPam ? &*oPam : nullptr );
+ }
+ if (bFound)
+ break;
+ }
+ }
+ return bFound;
+}
+
+} // namespace sw
+
+bool DoSearch(SwPaM & rSearchPam,
+ const i18nutil::SearchOptions2& rSearchOpt, utl::TextSearch& rSText,
+ SwMoveFnCollection const & fnMove, bool bSrchForward, bool bRegSearch,
+ bool bChkEmptyPara, bool bChkParaEnd,
+ AmbiguousIndex & nStart, AmbiguousIndex & nEnd, AmbiguousIndex const nTextLen,
+ SwTextNode const*const pNode, SwTextFrame const*const pFrame,
+ SwRootFrame const*const pLayout, SwPaM* pPam)
+{
+ bool bFound = false;
+ SwPosition& rPtPos = *pPam->GetPoint();
+ OUString sCleanStr;
+ std::vector<AmbiguousIndex> aFltArr;
+ LanguageType eLastLang = LANGUAGE_SYSTEM;
+ // if the search string contains a soft hyphen,
+ // we don't strip them from the text:
+ bool bRemoveSoftHyphens = true;
+ // if the search string contains a comment, we don't strip them from the text
+ const bool bRemoveCommentAnchors = rSearchOpt.searchString.indexOf( CH_TXTATR_INWORD ) == -1;
+
+ if ( bRegSearch )
+ {
+ if ( -1 != rSearchOpt.searchString.indexOf("\\xAD")
+ || -1 != rSearchOpt.searchString.indexOf("\\x{00AD}")
+ || -1 != rSearchOpt.searchString.indexOf("\\u00AD")
+ || -1 != rSearchOpt.searchString.indexOf("\\u00ad")
+ || -1 != rSearchOpt.searchString.indexOf("\\U000000AD")
+ || -1 != rSearchOpt.searchString.indexOf("\\N{SOFT HYPHEN}"))
+ {
+ bRemoveSoftHyphens = false;
+ }
+ }
+ else
+ {
+ if ( 1 == rSearchOpt.searchString.getLength() &&
+ CHAR_SOFTHYPHEN == rSearchOpt.searchString.toChar() )
+ bRemoveSoftHyphens = false;
+ }
+
+ if( bSrchForward )
+ sCleanStr = lcl_CleanStr(*pNode, pFrame, pLayout, nStart, nEnd,
+ aFltArr, bRemoveSoftHyphens, bRemoveCommentAnchors);
+ else
+ sCleanStr = lcl_CleanStr(*pNode, pFrame, pLayout, nEnd, nStart,
+ aFltArr, bRemoveSoftHyphens, bRemoveCommentAnchors);
+
+ std::unique_ptr<SwScriptIterator> pScriptIter;
+ sal_uInt16 nSearchScript = 0;
+ sal_uInt16 nCurrScript = 0;
+
+ if (SearchAlgorithms2::APPROXIMATE == rSearchOpt.AlgorithmType2)
+ {
+ pScriptIter.reset(new SwScriptIterator(sCleanStr, nStart.GetAnyIndex(), bSrchForward));
+ nSearchScript = g_pBreakIt->GetRealScriptOfText( rSearchOpt.searchString, 0 );
+ }
+
+ const AmbiguousIndex nStringEnd = nEnd;
+ bool bZeroMatch = false; // zero-length match, i.e. only $ anchor as regex
+ while ( ((bSrchForward && nStart < nStringEnd) ||
+ (!bSrchForward && nStringEnd < nStart)) && !bZeroMatch )
+ {
+ // SearchAlgorithms_APPROXIMATE works on a per word base so we have to
+ // provide the text searcher with the correct locale, because it uses
+ // the break-iterator
+ if ( pScriptIter )
+ {
+ nEnd.GetAnyIndex() = pScriptIter->GetScriptChgPos();
+ nCurrScript = pScriptIter->GetCurrScript();
+ if ( nSearchScript == nCurrScript )
+ {
+ const LanguageType eCurrLang = pLayout
+ ? pFrame->GetLangOfChar(bSrchForward
+ ? nStart.GetFrameIndex()
+ : nEnd.GetFrameIndex(),
+ 0, true)
+ : pNode->GetLang(bSrchForward
+ ? nStart.GetModelIndex()
+ : nEnd.GetModelIndex());
+
+ if ( eCurrLang != eLastLang )
+ {
+ const lang::Locale aLocale(
+ g_pBreakIt->GetLocale( eCurrLang ) );
+ rSText.SetLocale(rSearchOpt, aLocale);
+ eLastLang = eCurrLang;
+ }
+ }
+ pScriptIter->Next();
+ }
+ AmbiguousIndex nProxyStart = nStart;
+ AmbiguousIndex nProxyEnd = nEnd;
+ if( nSearchScript == nCurrScript &&
+ (rSText.*fnMove.fnSearch)( sCleanStr, &nProxyStart.GetAnyIndex(), &nProxyEnd.GetAnyIndex(), nullptr) &&
+ !(bZeroMatch = (nProxyStart == nProxyEnd)))
+ {
+ nStart = nProxyStart;
+ nEnd = nProxyEnd;
+ // set section correctly
+ *rSearchPam.GetPoint() = *pPam->GetPoint();
+ rSearchPam.SetMark();
+
+ // adjust start and end
+ if( !aFltArr.empty() )
+ {
+ // if backward search, switch positions temporarily
+ if (!bSrchForward) { std::swap(nStart, nEnd); }
+
+ AmbiguousIndex nNew = nStart;
+ for (size_t n = 0; n < aFltArr.size() && aFltArr[ n ] <= nStart; ++n )
+ {
+ ++nNew.GetAnyIndex();
+ }
+
+ nStart = nNew;
+ nNew = nEnd;
+ for( size_t n = 0; n < aFltArr.size() && aFltArr[ n ] < nEnd; ++n )
+ {
+ ++nNew.GetAnyIndex();
+ }
+
+ nEnd = nNew;
+ // if backward search, switch positions temporarily
+ if( !bSrchForward ) { std::swap(nStart, nEnd); }
+ }
+ if (pLayout)
+ {
+ *rSearchPam.GetMark() = pFrame->MapViewToModelPos(nStart.GetFrameIndex());
+ *rSearchPam.GetPoint() = pFrame->MapViewToModelPos(nEnd.GetFrameIndex());
+ }
+ else
+ {
+ rSearchPam.GetMark()->SetContent( nStart.GetModelIndex() );
+ rSearchPam.GetPoint()->SetContent( nEnd.GetModelIndex() );
+ }
+
+ // if backward search, switch point and mark
+ if( !bSrchForward )
+ rSearchPam.Exchange();
+ bFound = true;
+ break;
+ }
+ else
+ {
+ nEnd = nProxyEnd;
+ }
+ nStart = nEnd;
+ }
+
+ pScriptIter.reset();
+
+ if ( bFound )
+ return true;
+ else if ((bChkEmptyPara && !nStart.GetAnyIndex() && !nTextLen.GetAnyIndex())
+ || bChkParaEnd)
+ {
+ *rSearchPam.GetPoint() = *pPam->GetPoint();
+ if (pLayout)
+ {
+ *rSearchPam.GetPoint() = pFrame->MapViewToModelPos(
+ bChkParaEnd ? nTextLen.GetFrameIndex() : TextFrameIndex(0));
+ }
+ else
+ {
+ rSearchPam.GetPoint()->SetContent( bChkParaEnd ? nTextLen.GetModelIndex() : 0 );
+ }
+ rSearchPam.SetMark();
+ const SwNode *const pSttNd = bSrchForward
+ ? &rSearchPam.GetPoint()->GetNode() // end of the frame
+ : &rPtPos.GetNode(); // keep the bug as-is for now...
+ /* FIXME: this condition does not work for !bSrchForward backward
+ * search, it probably never did. (pSttNd != &rNdIdx.GetNode())
+ * is never true in this case. */
+ if( (bSrchForward || pSttNd != &rPtPos.GetNode()) &&
+ rSearchPam.Move(fnMoveForward, GoInContent) &&
+ (!bSrchForward || pSttNd != &rSearchPam.GetPoint()->GetNode()) &&
+ SwNodeOffset(1) == abs(rSearchPam.GetPoint()->GetNodeIndex() -
+ rSearchPam.GetMark()->GetNodeIndex()))
+ {
+ // if backward search, switch point and mark
+ if( !bSrchForward )
+ rSearchPam.Exchange();
+ return true;
+ }
+ }
+ return bFound;
+}
+
+namespace {
+
+/// parameters for search and replace in text
+struct SwFindParaText : public SwFindParas
+{
+ const i18nutil::SearchOptions2& m_rSearchOpt;
+ SwCursor& m_rCursor;
+ SwRootFrame const* m_pLayout;
+ utl::TextSearch m_aSText;
+ bool m_bReplace;
+ bool m_bSearchInNotes;
+
+ SwFindParaText(const i18nutil::SearchOptions2& rOpt, bool bSearchInNotes,
+ bool bRepl, SwCursor& rCursor, SwRootFrame const*const pLayout)
+ : m_rSearchOpt( rOpt )
+ , m_rCursor( rCursor )
+ , m_pLayout(pLayout)
+ , m_aSText(rOpt)
+ , m_bReplace( bRepl )
+ , m_bSearchInNotes( bSearchInNotes )
+ {}
+ virtual int DoFind(SwPaM &, SwMoveFnCollection const &, const SwPaM &, bool bInReadOnly, std::unique_ptr<SvxSearchItem>& xSearchItem) override;
+ virtual bool IsReplaceMode() const override;
+ virtual ~SwFindParaText();
+};
+
+}
+
+SwFindParaText::~SwFindParaText()
+{
+}
+
+int SwFindParaText::DoFind(SwPaM & rCursor, SwMoveFnCollection const & fnMove,
+ const SwPaM & rRegion, bool bInReadOnly,
+ std::unique_ptr<SvxSearchItem>& xSearchItem)
+{
+ if( bInReadOnly && m_bReplace )
+ bInReadOnly = false;
+
+ const bool bFnd = sw::FindTextImpl(rCursor, m_rSearchOpt, m_bSearchInNotes,
+ m_aSText, fnMove, rRegion, bInReadOnly, m_pLayout, xSearchItem);
+
+ if( bFnd && m_bReplace ) // replace string
+ {
+ // use replace method in SwDoc
+ const bool bRegExp(SearchAlgorithms2::REGEXP == m_rSearchOpt.AlgorithmType2);
+ const sal_Int32 nSttCnt = rCursor.Start()->GetContentIndex();
+ // add to shell-cursor-ring so that the regions will be moved eventually
+ SwPaM* pPrev(nullptr);
+ if( bRegExp )
+ {
+ pPrev = const_cast<SwPaM&>(rRegion).GetPrev();
+ const_cast<SwPaM&>(rRegion).GetRingContainer().merge( m_rCursor.GetRingContainer() );
+ }
+
+ std::optional<OUString> xRepl;
+ if (bRegExp)
+ xRepl = sw::ReplaceBackReferences(m_rSearchOpt, &rCursor, m_pLayout);
+ bool const bReplaced = sw::ReplaceImpl(rCursor,
+ xRepl ? *xRepl : m_rSearchOpt.replaceString,
+ bRegExp, m_rCursor.GetDoc(), m_pLayout);
+
+ m_rCursor.SaveTableBoxContent( rCursor.GetPoint() );
+
+ if( bRegExp )
+ {
+ // and remove region again
+ SwPaM* p;
+ SwPaM* pNext(const_cast<SwPaM*>(&rRegion));
+ do {
+ p = pNext;
+ pNext = p->GetNext();
+ p->MoveTo(const_cast<SwPaM*>(&rRegion));
+ } while( p != pPrev );
+ }
+ if (bRegExp && !bReplaced)
+ { // fdo#80715 avoid infinite loop if join failed
+ bool bRet = ((&fnMoveForward == &fnMove) ? &GoNextPara : &GoPrevPara)
+ (rCursor, fnMove);
+ (void) bRet;
+ assert(bRet); // if join failed, next node must be SwTextNode
+ }
+ else
+ rCursor.Start()->SetContent(nSttCnt);
+ return FIND_NO_RING;
+ }
+ return bFnd ? FIND_FOUND : FIND_NOT_FOUND;
+}
+
+bool SwFindParaText::IsReplaceMode() const
+{
+ return m_bReplace;
+}
+
+sal_Int32 SwCursor::Find_Text( const i18nutil::SearchOptions2& rSearchOpt, bool bSearchInNotes,
+ SwDocPositions nStart, SwDocPositions nEnd,
+ bool& bCancel, FindRanges eFndRngs, bool bReplace,
+ SwRootFrame const*const pLayout)
+{
+ // switch off OLE-notifications
+ SwDoc& rDoc = GetDoc();
+ Link<bool,void> aLnk( rDoc.GetOle2Link() );
+ rDoc.SetOle2Link( Link<bool,void>() );
+
+ bool const bStartUndo = rDoc.GetIDocumentUndoRedo().DoesUndo() && bReplace;
+ if (bStartUndo)
+ {
+ rDoc.GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE, nullptr );
+ }
+
+ bool bSearchSel = 0 != (rSearchOpt.searchFlag & SearchFlags::REG_NOT_BEGINOFLINE);
+ if( bSearchSel )
+ eFndRngs = static_cast<FindRanges>(eFndRngs | FindRanges::InSel);
+ SwFindParaText aSwFindParaText(rSearchOpt, bSearchInNotes, bReplace, *this, pLayout);
+ sal_Int32 nRet = FindAll( aSwFindParaText, nStart, nEnd, eFndRngs, bCancel );
+ rDoc.SetOle2Link( aLnk );
+ if( nRet && bReplace )
+ rDoc.getIDocumentState().SetModified();
+
+ if (bStartUndo)
+ {
+ SwRewriter rewriter(MakeUndoReplaceRewriter(
+ nRet, rSearchOpt.searchString, rSearchOpt.replaceString));
+ rDoc.GetIDocumentUndoRedo().EndUndo( SwUndoId::REPLACE, & rewriter );
+ }
+ return nRet;
+}
+
+namespace sw {
+
+bool ReplaceImpl(
+ SwPaM & rCursor,
+ OUString const& rReplacement,
+ bool const bRegExp,
+ SwDoc & rDoc,
+ SwRootFrame const*const pLayout)
+{
+ bool bReplaced(true);
+ IDocumentContentOperations & rIDCO(rDoc.getIDocumentContentOperations());
+#if 0
+ // FIXME there's some problem with multiple redlines here on Undo
+ std::vector<std::shared_ptr<SwUnoCursor>> ranges;
+ if (rDoc.getIDocumentRedlineAccess().IsRedlineOn()
+ || !pLayout
+ || !pLayout->IsHideRedlines()
+ || sw::GetRanges(ranges, rDoc, rCursor))
+ {
+ bReplaced = rIDCO.ReplaceRange(rCursor, rReplacement, bRegExp);
+ }
+ else
+ {
+ assert(!ranges.empty());
+ assert(ranges.front()->GetPoint()->GetNode() == ranges.front()->GetMark()->GetNode());
+ bReplaced = rIDCO.ReplaceRange(*ranges.front(), rReplacement, bRegExp);
+ for (auto it = ranges.begin() + 1; it != ranges.end(); ++it)
+ {
+ bReplaced &= rIDCO.DeleteAndJoin(**it);
+ }
+ }
+#else
+ IDocumentRedlineAccess const& rIDRA(rDoc.getIDocumentRedlineAccess());
+ if (pLayout && pLayout->IsHideRedlines()
+ && !rIDRA.IsRedlineOn() // otherwise: ReplaceRange will handle it
+ && (rIDRA.GetRedlineFlags() & RedlineFlags::ShowDelete)) // otherwise: ReplaceRange will DeleteRedline()
+ {
+ SwRedlineTable::size_type tmp;
+ rIDRA.GetRedline(*rCursor.Start(), &tmp);
+ while (tmp < rIDRA.GetRedlineTable().size())
+ {
+ SwRangeRedline const*const pRedline(rIDRA.GetRedlineTable()[tmp]);
+ if (*rCursor.End() <= *pRedline->Start())
+ {
+ break;
+ }
+ if (*pRedline->End() <= *rCursor.Start())
+ {
+ ++tmp;
+ continue;
+ }
+ if (pRedline->GetType() == RedlineType::Delete)
+ {
+ assert(*pRedline->Start() != *pRedline->End());
+ // search in hidden layout can't overlap redlines
+ assert(*rCursor.Start() <= *pRedline->Start() && *pRedline->End() <= *rCursor.End());
+ SwPaM pam(*pRedline, nullptr);
+ bReplaced &= rIDCO.DeleteAndJoin(pam);
+ }
+ else
+ {
+ ++tmp;
+ }
+ }
+ }
+ bReplaced &= rIDCO.ReplaceRange(rCursor, rReplacement, bRegExp);
+#endif
+ return bReplaced;
+}
+
+std::optional<OUString> ReplaceBackReferences(const i18nutil::SearchOptions2& rSearchOpt,
+ SwPaM *const pPam, SwRootFrame const*const pLayout)
+{
+ std::optional<OUString> xRet;
+ if( pPam && pPam->HasMark() &&
+ SearchAlgorithms2::REGEXP == rSearchOpt.AlgorithmType2 )
+ {
+ SwContentNode const*const pTextNode = pPam->GetPointContentNode();
+ SwContentNode const*const pMarkTextNode = pPam->GetMarkContentNode();
+ if (!pTextNode || !pTextNode->IsTextNode()
+ || !pMarkTextNode || !pMarkTextNode->IsTextNode())
+ {
+ return xRet;
+ }
+ SwTextFrame const*const pFrame(pLayout
+ ? static_cast<SwTextFrame const*>(pTextNode->getLayoutFrame(pLayout))
+ : nullptr);
+ const bool bParaEnd = rSearchOpt.searchString == "$" || rSearchOpt.searchString == "^$" || rSearchOpt.searchString == "$^";
+ if (bParaEnd || (pLayout
+ ? sw::FrameContainsNode(*pFrame, pPam->GetMark()->GetNodeIndex())
+ : pTextNode == pMarkTextNode))
+ {
+ utl::TextSearch aSText(rSearchOpt);
+ SearchResult aResult;
+ OUString aReplaceStr( rSearchOpt.replaceString );
+ if (bParaEnd)
+ {
+ static constexpr OUString aStr(u"\\n"_ustr);
+ aResult.subRegExpressions = 1;
+ aResult.startOffset = { 0 };
+ aResult.endOffset = { aStr.getLength() };
+ aSText.ReplaceBackReferences( aReplaceStr, aStr, aResult );
+ xRet = aReplaceStr;
+ }
+ else
+ {
+ AmbiguousIndex nStart;
+ AmbiguousIndex nEnd;
+ if (pLayout)
+ {
+ nStart.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->Start()));
+ nEnd.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->End()));
+ }
+ else
+ {
+ nStart.SetModelIndex(pPam->Start()->GetContentIndex());
+ nEnd.SetModelIndex(pPam->End()->GetContentIndex());
+ }
+ std::vector<AmbiguousIndex> aFltArr;
+ OUString const aStr = lcl_CleanStr(*pTextNode->GetTextNode(), pFrame, pLayout,
+ nStart, nEnd, aFltArr, false, false);
+ if (aSText.SearchForward(aStr, &nStart.GetAnyIndex(), &nEnd.GetAnyIndex(), &aResult))
+ {
+ aSText.ReplaceBackReferences( aReplaceStr, aStr, aResult );
+ xRet = aReplaceStr;
+ }
+ }
+ }
+ }
+ return xRet;
+}
+
+} // namespace sw
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/overlayrangesoutline.cxx b/sw/source/core/crsr/overlayrangesoutline.cxx
new file mode 100644
index 0000000000..265509a055
--- /dev/null
+++ b/sw/source/core/crsr/overlayrangesoutline.cxx
@@ -0,0 +1,104 @@
+/* -*- 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 "overlayrangesoutline.hxx"
+#include <svx/sdr/overlay/overlaymanager.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx>
+
+namespace
+{
+ // combine ranges geometrically to a single, ORed polygon
+ basegfx::B2DPolyPolygon impCombineRangesToPolyPolygon(const std::vector< basegfx::B2DRange >& rRanges)
+ {
+ const sal_uInt32 nCount(rRanges.size());
+ basegfx::B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ const basegfx::B2DPolygon aDiscretePolygon(basegfx::utils::createPolygonFromRect(rRanges[a]));
+
+ if(0 == a)
+ {
+ aRetval.append(aDiscretePolygon);
+ }
+ else
+ {
+ aRetval = basegfx::utils::solvePolygonOperationOr(aRetval, basegfx::B2DPolyPolygon(aDiscretePolygon));
+ }
+ }
+
+ return aRetval;
+ }
+}
+
+namespace sw::overlay
+{
+ drawinglayer::primitive2d::Primitive2DContainer OverlayRangesOutline::createOverlayObjectPrimitive2DSequence()
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aRetval;
+ const sal_uInt32 nCount(getRanges().size());
+
+ if( nCount )
+ {
+ const basegfx::BColor aRGBColor(getBaseColor().getBColor());
+ basegfx::B2DPolyPolygon aPolyPolygon(impCombineRangesToPolyPolygon(getRanges()));
+ const drawinglayer::primitive2d::Primitive2DReference aOutline(
+ new drawinglayer::primitive2d::PolyPolygonHairlinePrimitive2D(
+ std::move(aPolyPolygon),
+ aRGBColor));
+
+ aRetval.resize(1);
+ aRetval[0] = aOutline;
+ }
+
+ return aRetval;
+ }
+
+ OverlayRangesOutline::OverlayRangesOutline(
+ const Color& rColor,
+ std::vector< basegfx::B2DRange >&& rRanges )
+ : sdr::overlay::OverlayObject(rColor)
+ , maRanges(std::move(rRanges))
+ {
+ // no AA for highlight overlays
+ allowAntiAliase(false);
+ }
+
+ OverlayRangesOutline::~OverlayRangesOutline()
+ {
+ if( getOverlayManager() )
+ {
+ getOverlayManager()->remove(*this);
+ }
+ }
+
+ void OverlayRangesOutline::setRanges(std::vector< basegfx::B2DRange >&& rNew)
+ {
+ if(rNew != maRanges)
+ {
+ maRanges = std::move(rNew);
+ objectChange();
+ }
+ }
+} // end of namespace sw::overlay
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/overlayrangesoutline.hxx b/sw/source/core/crsr/overlayrangesoutline.hxx
new file mode 100644
index 0000000000..2eb053c721
--- /dev/null
+++ b/sw/source/core/crsr/overlayrangesoutline.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 .
+ */
+#ifndef INCLUDED_SW_SOURCE_CORE_CRSR_OVERLAYRANGESOUTLINE_HXX
+#define INCLUDED_SW_SOURCE_CORE_CRSR_OVERLAYRANGESOUTLINE_HXX
+
+#include <svx/sdr/overlay/overlayobject.hxx>
+#include <basegfx/range/b2drange.hxx>
+
+#include <vector>
+
+namespace sw::overlay
+ {
+ class OverlayRangesOutline : public sdr::overlay::OverlayObject
+ {
+ // geometry of overlay
+ std::vector< basegfx::B2DRange > maRanges;
+
+ // geometry creation for OverlayObject
+ virtual drawinglayer::primitive2d::Primitive2DContainer createOverlayObjectPrimitive2DSequence() override;
+
+ public:
+ OverlayRangesOutline(
+ const Color& rColor,
+ std::vector< basegfx::B2DRange >&& rRanges );
+
+ virtual ~OverlayRangesOutline() override;
+
+ // data read access
+ const std::vector< basegfx::B2DRange >& getRanges() const
+ {
+ return maRanges;
+ }
+
+ // data write access
+ void setRanges(std::vector< basegfx::B2DRange >&& rNew);
+ };
+
+} // end of namespace sw::overlay
+
+#endif // INCLUDED_SW_SOURCE_CORE_CRSR_OVERLAYRANGESOUTLINE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/pam.cxx b/sw/source/core/crsr/pam.cxx
new file mode 100644
index 0000000000..25bf8d0ef6
--- /dev/null
+++ b/sw/source/core/crsr/pam.cxx
@@ -0,0 +1,1407 @@
+/* -*- 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 <tools/gen.hxx>
+#include <editeng/protitem.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <unotools/configmgr.hxx>
+
+#include <cntfrm.hxx>
+#include <pagefrm.hxx>
+#include <doc.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <docary.hxx>
+#include <pam.hxx>
+#include <pamtyp.hxx>
+#include <txtfrm.hxx>
+#include <fmtcntnt.hxx>
+#include <frmatr.hxx>
+#include <flyfrm.hxx>
+#include <fmteiro.hxx>
+#include <section.hxx>
+#include <sectfrm.hxx>
+#include <ndtxt.hxx>
+#include <swcrsr.hxx>
+
+#include <IMark.hxx>
+#include <DocumentSettingManager.hxx>
+#include <hints.hxx>
+#include <txatbase.hxx>
+#include <osl/diagnose.h>
+#include <utility>
+#include <xmloff/odffields.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <editsh.hxx>
+#include <textcontentcontrol.hxx>
+
+// for the dump "MSC-" compiler
+static sal_Int32 GetSttOrEnd( bool bCondition, const SwContentNode& rNd )
+{
+ return bCondition ? 0 : rNd.Len();
+}
+
+SwPosition::SwPosition( const SwNodeIndex & rNodeIndex, const SwContentIndex & rContent )
+ : nNode( rNodeIndex ), nContent( rContent )
+{
+ assert((!rNodeIndex.GetNode().GetContentNode() || rNodeIndex.GetNode().GetContentNode() == rContent.GetContentNode())
+ && "parameters point to different nodes");
+}
+
+SwPosition::SwPosition( const SwNode & rNode, const SwContentIndex & rContent )
+ : nNode( rNode ), nContent( rContent )
+{
+ assert((!rNode.GetContentNode() || rNode.GetContentNode() == rContent.GetContentNode())
+ && "parameters point to different nodes");
+}
+
+SwPosition::SwPosition( const SwNodeIndex & rNodeIndex, const SwContentNode* pContentNode, sal_Int32 nContentOffset )
+ : nNode( rNodeIndex ), nContent( pContentNode, nContentOffset )
+{
+ assert((!pContentNode || pContentNode == &rNodeIndex.GetNode()) &&
+ "parameters point to different nodes");
+}
+
+SwPosition::SwPosition( const SwNode & rNode, const SwContentNode* pContentNode, sal_Int32 nContentOffset )
+ : nNode( rNode ), nContent( pContentNode, nContentOffset )
+{
+ assert((!pContentNode || pContentNode == &rNode) &&
+ "parameters point to different nodes");
+}
+
+SwPosition::SwPosition( const SwNodeIndex & rNodeIndex, SwNodeOffset nDiff, const SwContentNode* pContentNode, sal_Int32 nContentOffset )
+ : nNode( rNodeIndex, nDiff ), nContent( pContentNode, nContentOffset )
+{
+ assert((!pContentNode || pContentNode == &rNodeIndex.GetNode()) &&
+ "parameters point to different nodes");
+}
+
+SwPosition::SwPosition( const SwNodeIndex & rNodeIndex, SwNodeOffset nDiff )
+ : nNode( rNodeIndex, nDiff ), nContent( GetNode().GetContentNode() )
+{
+}
+
+SwPosition::SwPosition( const SwNode& rNode, SwNodeOffset nDiff )
+ : nNode( rNode, nDiff ), nContent( GetNode().GetContentNode() )
+{
+}
+
+SwPosition::SwPosition( SwNodes& rNodes, SwNodeOffset nIndex )
+ : nNode( rNodes, nIndex ), nContent( GetNode().GetContentNode() )
+{
+}
+
+SwPosition::SwPosition( const SwContentNode & rNode, const sal_Int32 nContentOffset )
+ : nNode( rNode ), nContent( &rNode, nContentOffset )
+{
+}
+
+SwPosition::SwPosition( const SwContentIndex & rContentIndex, short nDiff )
+ : nNode( *rContentIndex.GetContentNode() ), nContent( rContentIndex, nDiff )
+{
+}
+
+bool SwPosition::operator<(const SwPosition &rPos) const
+{
+ // cheaper to check for == first
+ if( nNode == rPos.nNode )
+ {
+ // note that positions with text node but no SwContentIndex registered are
+ // created for text frames anchored at para (see SwXFrame::getAnchor())
+ SwContentNode const*const pThisReg(GetContentNode());
+ SwContentNode const*const pOtherReg(rPos.GetContentNode());
+ if (pThisReg && pOtherReg)
+ {
+ return (nContent < rPos.nContent);
+ }
+ else // by convention position with no index is smaller
+ {
+ return pOtherReg != nullptr;
+ }
+ }
+ return nNode < rPos.nNode;
+}
+
+bool SwPosition::operator>(const SwPosition &rPos) const
+{
+ // cheaper to check for == first
+ if( nNode == rPos.nNode )
+ {
+ // note that positions with text node but no SwContentIndex registered are
+ // created for text frames anchored at para (see SwXFrame::getAnchor())
+ SwContentNode const*const pThisReg(GetContentNode());
+ SwContentNode const*const pOtherReg(rPos.GetContentNode());
+ if (pThisReg && pOtherReg)
+ {
+ return (nContent > rPos.nContent);
+ }
+ else // by convention position with no index is smaller
+ {
+ return pThisReg != nullptr;
+ }
+ }
+ return nNode > rPos.nNode;
+}
+
+bool SwPosition::operator<=(const SwPosition &rPos) const
+{
+ // cheaper to check for == first
+ if( nNode == rPos.nNode )
+ {
+ // note that positions with text node but no SwContentIndex registered are
+ // created for text frames anchored at para (see SwXFrame::getAnchor())
+ SwContentNode const*const pThisReg(GetContentNode());
+ SwContentNode const*const pOtherReg(rPos.GetContentNode());
+ if (pThisReg && pOtherReg)
+ {
+ return (nContent <= rPos.nContent);
+ }
+ else // by convention position with no index is smaller
+ {
+ return pThisReg == nullptr;
+ }
+ }
+ return nNode < rPos.nNode;
+}
+
+bool SwPosition::operator>=(const SwPosition &rPos) const
+{
+ // cheaper to check for == first
+ if( nNode == rPos.nNode )
+ {
+ // note that positions with text node but no SwContentIndex registered are
+ // created for text frames anchored at para (see SwXFrame::getAnchor())
+ SwContentNode const*const pThisReg(GetContentNode());
+ SwContentNode const*const pOtherReg(rPos.GetContentNode());
+ if (pThisReg && pOtherReg)
+ {
+ return (nContent >= rPos.nContent);
+ }
+ else // by convention position with no index is smaller
+ {
+ return pOtherReg == nullptr;
+ }
+ }
+ return nNode > rPos.nNode;
+}
+
+bool SwPosition::operator==(const SwPosition &rPos) const
+{
+ return (nNode == rPos.nNode)
+ && (nContent == rPos.nContent);
+}
+
+bool SwPosition::operator!=(const SwPosition &rPos) const
+{
+ return (nNode != rPos.nNode)
+ || (nContent != rPos.nContent);
+}
+
+SwDoc& SwPosition::GetDoc() const
+{
+ return GetNode().GetDoc();
+}
+
+void SwPosition::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwPosition"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nNode"), BAD_CAST(OString::number(sal_Int32(GetNodeIndex())).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nContent"), BAD_CAST(OString::number(GetContentIndex()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+void SwPosition::Assign( const SwNode& rNd, SwNodeOffset nDelta, sal_Int32 nContentOffset )
+{
+ nNode.Assign(rNd, nDelta);
+ assert((nNode.GetNode().GetContentNode() || nContentOffset == 0) && "setting contentoffset, but node is not SwContentNode");
+ nContent.Assign(nNode.GetNode().GetContentNode(), nContentOffset);
+}
+void SwPosition::Assign( SwNodeOffset nNodeOffset, sal_Int32 nContentOffset )
+{
+ nNode = nNodeOffset;
+ nContent.Assign(nNode.GetNode().GetContentNode(), nContentOffset);
+}
+void SwPosition::Assign( const SwContentNode& rNode, sal_Int32 nContentOffset )
+{
+ nNode = rNode;
+ nContent.Assign(&rNode, nContentOffset);
+}
+void SwPosition::Assign( const SwNode& rNd, sal_Int32 nContentOffset )
+{
+ nNode.Assign(rNd);
+ nContent.Assign(rNd.GetContentNode(), nContentOffset);
+}
+void SwPosition::Assign( const SwNodeIndex& rNdIdx, sal_Int32 nContentOffset )
+{
+ nNode = rNdIdx;
+ nContent.Assign(nNode.GetNode().GetContentNode(), nContentOffset);
+}
+void SwPosition::Adjust( SwNodeOffset nDelta )
+{
+ nNode += nDelta;
+ nContent.Assign(nNode.GetNode().GetContentNode(), 0);
+}
+void SwPosition::AdjustContent( sal_Int32 nDelta )
+{
+ assert(nNode.GetNode().GetContentNode() && "only valid to call this if we point to an SwContentNode");
+ nContent += nDelta;
+}
+void SwPosition::SetContent( sal_Int32 nContentIndex )
+{
+ assert(nNode.GetNode().GetContentNode() && "only valid to call this if we point to an SwContentNode");
+ nContent = nContentIndex;
+}
+void SwPosition::AssignStartIndex( const SwContentNode& rNd )
+{
+ nNode = rNd;
+ nContent.Assign(&rNd, 0);
+}
+void SwPosition::AssignEndIndex( const SwContentNode& rNd )
+{
+ nNode = rNd;
+ nContent.Assign(&rNd, rNd.Len());
+}
+
+
+std::ostream &operator <<(std::ostream& s, const SwPosition& position)
+{
+ return s << "SwPosition (node " << position.GetNodeIndex() << ", offset " << position.GetContentIndex() << ")";
+}
+
+namespace {
+
+enum CHKSECTION { Chk_Both, Chk_One, Chk_None };
+
+}
+
+static CHKSECTION lcl_TstIdx( SwNodeOffset nSttIdx, SwNodeOffset nEndIdx, const SwNode& rEndNd )
+{
+ SwNodeOffset nStt = rEndNd.StartOfSectionIndex(), nEnd = rEndNd.GetIndex();
+ CHKSECTION eSec = nStt < nSttIdx && nEnd >= nSttIdx ? Chk_One : Chk_None;
+ if( nStt < nEndIdx && nEnd >= nEndIdx )
+ return( eSec == Chk_One ? Chk_Both : Chk_One );
+ return eSec;
+}
+
+static bool lcl_ChkOneRange( CHKSECTION eSec, bool bChkSections,
+ const SwNode& rBaseEnd, SwNodeOffset nStt, SwNodeOffset nEnd )
+{
+ if( eSec != Chk_Both )
+ return false;
+
+ if( !bChkSections )
+ return true;
+
+ // search the surrounding section
+ const SwNodes& rNds = rBaseEnd.GetNodes();
+ const SwNode *pTmp, *pNd = rNds[ nStt ];
+ if( !pNd->IsStartNode() )
+ pNd = pNd->StartOfSectionNode();
+
+ if( pNd == rNds[ nEnd ]->StartOfSectionNode() )
+ return true; // same StartNode, same section
+
+ // already on a base node => error
+ if( !pNd->StartOfSectionIndex() )
+ return false;
+
+ for (;;)
+ {
+ pTmp = pNd->StartOfSectionNode();
+ if (pTmp->EndOfSectionNode() == &rBaseEnd )
+ break;
+ pNd = pTmp;
+ }
+
+ SwNodeOffset nSttIdx = pNd->GetIndex(), nEndIdx = pNd->EndOfSectionIndex();
+ return nSttIdx <= nStt && nStt <= nEndIdx &&
+ nSttIdx <= nEnd && nEnd <= nEndIdx;
+}
+
+/** Check if the given range is inside one of the defined top-level sections.
+ *
+ * The top-level sections are Content, AutoText, PostIts, Inserts, and Redlines.
+ *
+ * @param bChkSection if true, also check that the given range is inside
+ * a single second-level section inside any of the
+ * top-level sections, except for the Content section.
+ *
+ * @return <true> if valid range
+ */
+bool CheckNodesRange( const SwNode& rStt,
+ const SwNode& rEnd, bool bChkSection )
+{
+ const SwNodes& rNds = rStt.GetNodes();
+ SwNodeOffset nStt = rStt.GetIndex(), nEnd = rEnd.GetIndex();
+ CHKSECTION eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfContent() );
+ if( Chk_None != eSec )
+ return eSec == Chk_Both;
+
+ eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfAutotext() );
+ if( Chk_None != eSec )
+ return lcl_ChkOneRange( eSec, bChkSection,
+ rNds.GetEndOfAutotext(), nStt, nEnd );
+
+ eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfPostIts() );
+ if( Chk_None != eSec )
+ return lcl_ChkOneRange( eSec, bChkSection,
+ rNds.GetEndOfPostIts(), nStt, nEnd );
+
+ eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfInserts() );
+ if( Chk_None != eSec )
+ return lcl_ChkOneRange( eSec, bChkSection,
+ rNds.GetEndOfInserts(), nStt, nEnd );
+
+ eSec = lcl_TstIdx( nStt, nEnd, rNds.GetEndOfRedlines() );
+ if( Chk_None != eSec )
+ return lcl_ChkOneRange( eSec, bChkSection,
+ rNds.GetEndOfRedlines(), nStt, nEnd );
+
+ return false; // somewhere in between => error
+}
+
+bool GoNext(SwNode* pNd, SwContentIndex * pIdx, SwCursorSkipMode nMode )
+{
+ if( pNd->IsContentNode() )
+ return static_cast<SwContentNode*>(pNd)->GoNext( pIdx, nMode );
+ return false;
+}
+
+bool GoPrevious( SwNode* pNd, SwContentIndex * pIdx, SwCursorSkipMode nMode )
+{
+ if( pNd->IsContentNode() )
+ return static_cast<SwContentNode*>(pNd)->GoPrevious( pIdx, nMode );
+ return false;
+}
+
+SwContentNode* GoNextNds( SwNodeIndex* pIdx, bool bChk )
+{
+ SwNodeIndex aIdx( *pIdx );
+ SwContentNode* pNd = aIdx.GetNodes().GoNext( &aIdx );
+ if( pNd )
+ {
+ if( bChk && SwNodeOffset(1) != aIdx.GetIndex() - pIdx->GetIndex() &&
+ !CheckNodesRange( pIdx->GetNode(), aIdx.GetNode(), true ) )
+ pNd = nullptr;
+ else
+ *pIdx = aIdx;
+ }
+ return pNd;
+}
+
+SwContentNode* GoPreviousNds( SwNodeIndex * pIdx, bool bChk )
+{
+ SwNodeIndex aIdx( *pIdx );
+ SwContentNode* pNd = SwNodes::GoPrevious( &aIdx );
+ if( pNd )
+ {
+ if( bChk && SwNodeOffset(1) != pIdx->GetIndex() - aIdx.GetIndex() &&
+ !CheckNodesRange( pIdx->GetNode(), aIdx.GetNode(), true ) )
+ pNd = nullptr;
+ else
+ *pIdx = aIdx;
+ }
+ return pNd;
+}
+
+SwContentNode* GoNextPos( SwPosition* pIdx, bool bChk )
+{
+ SwNodeIndex aIdx( pIdx->GetNode() );
+ SwContentNode* pNd = aIdx.GetNodes().GoNext( &aIdx );
+ if( pNd )
+ {
+ if( bChk && SwNodeOffset(1) != aIdx.GetIndex() - pIdx->GetNodeIndex() &&
+ !CheckNodesRange( pIdx->GetNode(), aIdx.GetNode(), true ) )
+ pNd = nullptr;
+ else
+ pIdx->Assign(aIdx);
+ }
+ return pNd;
+}
+
+SwContentNode* GoPreviousPos( SwPosition * pIdx, bool bChk )
+{
+ SwNodeIndex aIdx( pIdx->GetNode() );
+ SwContentNode* pNd = SwNodes::GoPrevious( &aIdx );
+ if( pNd )
+ {
+ if( bChk && SwNodeOffset(1) != pIdx->GetNodeIndex() - aIdx.GetIndex() &&
+ !CheckNodesRange( pIdx->GetNode(), aIdx.GetNode(), true ) )
+ pNd = nullptr;
+ else
+ pIdx->Assign(aIdx);
+ }
+ return pNd;
+}
+
+SwPaM::SwPaM( const SwPosition& rPos, SwPaM* pRing )
+ : Ring( pRing )
+ , m_Bound1( rPos )
+ , m_Bound2( rPos.GetNode().GetNodes() ) // default initialize
+ , m_pPoint( &m_Bound1 )
+ , m_pMark( m_pPoint )
+ , m_bIsInFrontOfLabel( false )
+{
+}
+
+SwPaM::SwPaM( const SwPosition& rMark, const SwPosition& rPoint, SwPaM* pRing )
+ : Ring( pRing )
+ , m_Bound1( rMark )
+ , m_Bound2( rPoint )
+ , m_pPoint( &m_Bound2 )
+ , m_pMark( &m_Bound1 )
+ , m_bIsInFrontOfLabel( false )
+{
+}
+
+SwPaM::SwPaM( const SwNodeIndex& rMark, const SwNodeIndex& rPoint,
+ SwNodeOffset nMarkOffset, SwNodeOffset nPointOffset, SwPaM* pRing )
+ : Ring( pRing )
+ , m_Bound1( rMark )
+ , m_Bound2( rPoint )
+ , m_pPoint( &m_Bound2 )
+ , m_pMark( &m_Bound1 )
+ , m_bIsInFrontOfLabel( false )
+{
+ if ( nMarkOffset )
+ {
+ m_pMark->nNode += nMarkOffset;
+ }
+ if ( nPointOffset )
+ {
+ m_pPoint->nNode += nPointOffset;
+ }
+ m_Bound1.nContent.Assign( m_Bound1.GetNode().GetContentNode(), 0 );
+ m_Bound2.nContent.Assign( m_Bound2.GetNode().GetContentNode(), 0 );
+}
+
+SwPaM::SwPaM( const SwNode& rMark, const SwNode& rPoint,
+ SwNodeOffset nMarkOffset, SwNodeOffset nPointOffset, SwPaM* pRing )
+ : Ring( pRing )
+ , m_Bound1( rMark )
+ , m_Bound2( rPoint )
+ , m_pPoint( &m_Bound2 )
+ , m_pMark( &m_Bound1 )
+ , m_bIsInFrontOfLabel( false )
+{
+ if ( nMarkOffset )
+ {
+ m_pMark->nNode += nMarkOffset;
+ }
+ if ( nPointOffset )
+ {
+ m_pPoint->nNode += nPointOffset;
+ }
+ m_Bound1.nContent.Assign( m_Bound1.GetNode().GetContentNode(), 0 );
+ m_Bound2.nContent.Assign( m_Bound2.GetNode().GetContentNode(), 0 );
+}
+
+SwPaM::SwPaM( const SwNodeIndex& rMark, sal_Int32 nMarkContent,
+ const SwNodeIndex& rPoint, sal_Int32 nPointContent, SwPaM* pRing )
+ : Ring( pRing )
+ , m_Bound1( rMark )
+ , m_Bound2( rPoint )
+ , m_pPoint( &m_Bound2 )
+ , m_pMark( &m_Bound1 )
+ , m_bIsInFrontOfLabel( false )
+{
+ m_pPoint->nContent.Assign( rPoint.GetNode().GetContentNode(), nPointContent);
+ m_pMark ->nContent.Assign( rMark .GetNode().GetContentNode(), nMarkContent );
+}
+
+SwPaM::SwPaM( const SwNode& rMark, sal_Int32 nMarkContent,
+ const SwNode& rPoint, sal_Int32 nPointContent, SwPaM* pRing )
+ : Ring( pRing )
+ , m_Bound1( rMark )
+ , m_Bound2( rPoint )
+ , m_pPoint( &m_Bound2 )
+ , m_pMark( &m_Bound1 )
+ , m_bIsInFrontOfLabel( false )
+{
+ m_pPoint->nContent.Assign( m_pPoint->GetNode().GetContentNode(),
+ nPointContent);
+ m_pMark ->nContent.Assign( m_pMark ->GetNode().GetContentNode(),
+ nMarkContent );
+}
+
+SwPaM::SwPaM( const SwNode& rMark, SwNodeOffset nMarkOffset, sal_Int32 nMarkContent,
+ const SwNode& rPoint, SwNodeOffset nPointOffset, sal_Int32 nPointContent, SwPaM* pRing )
+ : Ring( pRing )
+ , m_Bound1( rMark )
+ , m_Bound2( rPoint )
+ , m_pPoint( &m_Bound2 )
+ , m_pMark( &m_Bound1 )
+ , m_bIsInFrontOfLabel( false )
+{
+ if ( nMarkOffset )
+ {
+ m_pMark->nNode += nMarkOffset;
+ }
+ if ( nPointOffset )
+ {
+ m_pPoint->nNode += nPointOffset;
+ }
+ m_pPoint->nContent.Assign( m_pPoint->GetNode().GetContentNode(),
+ nPointContent);
+ m_pMark ->nContent.Assign( m_pMark ->GetNode().GetContentNode(),
+ nMarkContent );
+}
+
+SwPaM::SwPaM( const SwNode& rNode, sal_Int32 nContent, SwPaM* pRing )
+ : Ring( pRing )
+ , m_Bound1( rNode )
+ , m_Bound2( m_Bound1.GetNode().GetNodes() ) // default initialize
+ , m_pPoint( &m_Bound1 )
+ , m_pMark( &m_Bound1 )
+ , m_bIsInFrontOfLabel( false )
+{
+ m_pPoint->nContent.Assign( m_pPoint->GetNode().GetContentNode(),
+ nContent );
+}
+
+SwPaM::SwPaM( const SwNode& rNode, SwNodeOffset nNdOffset, sal_Int32 nContent, SwPaM* pRing )
+ : Ring( pRing )
+ , m_Bound1( rNode, nNdOffset )
+ , m_Bound2( m_Bound1.GetNode().GetNodes() ) // default initialize
+ , m_pPoint( &m_Bound1 )
+ , m_pMark( &m_Bound1 )
+ , m_bIsInFrontOfLabel( false )
+{
+ m_pPoint->nContent.Assign( m_pPoint->GetNode().GetContentNode(),
+ nContent );
+}
+
+SwPaM::SwPaM( const SwNodeIndex& rNodeIdx, sal_Int32 nContent, SwPaM* pRing )
+ : Ring( pRing )
+ , m_Bound1( rNodeIdx )
+ , m_Bound2( rNodeIdx.GetNode().GetNodes() ) // default initialize
+ , m_pPoint( &m_Bound1 )
+ , m_pMark( &m_Bound1 )
+ , m_bIsInFrontOfLabel( false )
+{
+ m_pPoint->nContent.Assign( rNodeIdx.GetNode().GetContentNode(), nContent );
+}
+
+SwPaM::SwPaM( SwNodes& rNodes, SwNodeOffset nNdOffset, SwPaM* pRing )
+ : Ring( pRing )
+ , m_Bound1( rNodes, nNdOffset )
+ , m_Bound2( rNodes ) // default initialize
+ , m_pPoint( &m_Bound1 )
+ , m_pMark( &m_Bound1 )
+ , m_bIsInFrontOfLabel( false )
+{
+}
+
+SwPaM::~SwPaM() {}
+
+SwPaM::SwPaM(SwPaM const& rPam, SwPaM *const pRing)
+ : Ring(pRing)
+ , m_Bound1( *(rPam.m_pPoint) )
+ , m_Bound2( *(rPam.m_pMark) )
+ , m_pPoint( &m_Bound1 ), m_pMark( rPam.HasMark() ? &m_Bound2 : m_pPoint )
+ , m_bIsInFrontOfLabel( false )
+{
+}
+
+// @@@ semantic: no copy assignment for super class Ring.
+SwPaM &SwPaM::operator=( const SwPaM &rPam )
+{
+ if(this == &rPam)
+ return *this;
+
+ *m_pPoint = *( rPam.m_pPoint );
+ if ( rPam.HasMark() )
+ {
+ SetMark();
+ *m_pMark = *( rPam.m_pMark );
+ }
+ else
+ {
+ DeleteMark();
+ }
+ return *this;
+}
+
+void SwPaM::SetMark()
+{
+ if (m_pPoint == &m_Bound1)
+ {
+ m_pMark = &m_Bound2;
+ }
+ else
+ {
+ m_pMark = &m_Bound1;
+ }
+ (*m_pMark) = *m_pPoint;
+}
+
+/// movement of cursor
+bool SwPaM::Move( SwMoveFnCollection const & fnMove, SwGoInDoc fnGo )
+{
+ const bool bRet = (*fnGo)( *this, fnMove );
+
+ m_bIsInFrontOfLabel = false;
+ return bRet;
+}
+
+namespace sw {
+
+/** make a new region
+
+ Sets the first SwPaM onto the given SwPaM, or to the beginning or end of a
+ document. SPoint stays at its position, GetMark will be changed respectively.
+
+ @param fnMove Contains information if beginning or end of document.
+ @param pOrigRg The given region.
+ @param rPam returns newly created range, in Ring with parameter pOrigRg.
+*/
+void MakeRegion(SwMoveFnCollection const & fnMove,
+ const SwPaM & rOrigRg, std::optional<SwPaM>& rPam)
+{
+ rPam.emplace(rOrigRg, const_cast<SwPaM*>(&rOrigRg)); // given search range
+ // make sure that SPoint is on the "real" start position
+ // FORWARD: SPoint always smaller than GetMark
+ // BACKWARD: SPoint always bigger than GetMark
+ if( (rPam->GetMark()->*fnMove.fnCmpOp)( *rPam->GetPoint() ) )
+ rPam->Exchange();
+}
+
+} // namespace sw
+
+void SwPaM::Normalize(bool bPointFirst)
+{
+ if (HasMark())
+ if ( ( bPointFirst && *m_pPoint > *m_pMark) ||
+ (!bPointFirst && *m_pPoint < *m_pMark) )
+ {
+ Exchange();
+ }
+}
+
+/// return page number at cursor (for reader and page bound frames)
+sal_uInt16 SwPaM::GetPageNum( bool bAtPoint, const Point* pLayPos )
+{
+ const SwContentFrame* pCFrame;
+ const SwPageFrame *pPg;
+ const SwContentNode *pNd ;
+ const SwPosition* pPos = bAtPoint ? m_pPoint : m_pMark;
+
+ std::pair<Point, bool> tmp;
+ if (pLayPos)
+ {
+ tmp.first = *pLayPos;
+ tmp.second = false;
+ }
+ if( nullptr != ( pNd = pPos->GetNode().GetContentNode() ) &&
+ nullptr != (pCFrame = pNd->getLayoutFrame(pNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(), pPos, pLayPos ? &tmp : nullptr)) &&
+ nullptr != ( pPg = pCFrame->FindPageFrame() ))
+ return pPg->GetPhyPageNum();
+ return 0;
+}
+
+// form view - see also SwCursorShell::IsCursorReadonly()
+static const SwFrame* lcl_FindEditInReadonlyFrame( const SwFrame& rFrame )
+{
+ const SwFrame* pRet = nullptr;
+
+ const SwFlyFrame* pFly;
+ const SwSectionFrame* pSectionFrame;
+
+ if( rFrame.IsInFly() &&
+ (pFly = rFrame.FindFlyFrame())->GetFormat()->GetEditInReadonly().GetValue() &&
+ pFly->Lower() &&
+ !pFly->Lower()->IsNoTextFrame() )
+ {
+ pRet = pFly;
+ }
+ else if ( rFrame.IsInSct() &&
+ nullptr != ( pSectionFrame = rFrame.FindSctFrame() )->GetSection() &&
+ pSectionFrame->GetSection()->IsEditInReadonlyFlag() )
+ {
+ pRet = pSectionFrame;
+ }
+
+ return pRet;
+}
+
+/// is in protected section or selection surrounds something protected
+bool SwPaM::HasReadonlySel(bool bFormView, bool const isReplace) const
+{
+ bool bRet = false;
+
+ const SwContentNode* pNd = GetPoint()->GetNode().GetContentNode();
+ const SwContentFrame *pFrame = nullptr;
+ if ( pNd != nullptr )
+ {
+ Point aTmpPt;
+ std::pair<Point, bool> const tmp(aTmpPt, false);
+ pFrame = pNd->getLayoutFrame(
+ pNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
+ GetPoint(), &tmp);
+ }
+
+ // Will be set if point are inside edit-in-readonly environment
+ const SwFrame* pPointEditInReadonlyFrame = nullptr;
+ if ( pFrame != nullptr
+ && ( pFrame->IsProtected()
+ || ( bFormView
+ && nullptr == ( pPointEditInReadonlyFrame = lcl_FindEditInReadonlyFrame( *pFrame ) ) ) ) )
+ {
+ bRet = true;
+ }
+ else if( pNd != nullptr )
+ {
+ const SwSectionNode* pSNd = pNd->GetSectionNode();
+ if ( pSNd != nullptr
+ && ( pSNd->GetSection().IsProtectFlag()
+ || ( bFormView
+ && !pSNd->GetSection().IsEditInReadonlyFlag()) ) )
+ {
+ bRet = true;
+ }
+ else
+ {
+ const SwSectionNode* pParentSectionNd = pNd->FindSectionNode();
+ if ( pParentSectionNd != nullptr
+ && ( pParentSectionNd->GetSection().IsProtectFlag()
+ || ( bFormView && !pParentSectionNd->GetSection().IsEditInReadonlyFlag()) ) )
+ {
+ bRet = true;
+ }
+ }
+ }
+
+ if ( !bRet
+ && HasMark()
+ && GetPoint()->nNode != GetMark()->nNode )
+ {
+ pNd = GetMark()->GetNode().GetContentNode();
+ pFrame = nullptr;
+ if ( pNd != nullptr )
+ {
+ Point aTmpPt;
+ std::pair<Point, bool> const tmp(aTmpPt, false);
+ pFrame = pNd->getLayoutFrame(
+ pNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
+ GetMark(), &tmp);
+ }
+
+ const SwFrame* pMarkEditInReadonlyFrame = nullptr;
+ if ( pFrame != nullptr
+ && ( pFrame->IsProtected()
+ || ( bFormView
+ && nullptr == ( pMarkEditInReadonlyFrame = lcl_FindEditInReadonlyFrame( *pFrame ) ) ) ) )
+ {
+ bRet = true;
+ }
+ else if( pNd != nullptr )
+ {
+ const SwSectionNode* pSNd = pNd->GetSectionNode();
+ if ( pSNd != nullptr
+ && ( pSNd->GetSection().IsProtectFlag()
+ || ( bFormView
+ && !pSNd->GetSection().IsEditInReadonlyFlag()) ) )
+ {
+ bRet = true;
+ }
+ }
+
+ if ( !bRet && bFormView )
+ {
+ // Check if start and end frame are inside the _same_
+ // edit-in-readonly-environment. Otherwise we better return 'true'
+ if ( pPointEditInReadonlyFrame != pMarkEditInReadonlyFrame )
+ bRet = true;
+ }
+
+ // check for protected section inside the selection
+ if( !bRet )
+ {
+ SwNodeOffset nSttIdx = GetMark()->GetNodeIndex(),
+ nEndIdx = GetPoint()->GetNodeIndex();
+ if( nEndIdx < nSttIdx )
+ std::swap( nSttIdx, nEndIdx );
+
+ // If a protected section should be between nodes, then the
+ // selection needs to contain already x nodes.
+ // (TextNd, SectNd, TextNd, EndNd, TextNd )
+ if( nSttIdx + SwNodeOffset(3) < nEndIdx )
+ {
+ const SwSectionFormats& rFormats = GetDoc().GetSections();
+ for( SwSectionFormats::size_type n = rFormats.size(); n; )
+ {
+ const SwSectionFormat* pFormat = rFormats[ --n ];
+ if( pFormat->GetProtect().IsContentProtected() )
+ {
+ const SwFormatContent& rContent = pFormat->GetContent(false);
+ OSL_ENSURE( rContent.GetContentIdx(), "where is the SectionNode?" );
+ SwNodeOffset nIdx = rContent.GetContentIdx()->GetIndex();
+ if( nSttIdx <= nIdx && nEndIdx >= nIdx &&
+ rContent.GetContentIdx()->GetNode().GetNodes().IsDocNodes() )
+ {
+ bRet = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ const SwDoc& rDoc = GetDoc();
+ // Legacy text/combo/checkbox: never return read-only when inside these form fields.
+ const IDocumentMarkAccess* pMarksAccess = rDoc.getIDocumentMarkAccess();
+ sw::mark::IFieldmark* pA = GetPoint() ? pMarksAccess->getInnerFieldmarkFor(*GetPoint()) : nullptr;
+ sw::mark::IFieldmark* pB = GetMark() ? pMarksAccess->getInnerFieldmarkFor(*GetMark()) : pA;
+ // prevent the user from accidentally deleting the field itself when modifying the text.
+ const bool bAtStartA = (pA != nullptr) && (pA->GetMarkStart() == *GetPoint());
+ const bool bAtStartB = (pB != nullptr) && (pB->GetMarkStart() == *GetMark());
+
+ if (!utl::ConfigManager::IsFuzzing() && officecfg::Office::Common::Filter::Microsoft::Import::ForceImportWWFieldsAsGenericFields::get())
+ {
+ ; // allow editing all fields in generic mode
+ }
+ else if (!bRet)
+ {
+ bool bUnhandledMark = pA && pA->GetFieldname( ) == ODF_UNHANDLED;
+ // Unhandled fieldmarks case shouldn't be edited manually to avoid breaking anything
+ if ( ( pA == pB ) && bUnhandledMark )
+ bRet = true;
+ else
+ {
+ if ((pA == pB) && (bAtStartA != bAtStartB))
+ bRet = true;
+ else if (pA != pB)
+ {
+ // If both points are either outside or at marks edges (i.e. selection either
+ // touches fields, or fully encloses it), then don't disable editing
+ bRet = !( ( !pA || bAtStartA ) && ( !pB || bAtStartB ) );
+ }
+ if( !bRet && rDoc.GetDocumentSettingManager().get( DocumentSettingId::PROTECT_FORM ) && (pA || pB) )
+ {
+ // Form protection case
+ bRet = ( pA == nullptr ) || ( pB == nullptr ) || bAtStartA || bAtStartB;
+ }
+ }
+ }
+ else
+ {
+ // Allow editing when the cursor/selection is fully inside of a legacy form field.
+ bRet = !( pA != nullptr && !bAtStartA && !bAtStartB && pA == pB );
+
+ if (bRet)
+ {
+ // Also allow editing inside content controls in general, similar to form fields.
+ // Specific types will be disabled below.
+ if (const SwEditShell* pEditShell = rDoc.GetEditShell())
+ bRet = !pEditShell->CursorInsideContentControl();
+ }
+ }
+
+ if (!bRet)
+ {
+ // Paragraph Signatures and Classification fields are read-only.
+ if (const SwEditShell* pEditShell = rDoc.GetEditShell())
+ bRet = pEditShell->IsCursorInParagraphMetadataField();
+ }
+
+ if (!bRet &&
+ rDoc.getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_BOOKMARKS))
+ {
+ if (rDoc.getIDocumentMarkAccess()->isBookmarkDeleted(*this, isReplace))
+ {
+ return true;
+ }
+ }
+ if (!bRet &&
+ rDoc.getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_FIELDS))
+ {
+ SwPosition const& rStart(*Start());
+ SwPosition const& rEnd(*End());
+ for (SwNodeIndex n(rStart.GetNode()); n <= rEnd.GetNode(); ++n)
+ {
+ if (SwTextNode const*const pNode = n.GetNode().GetTextNode())
+ {
+ if (SwpHints const*const pHints = pNode->GetpSwpHints())
+ {
+ for (size_t i = 0; i < pHints->Count(); ++i)
+ {
+ SwTextAttr const*const pHint(pHints->Get(i));
+ if (n == rStart.GetNode() && pHint->GetStart() < rStart.GetContentIndex())
+ {
+ continue; // before selection
+ }
+ if (n == rEnd.GetNode() && rEnd.GetContentIndex() <= pHint->GetStart())
+ {
+ break; // after selection
+ }
+ if (pHint->Which() == RES_TXTATR_FIELD
+ // placeholders don't work if you can't delete them
+ && pHint->GetFormatField().GetField()->GetTyp()->Which() != SwFieldIds::JumpEdit)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!bRet)
+ {
+ // See if we're inside a read-only content control.
+ const SwPosition* pStart = Start();
+ SwTextNode* pTextNode = pStart->GetNode().GetTextNode();
+ if (pTextNode)
+ {
+ sal_Int32 nIndex = pStart->GetContentIndex();
+ SwTextAttr* pAttr
+ = pTextNode->GetTextAttrAt(nIndex, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent);
+ auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr);
+ if (pTextContentControl)
+ {
+ const SwFormatContentControl& rFormatContentControl
+ = pTextContentControl->GetContentControl();
+ std::shared_ptr<SwContentControl> pContentControl
+ = rFormatContentControl.GetContentControl();
+ if (pContentControl && !pContentControl->GetReadWrite())
+ {
+ switch (pContentControl->GetType())
+ {
+ case SwContentControlType::CHECKBOX:
+ case SwContentControlType::PICTURE:
+ case SwContentControlType::DROP_DOWN_LIST:
+ bRet = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+bool SwPaM::HasHiddenSections() const
+{
+ bool bRet = false;
+
+ if (HasMark() && GetPoint()->nNode != GetMark()->nNode)
+ {
+ // check for hidden section inside the selection
+ SwNodeOffset nSttIdx = Start()->GetNodeIndex(), nEndIdx = End()->GetNodeIndex();
+
+ if (nSttIdx + SwNodeOffset(3) < nEndIdx)
+ {
+ const SwSectionFormats& rFormats = GetDoc().GetSections();
+ for (SwSectionFormats::size_type n = rFormats.size(); n;)
+ {
+ const SwSectionFormat* pFormat = rFormats[--n];
+ if (pFormat->GetSection()->IsHidden())
+ {
+ const SwFormatContent& rContent = pFormat->GetContent(false);
+ OSL_ENSURE(rContent.GetContentIdx(), "where is the SectionNode?");
+ SwNodeOffset nIdx = rContent.GetContentIdx()->GetIndex();
+ if (nSttIdx <= nIdx && nEndIdx >= nIdx
+ && rContent.GetContentIdx()->GetNode().GetNodes().IsDocNodes())
+ {
+ bRet = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+/// This function returns the next node in direction of search. If there is no
+/// left or the next is out of the area, then a null-pointer is returned.
+/// @param rbFirst If <true> then first time request. If so than the position of
+/// the PaM must not be changed!
+SwContentNode* GetNode( SwPaM & rPam, bool& rbFirst, SwMoveFnCollection const & fnMove,
+ bool const bInReadOnly, SwRootFrame const*const i_pLayout)
+{
+ SwRootFrame const*const pLayout(i_pLayout ? i_pLayout :
+ rPam.GetDoc().getIDocumentLayoutAccess().GetCurrentLayout());
+ SwContentNode * pNd = nullptr;
+ if( ((*rPam.GetPoint()).*fnMove.fnCmpOp)( *rPam.GetMark() ) ||
+ ( *rPam.GetPoint() == *rPam.GetMark() && rbFirst ) )
+ {
+ if( rbFirst )
+ {
+ rbFirst = false;
+ pNd = rPam.GetPointContentNode();
+ if( pNd )
+ {
+ SwContentFrame const*const pFrame(pNd->getLayoutFrame(pLayout));
+ if(
+ (
+ nullptr == pFrame ||
+ ( !bInReadOnly && pFrame->IsProtected() ) ||
+ (pFrame->IsTextFrame() && static_cast<SwTextFrame const*>(pFrame)->IsHiddenNow())
+ ) ||
+ ( !bInReadOnly && pNd->FindSectionNode() &&
+ pNd->FindSectionNode()->GetSection().IsProtect()
+ )
+ )
+ {
+ pNd = nullptr;
+ }
+ }
+ }
+
+ if( !pNd ) // is the cursor not on a ContentNode?
+ {
+ SwPosition aPos( *rPam.GetPoint() );
+ bool bSrchForward = &fnMove == &fnMoveForward;
+ SwNodes& rNodes = aPos.GetNodes();
+
+ // go to next/previous ContentNode
+ while( true )
+ {
+ if (i_pLayout && aPos.GetNode().IsTextNode())
+ {
+ auto const fal(sw::GetFirstAndLastNode(*pLayout, aPos.GetNode()));
+ aPos.Assign( bSrchForward ? *fal.second : *fal.first );
+ }
+
+ pNd = bSrchForward
+ ? rNodes.GoNextSection( &aPos, true, !bInReadOnly )
+ : SwNodes::GoPrevSection( &aPos, true, !bInReadOnly );
+ if( pNd )
+ {
+ if (!bSrchForward)
+ aPos.AssignEndIndex( *pNd );
+ // is the position still in the area
+ if( (aPos.*fnMove.fnCmpOp)( *rPam.GetMark() ) )
+ {
+ // only in AutoTextSection can be nodes that are hidden
+ SwContentFrame const*const pFrame(pNd->getLayoutFrame(pLayout));
+ if (nullptr == pFrame ||
+ ( !bInReadOnly && pFrame->IsProtected() ) ||
+ ( pFrame->IsTextFrame() &&
+ static_cast<SwTextFrame const*>(pFrame)->IsHiddenNow()))
+ {
+ pNd = nullptr;
+ continue;
+ }
+ *rPam.GetPoint() = aPos;
+ }
+ else
+ pNd = nullptr; // no valid node
+ break;
+ }
+ break;
+ }
+ }
+ }
+ return pNd;
+}
+
+void GoStartDoc( SwPosition * pPos )
+{
+ SwNodes& rNodes = pPos->GetNodes();
+ pPos->Assign( *rNodes.GetEndOfContent().StartOfSectionNode() );
+ // we always need to find a ContentNode!
+ rNodes.GoNext( pPos );
+}
+
+void GoEndDoc( SwPosition * pPos )
+{
+ SwNodes& rNodes = pPos->GetNodes();
+ pPos->Assign( rNodes.GetEndOfContent() );
+ SwContentNode* pCNd = GoPreviousPos( pPos, true );
+ if( pCNd )
+ pPos->AssignEndIndex(*pCNd);
+}
+
+void GoStartSection( SwPosition * pPos )
+{
+ // jump to section's beginning
+ SwNodes& rNodes = pPos->GetNodes();
+ sal_uInt16 nLevel = SwNodes::GetSectionLevel( pPos->GetNode() );
+ if( pPos->GetNode() < *rNodes.GetEndOfContent().StartOfSectionNode() )
+ nLevel--;
+ do { SwNodes::GoStartOfSection( &pPos->nNode ); } while( nLevel-- );
+
+ // already on a ContentNode
+ pPos->AssignStartIndex(*pPos->GetNode().GetContentNode());
+}
+
+void GoStartOfSection( SwPosition * pPos )
+{
+ // jump to section's beginning
+ SwNodes::GoStartOfSection( &pPos->nNode );
+ pPos->nContent.Assign(pPos->GetNode().GetContentNode(), 0);
+}
+
+/// go to the end of the current base section
+void GoEndSection( SwPosition * pPos )
+{
+ // jump to section's beginning/end
+ SwNodes& rNodes = pPos->GetNodes();
+ sal_uInt16 nLevel = SwNodes::GetSectionLevel( pPos->GetNode() );
+ if( pPos->GetNode() < *rNodes.GetEndOfContent().StartOfSectionNode() )
+ nLevel--;
+ do { SwNodes::GoEndOfSection( &pPos->nNode ); } while( nLevel-- );
+
+ // now on an EndNode, thus to the previous ContentNode
+ if( SwContentNode* pCNd = GoPreviousNds( &pPos->nNode, true ) )
+ pPos->AssignEndIndex(*pCNd);
+}
+
+void GoEndOfSection( SwPosition * pPos )
+{
+ SwNodes::GoEndOfSection( &pPos->nNode );
+ SwContentNode* pCNd = pPos->nNode.GetNode().GetContentNode();
+ pPos->nContent.Assign(pCNd, pCNd ? pCNd->Len() : 0);
+}
+
+bool GoInDoc( SwPaM & rPam, SwMoveFnCollection const & fnMove )
+{
+ (*fnMove.fnDoc)( rPam.GetPoint() );
+ return true;
+}
+
+bool GoInSection( SwPaM & rPam, SwMoveFnCollection const & fnMove )
+{
+ (*fnMove.fnSections)( rPam.GetPoint() );
+ return true;
+}
+
+bool GoInNode( SwPaM & rPam, SwMoveFnCollection const & fnMove )
+{
+ SwContentNode *pNd = (*fnMove.fnPos)( rPam.GetPoint(), true );
+ if( pNd )
+ rPam.GetPoint()->SetContent(
+ ::GetSttOrEnd( &fnMove == &fnMoveForward, *pNd ) );
+ return pNd;
+}
+
+bool GoInContent( SwPaM & rPam, SwMoveFnCollection const & fnMove )
+{
+ if( (*fnMove.fnNd)( &rPam.GetPoint()->GetNode(),
+ &rPam.GetPoint()->nContent, SwCursorSkipMode::Chars ))
+ return true;
+ return GoInNode( rPam, fnMove );
+}
+
+bool GoInContentCells( SwPaM & rPam, SwMoveFnCollection const & fnMove )
+{
+ if( (*fnMove.fnNd)( &rPam.GetPoint()->GetNode(),
+ &rPam.GetPoint()->nContent, SwCursorSkipMode::Cells ))
+ return true;
+ return GoInNode( rPam, fnMove );
+}
+
+bool GoInContentSkipHidden( SwPaM & rPam, SwMoveFnCollection const & fnMove )
+{
+ if( (*fnMove.fnNd)( &rPam.GetPoint()->GetNode(),
+ &rPam.GetPoint()->nContent, SwCursorSkipMode::Chars | SwCursorSkipMode::Hidden ) )
+ return true;
+ return GoInNode( rPam, fnMove );
+}
+
+bool GoInContentCellsSkipHidden( SwPaM & rPam, SwMoveFnCollection const & fnMove )
+{
+ if( (*fnMove.fnNd)( &rPam.GetPoint()->GetNode(),
+ &rPam.GetPoint()->nContent, SwCursorSkipMode::Cells | SwCursorSkipMode::Hidden ) )
+ return true;
+ return GoInNode( rPam, fnMove );
+}
+
+bool GoPrevPara( SwPaM & rPam, SwMoveFnCollection const & aPosPara )
+{
+ if( rPam.Move( fnMoveBackward, GoInNode ) )
+ {
+ // always on a ContentNode
+ SwPosition& rPos = *rPam.GetPoint();
+ SwContentNode * pNd = rPos.GetNode().GetContentNode();
+ rPos.SetContent( ::GetSttOrEnd( &aPosPara == &fnMoveForward, *pNd ) );
+ return true;
+ }
+ return false;
+}
+
+bool GoCurrPara( SwPaM & rPam, SwMoveFnCollection const & aPosPara )
+{
+ SwPosition& rPos = *rPam.GetPoint();
+ SwContentNode * pNd = rPos.GetNode().GetContentNode();
+ if( pNd )
+ {
+ const sal_Int32 nOld = rPos.GetContentIndex();
+ const sal_Int32 nNew = &aPosPara == &fnMoveForward ? 0 : pNd->Len();
+ // if already at beginning/end then to the next/previous
+ if( nOld != nNew )
+ {
+ rPos.SetContent( nNew );
+ return true;
+ }
+ }
+ // move node to next/previous ContentNode
+ if( ( &aPosPara==&fnParaStart && nullptr != ( pNd =
+ GoPreviousPos( &rPos, true ))) ||
+ ( &aPosPara==&fnParaEnd && nullptr != ( pNd =
+ GoNextPos( &rPos, true ))) )
+ {
+ rPos.SetContent( ::GetSttOrEnd( &aPosPara == &fnMoveForward, *pNd ));
+ return true;
+ }
+ return false;
+}
+
+bool GoNextPara( SwPaM & rPam, SwMoveFnCollection const & aPosPara )
+{
+ if( rPam.Move( fnMoveForward, GoInNode ) )
+ {
+ // always on a ContentNode
+ SwPosition& rPos = *rPam.GetPoint();
+ SwContentNode * pNd = rPos.GetNode().GetContentNode();
+ rPos.SetContent( ::GetSttOrEnd( &aPosPara == &fnMoveForward, *pNd ) );
+ return true;
+ }
+ return false;
+}
+
+bool GoCurrSection( SwPaM & rPam, SwMoveFnCollection const & fnMove )
+{
+ SwPosition& rPos = *rPam.GetPoint();
+ SwPosition aSavePos( rPos ); // position for comparison
+ (fnMove.fnSection)( &rPos );
+ SwContentNode *pNd;
+ if( nullptr == ( pNd = rPos.GetNode().GetContentNode()) &&
+ nullptr == ( pNd = (*fnMove.fnPos)( &rPos, true )) )
+ {
+ rPos = aSavePos; // do not change cursor
+ return false;
+ }
+
+ rPos.SetContent( ::GetSttOrEnd( &fnMove == &fnMoveForward, *pNd ) );
+ return aSavePos != rPos;
+}
+
+OUString SwPaM::GetText() const
+{
+ OUStringBuffer aResult;
+
+ SwNodeIndex aNodeIndex = Start()->nNode;
+
+ // The first node can be already the end node.
+ // Use a "forever" loop with an exit condition in the middle
+ // of its body, in order to correctly handle all cases.
+ bool bIsStartNode = true;
+ for (;;)
+ {
+ const bool bIsEndNode = aNodeIndex == End()->nNode;
+ SwTextNode * pTextNode = aNodeIndex.GetNode().GetTextNode();
+
+ if (pTextNode != nullptr)
+ {
+ if (!bIsStartNode)
+ {
+ aResult.append(CH_TXTATR_NEWLINE); // use newline for para break
+ }
+ const OUString& aTmpStr = pTextNode->GetText();
+
+ if (bIsStartNode || bIsEndNode)
+ {
+ // Handle corner cases of start/end node(s)
+ const sal_Int32 nStart = bIsStartNode
+ ? Start()->GetContentIndex()
+ : 0;
+ const sal_Int32 nEnd = bIsEndNode
+ ? End()->GetContentIndex()
+ : aTmpStr.getLength();
+
+ aResult.append(aTmpStr.subView(nStart, nEnd-nStart));
+ }
+ else
+ {
+ aResult.append(aTmpStr);
+ }
+ }
+
+ if (bIsEndNode)
+ {
+ break;
+ }
+
+ ++aNodeIndex;
+ bIsStartNode = false;
+ }
+
+ return aResult.makeStringAndClear();
+}
+
+void SwPaM::InvalidatePaM()
+{
+ for (SwNodeIndex index(Start()->GetNode()); index <= End()->GetNode(); ++index)
+ {
+ if (SwTextNode *const pTextNode = index.GetNode().GetTextNode())
+ {
+ // pretend that the PaM marks changed formatting to reformat...
+ sal_Int32 const nStart(
+ index == Start()->nNode ? Start()->GetContentIndex() : 0);
+ // this should work even for length of 0
+ SwUpdateAttr const aHint(
+ nStart,
+ index == End()->nNode
+ ? End()->GetContentIndex() - nStart
+ : pTextNode->Len() - nStart,
+ 0);
+ pTextNode->TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint));
+ }
+ // other node types not invalidated
+ }
+}
+
+void SwPaM::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwPaM"));
+
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("point"));
+ GetPoint()->dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+
+ if (HasMark())
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mark"));
+ GetMark()->dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+std::ostream &operator <<(std::ostream& s, const SwPaM& pam)
+{
+ if( pam.HasMark())
+ return s << "SwPaM (point " << *pam.GetPoint() << ", mark " << *pam.GetMark() << ")";
+ else
+ return s << "SwPaM (point " << *pam.GetPoint() << ")";
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/paminit.cxx b/sw/source/core/crsr/paminit.cxx
new file mode 100644
index 0000000000..a415aee2c2
--- /dev/null
+++ b/sw/source/core/crsr/paminit.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 <pam.hxx>
+#include <pamtyp.hxx>
+#include <cshtyp.hxx>
+
+const SwMoveFnCollection aFwrd = {
+ /* fnNd */ &GoNext,
+ /* fnNds */ &GoNextNds,
+ /* fnPos */ &GoNextPos,
+ /* fnDoc */ &GoEndDoc,
+ /* fnSections */ &GoEndSection,
+ /* fnCmpOp */ &SwPosition::operator<,
+ /* fnGetHint */ &GetFrwrdTextHint,
+ /* fnSearch */ &utl::TextSearch::SearchForward,
+ /* fnSection */ &GoStartOfSection
+};
+
+const SwMoveFnCollection aBwrd = {
+ /* fnNd */ &GoPrevious,
+ /* fnNds */ &GoPreviousNds,
+ /* fnPos */ &GoPreviousPos,
+ /* fnDoc */ &GoStartDoc,
+ /* fnSections */ &GoStartSection,
+ /* fnCmpOp */ &SwPosition::operator>,
+ /* fnGetHint */ &GetBkwrdTextHint,
+ /* fnSearch */ &utl::TextSearch::SearchBackward,
+ /* fnSection */ &GoEndOfSection
+};
+
+SwMoveFnCollection const & fnParaStart = aFwrd;
+SwMoveFnCollection const & fnParaEnd = aBwrd;
+
+SwMoveFnCollection const & fnSectionStart = aFwrd;
+SwMoveFnCollection const & fnSectionEnd = aBwrd;
+
+SwMoveFnCollection const & fnTableStart = aFwrd;
+SwMoveFnCollection const & fnTableEnd = aBwrd;
+
+SwMoveFnCollection const & fnRegionStart = aFwrd;
+SwMoveFnCollection const & fnRegionEnd = aBwrd;
+
+SwMoveFnCollection const & fnMoveBackward = aBwrd;
+SwMoveFnCollection const & fnMoveForward = aFwrd;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/swcrsr.cxx b/sw/source/core/crsr/swcrsr.cxx
new file mode 100644
index 0000000000..8d0246bed1
--- /dev/null
+++ b/sw/source/core/crsr/swcrsr.cxx
@@ -0,0 +1,2646 @@
+/* -*- 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 <hintids.hxx>
+#include <editeng/protitem.hxx>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <unotools/charclass.hxx>
+#include <svl/ctloptions.hxx>
+#include <svl/srchitem.hxx>
+#include <swmodule.hxx>
+#include <fmtcntnt.hxx>
+#include <swtblfmt.hxx>
+#include <swcrsr.hxx>
+#include <unocrsr.hxx>
+#include <bookmark.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <docary.hxx>
+#include <ndtxt.hxx>
+#include <section.hxx>
+#include <swtable.hxx>
+#include <cntfrm.hxx>
+#include <rootfrm.hxx>
+#include <txtfrm.hxx>
+#include <notxtfrm.hxx>
+#include <scriptinfo.hxx>
+#include <crstate.hxx>
+#include <docsh.hxx>
+#include <viewsh.hxx>
+#include <frmatr.hxx>
+#include <breakit.hxx>
+#include <mdiexp.hxx>
+#include <strings.hrc>
+#include <redline.hxx>
+#include <txatbase.hxx>
+#include <IDocumentMarkAccess.hxx>
+#include <memory>
+#include <comphelper/lok.hxx>
+#include <editsh.hxx>
+
+#include <viewopt.hxx>
+
+using namespace ::com::sun::star::i18n;
+
+const sal_uInt16 coSrchRplcThreshold = 60000;
+
+namespace {
+
+struct PercentHdl
+{
+ SwDocShell* pDSh;
+ sal_uLong nActPos;
+ bool bBack, bNodeIdx;
+
+ PercentHdl( sal_uLong nStt, sal_uLong nEnd, SwDocShell* pSh )
+ : pDSh(pSh), nActPos(nStt), bBack(false), bNodeIdx(false)
+ {
+ bBack = (nStt > nEnd);
+ if( bBack )
+ std::swap( nStt, nEnd );
+ ::StartProgress( STR_STATSTR_SEARCH, nStt, nEnd );
+ }
+
+ explicit PercentHdl( const SwPaM& rPam )
+ : pDSh( rPam.GetDoc().GetDocShell() )
+ {
+ sal_Int32 nStt, nEnd;
+ if( rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode() )
+ {
+ bNodeIdx = false;
+ nStt = rPam.GetMark()->GetContentIndex();
+ nEnd = rPam.GetPoint()->GetContentIndex();
+ }
+ else
+ {
+ bNodeIdx = true;
+ nStt = sal_Int32(rPam.GetMark()->GetNodeIndex());
+ nEnd = sal_Int32(rPam.GetPoint()->GetNodeIndex());
+ }
+ nActPos = nStt;
+ bBack = (nStt > nEnd );
+ if( bBack )
+ std::swap( nStt, nEnd );
+ ::StartProgress( STR_STATSTR_SEARCH, nStt, nEnd, pDSh );
+ }
+
+ ~PercentHdl() { ::EndProgress( pDSh ); }
+
+ void NextPos( sal_uLong nPos ) const
+ { ::SetProgressState( bBack ? nActPos - nPos : nPos, pDSh ); }
+
+ void NextPos( SwPosition const & rPos ) const
+ {
+ sal_Int32 nPos;
+ if( bNodeIdx )
+ nPos = sal_Int32(rPos.GetNodeIndex());
+ else
+ nPos = rPos.GetContentIndex();
+ ::SetProgressState( bBack ? nActPos - nPos : nPos, pDSh );
+ }
+};
+
+}
+
+SwCursor::SwCursor( const SwPosition &rPos, SwPaM* pRing )
+ : SwPaM( rPos, pRing )
+ , m_nRowSpanOffset(0)
+ , m_nCursorBidiLevel(0)
+ , m_bColumnSelection(false)
+{
+}
+
+// @@@ semantic: no copy ctor.
+SwCursor::SwCursor(SwCursor const& rCpy, SwPaM *const pRing)
+ : SwPaM( rCpy, pRing )
+ , m_nRowSpanOffset(rCpy.m_nRowSpanOffset)
+ , m_nCursorBidiLevel(rCpy.m_nCursorBidiLevel)
+ , m_bColumnSelection(rCpy.m_bColumnSelection)
+{
+}
+
+SwCursor::~SwCursor()
+{
+}
+
+SwCursor* SwCursor::Create( SwPaM* pRing ) const
+{
+ return new SwCursor( *GetPoint(), pRing );
+}
+
+bool SwCursor::IsReadOnlyAvailable() const
+{
+ return false;
+}
+
+bool SwCursor::IsSkipOverHiddenSections() const
+{
+ return true;
+}
+
+bool SwCursor::IsSkipOverProtectSections() const
+{
+ return !IsReadOnlyAvailable();
+}
+
+// CreateNewSavePos is virtual so that derived classes of cursor can implement
+// own SaveObjects if needed and validate them in the virtual check routines.
+void SwCursor::SaveState()
+{
+ m_vSavePos.emplace_back( *this );
+}
+
+void SwCursor::RestoreState()
+{
+ if (!m_vSavePos.empty()) // Robust
+ {
+ m_vSavePos.pop_back();
+ }
+}
+
+/// determine if point is outside of the node-array's content area
+bool SwCursor::IsNoContent() const
+{
+ return GetPoint()->GetNodeIndex() <
+ GetDoc().GetNodes().GetEndOfExtras().GetIndex();
+}
+
+bool SwCursor::IsSelOvrCheck(SwCursorSelOverFlags)
+{
+ return false;
+}
+
+// extracted from IsSelOvr()
+bool SwTableCursor::IsSelOvrCheck(SwCursorSelOverFlags eFlags)
+{
+ SwNodes& rNds = GetDoc().GetNodes();
+ // check sections of nodes array
+ if( (SwCursorSelOverFlags::CheckNodeSection & eFlags)
+ && HasMark() )
+ {
+ SwNodeIndex aOldPos( rNds, GetSavePos()->nNode );
+ if( !CheckNodesRange( aOldPos.GetNode(), GetPoint()->GetNode(), true ))
+ {
+ GetPoint()->Assign( aOldPos );
+ GetPoint()->SetContent( GetSavePos()->nContent );
+ return true;
+ }
+ }
+ return SwCursor::IsSelOvrCheck(eFlags);
+}
+
+namespace
+{
+ const SwTextAttr* InputFieldAtPos(SwPosition const *pPos)
+ {
+ SwTextNode* pTextNd = pPos->GetNode().GetTextNode();
+ if (!pTextNd)
+ return nullptr;
+ return pTextNd->GetTextAttrAt(pPos->GetContentIndex(), RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent);
+ }
+}
+
+bool SwCursor::IsSelOvr(SwCursorSelOverFlags const eFlags)
+{
+ SwDoc& rDoc = GetDoc();
+ SwNodes& rNds = rDoc.GetNodes();
+
+ bool bSkipOverHiddenSections = IsSkipOverHiddenSections();
+ bool bSkipOverProtectSections = IsSkipOverProtectSections();
+
+ if ( IsSelOvrCheck( eFlags ) )
+ {
+ return true;
+ }
+
+ if (m_vSavePos.back().nNode != GetPoint()->GetNodeIndex() &&
+ // (1997) in UI-ReadOnly everything is allowed
+ ( !rDoc.GetDocShell() || !rDoc.GetDocShell()->IsReadOnlyUI() ))
+ {
+ // check new sections
+ SwPosition& rPtPos = *GetPoint();
+ const SwSectionNode* pSectNd = rPtPos.GetNode().FindSectionNode();
+ if( pSectNd &&
+ ((bSkipOverHiddenSections && pSectNd->GetSection().IsHiddenFlag() ) ||
+ (bSkipOverProtectSections && pSectNd->GetSection().IsProtectFlag() )))
+ {
+ if( !( SwCursorSelOverFlags::ChangePos & eFlags ) )
+ {
+ // then we're already done
+ RestoreSavePos();
+ return true;
+ }
+
+ // set cursor to new position:
+ SwNodeIndex aIdx( rPtPos.GetNode() );
+ sal_Int32 nContentPos = m_vSavePos.back().nContent;
+ bool bGoNxt = m_vSavePos.back().nNode < rPtPos.GetNodeIndex();
+ SwContentNode* pCNd = bGoNxt
+ ? rNds.GoNextSection( &rPtPos, bSkipOverHiddenSections, bSkipOverProtectSections)
+ : SwNodes::GoPrevSection( &rPtPos, bSkipOverHiddenSections, bSkipOverProtectSections);
+ if( !pCNd && ( SwCursorSelOverFlags::EnableRevDirection & eFlags ))
+ {
+ bGoNxt = !bGoNxt;
+ pCNd = bGoNxt ? rNds.GoNextSection( &rPtPos, bSkipOverHiddenSections, bSkipOverProtectSections)
+ : SwNodes::GoPrevSection( &rPtPos, bSkipOverHiddenSections, bSkipOverProtectSections);
+ }
+
+ bool bIsValidPos = nullptr != pCNd;
+ const bool bValidNodesRange = bIsValidPos &&
+ ::CheckNodesRange( rPtPos.GetNode(), aIdx.GetNode(), true );
+ if( !bValidNodesRange )
+ {
+ rPtPos.Assign( m_vSavePos.back().nNode );
+ pCNd = rPtPos.GetNode().GetContentNode();
+ if( !pCNd )
+ {
+ bIsValidPos = false;
+ nContentPos = 0;
+ rPtPos.Assign( aIdx );
+ pCNd = rPtPos.GetNode().GetContentNode();
+ if( !pCNd )
+ {
+ // then to the beginning of the document
+ rPtPos.Assign( rNds.GetEndOfExtras() );
+ pCNd = rNds.GoNext( &rPtPos );
+ }
+ }
+ }
+
+ // register ContentIndex:
+ const sal_Int32 nTmpPos = bIsValidPos ? (bGoNxt ? 0 : pCNd->Len()) : nContentPos;
+ GetPoint()->SetContent( nTmpPos );
+ if( !bIsValidPos || !bValidNodesRange ||
+ IsInProtectTable( true ) )
+ return true;
+ }
+
+ // is there a protected section in the section?
+ if( HasMark() && bSkipOverProtectSections)
+ {
+ SwNodeOffset nSttIdx = GetMark()->GetNodeIndex(),
+ nEndIdx = GetPoint()->GetNodeIndex();
+ if( nEndIdx <= nSttIdx )
+ std::swap( nSttIdx, nEndIdx );
+
+ const SwSectionFormats& rFormats = rDoc.GetSections();
+ for( SwSectionFormats::size_type n = 0; n < rFormats.size(); ++n )
+ {
+ const SwSectionFormat* pFormat = rFormats[n];
+ const SvxProtectItem& rProtect = pFormat->GetProtect();
+ if( rProtect.IsContentProtected() )
+ {
+ const SwFormatContent& rContent = pFormat->GetContent(false);
+ OSL_ENSURE( rContent.GetContentIdx(), "No SectionNode?" );
+ SwNodeOffset nIdx = rContent.GetContentIdx()->GetIndex();
+ if( nSttIdx <= nIdx && nEndIdx >= nIdx )
+ {
+ // if it is no linked section then we cannot select it
+ const SwSection& rSect = *pFormat->GetSection();
+ if( SectionType::Content == rSect.GetType() )
+ {
+ RestoreSavePos();
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ const SwNode* pNd = &GetPoint()->GetNode();
+ if( pNd->IsContentNode() && !dynamic_cast<SwUnoCursor*>(this) )
+ {
+ const SwContentFrame* pFrame = static_cast<const SwContentNode*>(pNd)->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() );
+ // ^ null
+ if ( (SwCursorSelOverFlags::ChangePos & eFlags) //allowed to change position if it's a bad one
+ && pFrame && pFrame->isFrameAreaDefinitionValid()
+ && !pFrame->getFrameArea().Height() //a bad zero height position
+ && !InputFieldAtPos(GetPoint()) ) //unless it's a (vertical) input field
+ {
+ // skip to the next/prev valid paragraph with a layout
+ SwPosition& rPtPos = *GetPoint();
+ bool bGoNxt = m_vSavePos.back().nNode < rPtPos.GetNodeIndex();
+ for (;;)
+ {
+ pFrame = bGoNxt ? pFrame->FindNextCnt(true) : pFrame->FindPrevCnt();
+ if (!pFrame || 0 != pFrame->getFrameArea().Height() )
+ break;
+ }
+
+ // #i72394# skip to prev/next valid paragraph with a layout in case
+ // the first search did not succeed:
+ if( !pFrame )
+ {
+ bGoNxt = !bGoNxt;
+ pFrame = static_cast<const SwContentNode*>(pNd)->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() );
+ while ( pFrame && 0 == pFrame->getFrameArea().Height() )
+ {
+ pFrame = bGoNxt ? pFrame->FindNextCnt(true) : pFrame->FindPrevCnt();
+ }
+ }
+
+ if (pFrame != nullptr)
+ {
+ if (pFrame->IsTextFrame())
+ {
+ SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(pFrame));
+ *GetPoint() = pTextFrame->MapViewToModelPos(TextFrameIndex(
+ bGoNxt ? 0 : pTextFrame->GetText().getLength()));
+ }
+ else
+ {
+ assert(pFrame->IsNoTextFrame());
+ SwContentNode *const pCNd = const_cast<SwContentNode*>(
+ static_cast<SwNoTextFrame const*>(pFrame)->GetNode());
+ assert(pCNd);
+
+ // set this ContentNode as new position
+ rPtPos.Assign( *pCNd );
+ // assign corresponding ContentIndex
+ const sal_Int32 nTmpPos = bGoNxt ? 0 : pCNd->Len();
+ GetPoint()->SetContent( nTmpPos );
+ }
+
+
+ if (rPtPos.GetNodeIndex() == m_vSavePos.back().nNode
+ && GetPoint()->GetContentIndex() == m_vSavePos.back().nContent)
+ {
+ // new position equals saved one
+ // --> trigger restore of saved pos by setting <pFrame> to NULL - see below
+ pFrame = nullptr;
+ }
+
+ if ( IsInProtectTable( true ) )
+ {
+ // new position in protected table
+ // --> trigger restore of saved pos by setting <pFrame> to NULL - see below
+ pFrame = nullptr;
+ }
+ }
+ }
+
+ if( !pFrame )
+ {
+ assert(!m_vSavePos.empty());
+ SwContentNode const*const pSaveNode(rNds[m_vSavePos.back().nNode]->GetContentNode());
+ // if the old position already didn't have a frame, allow moving
+ // anyway, hope the caller can handle that
+ if (pSaveNode && pSaveNode->getLayoutFrame(rDoc.getIDocumentLayoutAccess().GetCurrentLayout()))
+ {
+ DeleteMark();
+ RestoreSavePos();
+ return true; // we need a frame
+ }
+ }
+ }
+
+ // is the cursor allowed to be in a protected node?
+ if( !( SwCursorSelOverFlags::ChangePos & eFlags ) && !IsAtValidPos() )
+ {
+ DeleteMark();
+ RestoreSavePos();
+ return true;
+ }
+
+ if( !HasMark() )
+ return false;
+
+ // check for invalid sections
+ if( !::CheckNodesRange( GetMark()->GetNode(), GetPoint()->GetNode(), true ))
+ {
+ DeleteMark();
+ RestoreSavePos();
+ return true; // we need a frame
+ }
+
+ pNd = &GetMark()->GetNode();
+ if( pNd->IsContentNode()
+ && !static_cast<const SwContentNode*>(pNd)->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() )
+ && !dynamic_cast<SwUnoCursor*>(this) )
+ {
+ DeleteMark();
+ RestoreSavePos();
+ return true; // we need a frame
+ }
+
+ // ensure that selection is only inside an InputField or contains the InputField completely
+ {
+ const SwTextAttr* pInputFieldTextAttrAtPoint = InputFieldAtPos(GetPoint());
+ const SwTextAttr* pInputFieldTextAttrAtMark = InputFieldAtPos(GetMark());
+
+ if ( pInputFieldTextAttrAtPoint != pInputFieldTextAttrAtMark )
+ {
+ const SwNodeOffset nRefNodeIdx =
+ ( SwCursorSelOverFlags::Toggle & eFlags )
+ ? m_vSavePos.back().nNode
+ : GetMark()->GetNodeIndex();
+ const sal_Int32 nRefContentIdx =
+ ( SwCursorSelOverFlags::Toggle & eFlags )
+ ? m_vSavePos.back().nContent
+ : GetMark()->GetContentIndex();
+ const bool bIsForwardSelection =
+ nRefNodeIdx < GetPoint()->GetNodeIndex()
+ || ( nRefNodeIdx == GetPoint()->GetNodeIndex()
+ && nRefContentIdx < GetPoint()->GetContentIndex() );
+
+ if ( pInputFieldTextAttrAtPoint != nullptr )
+ {
+ const sal_Int32 nNewPointPos =
+ bIsForwardSelection ? *(pInputFieldTextAttrAtPoint->End()) : pInputFieldTextAttrAtPoint->GetStart();
+ GetPoint()->SetContent( nNewPointPos );
+ }
+
+ if ( pInputFieldTextAttrAtMark != nullptr )
+ {
+ const sal_Int32 nNewMarkPos =
+ bIsForwardSelection ? pInputFieldTextAttrAtMark->GetStart() : *(pInputFieldTextAttrAtMark->End());
+ GetMark()->SetContent( nNewMarkPos );
+ }
+ }
+ }
+
+ const SwTableNode* pPtNd = GetPoint()->GetNode().FindTableNode();
+ const SwTableNode* pMrkNd = GetMark()->GetNode().FindTableNode();
+ // both in no or in same table node
+ if( ( !pMrkNd && !pPtNd ) || pPtNd == pMrkNd )
+ return false;
+
+ // in different tables or only mark in table
+ if( pMrkNd )
+ {
+ // not allowed, so go back to old position
+ RestoreSavePos();
+ // Cursor stays at old position
+ return true;
+ }
+
+ // Note: this cannot happen in TableMode
+ // Only Point in Table then go behind/in front of table
+ if (SwCursorSelOverFlags::ChangePos & eFlags)
+ {
+ bool bSelTop = GetPoint()->GetNodeIndex() <
+ ((SwCursorSelOverFlags::Toggle & eFlags)
+ ? m_vSavePos.back().nNode : GetMark()->GetNodeIndex());
+
+ do { // loop for table after table
+ SwNodeOffset nSEIdx = pPtNd->EndOfSectionIndex();
+ SwNodeOffset nSttEndTable = nSEIdx + 1;
+
+ if( bSelTop )
+ nSttEndTable = rNds[ nSEIdx ]->StartOfSectionIndex() - 1;
+
+ GetPoint()->Assign( nSttEndTable );
+ const SwNode* pMyNd = &(GetPointNode());
+
+ if( pMyNd->IsSectionNode() || ( pMyNd->IsEndNode() &&
+ pMyNd->StartOfSectionNode()->IsSectionNode() ) )
+ {
+ pMyNd = bSelTop
+ ? SwNodes::GoPrevSection( GetPoint(),true,false )
+ : rNds.GoNextSection( GetPoint(),true,false );
+
+ /* #i12312# Handle failure of Go{Prev|Next}Section */
+ if ( nullptr == pMyNd)
+ break;
+
+ pPtNd = pMyNd->FindTableNode();
+ if( pPtNd )
+ continue;
+ }
+
+ // we permit these
+ if( pMyNd->IsContentNode() &&
+ ::CheckNodesRange( GetMark()->GetNode(),
+ GetPoint()->GetNode(), true ))
+ {
+ // table in table
+ const SwTableNode* pOuterTableNd = pMyNd->FindTableNode();
+ if ( pOuterTableNd )
+ pMyNd = pOuterTableNd;
+ else
+ {
+ SwContentNode* pCNd = const_cast<SwContentNode*>(static_cast<const SwContentNode*>(pMyNd));
+ GetPoint()->SetContent( bSelTop ? pCNd->Len() : 0 );
+ return false;
+ }
+ }
+ if( bSelTop )
+ {
+ if ( !pMyNd->IsEndNode() )
+ break;
+ pPtNd = pMyNd->FindTableNode();
+ }
+ else
+ pPtNd = pMyNd->GetTableNode();
+ if (!pPtNd)
+ break;
+ } while( true );
+ }
+
+ // stay on old position
+ RestoreSavePos();
+ return true;
+}
+
+bool SwCursor::IsInProtectTable( bool bMove, bool bChgCursor )
+{
+ SwContentNode* pCNd = GetPointContentNode();
+ if( !pCNd )
+ return false;
+
+ // No table, no protected cell:
+ const SwTableNode* pTableNode = pCNd->FindTableNode();
+ if ( !pTableNode )
+ return false;
+
+ // Current position == last save position?
+ if (m_vSavePos.back().nNode == GetPoint()->GetNodeIndex())
+ return false;
+
+ // Check for covered cell:
+ bool bInCoveredCell = false;
+ const SwStartNode* pTmpSttNode = pCNd->FindTableBoxStartNode();
+ OSL_ENSURE( pTmpSttNode, "In table, therefore I expect to get a SwTableBoxStartNode" );
+ const SwTableBox* pBox = pTmpSttNode ? pTableNode->GetTable().GetTableBox( pTmpSttNode->GetIndex() ) : nullptr; //Robust #151355
+ if ( pBox && pBox->getRowSpan() < 1 ) // Robust #151270
+ bInCoveredCell = true;
+
+ // Positions of covered cells are not acceptable:
+ if ( !bInCoveredCell )
+ {
+ // Position not protected?
+ if ( !pCNd->IsProtect() )
+ return false;
+
+ // Cursor in protected cells allowed?
+ if ( IsReadOnlyAvailable() )
+ return false;
+ }
+
+ // If we reach this point, we are in a protected or covered table cell!
+
+ if( !bMove )
+ {
+ if( bChgCursor )
+ // restore the last save position
+ RestoreSavePos();
+
+ return true; // Cursor stays at old position
+ }
+
+ // We are in a protected table cell. Traverse top to bottom?
+ if (m_vSavePos.back().nNode < GetPoint()->GetNodeIndex())
+ {
+ // search next valid box
+ // if there is another StartNode after the EndNode of a cell then
+ // there is another cell
+ SwNodeIndex aCellStt( *GetPointNode().FindTableBoxStartNode()->EndOfSectionNode(), 1 );
+ bool bProt = true;
+GoNextCell:
+ for (;;) {
+ if( !aCellStt.GetNode().IsStartNode() )
+ break;
+ ++aCellStt;
+ pCNd = aCellStt.GetNode().GetContentNode();
+ if( !pCNd )
+ pCNd = aCellStt.GetNodes().GoNext( &aCellStt );
+ bProt = pCNd->IsProtect();
+ if( !bProt )
+ break;
+ aCellStt.Assign( *pCNd->FindTableBoxStartNode()->EndOfSectionNode(), 1 );
+ }
+
+SetNextCursor:
+ if( !bProt ) // found free cell
+ {
+ GetPoint()->Assign( aCellStt );
+ SwContentNode* pTmpCNd = GetPointContentNode();
+ if( pTmpCNd )
+ {
+ GetPoint()->SetContent( 0 );
+ return false;
+ }
+ return IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos );
+ }
+ // end of table, so go to next node
+ ++aCellStt;
+ SwNode* pNd = &aCellStt.GetNode();
+ if( pNd->IsEndNode() || HasMark())
+ {
+ // if only table in FlyFrame or SSelection then stay on old position
+ if( bChgCursor )
+ RestoreSavePos();
+ return true;
+ }
+ else if( pNd->IsTableNode() )
+ {
+ ++aCellStt;
+ goto GoNextCell;
+ }
+
+ bProt = false; // index is now on a content node
+ goto SetNextCursor;
+ }
+
+ // search for the previous valid box
+ {
+ // if there is another EndNode in front of the StartNode than there
+ // exists a previous cell
+ SwNodeIndex aCellStt( *GetPointNode().FindTableBoxStartNode(), -1 );
+ SwNode* pNd;
+ bool bProt = true;
+GoPrevCell:
+ for (;;) {
+ pNd = &aCellStt.GetNode();
+ if( !pNd->IsEndNode() )
+ break;
+ aCellStt.Assign( *pNd->StartOfSectionNode(), +1 );
+ pCNd = aCellStt.GetNode().GetContentNode();
+ if( !pCNd )
+ pCNd = pNd->GetNodes().GoNext( &aCellStt );
+ bProt = pCNd->IsProtect();
+ if( !bProt )
+ break;
+ aCellStt.Assign( *pNd->FindTableBoxStartNode(), -1 );
+ }
+
+SetPrevCursor:
+ if( !bProt ) // found free cell
+ {
+ GetPoint()->Assign( aCellStt );
+ SwContentNode* pTmpCNd = GetPointContentNode();
+ if( pTmpCNd )
+ {
+ GetPoint()->SetContent( 0 );
+ return false;
+ }
+ return IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos );
+ }
+ // at the beginning of a table, so go to next node
+ --aCellStt;
+ pNd = &aCellStt.GetNode();
+ if( pNd->IsStartNode() || HasMark() )
+ {
+ // if only table in FlyFrame or SSelection then stay on old position
+ if( bChgCursor )
+ RestoreSavePos();
+ return true;
+ }
+ else if( pNd->StartOfSectionNode()->IsTableNode() )
+ {
+ --aCellStt;
+ goto GoPrevCell;
+ }
+
+ bProt = false; // index is now on a content node
+ goto SetPrevCursor;
+ }
+}
+
+/// Return <true> if cursor can be set to this position
+bool SwCursor::IsAtValidPos( bool bPoint ) const
+{
+ const SwDoc& rDoc = GetDoc();
+ const SwPosition* pPos = bPoint ? GetPoint() : GetMark();
+ const SwNode* pNd = &pPos->GetNode();
+
+ if( pNd->IsContentNode() && !static_cast<const SwContentNode*>(pNd)->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ) &&
+ !dynamic_cast<const SwUnoCursor*>(this) )
+ {
+ return false;
+ }
+
+ // #i45129# - in UI-ReadOnly everything is allowed
+ if( !rDoc.GetDocShell() || !rDoc.GetDocShell()->IsReadOnlyUI() )
+ return true;
+
+ const bool bCursorInReadOnly = IsReadOnlyAvailable();
+ if( !bCursorInReadOnly && pNd->IsProtect() )
+ return false;
+
+ const SwSectionNode* pSectNd = pNd->FindSectionNode();
+ return !pSectNd
+ || !(pSectNd->GetSection().IsHiddenFlag() ||
+ ( !bCursorInReadOnly && pSectNd->GetSection().IsProtectFlag() ));
+}
+
+void SwCursor::SaveTableBoxContent( const SwPosition* ) {}
+
+/// set range for search in document
+SwMoveFnCollection const & SwCursor::MakeFindRange( SwDocPositions nStart,
+ SwDocPositions nEnd, SwPaM* pRange ) const
+{
+ pRange->SetMark();
+ FillFindPos( nStart, *pRange->GetMark() );
+ FillFindPos( nEnd, *pRange->GetPoint() );
+
+ // determine direction of search
+ return ( SwDocPositions::Start == nStart || SwDocPositions::OtherStart == nStart ||
+ (SwDocPositions::Curr == nStart &&
+ (SwDocPositions::End == nEnd || SwDocPositions::OtherEnd == nEnd ) ))
+ ? fnMoveForward : fnMoveBackward;
+}
+
+static sal_Int32 lcl_FindSelection( SwFindParas& rParas, SwCursor* pCurrentCursor,
+ SwMoveFnCollection const & fnMove, SwCursor*& pFndRing,
+ SwPaM& aRegion, FindRanges eFndRngs,
+ bool bInReadOnly, bool& bCancel )
+{
+ SwDoc& rDoc = pCurrentCursor->GetDoc();
+ bool const bDoesUndo = rDoc.GetIDocumentUndoRedo().DoesUndo();
+ int nFndRet = 0;
+ sal_Int32 nFound = 0;
+ const bool bSrchBkwrd = &fnMove == &fnMoveBackward;
+ SwPaM *pTmpCursor = pCurrentCursor, *pSaveCursor = pCurrentCursor;
+ std::unique_ptr<SvxSearchItem> xSearchItem;
+
+ // only create progress bar for ShellCursor
+ bool bIsUnoCursor = dynamic_cast<SwUnoCursor*>(pCurrentCursor) != nullptr;
+ std::unique_ptr<PercentHdl> pPHdl;
+ sal_uInt16 nCursorCnt = 0;
+ if( FindRanges::InSel & eFndRngs )
+ {
+ while( pCurrentCursor != ( pTmpCursor = pTmpCursor->GetNext() ))
+ ++nCursorCnt;
+ if( nCursorCnt && !bIsUnoCursor )
+ pPHdl.reset(new PercentHdl( 0, nCursorCnt, rDoc.GetDocShell() ));
+ }
+ else
+ pSaveCursor = pSaveCursor->GetPrev();
+
+ bool bEnd = false;
+ do {
+ aRegion.SetMark();
+ // independent from search direction: SPoint is always bigger than mark
+ // if the search area is valid
+ SwPosition *pSttPos = aRegion.GetMark(),
+ *pEndPos = aRegion.GetPoint();
+ *pSttPos = *pTmpCursor->Start();
+ *pEndPos = *pTmpCursor->End();
+ if( bSrchBkwrd )
+ aRegion.Exchange();
+
+ if( !nCursorCnt && !pPHdl && !bIsUnoCursor )
+ pPHdl.reset(new PercentHdl( aRegion ));
+
+ // as long as found and not at same position
+ while( *pSttPos <= *pEndPos )
+ {
+ nFndRet = rParas.DoFind(*pCurrentCursor, fnMove, aRegion, bInReadOnly, xSearchItem);
+ if( 0 == nFndRet ||
+ ( pFndRing &&
+ *pFndRing->GetPoint() == *pCurrentCursor->GetPoint() &&
+ *pFndRing->GetMark() == *pCurrentCursor->GetMark() ))
+ break;
+ if( !( FIND_NO_RING & nFndRet ))
+ {
+ // #i24084# - create ring similar to the one in CreateCursor
+ SwCursor* pNew = pCurrentCursor->Create( pFndRing );
+ if( !pFndRing )
+ pFndRing = pNew;
+
+ pNew->SetMark();
+ *pNew->GetMark() = *pCurrentCursor->GetMark();
+ }
+
+ ++nFound;
+
+ if( !( eFndRngs & FindRanges::InSelAll) )
+ {
+ bEnd = true;
+ break;
+ }
+
+ if ((coSrchRplcThreshold == nFound)
+ && rDoc.GetIDocumentUndoRedo().DoesUndo()
+ && rParas.IsReplaceMode())
+ {
+ short nRet = pCurrentCursor->MaxReplaceArived();
+ if( RET_YES == nRet )
+ {
+ rDoc.GetIDocumentUndoRedo().DelAllUndoObj();
+ rDoc.GetIDocumentUndoRedo().DoUndo(false);
+ }
+ else
+ {
+ bEnd = true;
+ if(RET_CANCEL == nRet)
+ {
+ bCancel = true;
+ }
+ break;
+ }
+ }
+
+ if( bSrchBkwrd )
+ // move pEndPos in front of the found area
+ *pEndPos = *pCurrentCursor->Start();
+ else
+ // move pSttPos behind the found area
+ *pSttPos = *pCurrentCursor->End();
+
+ if( *pSttPos == *pEndPos )
+ // in area but at the end => done
+ break;
+
+ if( !nCursorCnt && pPHdl )
+ {
+ pPHdl->NextPos( *aRegion.GetMark() );
+ }
+ }
+
+ if( bEnd || !( eFndRngs & ( FindRanges::InSelAll | FindRanges::InSel )) )
+ break;
+
+ pTmpCursor = pTmpCursor->GetNext();
+ if( nCursorCnt && pPHdl )
+ {
+ pPHdl->NextPos( ++pPHdl->nActPos );
+ }
+
+ } while( pTmpCursor != pSaveCursor && pTmpCursor->GetNext() != pTmpCursor);
+
+ if( nFound && !pFndRing ) // if no ring should be created
+ pFndRing = pCurrentCursor->Create();
+
+ rDoc.GetIDocumentUndoRedo().DoUndo(bDoesUndo);
+ return nFound;
+}
+
+static bool lcl_MakeSelFwrd( const SwNode& rSttNd, const SwNode& rEndNd,
+ SwPaM& rPam, bool bFirst )
+{
+ if( rSttNd.GetIndex() + 1 == rEndNd.GetIndex() )
+ return false;
+
+ SwNodes& rNds = rPam.GetDoc().GetNodes();
+ rPam.DeleteMark();
+ SwContentNode* pCNd;
+ if( !bFirst )
+ {
+ rPam.GetPoint()->Assign(rSttNd);
+ pCNd = rNds.GoNext( rPam.GetPoint() );
+ if( !pCNd )
+ return false;
+ rPam.GetPoint()->AssignStartIndex(*pCNd);
+ }
+ else if( rSttNd.GetIndex() > rPam.GetPoint()->GetNodeIndex() ||
+ rPam.GetPoint()->GetNodeIndex() >= rEndNd.GetIndex() )
+ // not in this section
+ return false;
+
+ rPam.SetMark();
+ rPam.GetPoint()->Assign(rEndNd);
+ pCNd = SwNodes::GoPrevious( rPam.GetPoint() );
+ if( !pCNd )
+ return false;
+ rPam.GetPoint()->AssignEndIndex(*pCNd);
+
+ return *rPam.GetMark() < *rPam.GetPoint();
+}
+
+static bool lcl_MakeSelBkwrd( const SwNode& rSttNd, const SwNode& rEndNd,
+ SwPaM& rPam, bool bFirst )
+{
+ if( rEndNd.GetIndex() + 1 == rSttNd.GetIndex() )
+ return false;
+
+ SwNodes& rNds = rPam.GetDoc().GetNodes();
+ rPam.DeleteMark();
+ SwContentNode* pCNd;
+ if( !bFirst )
+ {
+ rPam.GetPoint()->Assign(rSttNd);
+ pCNd = SwNodes::GoPrevious( rPam.GetPoint() );
+ if( !pCNd )
+ return false;
+ rPam.GetPoint()->AssignEndIndex(*pCNd);
+ }
+ else if( rEndNd.GetIndex() > rPam.GetPoint()->GetNodeIndex() ||
+ rPam.GetPoint()->GetNodeIndex() >= rSttNd.GetIndex() )
+ return false; // not in this section
+
+ rPam.SetMark();
+ rPam.GetPoint()->Assign(rEndNd);
+ pCNd = rNds.GoNext( rPam.GetPoint() );
+ if( !pCNd )
+ return false;
+ rPam.GetPoint()->SetContent(0);
+
+ return *rPam.GetPoint() < *rPam.GetMark();
+}
+
+// this method "searches" for all use cases because in SwFindParas is always the
+// correct parameters and respective search method
+sal_Int32 SwCursor::FindAll( SwFindParas& rParas,
+ SwDocPositions nStart, SwDocPositions nEnd,
+ FindRanges eFndRngs, bool& bCancel )
+{
+ bCancel = false;
+ SwCursorSaveState aSaveState( *this );
+
+ // create region without adding it to the ring
+ SwPaM aRegion( *GetPoint() );
+ SwMoveFnCollection const & fnMove = MakeFindRange( nStart, nEnd, &aRegion );
+
+ sal_Int32 nFound = 0;
+ const bool bMvBkwrd = &fnMove == &fnMoveBackward;
+ bool bInReadOnly = IsReadOnlyAvailable();
+ std::unique_ptr<SvxSearchItem> xSearchItem;
+
+ SwCursor* pFndRing = nullptr;
+ SwNodes& rNds = GetDoc().GetNodes();
+
+ // search in sections?
+ if( FindRanges::InSel & eFndRngs )
+ {
+ // if string was not found in region then get all sections (cursors
+ // stays unchanged)
+ nFound = lcl_FindSelection( rParas, this, fnMove,
+ pFndRing, aRegion, eFndRngs,
+ bInReadOnly, bCancel );
+ if( 0 == nFound )
+ return nFound;
+
+ // found string at least once; it's all in new Cursor ring thus delete old one
+ while( GetNext() != this )
+ delete GetNext();
+
+ *GetPoint() = *pFndRing->GetPoint();
+ SetMark();
+ *GetMark() = *pFndRing->GetMark();
+ pFndRing->GetRingContainer().merge( GetRingContainer() );
+ delete pFndRing;
+ }
+ else if( FindRanges::InOther & eFndRngs )
+ {
+ // put cursor as copy of current into ring
+ // chaining points always to first created, so forward
+ SwCursor* pSav = Create( this ); // save the current cursor
+
+ // if already outside of body text search from this position or start at
+ // 1. base section
+ if( bMvBkwrd
+ ? lcl_MakeSelBkwrd( rNds.GetEndOfExtras(),
+ *rNds.GetEndOfPostIts().StartOfSectionNode(),
+ *this, rNds.GetEndOfExtras().GetIndex() >=
+ GetPoint()->GetNodeIndex() )
+ : lcl_MakeSelFwrd( *rNds.GetEndOfPostIts().StartOfSectionNode(),
+ rNds.GetEndOfExtras(), *this,
+ rNds.GetEndOfExtras().GetIndex() >=
+ GetPoint()->GetNodeIndex() ))
+ {
+ nFound = lcl_FindSelection( rParas, this, fnMove, pFndRing,
+ aRegion, eFndRngs, bInReadOnly, bCancel );
+ }
+
+ if( !nFound )
+ {
+ // put back the old one
+ *GetPoint() = *pSav->GetPoint();
+ if( pSav->HasMark() )
+ {
+ SetMark();
+ *GetMark() = *pSav->GetMark();
+ }
+ else
+ DeleteMark();
+ return 0;
+ }
+
+ if( !( FindRanges::InSelAll & eFndRngs ))
+ {
+ // there should only be a single one, thus add it
+ // independent from search direction: SPoint is always bigger than
+ // mark if the search area is valid
+ *GetPoint() = *pFndRing->GetPoint();
+ SetMark();
+ *GetMark() = *pFndRing->GetMark();
+ }
+ else
+ {
+ // found string at least once; it's all in new Cursor ring thus delete old one
+ while( GetNext() != this )
+ delete GetNext();
+
+ *GetPoint() = *pFndRing->GetPoint();
+ SetMark();
+ *GetMark() = *pFndRing->GetMark();
+ pFndRing->GetRingContainer().merge( GetRingContainer() );
+ }
+ delete pFndRing;
+ }
+ else if( FindRanges::InSelAll & eFndRngs )
+ {
+ SwCursor* pSav = Create( this ); // save the current cursor
+
+ const SwNode* pSttNd = ( FindRanges::InBodyOnly & eFndRngs )
+ ? rNds.GetEndOfContent().StartOfSectionNode()
+ : rNds.GetEndOfPostIts().StartOfSectionNode();
+
+ if( bMvBkwrd
+ ? lcl_MakeSelBkwrd( rNds.GetEndOfContent(), *pSttNd, *this, false )
+ : lcl_MakeSelFwrd( *pSttNd, rNds.GetEndOfContent(), *this, false ))
+ {
+ nFound = lcl_FindSelection( rParas, this, fnMove, pFndRing,
+ aRegion, eFndRngs, bInReadOnly, bCancel );
+ }
+
+ if( !nFound )
+ {
+ // put back the old one
+ *GetPoint() = *pSav->GetPoint();
+ if( pSav->HasMark() )
+ {
+ SetMark();
+ *GetMark() = *pSav->GetMark();
+ }
+ else
+ DeleteMark();
+ return 0;
+ }
+ while( GetNext() != this )
+ delete GetNext();
+
+ *GetPoint() = *pFndRing->GetPoint();
+ SetMark();
+ *GetMark() = *pFndRing->GetMark();
+ pFndRing->GetRingContainer().merge( GetRingContainer() );
+ delete pFndRing;
+ }
+ else
+ {
+ // if a GetMark is set then keep the GetMark of the found object
+ // This allows spanning an area with this search.
+ SwPosition aMarkPos( *GetMark() );
+ const bool bMarkPos = HasMark() && (eFndRngs == FindRanges::InBody);
+
+ nFound = rParas.DoFind(*this, fnMove, aRegion, bInReadOnly, xSearchItem) ? 1 : 0;
+ if (0 != nFound && bMarkPos)
+ *GetMark() = aMarkPos;
+ }
+
+ if( nFound && SwCursor::IsSelOvr( SwCursorSelOverFlags::Toggle ) )
+ nFound = 0;
+ return nFound;
+}
+
+void SwCursor::FillFindPos( SwDocPositions ePos, SwPosition& rPos ) const
+{
+ bool bIsStart = true;
+ SwContentNode* pCNd = nullptr;
+ SwNodes& rNds = GetDoc().GetNodes();
+
+ switch( ePos )
+ {
+ case SwDocPositions::Start:
+ rPos.Assign(*rNds.GetEndOfContent().StartOfSectionNode());
+ pCNd = rNds.GoNext( &rPos );
+ break;
+ case SwDocPositions::End:
+ rPos.Assign(rNds.GetEndOfContent());
+ pCNd = SwNodes::GoPrevious( &rPos );
+ bIsStart = false;
+ break;
+ case SwDocPositions::OtherStart:
+ rPos.Assign( *rNds[ SwNodeOffset(0) ] );
+ pCNd = rNds.GoNext( &rPos );
+ break;
+ case SwDocPositions::OtherEnd:
+ rPos.Assign( *rNds.GetEndOfContent().StartOfSectionNode() );
+ pCNd = SwNodes::GoPrevious( &rPos );
+ bIsStart = false;
+ break;
+ default:
+ rPos = *GetPoint();
+ }
+
+ if( pCNd && !bIsStart )
+ {
+ rPos.AssignEndIndex( *pCNd );
+ }
+}
+
+short SwCursor::MaxReplaceArived()
+{
+ return RET_YES;
+}
+
+namespace {
+
+struct HideWrapper
+{
+ // either the frame's text or the node's text (possibly pre-filtered)
+ OUString const* m_pText;
+ // this is actually a TextFrameIndex but all of the i18n code uses sal_Int32
+ sal_Int32 m_nPtIndex;
+ // if mapping is needed, use this frame
+ SwTextFrame * m_pFrame;
+ // input in the constructor, output (via mapping) in the destructor
+ SwTextNode *& m_rpTextNode;
+ sal_Int32 & m_rPtPos;
+
+ HideWrapper(SwRootFrame const*const pLayout,
+ SwTextNode *& rpTextNode, sal_Int32 & rPtPos,
+ OUString const*const pFilteredNodeText = nullptr)
+ : m_pText(pFilteredNodeText)
+ , m_pFrame(nullptr)
+ , m_rpTextNode(rpTextNode)
+ , m_rPtPos(rPtPos)
+ {
+ if (pLayout && pLayout->HasMergedParas())
+ {
+ m_pFrame = static_cast<SwTextFrame*>(rpTextNode->getLayoutFrame(pLayout));
+ m_pText = &m_pFrame->GetText();
+ m_nPtIndex = sal_Int32(m_pFrame->MapModelToView(rpTextNode, rPtPos));
+ }
+ else
+ {
+ if (!m_pText)
+ {
+ m_pText = &rpTextNode->GetText();
+ }
+ m_nPtIndex = rPtPos;
+ }
+ }
+ ~HideWrapper()
+ {
+ AssignBack(m_rpTextNode, m_rPtPos);
+ }
+ void AssignBack(SwTextNode *& rpTextNode, sal_Int32 & rPtPos)
+ {
+ if (0 <= m_nPtIndex && m_pFrame)
+ {
+ std::pair<SwTextNode*, sal_Int32> const pos(
+ m_pFrame->MapViewToModel(TextFrameIndex(m_nPtIndex)));
+ rpTextNode = pos.first;
+ rPtPos = pos.second;
+ }
+ else
+ {
+ rPtPos = m_nPtIndex;
+ }
+ }
+};
+
+} // namespace
+
+bool SwCursor::SelectWord( SwViewShell const * pViewShell, const Point* pPt )
+{
+ return SelectWordWT( pViewShell, WordType::ANYWORD_IGNOREWHITESPACES, pPt );
+}
+
+bool SwCursor::IsStartWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) const
+{
+ bool bRet = false;
+ SwTextNode* pTextNd = GetPointNode().GetTextNode();
+ if (pTextNd)
+ {
+ sal_Int32 nPtPos = GetPoint()->GetContentIndex();
+
+ HideWrapper w(pLayout, pTextNd, nPtPos);
+
+ bRet = g_pBreakIt->GetBreakIter()->isBeginWord(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos )),
+ nWordType );
+ }
+ return bRet;
+}
+
+bool SwCursor::IsEndWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) const
+{
+ bool bRet = false;
+ SwTextNode* pTextNd = GetPointNode().GetTextNode();
+ if (pTextNd)
+ {
+ sal_Int32 nPtPos = GetPoint()->GetContentIndex();
+
+ HideWrapper w(pLayout, pTextNd, nPtPos);
+
+ bRet = g_pBreakIt->GetBreakIter()->isEndWord(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
+ nWordType );
+
+ }
+ return bRet;
+}
+
+bool SwCursor::IsInWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) const
+{
+ bool bRet = false;
+ SwTextNode* pTextNd = GetPointNode().GetTextNode();
+ if (pTextNd)
+ {
+ sal_Int32 nPtPos = GetPoint()->GetContentIndex();
+
+ {
+ HideWrapper w(pLayout, pTextNd, nPtPos);
+
+ Boundary aBoundary = g_pBreakIt->GetBreakIter()->getWordBoundary(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
+ nWordType,
+ true );
+
+ bRet = aBoundary.startPos != aBoundary.endPos &&
+ aBoundary.startPos <= w.m_nPtIndex &&
+ w.m_nPtIndex <= aBoundary.endPos;
+ w.m_nPtIndex = aBoundary.startPos; // hack: convert startPos back...
+ }
+ if(bRet)
+ {
+ const CharClass& rCC = GetAppCharClass();
+ bRet = rCC.isLetterNumeric(pTextNd->GetText(), nPtPos);
+ }
+ }
+ return bRet;
+}
+
+bool SwCursor::IsStartEndSentence(bool bEnd, SwRootFrame const*const pLayout) const
+{
+ bool bRet = bEnd ?
+ GetPointContentNode() && GetPoint()->GetContentIndex() == GetPointContentNode()->Len() :
+ GetPoint()->GetContentIndex() == 0;
+
+ if ((pLayout != nullptr && pLayout->HasMergedParas()) || !bRet)
+ {
+ SwCursor aCursor(*GetPoint(), nullptr);
+ SwPosition aOrigPos = *aCursor.GetPoint();
+ aCursor.GoSentence(bEnd ? SwCursor::END_SENT : SwCursor::START_SENT, pLayout);
+ bRet = aOrigPos == *aCursor.GetPoint();
+ }
+ return bRet;
+}
+
+bool SwCursor::GoStartWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout)
+{
+ bool bRet = false;
+ SwTextNode* pTextNd = GetPointNode().GetTextNode();
+ if (pTextNd)
+ {
+ SwCursorSaveState aSave( *this );
+ sal_Int32 nPtPos = GetPoint()->GetContentIndex();
+
+ {
+ HideWrapper w(pLayout, pTextNd, nPtPos);
+
+ w.m_nPtIndex = g_pBreakIt->GetBreakIter()->getWordBoundary(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
+ nWordType,
+ false ).startPos;
+ }
+
+ if (nPtPos < pTextNd->GetText().getLength() && nPtPos >= 0)
+ {
+ GetPoint()->Assign(*pTextNd, nPtPos);
+ if( !IsSelOvr() )
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+bool SwCursor::GoEndWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout)
+{
+ bool bRet = false;
+ SwTextNode* pTextNd = GetPointNode().GetTextNode();
+ if (pTextNd)
+ {
+ SwCursorSaveState aSave( *this );
+ sal_Int32 nPtPos = GetPoint()->GetContentIndex();
+
+ {
+ HideWrapper w(pLayout, pTextNd, nPtPos);
+
+ w.m_nPtIndex = g_pBreakIt->GetBreakIter()->getWordBoundary(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
+ nWordType,
+ true ).endPos;
+ }
+
+ if (nPtPos <= pTextNd->GetText().getLength() && nPtPos >= 0 &&
+ GetPoint()->GetContentIndex() != nPtPos )
+ {
+ GetPoint()->Assign(*pTextNd, nPtPos);
+ if( !IsSelOvr() )
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+bool SwCursor::GoNextWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout)
+{
+ bool bRet = false;
+ SwTextNode* pTextNd = GetPointNode().GetTextNode();
+ if (pTextNd)
+ {
+ SwCursorSaveState aSave( *this );
+ sal_Int32 nPtPos = GetPoint()->GetContentIndex();
+
+ {
+ HideWrapper w(pLayout, pTextNd, nPtPos);
+
+ w.m_nPtIndex = g_pBreakIt->GetBreakIter()->nextWord(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale( pTextNd->GetLang(nPtPos, 1) ),
+ nWordType ).startPos;
+ }
+
+ if (nPtPos <= pTextNd->GetText().getLength() && nPtPos >= 0)
+ {
+ GetPoint()->Assign(*pTextNd, nPtPos);
+ if( !IsSelOvr() )
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+bool SwCursor::GoPrevWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout)
+{
+ bool bRet = false;
+ SwTextNode* pTextNd = GetPointNode().GetTextNode();
+ if (pTextNd)
+ {
+ SwCursorSaveState aSave( *this );
+ sal_Int32 nPtPos = GetPoint()->GetContentIndex();
+
+ {
+ HideWrapper w(pLayout, pTextNd, nPtPos);
+
+ const sal_Int32 nPtStart = w.m_nPtIndex;
+ if (w.m_nPtIndex)
+ {
+ --w.m_nPtIndex;
+ w.AssignBack(pTextNd, nPtPos);
+ }
+
+ w.m_nPtIndex = g_pBreakIt->GetBreakIter()->previousWord(
+ *w.m_pText, nPtStart,
+ g_pBreakIt->GetLocale( pTextNd->GetLang(nPtPos, 1) ),
+ nWordType ).startPos;
+ }
+
+ if (nPtPos < pTextNd->GetText().getLength() && nPtPos >= 0)
+ {
+ GetPoint()->Assign(*pTextNd, nPtPos);
+ if( !IsSelOvr() )
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+bool SwCursor::SelectWordWT( SwViewShell const * pViewShell, sal_Int16 nWordType, const Point* pPt )
+{
+ SwCursorSaveState aSave( *this );
+
+ bool bRet = false;
+ DeleteMark();
+ const SwRootFrame* pLayout = pViewShell->GetLayout();
+ if( pPt && nullptr != pLayout )
+ {
+ // set the cursor to the layout position
+ Point aPt( *pPt );
+ pLayout->GetModelPositionForViewPoint( GetPoint(), aPt );
+ }
+
+ SwTextNode* pTextNd = GetPointNode().GetTextNode();
+ if (pTextNd)
+ {
+ // Should we select the whole fieldmark?
+ const IDocumentMarkAccess* pMarksAccess = GetDoc().getIDocumentMarkAccess( );
+ sw::mark::IFieldmark const*const pMark(pMarksAccess->getInnerFieldmarkFor(*GetPoint()));
+ if (pMark && (IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK
+ || IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::DATE_FIELDMARK))
+ {
+ *GetPoint() = sw::mark::FindFieldSep(*pMark);
+ GetPoint()->AdjustContent(+1); // Don't select the separator
+
+ const SwPosition& rEnd = pMark->GetMarkEnd();
+
+ assert(pMark->GetMarkEnd() != *GetPoint());
+ SetMark();
+ *GetMark() = rEnd;
+ GetMark()->AdjustContent(-1); // Don't select the end delimiter
+
+ bRet = true;
+ }
+ else
+ {
+ bool bForward = true;
+ sal_Int32 nPtPos = GetPoint()->GetContentIndex();
+
+ HideWrapper w(pViewShell->GetLayout(), pTextNd, nPtPos);
+
+ Boundary aBndry( g_pBreakIt->GetBreakIter()->getWordBoundary(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
+ nWordType,
+ bForward ));
+
+ if (comphelper::LibreOfficeKit::isActive() && aBndry.startPos == aBndry.endPos && w.m_nPtIndex > 0)
+ {
+ // nPtPos is the end of the paragraph, select the last word then.
+ --w.m_nPtIndex;
+ w.AssignBack(pTextNd, nPtPos);
+
+ aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale( pTextNd->GetLang( nPtPos ) ),
+ nWordType,
+ bForward );
+
+ }
+
+ SwTextNode * pStartNode(pTextNd);
+ sal_Int32 nStartIndex;
+ w.m_nPtIndex = aBndry.startPos;
+ w.AssignBack(pStartNode, nStartIndex);
+
+ SwTextNode * pEndNode(pTextNd);
+ sal_Int32 nEndIndex;
+ w.m_nPtIndex = aBndry.endPos;
+ w.AssignBack(pEndNode, nEndIndex);
+
+ if( aBndry.startPos != aBndry.endPos )
+ {
+ GetPoint()->Assign(*pEndNode, nEndIndex);
+ if( !IsSelOvr() )
+ {
+ SetMark();
+ GetMark()->Assign(*pStartNode, nStartIndex);
+ if (sw::mark::IMark* pAnnotationMark = pMarksAccess->getAnnotationMarkFor(*GetPoint()))
+ {
+ // An annotation mark covers the selected word. Check
+ // if it covers only the word: in that case we select
+ // the comment anchor as well.
+ bool bStartMatch = GetMark()->GetNode() == pAnnotationMark->GetMarkStart().GetNode() &&
+ GetMark()->GetContentIndex() == pAnnotationMark->GetMarkStart().GetContentIndex();
+ bool bEndMatch = GetPoint()->GetNode() == pAnnotationMark->GetMarkEnd().GetNode() &&
+ GetPoint()->GetContentIndex() + 1 == pAnnotationMark->GetMarkEnd().GetContentIndex();
+ if (bStartMatch && bEndMatch)
+ GetPoint()->AdjustContent(+1);
+ }
+ if( !IsSelOvr() )
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ if( !bRet )
+ {
+ DeleteMark();
+ RestoreSavePos();
+ }
+ return bRet;
+}
+
+static OUString lcl_MaskDeletedRedlines( const SwTextNode* pTextNd )
+{
+ OUString aRes;
+ if (pTextNd)
+ {
+ //mask deleted redlines
+ OUString sNodeText(pTextNd->GetText());
+ const SwDoc& rDoc = pTextNd->GetDoc();
+ const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() );
+ if ( bShowChg )
+ {
+ SwRedlineTable::size_type nAct = rDoc.getIDocumentRedlineAccess().GetRedlinePos( *pTextNd, RedlineType::Any );
+ for ( ; nAct < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); nAct++ )
+ {
+ const SwRangeRedline* pRed = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nAct ];
+ if ( pRed->Start()->GetNode() > *pTextNd )
+ break;
+
+ if( RedlineType::Delete == pRed->GetType() )
+ {
+ sal_Int32 nStart, nEnd;
+ pRed->CalcStartEnd( pTextNd->GetIndex(), nStart, nEnd );
+
+ while ( nStart < nEnd && nStart < sNodeText.getLength() )
+ sNodeText = sNodeText.replaceAt( nStart++, 1, rtl::OUStringChar(CH_TXTATR_INWORD) );
+ }
+ }
+ }
+ aRes = sNodeText;
+ }
+ return aRes;
+}
+
+bool SwCursor::GoSentence(SentenceMoveType eMoveType, SwRootFrame const*const pLayout)
+{
+ bool bRet = false;
+ SwTextNode* pTextNd = GetPointNode().GetTextNode();
+ if (pTextNd)
+ {
+ OUString const sNodeText(lcl_MaskDeletedRedlines(pTextNd));
+
+ SwCursorSaveState aSave( *this );
+ sal_Int32 nPtPos = GetPoint()->GetContentIndex();
+
+ {
+ HideWrapper w(pLayout, pTextNd, nPtPos, &sNodeText);
+
+ switch ( eMoveType )
+ {
+ case START_SENT: /* when modifying: see also ExpandToSentenceBorders below! */
+ w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos)));
+ break;
+ case END_SENT: /* when modifying: see also ExpandToSentenceBorders below! */
+ w.m_nPtIndex = g_pBreakIt->GetBreakIter()->endOfSentence(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos)));
+ break;
+ case NEXT_SENT:
+ {
+ w.m_nPtIndex = g_pBreakIt->GetBreakIter()->endOfSentence(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos)));
+ if (w.m_nPtIndex >= 0 && w.m_nPtIndex < w.m_pText->getLength())
+ {
+ do
+ {
+ ++w.m_nPtIndex;
+ }
+ while (w.m_nPtIndex < w.m_pText->getLength()
+ && (*w.m_pText)[w.m_nPtIndex] == ' ');
+ }
+ break;
+ }
+ case PREV_SENT:
+ w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos)));
+
+ if (w.m_nPtIndex == 0)
+ return false; // the previous sentence is not in this paragraph
+ if (w.m_nPtIndex > 0)
+ {
+ w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence(
+ *w.m_pText, w.m_nPtIndex - 1,
+ g_pBreakIt->GetLocale(pTextNd->GetLang(nPtPos)));
+ }
+ break;
+ }
+ }
+
+ // it is allowed to place the PaM just behind the last
+ // character in the text thus <= ...Len
+ if (nPtPos <= pTextNd->GetText().getLength() && nPtPos >= 0)
+ {
+ GetPoint()->Assign(*pTextNd, nPtPos);
+ if( !IsSelOvr() )
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+void SwCursor::ExpandToSentenceBorders(SwRootFrame const*const pLayout)
+{
+ SwTextNode* pStartNd = Start()->GetNode().GetTextNode();
+ SwTextNode* pEndNd = End()->GetNode().GetTextNode();
+ if (!pStartNd || !pEndNd)
+ return;
+
+ if (!HasMark())
+ SetMark();
+
+ OUString sStartText( lcl_MaskDeletedRedlines( pStartNd ) );
+ OUString sEndText( pStartNd == pEndNd? sStartText : lcl_MaskDeletedRedlines( pEndNd ) );
+
+ SwCursorSaveState aSave( *this );
+ sal_Int32 nStartPos = Start()->GetContentIndex();
+ sal_Int32 nEndPos = End()->GetContentIndex();
+
+ {
+ HideWrapper w(pLayout, pStartNd, nStartPos, &sStartText);
+
+ w.m_nPtIndex = g_pBreakIt->GetBreakIter()->beginOfSentence(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale( pStartNd->GetLang( nStartPos ) ) );
+ }
+ {
+ HideWrapper w(pLayout, pEndNd, nEndPos, &sEndText);
+
+ w.m_nPtIndex = g_pBreakIt->GetBreakIter()->endOfSentence(
+ *w.m_pText, w.m_nPtIndex,
+ g_pBreakIt->GetLocale( pEndNd->GetLang( nEndPos ) ) );
+ }
+
+ // it is allowed to place the PaM just behind the last
+ // character in the text thus <= ...Len
+ if (nStartPos <= pStartNd->GetText().getLength() && nStartPos >= 0)
+ {
+ GetMark()->Assign(*pStartNd, nStartPos);
+ }
+ if (nEndPos <= pEndNd->GetText().getLength() && nEndPos >= 0)
+ {
+ GetPoint()->Assign(*pEndNd, nEndPos);
+ }
+}
+
+bool SwTableCursor::LeftRight( bool bLeft, sal_uInt16 nCnt, SwCursorSkipMode /*nMode*/,
+ bool /*bVisualAllowed*/, bool /*bSkipHidden*/, bool /*bInsertCursor*/,
+ SwRootFrame const*, bool /*isFieldNames*/)
+{
+ return bLeft ? GoPrevCell( nCnt )
+ : GoNextCell( nCnt );
+}
+
+// calculate cursor bidi level: extracted from LeftRight()
+const SwContentFrame*
+SwCursor::DoSetBidiLevelLeftRight(
+ bool & io_rbLeft, bool bVisualAllowed, bool bInsertCursor)
+{
+ // calculate cursor bidi level
+ const SwContentFrame* pSttFrame = nullptr;
+ SwNode& rNode = GetPoint()->GetNode();
+
+ if( rNode.IsTextNode() )
+ {
+ const SwTextNode& rTNd = *rNode.GetTextNode();
+ sal_Int32 nPos = GetPoint()->GetContentIndex();
+
+ if ( bVisualAllowed && SvtCTLOptions::IsCTLFontEnabled() &&
+ SvtCTLOptions::MOVEMENT_VISUAL == SvtCTLOptions::GetCTLCursorMovement() )
+ {
+ // for visual cursor travelling (used in bidi layout)
+ // we first have to convert the logic to a visual position
+ Point aPt;
+ std::pair<Point, bool> const tmp(aPt, true);
+ pSttFrame = rTNd.getLayoutFrame(
+ GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
+ GetPoint(), &tmp);
+ if( pSttFrame )
+ {
+ sal_uInt8 nCursorLevel = GetCursorBidiLevel();
+ bool bForward = ! io_rbLeft;
+ SwTextFrame *const pTF(const_cast<SwTextFrame*>(
+ static_cast<const SwTextFrame*>(pSttFrame)));
+ TextFrameIndex nTFIndex(pTF->MapModelToViewPos(*GetPoint()));
+ pTF->PrepareVisualMove( nTFIndex, nCursorLevel,
+ bForward, bInsertCursor );
+ *GetPoint() = pTF->MapViewToModelPos(nTFIndex);
+ SetCursorBidiLevel( nCursorLevel );
+ io_rbLeft = ! bForward;
+ }
+ }
+ else
+ {
+ SwTextFrame const* pFrame;
+ const SwScriptInfo* pSI = SwScriptInfo::GetScriptInfo(rTNd, &pFrame);
+ if ( pSI )
+ {
+ const sal_Int32 nMoveOverPos = io_rbLeft ?
+ ( nPos ? nPos - 1 : 0 ) :
+ nPos;
+ TextFrameIndex nIndex(pFrame->MapModelToView(&rTNd, nMoveOverPos));
+ SetCursorBidiLevel( pSI->DirType(nIndex) );
+ }
+ }
+ }
+ return pSttFrame;
+}
+
+bool SwCursor::LeftRight( bool bLeft, sal_uInt16 nCnt, SwCursorSkipMode nMode,
+ bool bVisualAllowed,bool bSkipHidden, bool bInsertCursor,
+ SwRootFrame const*const pLayout, bool isFieldNames)
+{
+ // calculate cursor bidi level
+ SwNode& rNode = GetPoint()->GetNode();
+ const SwContentFrame* pSttFrame = // may side-effect bLeft!
+ DoSetBidiLevelLeftRight(bLeft, bVisualAllowed, bInsertCursor);
+
+ // can the cursor be moved n times?
+ SwCursorSaveState aSave( *this );
+ SwMoveFnCollection const & fnMove = bLeft ? fnMoveBackward : fnMoveForward;
+
+ SwGoInDoc fnGo;
+ if ( bSkipHidden )
+ fnGo = SwCursorSkipMode::Cells == nMode ? GoInContentCellsSkipHidden : GoInContentSkipHidden;
+ else
+ fnGo = SwCursorSkipMode::Cells == nMode ? GoInContentCells : GoInContent;
+
+ SwTextFrame const* pFrame(nullptr);
+ if (pLayout)
+ {
+ pFrame = static_cast<SwTextFrame*>(rNode.GetContentNode()->getLayoutFrame(pLayout));
+ if (pFrame)
+ {
+ while (pFrame->GetPrecede())
+ {
+ pFrame = static_cast<SwTextFrame const*>(pFrame->GetPrecede());
+ }
+ }
+ }
+
+ while( nCnt )
+ {
+ SwNodeIndex aOldNodeIdx( GetPoint()->GetNode() );
+
+ TextFrameIndex beforeIndex(-1);
+ if (pFrame)
+ {
+ beforeIndex = pFrame->MapModelToViewPos(*GetPoint());
+ }
+
+ if (!bLeft && pLayout && pLayout->GetFieldmarkMode() == sw::FieldmarkMode::ShowResult)
+ {
+ SwTextNode const*const pNode(GetPoint()->GetNode().GetTextNode());
+ assert(pNode);
+ if (pNode->Len() != GetPoint()->GetContentIndex()
+ && pNode->GetText()[GetPoint()->GetContentIndex()] == CH_TXT_ATR_FIELDSTART)
+ {
+ IDocumentMarkAccess const& rIDMA(*GetDoc().getIDocumentMarkAccess());
+ sw::mark::IFieldmark const*const pMark(rIDMA.getFieldmarkAt(*GetPoint()));
+ assert(pMark);
+ *GetPoint() = sw::mark::FindFieldSep(*pMark);
+ }
+ }
+
+ if ( !Move( fnMove, fnGo ) )
+ {
+ const SwEditShell* pSh = GetDoc().GetEditShell();
+ const SwViewOption* pViewOptions = pSh ? pSh->GetViewOptions() : nullptr;
+ if (pViewOptions && pViewOptions->IsShowOutlineContentVisibilityButton())
+ {
+ // Fixes crash that occurs in documents with outline content folded at the end of
+ // the document. When the cursor is at the end of the visible document and
+ // right arrow key is pressed Move fails after moving the cursor to the
+ // end of the document model, which doesn't have a node frame and causes
+ // weird numbers to be displayed in the statusbar page number count. Left
+ // arrow, when in this state, causes a crash without RestoredSavePos() added here.
+ RestoreSavePos();
+ }
+ break;
+ }
+
+ if (pFrame)
+ {
+ SwTextFrame const* pNewFrame(static_cast<SwTextFrame const*>(
+ GetPoint()->GetNode().GetContentNode()->getLayoutFrame(pLayout)));
+ if (pNewFrame)
+ {
+ while (pNewFrame->GetPrecede())
+ {
+ pNewFrame = static_cast<SwTextFrame const*>(pNewFrame->GetPrecede());
+ }
+ }
+ // sw_redlinehide: fully redline-deleted nodes don't have frames...
+ if (pFrame == pNewFrame || !pNewFrame)
+ {
+ if (!pNewFrame || beforeIndex == pFrame->MapModelToViewPos(*GetPoint()))
+ {
+ continue; // moving inside delete redline, doesn't count...
+ }
+ }
+ else
+ {
+ // assume iteration is stable & returns the same frame
+ assert(!pFrame->IsAnFollow(pNewFrame) && !pNewFrame->IsAnFollow(pFrame));
+ pFrame = pNewFrame;
+ }
+ }
+
+ if (bLeft && pLayout && pLayout->GetFieldmarkMode() == sw::FieldmarkMode::ShowCommand)
+ {
+ SwTextNode const*const pNode(GetPoint()->GetNode().GetTextNode());
+ assert(pNode);
+ if (pNode->Len() != GetPoint()->GetContentIndex()
+ && pNode->GetText()[GetPoint()->GetContentIndex()] == CH_TXT_ATR_FIELDEND)
+ {
+ IDocumentMarkAccess const& rIDMA(*GetDoc().getIDocumentMarkAccess());
+ sw::mark::IFieldmark const*const pMark(rIDMA.getFieldmarkAt(*GetPoint()));
+ assert(pMark);
+ *GetPoint() = sw::mark::FindFieldSep(*pMark);
+ }
+ }
+
+ if (isFieldNames)
+ {
+ SwTextNode const*const pNode(GetPoint()->GetNode().GetTextNode());
+ assert(pNode);
+ SwTextAttr const*const pInputField(pNode->GetTextAttrAt(
+ GetPoint()->GetContentIndex(), RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
+ if (pInputField)
+ {
+ continue; // skip over input fields
+ }
+ }
+
+ // If we were located inside a covered cell but our position has been
+ // corrected, we check if the last move has moved the cursor to a
+ // different table cell. In this case we set the cursor to the stored
+ // covered position and redo the move:
+ if (m_nRowSpanOffset)
+ {
+ const SwNode* pOldTabBoxSttNode = aOldNodeIdx.GetNode().FindTableBoxStartNode();
+ const SwTableNode* pOldTabSttNode = pOldTabBoxSttNode ? pOldTabBoxSttNode->FindTableNode() : nullptr;
+ const SwNode* pNewTabBoxSttNode = GetPoint()->GetNode().FindTableBoxStartNode();
+ const SwTableNode* pNewTabSttNode = pNewTabBoxSttNode ? pNewTabBoxSttNode->FindTableNode() : nullptr;
+
+ const bool bCellChanged = pOldTabSttNode && pNewTabSttNode &&
+ pOldTabSttNode == pNewTabSttNode &&
+ pOldTabBoxSttNode && pNewTabBoxSttNode &&
+ pOldTabBoxSttNode != pNewTabBoxSttNode;
+
+ if ( bCellChanged )
+ {
+ // Set cursor to start/end of covered cell:
+ SwTableBox* pTableBox = pOldTabBoxSttNode->GetTableBox();
+ if ( pTableBox && pTableBox->getRowSpan() > 1 )
+ {
+ pTableBox = & pTableBox->FindEndOfRowSpan(
+ pOldTabSttNode->GetTable(),
+ o3tl::narrowing<sal_uInt16>(pTableBox->getRowSpan() + m_nRowSpanOffset));
+ SwPosition& rPtPos = *GetPoint();
+ SwNodeIndex aNewIdx( *pTableBox->GetSttNd() );
+ rPtPos.Assign( aNewIdx );
+
+ GetDoc().GetNodes().GoNextSection( &rPtPos, false, false );
+ SwContentNode* pContentNode = GetPointContentNode();
+ if ( pContentNode )
+ {
+ GetPoint()->SetContent( bLeft ? pContentNode->Len() : 0 );
+
+ // Redo the move:
+ if ( !Move( fnMove, fnGo ) )
+ break;
+ }
+ }
+ m_nRowSpanOffset = 0;
+ }
+ }
+
+ // Check if I'm inside a covered cell. Correct cursor if necessary and
+ // store covered cell:
+ const SwNode* pTableBoxStartNode = GetPoint()->GetNode().FindTableBoxStartNode();
+ if ( pTableBoxStartNode )
+ {
+ const SwTableBox* pTableBox = pTableBoxStartNode->GetTableBox();
+ if ( pTableBox && pTableBox->getRowSpan() < 1 )
+ {
+ // Store the row span offset:
+ m_nRowSpanOffset = pTableBox->getRowSpan();
+
+ // Move cursor to non-covered cell:
+ const SwTableNode* pTableNd = pTableBoxStartNode->FindTableNode();
+ pTableBox = & pTableBox->FindStartOfRowSpan( pTableNd->GetTable() );
+ SwPosition& rPtPos = *GetPoint();
+ SwNodeIndex aNewIdx( *pTableBox->GetSttNd() );
+ rPtPos.Assign( aNewIdx );
+
+ GetDoc().GetNodes().GoNextSection( &rPtPos, false, false );
+ SwContentNode* pContentNode = GetPointContentNode();
+ if ( pContentNode )
+ {
+ GetPoint()->SetContent( bLeft ? pContentNode->Len() : 0 );
+ }
+ }
+ }
+ --nCnt;
+ }
+
+ // here come some special rules for visual cursor travelling
+ if ( pSttFrame )
+ {
+ SwNode& rTmpNode = GetPoint()->GetNode();
+ if ( &rTmpNode != &rNode && rTmpNode.IsTextNode() )
+ {
+ Point aPt;
+ std::pair<Point, bool> const tmp(aPt, true);
+ const SwContentFrame* pEndFrame = rTmpNode.GetTextNode()->getLayoutFrame(
+ GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
+ GetPoint(), &tmp);
+ if ( pEndFrame )
+ {
+ if ( ! pEndFrame->IsRightToLeft() != ! pSttFrame->IsRightToLeft() )
+ {
+ if ( ! bLeft )
+ pEndFrame->RightMargin( this );
+ else
+ pEndFrame->LeftMargin( this );
+ }
+ }
+ }
+ }
+
+ return 0 == nCnt && !IsInProtectTable( true ) &&
+ !IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos );
+}
+
+// calculate cursor bidi level: extracted from UpDown()
+void SwCursor::DoSetBidiLevelUpDown()
+{
+ SwNode& rNode = GetPoint()->GetNode();
+ if ( !rNode.IsTextNode() )
+ return;
+
+ SwTextFrame const* pFrame;
+ const SwScriptInfo* pSI =
+ SwScriptInfo::GetScriptInfo( *rNode.GetTextNode(), &pFrame );
+ if ( !pSI )
+ return;
+
+ const sal_Int32 nPos = GetPoint()->GetContentIndex();
+
+ if (!(nPos && nPos < rNode.GetTextNode()->GetText().getLength()))
+ return;
+
+ TextFrameIndex const nIndex(pFrame->MapModelToView(rNode.GetTextNode(), nPos));
+ const sal_uInt8 nCurrLevel = pSI->DirType( nIndex );
+ const sal_uInt8 nPrevLevel = pSI->DirType( nIndex - TextFrameIndex(1) );
+
+ if ( nCurrLevel % 2 != nPrevLevel % 2 )
+ {
+ // set cursor level to the lower of the two levels
+ SetCursorBidiLevel( std::min( nCurrLevel, nPrevLevel ) );
+ }
+ else
+ SetCursorBidiLevel( nCurrLevel );
+}
+
+bool SwCursor::UpDown( bool bUp, sal_uInt16 nCnt,
+ Point const * pPt, tools::Long nUpDownX,
+ SwRootFrame & rLayout)
+{
+ SwTableCursor* pTableCursor = dynamic_cast<SwTableCursor*>(this);
+ bool bAdjustTableCursor = false;
+
+ // If the point/mark of the table cursor in the same box then set cursor to
+ // beginning of the box
+ if( pTableCursor && GetPointNode().StartOfSectionNode() ==
+ GetMarkNode().StartOfSectionNode() )
+ {
+ if ( End() != GetPoint() )
+ Exchange();
+ bAdjustTableCursor = true;
+ }
+
+ bool bRet = false;
+ Point aPt;
+ if( pPt )
+ aPt = *pPt;
+ std::pair<Point, bool> const temp(aPt, true);
+ SwContentFrame* pFrame = GetPointContentNode()->getLayoutFrame(&rLayout, GetPoint(), &temp);
+
+ if( pFrame )
+ {
+ SwCursorSaveState aSave( *this );
+
+ if( !pPt )
+ {
+ SwRect aTmpRect;
+ pFrame->GetCharRect( aTmpRect, *GetPoint() );
+ aPt = aTmpRect.Pos();
+
+ nUpDownX = pFrame->IsVertical() ?
+ aPt.getY() - pFrame->getFrameArea().Top() :
+ aPt.getX() - pFrame->getFrameArea().Left();
+ }
+
+ // It is allowed to move footnotes in other footnotes but not sections
+ const bool bChkRange = !pFrame->IsInFootnote() || HasMark();
+ const SwPosition aOldPos( *GetPoint() );
+ const bool bInReadOnly = IsReadOnlyAvailable();
+
+ if ( bAdjustTableCursor && !bUp )
+ {
+ // Special case: We have a table cursor but the start box has more
+ // than one paragraph. If we want to go down, we have to set the
+ // point to the last frame in the table box. This is only necessary
+ // if we do not already have a table selection
+ const SwStartNode* pTableNd = GetPointNode().FindTableBoxStartNode();
+ OSL_ENSURE( pTableNd, "pTableCursor without SwTableNode?" );
+
+ if ( pTableNd ) // safety first
+ {
+ const SwNode* pEndNd = pTableNd->EndOfSectionNode();
+ GetPoint()->Assign( *pEndNd );
+ pTableCursor->Move( fnMoveBackward, GoInNode );
+ std::pair<Point, bool> const tmp(aPt, true);
+ pFrame = GetPointContentNode()->getLayoutFrame(&rLayout, GetPoint(), &tmp);
+ }
+ }
+
+ while( nCnt &&
+ (bUp ? pFrame->UnitUp( this, nUpDownX, bInReadOnly )
+ : pFrame->UnitDown( this, nUpDownX, bInReadOnly ) ) &&
+ CheckNodesRange( aOldPos.GetNode(), GetPoint()->GetNode(), bChkRange ))
+ {
+ std::pair<Point, bool> const tmp(aPt, true);
+ pFrame = GetPointContentNode()->getLayoutFrame(&rLayout, GetPoint(), &tmp);
+ --nCnt;
+ }
+
+ // iterate over whole number of items?
+ if( !nCnt && !IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos ) )
+ {
+ if( !pTableCursor )
+ {
+ // try to position the cursor at half of the char-rect's height
+ DisableCallbackAction a(rLayout);
+ std::pair<Point, bool> const tmp(aPt, true);
+ pFrame = GetPointContentNode()->getLayoutFrame(&rLayout, GetPoint(), &tmp);
+ SwCursorMoveState eTmpState( CursorMoveState::UpDown );
+ eTmpState.m_bSetInReadOnly = bInReadOnly;
+ SwRect aTmpRect;
+ pFrame->GetCharRect( aTmpRect, *GetPoint(), &eTmpState );
+ if ( pFrame->IsVertical() )
+ {
+ aPt.setX(aTmpRect.Center().getX());
+ pFrame->Calc(rLayout.GetCurrShell()->GetOut());
+ aPt.setY(pFrame->getFrameArea().Top() + nUpDownX);
+ }
+ else
+ {
+ aPt.setY(aTmpRect.Center().getY());
+ pFrame->Calc(rLayout.GetCurrShell()->GetOut());
+ aPt.setX(pFrame->getFrameArea().Left() + nUpDownX);
+ }
+ pFrame->GetModelPositionForViewPoint( GetPoint(), aPt, &eTmpState );
+ }
+ bRet = !IsSelOvr( SwCursorSelOverFlags::Toggle | SwCursorSelOverFlags::ChangePos );
+ }
+ else if (!pFrame->IsInFootnote()) // tdf#150457 Jump to the begin/end
+ // of the first/last line only if the
+ // cursor is not inside a footnote
+ {
+ sal_Int32 nOffset = 0;
+
+ // Jump to beginning or end of line when the cursor at first or last line.
+ if(!bUp)
+ {
+ SwTextNode* pTextNd = GetPoint()->GetNode().GetTextNode();
+ if (pTextNd)
+ nOffset = pTextNd->GetText().getLength();
+ }
+ const SwPosition aPos(*GetPointContentNode(), nOffset);
+
+ //if cursor has already been at start or end of file,
+ //Update cursor to change nUpDownX.
+ if ( aOldPos.GetContentIndex() == nOffset )
+ {
+ if (SwEditShell* pSh = GetDoc().GetEditShell())
+ pSh->UpdateCursor();
+ bRet = false;
+ }
+ else{
+ *GetPoint() = aPos; // just give a new position
+ bRet = true;
+ }
+
+ }
+ else
+ *GetPoint() = aOldPos;
+
+ DoSetBidiLevelUpDown(); // calculate cursor bidi level
+ }
+ return bRet;
+}
+
+bool SwCursor::LeftRightMargin(SwRootFrame const& rLayout, bool bLeft, bool bAPI)
+{
+ Point aPt;
+ std::pair<Point, bool> const tmp(aPt, true);
+ SwContentFrame const*const pFrame = GetPointContentNode()->getLayoutFrame(
+ &rLayout, GetPoint(), &tmp);
+
+ // calculate cursor bidi level
+ if ( pFrame )
+ SetCursorBidiLevel( pFrame->IsRightToLeft() ? 1 : 0 );
+
+ SwCursorSaveState aSave( *this );
+ return pFrame
+ && (bLeft ? pFrame->LeftMargin( this ) : pFrame->RightMargin( this, bAPI ) )
+ && !IsSelOvr( SwCursorSelOverFlags::Toggle | SwCursorSelOverFlags::ChangePos );
+}
+
+bool SwCursor::IsAtLeftRightMargin(SwRootFrame const& rLayout, bool bLeft, bool bAPI) const
+{
+ bool bRet = false;
+ Point aPt;
+ std::pair<Point, bool> const tmp(aPt, true);
+ SwContentFrame const*const pFrame = GetPointContentNode()->getLayoutFrame(
+ &rLayout, GetPoint(), &tmp);
+ if( pFrame )
+ {
+ SwPaM aPam( *GetPoint() );
+ if( !bLeft && aPam.GetPoint()->GetContentIndex() )
+ aPam.GetPoint()->AdjustContent(-1);
+ bRet = (bLeft ? pFrame->LeftMargin( &aPam )
+ : pFrame->RightMargin( &aPam, bAPI ))
+ && (!pFrame->IsTextFrame()
+ || static_cast<SwTextFrame const*>(pFrame)->MapModelToViewPos(*aPam.GetPoint())
+ == static_cast<SwTextFrame const*>(pFrame)->MapModelToViewPos(*GetPoint()));
+ }
+ return bRet;
+}
+
+bool SwCursor::SttEndDoc( bool bStt )
+{
+ SwCursorSaveState aSave( *this );
+ // Never jump over section boundaries during selection!
+ // Can the cursor still moved on?
+ SwMoveFnCollection const & fnMove = bStt ? fnMoveBackward : fnMoveForward;
+ bool bRet = (!HasMark() || !IsNoContent() ) &&
+ Move( fnMove, GoInDoc ) &&
+ !IsInProtectTable( true ) &&
+ !IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos |
+ SwCursorSelOverFlags::EnableRevDirection );
+ return bRet;
+}
+
+bool SwCursor::GoPrevNextCell( bool bNext, sal_uInt16 nCnt )
+{
+ const SwTableNode* pTableNd = GetPoint()->GetNode().FindTableNode();
+ if( !pTableNd )
+ return false;
+
+ // If there is another EndNode in front of the cell's StartNode then there
+ // exists a previous cell
+ SwCursorSaveState aSave( *this );
+ SwPosition& rPtPos = *GetPoint();
+
+ while( nCnt-- )
+ {
+ const SwNode* pTableBoxStartNode = rPtPos.GetNode().FindTableBoxStartNode();
+ const SwTableBox* pTableBox = pTableBoxStartNode->GetTableBox();
+
+ // Check if we have to move the cursor to a covered cell before
+ // proceeding:
+ if (m_nRowSpanOffset)
+ {
+ if ( pTableBox && pTableBox->getRowSpan() > 1 )
+ {
+ pTableBox = & pTableBox->FindEndOfRowSpan( pTableNd->GetTable(),
+ o3tl::narrowing<sal_uInt16>(pTableBox->getRowSpan() + m_nRowSpanOffset));
+ rPtPos.Assign( *pTableBox->GetSttNd() );
+ pTableBoxStartNode = rPtPos.GetNode().FindTableBoxStartNode();
+ }
+ m_nRowSpanOffset = 0;
+ }
+
+ const SwNode* pTmpNode = bNext ?
+ pTableBoxStartNode->EndOfSectionNode() :
+ pTableBoxStartNode;
+
+ SwNodeIndex aCellIdx( *pTmpNode, bNext ? 1 : -1 );
+ if( (bNext && !aCellIdx.GetNode().IsStartNode()) ||
+ (!bNext && !aCellIdx.GetNode().IsEndNode()) )
+ return false;
+
+ if (bNext)
+ rPtPos.Assign( aCellIdx );
+ else
+ rPtPos.Assign(*aCellIdx.GetNode().StartOfSectionNode());
+
+ pTableBoxStartNode = rPtPos.GetNode().FindTableBoxStartNode();
+ pTableBox = pTableBoxStartNode->GetTableBox();
+ if ( pTableBox && pTableBox->getRowSpan() < 1 )
+ {
+ m_nRowSpanOffset = pTableBox->getRowSpan();
+ // move cursor to non-covered cell:
+ pTableBox = & pTableBox->FindStartOfRowSpan( pTableNd->GetTable() );
+ rPtPos.Assign( *pTableBox->GetSttNd() );
+ }
+ }
+
+ rPtPos.Adjust(SwNodeOffset(1));
+ if( !rPtPos.GetNode().IsContentNode() )
+ GetDoc().GetNodes().GoNextSection( &rPtPos, true, false );
+ GetPoint()->SetContent( 0 );
+
+ return !IsInProtectTable( true );
+}
+
+bool SwTableCursor::GotoTable( const OUString& )
+{
+ return false; // invalid action
+}
+
+bool SwCursor::GotoTable( const OUString& rName )
+{
+ bool bRet = false;
+ if ( !HasMark() )
+ {
+ SwTable* pTmpTable = SwTable::FindTable( GetDoc().FindTableFormatByName( rName ) );
+ if( pTmpTable )
+ {
+ // a table in a normal nodes array
+ SwCursorSaveState aSave( *this );
+ GetPoint()->Assign( *pTmpTable->GetTabSortBoxes()[ 0 ]->
+ GetSttNd()->FindTableNode() );
+ Move( fnMoveForward, GoInContent );
+ bRet = !IsSelOvr();
+ }
+ }
+ return bRet;
+}
+
+bool SwCursor::GotoTableBox( const OUString& rName )
+{
+ bool bRet = false;
+ const SwTableNode* pTableNd = GetPoint()->GetNode().FindTableNode();
+ if( pTableNd )
+ {
+ // retrieve box by name
+ const SwTableBox* pTableBox = pTableNd->GetTable().GetTableBox( rName );
+ if( pTableBox && pTableBox->GetSttNd() &&
+ ( !pTableBox->GetFrameFormat()->GetProtect().IsContentProtected() ||
+ IsReadOnlyAvailable() ) )
+ {
+ SwCursorSaveState aSave( *this );
+ GetPoint()->Assign( *pTableBox->GetSttNd() );
+ Move( fnMoveForward, GoInContent );
+ bRet = !IsSelOvr();
+ }
+ }
+ return bRet;
+}
+
+bool SwCursor::MovePara(SwWhichPara fnWhichPara, SwMoveFnCollection const & fnPosPara )
+{
+ // for optimization test something before
+ const SwNode* pNd = &GetPoint()->GetNode();
+ bool bShortCut = false;
+ if ( fnWhichPara == GoCurrPara )
+ {
+ // #i41048#
+ // If fnWhichPara == GoCurrPara then (*fnWhichPara)( *this, fnPosPara )
+ // can already move the cursor to a different text node. In this case
+ // we better check if IsSelOvr().
+ const SwContentNode* pContentNd = pNd->GetContentNode();
+ if ( pContentNd )
+ {
+ const sal_Int32 nSttEnd = &fnPosPara == &fnMoveForward ? 0 : pContentNd->Len();
+ if ( GetPoint()->GetContentIndex() != nSttEnd )
+ bShortCut = true;
+ }
+ }
+ else
+ {
+ if ( pNd->IsTextNode() &&
+ pNd->GetNodes()[ pNd->GetIndex() +
+ SwNodeOffset(fnWhichPara == GoNextPara ? 1 : -1 ) ]->IsTextNode() )
+ bShortCut = true;
+ }
+
+ if ( bShortCut )
+ return (*fnWhichPara)( *this, fnPosPara );
+
+ // else we must use the SaveStructure, because the next/prev is not
+ // a same node type.
+ SwCursorSaveState aSave( *this );
+ return (*fnWhichPara)( *this, fnPosPara ) &&
+ !IsInProtectTable( true ) &&
+ !IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos );
+}
+
+bool SwCursor::MoveSection( SwWhichSection fnWhichSect,
+ SwMoveFnCollection const & fnPosSect)
+{
+ SwCursorSaveState aSave( *this );
+ return (*fnWhichSect)( *this, fnPosSect ) &&
+ !IsInProtectTable( true ) &&
+ !IsSelOvr( SwCursorSelOverFlags::Toggle |
+ SwCursorSelOverFlags::ChangePos );
+}
+
+void SwCursor::RestoreSavePos()
+{
+ // This method is not supposed to be used in cases when nodes may be
+ // deleted; detect such cases, but do not crash (example: fdo#40831).
+ SwNodeOffset uNodeCount(GetPoint()->GetNodes().Count());
+ OSL_ENSURE(m_vSavePos.empty() || m_vSavePos.back().nNode < uNodeCount,
+ "SwCursor::RestoreSavePos: invalid node: "
+ "probably something was deleted; consider using SwUnoCursor instead");
+ if (m_vSavePos.empty() || m_vSavePos.back().nNode >= uNodeCount)
+ return;
+
+ GetPoint()->Assign( m_vSavePos.back().nNode );
+
+ sal_Int32 nIdx = 0;
+ if ( GetPointContentNode() )
+ {
+ if (m_vSavePos.back().nContent <= GetPointContentNode()->Len())
+ nIdx = m_vSavePos.back().nContent;
+ else
+ {
+ nIdx = GetPointContentNode()->Len();
+ OSL_FAIL("SwCursor::RestoreSavePos: invalid content index");
+ }
+ }
+ GetPoint()->SetContent( nIdx );
+}
+
+SwTableCursor::SwTableCursor( const SwPosition &rPos )
+ : SwCursor( rPos, nullptr )
+{
+ m_bParked = false;
+ m_bChanged = false;
+ m_nTablePtNd = SwNodeOffset(0);
+ m_nTableMkNd = SwNodeOffset(0);
+ m_nTablePtCnt = 0;
+ m_nTableMkCnt = 0;
+}
+
+SwTableCursor::~SwTableCursor() {}
+
+static bool
+lcl_SeekEntry(const SwSelBoxes& rTmp, SwStartNode const*const pSrch,
+ size_t & o_rFndPos)
+{
+ SwNodeOffset nIdx = pSrch->GetIndex();
+
+ size_t nO = rTmp.size();
+ if( nO > 0 )
+ {
+ nO--;
+ size_t nU = 0;
+ while( nU <= nO )
+ {
+ size_t nM = nU + ( nO - nU ) / 2;
+ if( rTmp[ nM ]->GetSttNd() == pSrch )
+ {
+ o_rFndPos = nM;
+ return true;
+ }
+ else if( rTmp[ nM ]->GetSttIdx() < nIdx )
+ nU = nM + 1;
+ else if( nM == 0 )
+ return false;
+ else
+ nO = nM - 1;
+ }
+ }
+ return false;
+}
+
+SwCursor* SwTableCursor::MakeBoxSels( SwCursor* pCurrentCursor )
+{
+ if (m_bChanged)
+ {
+ if (m_bParked)
+ {
+ // move back into content
+ Exchange();
+ Move( fnMoveForward );
+ Exchange();
+ Move( fnMoveForward );
+ m_bParked = false;
+ }
+
+ m_bChanged = false;
+
+ // create temporary copies so that all boxes that
+ // have already cursors can be removed
+ SwSelBoxes aTmp(m_SelectedBoxes);
+
+ // compare old and new ones
+ SwNodes& rNds = pCurrentCursor->GetDoc().GetNodes();
+ const SwStartNode* pSttNd;
+ SwCursor* pCur = pCurrentCursor;
+ do {
+ size_t nPos;
+ bool bDel = false;
+ pSttNd = pCur->GetPoint()->GetNode().FindTableBoxStartNode();
+ if( !pCur->HasMark() || !pSttNd ||
+ pSttNd != pCur->GetMark()->GetNode().FindTableBoxStartNode() )
+ bDel = true;
+
+ else if( lcl_SeekEntry( aTmp, pSttNd, nPos ))
+ {
+ SwNodeIndex aIdx( *pSttNd, 1 );
+ const SwNode* pNd = &aIdx.GetNode();
+ if( !pNd->IsContentNode() )
+ pNd = rNds.GoNextSection( &aIdx, true, false );
+
+ SwPosition* pPos = pCur->GetMark();
+ if( pNd != &pPos->GetNode() )
+ pPos->Assign( *pNd );
+ pPos->SetContent( 0 );
+
+ aIdx.Assign( *pSttNd->EndOfSectionNode(), - 1 );
+ pNd = &aIdx.GetNode();
+ if( !pNd->IsContentNode() )
+ pNd = SwNodes::GoPrevSection( &aIdx, true, false );
+
+ pPos = pCur->GetPoint();
+ if (pNd && pNd != &pPos->GetNode())
+ pPos->Assign( *pNd );
+ pPos->SetContent( pNd ? static_cast<const SwContentNode*>(pNd)->Len() : 0);
+
+ aTmp.erase( aTmp.begin() + nPos );
+ }
+ else
+ bDel = true;
+
+ pCur = pCur->GetNext();
+ if( bDel )
+ {
+ SwCursor* pDel = pCur->GetPrev();
+ if (pDel == dynamic_cast<SwShellCursor*>(pCurrentCursor))
+ pCurrentCursor = pDel->GetPrev();
+
+ if( pDel == pCurrentCursor )
+ pCurrentCursor->DeleteMark();
+ else
+ delete pDel;
+ }
+ } while ( pCurrentCursor != pCur );
+
+ for (size_t nPos = 0; nPos < aTmp.size(); ++nPos)
+ {
+ pSttNd = aTmp[ nPos ]->GetSttNd();
+
+ SwNodeIndex aIdx( *pSttNd, 1 );
+ if( &aIdx.GetNodes() != &rNds )
+ break;
+ SwNode* pNd = &aIdx.GetNode();
+ if( !pNd->IsContentNode() )
+ pNd = rNds.GoNextSection( &aIdx, true, false );
+
+ SwPaM *const pNew = (!pCurrentCursor->IsMultiSelection() && !pCurrentCursor->HasMark())
+ ? pCurrentCursor
+ : pCurrentCursor->Create( pCurrentCursor );
+ pNew->GetPoint()->Assign( *pNd );
+ pNew->SetMark();
+
+ SwPosition* pPos = pNew->GetPoint();
+ pPos->Assign( *pSttNd->EndOfSectionNode(), - 1 );
+ pNd = &pPos->GetNode();
+ if( !pNd->IsContentNode() )
+ pNd = SwNodes::GoPrevSection( pPos, true, false );
+ if (pNd)
+ pPos->AssignEndIndex(*static_cast<SwContentNode*>(pNd));
+ }
+ }
+ return pCurrentCursor;
+}
+
+void SwTableCursor::InsertBox( const SwTableBox& rTableBox )
+{
+ SwTableBox* pBox = const_cast<SwTableBox*>(&rTableBox);
+ m_SelectedBoxes.insert(pBox);
+ m_bChanged = true;
+}
+
+void SwTableCursor::DeleteBox(size_t const nPos)
+{
+ m_SelectedBoxes.erase(m_SelectedBoxes.begin() + nPos);
+ m_bChanged = true;
+}
+
+bool SwTableCursor::NewTableSelection()
+{
+ bool bRet = false;
+ const SwNode *pStart = GetPointNode().FindTableBoxStartNode();
+ const SwNode *pEnd = GetMarkNode().FindTableBoxStartNode();
+ if( pStart && pEnd )
+ {
+ const SwTableNode *pTableNode = pStart->FindTableNode();
+ if( pTableNode == pEnd->FindTableNode() &&
+ pTableNode->GetTable().IsNewModel() )
+ {
+ bRet = true;
+ SwSelBoxes aNew(m_SelectedBoxes);
+ pTableNode->GetTable().CreateSelection( pStart, pEnd, aNew,
+ SwTable::SEARCH_NONE, false );
+ ActualizeSelection( aNew );
+ }
+ }
+ return bRet;
+}
+
+void SwTableCursor::ActualizeSelection( const SwSelBoxes &rNew )
+{
+ size_t nOld = 0, nNew = 0;
+ while (nOld < m_SelectedBoxes.size() && nNew < rNew.size())
+ {
+ SwTableBox const*const pPOld = m_SelectedBoxes[ nOld ];
+ const SwTableBox* pPNew = rNew[ nNew ];
+ if( pPOld == pPNew )
+ { // this box will stay
+ ++nOld;
+ ++nNew;
+ }
+ else if( pPOld->GetSttIdx() < pPNew->GetSttIdx() )
+ {
+ DeleteBox( nOld ); // this box has to go
+ }
+ else
+ {
+ InsertBox( *pPNew ); // this is a new one
+ ++nOld;
+ ++nNew;
+ }
+ }
+
+ while (nOld < m_SelectedBoxes.size())
+ {
+ DeleteBox( nOld ); // some more to delete
+ }
+
+ for ( ; nNew < rNew.size(); ++nNew ) // some more to insert
+ {
+ InsertBox( *rNew[ nNew ] );
+ }
+}
+
+bool SwTableCursor::IsCursorMovedUpdate()
+{
+ if( !IsCursorMoved() )
+ return false;
+
+ m_nTableMkNd = GetMark()->GetNodeIndex();
+ m_nTablePtNd = GetPoint()->GetNodeIndex();
+ m_nTableMkCnt = GetMark()->GetContentIndex();
+ m_nTablePtCnt = GetPoint()->GetContentIndex();
+ return true;
+}
+
+/// park table cursor on the boxes' start node
+void SwTableCursor::ParkCursor()
+{
+ // de-register index from text node
+ SwNode* pNd = &GetPoint()->GetNode();
+ if( !pNd->IsStartNode() )
+ pNd = pNd->StartOfSectionNode();
+ GetPoint()->Assign(*pNd);
+
+ pNd = &GetMark()->GetNode();
+ if( !pNd->IsStartNode() )
+ pNd = pNd->StartOfSectionNode();
+ GetMark()->Assign(*pNd);
+
+ m_bChanged = true;
+ m_bParked = true;
+}
+
+bool SwTableCursor::HasReadOnlyBoxSel() const
+{
+ bool bRet = false;
+ for (size_t n = m_SelectedBoxes.size(); n; )
+ {
+ if (m_SelectedBoxes[--n]->GetFrameFormat()->GetProtect().IsContentProtected())
+ {
+ bRet = true;
+ break;
+ }
+ }
+ return bRet;
+}
+
+bool SwTableCursor::HasHiddenBoxSel() const
+{
+ bool bRet = false;
+ for (size_t n = m_SelectedBoxes.size(); n; )
+ {
+ if (m_SelectedBoxes[--n]->GetFrameFormat()->IsHidden())
+ {
+ bRet = true;
+ break;
+ }
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/trvlcol.cxx b/sw/source/core/crsr/trvlcol.cxx
new file mode 100644
index 0000000000..c680e5a87c
--- /dev/null
+++ b/sw/source/core/crsr/trvlcol.cxx
@@ -0,0 +1,103 @@
+/* -*- 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 <crsrsh.hxx>
+#include <layfrm.hxx>
+#include <cntfrm.hxx>
+#include <swcrsr.hxx>
+#include <viscrs.hxx>
+#include "callnk.hxx"
+
+SwLayoutFrame* GetCurrColumn( const SwLayoutFrame* pLayFrame )
+{
+ while( pLayFrame && !pLayFrame->IsColumnFrame() )
+ pLayFrame = pLayFrame->GetUpper();
+ return const_cast<SwLayoutFrame*>(pLayFrame);
+}
+
+SwLayoutFrame* GetNextColumn( const SwLayoutFrame* pLayFrame )
+{
+ SwLayoutFrame* pActCol = GetCurrColumn( pLayFrame );
+ return pActCol ? static_cast<SwLayoutFrame*>(pActCol->GetNext()) : nullptr;
+}
+
+SwLayoutFrame* GetPrevColumn( const SwLayoutFrame* pLayFrame )
+{
+ SwLayoutFrame* pActCol = GetCurrColumn( pLayFrame );
+ return pActCol ? static_cast<SwLayoutFrame*>(pActCol->GetPrev()) : nullptr;
+}
+
+SwContentFrame* GetColumnStt( const SwLayoutFrame* pColFrame )
+{
+ return pColFrame ? const_cast<SwContentFrame*>(pColFrame->ContainsContent()) : nullptr;
+}
+
+SwContentFrame* GetColumnEnd( const SwLayoutFrame* pColFrame )
+{
+ SwContentFrame *pRet = GetColumnStt( pColFrame );
+ if( !pRet )
+ return nullptr;
+
+ SwContentFrame *pNxt = pRet->GetNextContentFrame();
+ while( pNxt && pColFrame->IsAnLower( pNxt ) )
+ {
+ pRet = pNxt;
+ pNxt = pNxt->GetNextContentFrame();
+ }
+ return pRet;
+}
+
+void SwCursorShell::MoveColumn( SwWhichColumn fnWhichCol, SwPosColumn fnPosCol )
+{
+ if( m_pTableCursor )
+ return;
+ SwLayoutFrame* pLayFrame = GetCurrFrame()->GetUpper();
+ if( !pLayFrame )
+ return;
+ pLayFrame = (*fnWhichCol)( pLayFrame );
+ if( !pLayFrame )
+ return;
+
+ SwContentFrame* pCnt = (*fnPosCol)( pLayFrame );
+ if( !pCnt )
+ return;
+
+ CurrShell aCurr( this );
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ pCnt->Calc(GetOut());
+
+ Point aPt( pCnt->getFrameArea().Pos() + pCnt->getFramePrintArea().Pos() );
+ if( fnPosCol == GetColumnEnd )
+ {
+ aPt.setX(aPt.getX() + pCnt->getFramePrintArea().Width());
+ aPt.setY(aPt.getY() + pCnt->getFramePrintArea().Height());
+ }
+
+ pCnt->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), aPt );
+
+ if( !m_pCurrentCursor->IsInProtectTable( true ) &&
+ !m_pCurrentCursor->IsSelOvr() )
+ {
+ UpdateCursor();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/trvlfnfl.cxx b/sw/source/core/crsr/trvlfnfl.cxx
new file mode 100644
index 0000000000..369e604134
--- /dev/null
+++ b/sw/source/core/crsr/trvlfnfl.cxx
@@ -0,0 +1,384 @@
+/* -*- 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 <crsrsh.hxx>
+#include <doc.hxx>
+#include <pagefrm.hxx>
+#include <cntfrm.hxx>
+#include <ftnfrm.hxx>
+#include <swcrsr.hxx>
+#include <ndtxt.hxx>
+#include <txtfrm.hxx>
+#include <txtftn.hxx>
+#include <ftnidx.hxx>
+#include <viscrs.hxx>
+#include "callnk.hxx"
+#include <svx/srchdlg.hxx>
+#include <wrtsh.hxx>
+
+bool SwCursorShell::CallCursorShellFN( FNCursorShell fnCursor )
+{
+ if (SwWrtShell* pWrtSh = dynamic_cast<SwWrtShell*>(this))
+ pWrtSh->addCurrentPosition();
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ bool bRet = (this->*fnCursor)();
+ if( bRet )
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
+ SwCursorShell::READONLY );
+ return bRet;
+}
+
+bool SwCursorShell::CallCursorFN( FNCursor fnCursor )
+{
+ if (SwWrtShell* pWrtSh = dynamic_cast<SwWrtShell*>(this))
+ pWrtSh->addCurrentPosition();
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursor* pCursor = getShellCursor( true );
+ bool bRet = (pCursor->*fnCursor)();
+ if( bRet )
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
+ SwCursorShell::READONLY );
+ return bRet;
+}
+
+bool SwCursor::GotoFootnoteText()
+{
+ // jump from content to footnote
+ bool bRet = false;
+ SwTextNode* pTextNd = GetPoint()->GetNode().GetTextNode();
+
+ SwTextAttr *const pFootnote( pTextNd
+ ? pTextNd->GetTextAttrForCharAt(
+ GetPoint()->GetContentIndex(), RES_TXTATR_FTN)
+ : nullptr);
+ if (pFootnote)
+ {
+ SwCursorSaveState aSaveState( *this );
+ GetPoint()->Assign( *static_cast<SwTextFootnote*>(pFootnote)->GetStartNode() );
+
+ SwContentNode* pCNd = GetDoc().GetNodes().GoNextSection(
+ GetPoint(),
+ true, !IsReadOnlyAvailable() );
+ if( pCNd )
+ {
+ bRet = !IsSelOvr( SwCursorSelOverFlags::CheckNodeSection |
+ SwCursorSelOverFlags::Toggle );
+ }
+ }
+ return bRet;
+}
+
+bool SwCursorShell::GotoFootnoteText()
+{
+ bool bRet = CallCursorFN( &SwCursor::GotoFootnoteText );
+ if( !bRet )
+ {
+ SwTextNode* pTextNd = GetCursor_() ?
+ GetCursor_()->GetPoint()->GetNode().GetTextNode() : nullptr;
+ if( pTextNd )
+ {
+ std::pair<Point, bool> const tmp(GetCursor_()->GetSttPos(), true);
+ const SwFrame *pFrame = pTextNd->getLayoutFrame( GetLayout(),
+ GetCursor_()->Start(), &tmp);
+ const SwFootnoteBossFrame* pFootnoteBoss;
+ bool bSkip = pFrame && pFrame->IsInFootnote();
+ while( pFrame )
+ {
+ pFootnoteBoss = pFrame->FindFootnoteBossFrame();
+ if (!pFootnoteBoss)
+ break;
+ pFrame = pFootnoteBoss->FindFootnoteCont();
+ if( pFrame )
+ {
+ if( bSkip )
+ bSkip = false;
+ else
+ {
+ const SwContentFrame* pCnt = static_cast<const SwLayoutFrame*>
+ (pFrame)->ContainsContent();
+ if( pCnt )
+ {
+ SwTextFrame const*const pTF(
+ static_cast<const SwTextFrame*>(pCnt));
+ *GetCursor_()->GetPoint() =
+ pTF->MapViewToModelPos(pTF->GetOffset());
+ UpdateCursor( SwCursorShell::SCROLLWIN |
+ SwCursorShell::CHKRANGE | SwCursorShell::READONLY );
+ bRet = true;
+ break;
+ }
+ }
+ }
+ if( pFootnoteBoss->GetNext() && !pFootnoteBoss->IsPageFrame() )
+ pFrame = pFootnoteBoss->GetNext();
+ else
+ pFrame = pFootnoteBoss->GetUpper();
+ }
+ }
+ }
+ return bRet;
+}
+
+bool SwCursor::GotoFootnoteAnchor()
+{
+ // jump from footnote to anchor
+ const SwNode* pSttNd = GetPointNode().FindFootnoteStartNode();
+ if( pSttNd )
+ {
+ // search in all footnotes in document for this StartIndex
+ const SwFootnoteIdxs& rFootnoteArr = pSttNd->GetDoc().GetFootnoteIdxs();
+ for( size_t n = 0; n < rFootnoteArr.size(); ++n )
+ {
+ const SwTextFootnote* pTextFootnote = rFootnoteArr[ n ];
+ if( nullptr != pTextFootnote->GetStartNode() &&
+ pSttNd == &pTextFootnote->GetStartNode()->GetNode() )
+ {
+ SwCursorSaveState aSaveState( *this );
+
+ SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode());
+ GetPoint()->Assign( rTNd, pTextFootnote->GetStart() );
+
+ return !IsSelOvr( SwCursorSelOverFlags::CheckNodeSection |
+ SwCursorSelOverFlags::Toggle );
+ }
+ }
+ }
+ return false;
+}
+
+bool SwCursorShell::GotoFootnoteAnchor()
+{
+ if (SwWrtShell* pWrtSh = dynamic_cast<SwWrtShell*>(this))
+ pWrtSh->addCurrentPosition();
+
+ // jump from footnote to anchor
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ bool bRet = m_pCurrentCursor->GotoFootnoteAnchor();
+ if( bRet )
+ {
+ // special treatment for table header row
+ m_pCurrentCursor->GetPtPos() = Point();
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
+ SwCursorShell::READONLY );
+ }
+ return bRet;
+}
+
+static bool CmpLE( const SwTextFootnote& rFootnote, SwNodeOffset nNd, sal_Int32 nCnt )
+{
+ const SwNodeOffset nTNd = rFootnote.GetTextNode().GetIndex();
+ return nTNd < nNd || ( nTNd == nNd && rFootnote.GetStart() <= nCnt );
+}
+
+static bool CmpL( const SwTextFootnote& rFootnote, SwNodeOffset nNd, sal_Int32 nCnt )
+{
+ const SwNodeOffset nTNd = rFootnote.GetTextNode().GetIndex();
+ return nTNd < nNd || ( nTNd == nNd && rFootnote.GetStart() < nCnt );
+}
+
+bool SwCursor::GotoNextFootnoteAnchor()
+{
+ const SwFootnoteIdxs& rFootnoteArr = GetDoc().GetFootnoteIdxs();
+ const SwTextFootnote* pTextFootnote = nullptr;
+ size_t nPos = 0;
+
+ if( rFootnoteArr.empty() )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+
+ if( rFootnoteArr.SeekEntry( GetPoint()->GetNode(), &nPos ))
+ {
+ // there is a footnote with this index, so search also for the next one
+ if( nPos < rFootnoteArr.size() )
+ {
+ SwNodeOffset nNdPos = GetPoint()->GetNodeIndex();
+ const sal_Int32 nCntPos = GetPoint()->GetContentIndex();
+
+ pTextFootnote = rFootnoteArr[ nPos ];
+ // search forwards
+ if( CmpLE( *pTextFootnote, nNdPos, nCntPos ) )
+ {
+ pTextFootnote = nullptr;
+ for( ++nPos; nPos < rFootnoteArr.size(); ++nPos )
+ {
+ pTextFootnote = rFootnoteArr[ nPos ];
+ if( !CmpLE( *pTextFootnote, nNdPos, nCntPos ) )
+ break; // found
+ pTextFootnote = nullptr;
+ }
+ }
+ else if( nPos )
+ {
+ // search backwards
+ pTextFootnote = nullptr;
+ while( nPos )
+ {
+ pTextFootnote = rFootnoteArr[ --nPos ];
+ if( CmpLE( *pTextFootnote, nNdPos, nCntPos ) )
+ {
+ pTextFootnote = rFootnoteArr[ ++nPos ];
+ break; // found
+ }
+ }
+ }
+ }
+ }
+ else if( nPos < rFootnoteArr.size() )
+ pTextFootnote = rFootnoteArr[ nPos ];
+
+ if (pTextFootnote == nullptr)
+ {
+ pTextFootnote = rFootnoteArr[ 0 ];
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped );
+ }
+ else
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
+
+ bool bRet = nullptr != pTextFootnote;
+ if( bRet )
+ {
+ SwCursorSaveState aSaveState( *this );
+
+ SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode());
+ GetPoint()->Assign( rTNd, pTextFootnote->GetStart() );
+ bRet = !IsSelOvr();
+ }
+ return bRet;
+}
+
+bool SwCursor::GotoPrevFootnoteAnchor()
+{
+ const SwFootnoteIdxs& rFootnoteArr = GetDoc().GetFootnoteIdxs();
+ const SwTextFootnote* pTextFootnote = nullptr;
+ size_t nPos = 0;
+
+ if( rFootnoteArr.empty() )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+
+ if( rFootnoteArr.SeekEntry( GetPoint()->GetNode(), &nPos ) )
+ {
+ // there is a footnote with this index, so search also for the next one
+ SwNodeOffset nNdPos = GetPoint()->GetNodeIndex();
+ const sal_Int32 nCntPos = GetPoint()->GetContentIndex();
+
+ pTextFootnote = rFootnoteArr[ nPos ];
+ // search forwards
+ if( CmpL( *pTextFootnote, nNdPos, nCntPos ))
+ {
+ for( ++nPos; nPos < rFootnoteArr.size(); ++nPos )
+ {
+ pTextFootnote = rFootnoteArr[ nPos ];
+ if( !CmpL( *pTextFootnote, nNdPos, nCntPos ) )
+ {
+ pTextFootnote = rFootnoteArr[ nPos-1 ];
+ break;
+ }
+ }
+ }
+ else if( nPos )
+ {
+ // search backwards
+ pTextFootnote = nullptr;
+ while( nPos )
+ {
+ pTextFootnote = rFootnoteArr[ --nPos ];
+ if( CmpL( *pTextFootnote, nNdPos, nCntPos ))
+ break; // found
+ pTextFootnote = nullptr;
+ }
+ }
+ else
+ pTextFootnote = nullptr;
+ }
+ else if( nPos )
+ pTextFootnote = rFootnoteArr[ nPos-1 ];
+
+ if( pTextFootnote == nullptr )
+ {
+ pTextFootnote = rFootnoteArr[ rFootnoteArr.size() - 1 ];
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped );
+ }
+ else
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
+
+ bool bRet = nullptr != pTextFootnote;
+ if( bRet )
+ {
+ SwCursorSaveState aSaveState( *this );
+
+ SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode());
+ GetPoint()->Assign( rTNd, pTextFootnote->GetStart() );
+ bRet = !IsSelOvr();
+ }
+ return bRet;
+}
+
+bool SwCursorShell::GotoNextFootnoteAnchor()
+{
+ return CallCursorFN( &SwCursor::GotoNextFootnoteAnchor );
+}
+
+bool SwCursorShell::GotoPrevFootnoteAnchor()
+{
+ return CallCursorFN( &SwCursor::GotoPrevFootnoteAnchor );
+}
+
+/// jump from border to anchor
+void SwCursorShell::GotoFlyAnchor()
+{
+ CurrShell aCurr( this );
+ const SwFrame* pFrame = GetCurrFrame();
+ do {
+ pFrame = pFrame->GetUpper();
+ } while( pFrame && !pFrame->IsFlyFrame() );
+
+ if( !pFrame ) // no FlyFrame
+ return;
+
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ SwCursorSaveState aSaveState( *m_pCurrentCursor );
+
+ // jump in BodyFrame closest to FlyFrame
+ SwRect aTmpRect( m_aCharRect );
+ if( !pFrame->getFrameArea().Contains( aTmpRect ))
+ aTmpRect = pFrame->getFrameArea();
+ Point aPt( aTmpRect.Left(), aTmpRect.Top() +
+ ( aTmpRect.Bottom() - aTmpRect.Top() ) / 2 );
+ aPt.setX(aPt.getX() > (pFrame->getFrameArea().Left() + (pFrame->getFrameArea().SSize().Width() / 2 ))
+ ? pFrame->getFrameArea().Right()
+ : pFrame->getFrameArea().Left());
+
+ const SwPageFrame* pPageFrame = pFrame->FindPageFrame();
+ const SwContentFrame* pFndFrame = pPageFrame->GetContentPos( aPt, false, true );
+ pFndFrame->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), aPt );
+
+ bool bRet = !m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr();
+ if( bRet )
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
+ SwCursorShell::READONLY );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/trvlreg.cxx b/sw/source/core/crsr/trvlreg.cxx
new file mode 100644
index 0000000000..6ae638bd04
--- /dev/null
+++ b/sw/source/core/crsr/trvlreg.cxx
@@ -0,0 +1,280 @@
+/* -*- 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 <crsrsh.hxx>
+#include <doc.hxx>
+#include <swcrsr.hxx>
+#include <docary.hxx>
+#include <fmtcntnt.hxx>
+#include <viscrs.hxx>
+#include "callnk.hxx"
+#include <pamtyp.hxx>
+#include <section.hxx>
+#include <svx/srchdlg.hxx>
+
+bool GotoPrevRegion( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosRegion,
+ bool bInReadOnly )
+{
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
+ SwNodeIndex aIdx( rCurrentCursor.GetPoint()->GetNode() );
+ SwSectionNode* pNd = aIdx.GetNode().FindSectionNode();
+ if( pNd )
+ aIdx.Assign( *pNd, -1 );
+
+ SwNodeIndex aOldIdx = aIdx;
+ SwNodeOffset nLastNd(rCurrentCursor.GetDoc().GetNodes().Count() - 1);
+ do {
+ while( aIdx.GetIndex() )
+ {
+ pNd = aIdx.GetNode().StartOfSectionNode()->GetSectionNode();
+ if (pNd)
+ break;
+ --aIdx;
+ if ( aIdx == aOldIdx )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+ }
+
+ if ( !aIdx.GetIndex() )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped );
+ aIdx = nLastNd;
+ continue;
+ }
+
+ assert( pNd ); // coverity, should never be nullptr
+ {
+ if( pNd->GetSection().IsHiddenFlag() ||
+ ( !bInReadOnly &&
+ pNd->GetSection().IsProtectFlag() ))
+ {
+ // skip protected or hidden ones
+ aIdx.Assign( *pNd, - 1 );
+ continue;
+ }
+ else if( &fnPosRegion == &fnMoveForward )
+ {
+ aIdx = *pNd;
+ SwContentNode* pCNd = pNd->GetNodes().GoNextSection( &aIdx,
+ true, !bInReadOnly );
+ if( !pCNd )
+ {
+ --aIdx;
+ continue;
+ }
+ rCurrentCursor.GetPoint()->SetContent( 0 );
+ }
+ else
+ {
+ aIdx = *pNd->EndOfSectionNode();
+ SwContentNode* pCNd = SwNodes::GoPrevSection( &aIdx,
+ true, !bInReadOnly );
+ if( !pCNd )
+ {
+ aIdx.Assign( *pNd, - 1 );
+ continue;
+ }
+ rCurrentCursor.GetPoint()->SetContent( pCNd->Len() );
+ }
+ rCurrentCursor.GetPoint()->Assign( aIdx );
+ return true;
+ }
+ } while( true );
+
+ // the flow is such that it is not possible to get here
+ return false;
+}
+
+bool GotoNextRegion( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosRegion,
+ bool bInReadOnly )
+{
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
+ SwNodeIndex aIdx( rCurrentCursor.GetPoint()->GetNode() );
+ SwSectionNode* pNd = aIdx.GetNode().FindSectionNode();
+ if( pNd )
+ aIdx.Assign( *pNd->EndOfSectionNode(), - 1 );
+
+ SwNodeIndex aOldIdx = aIdx;
+ SwNodeOffset nEndCount = aIdx.GetNode().GetNodes().Count() - 1;
+ do {
+ while( aIdx.GetIndex() < nEndCount )
+ {
+ pNd = aIdx.GetNode().GetSectionNode();
+ if (pNd)
+ break;
+ ++aIdx;
+ if ( aIdx == aOldIdx )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+ }
+
+ if ( aIdx.GetIndex() == nEndCount )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped );
+ aIdx = SwNodeOffset(0);
+ continue;
+ }
+
+ assert( pNd ); // coverity, should never be nullptr
+ {
+ if( pNd->GetSection().IsHiddenFlag() ||
+ ( !bInReadOnly &&
+ pNd->GetSection().IsProtectFlag() ))
+ {
+ // skip protected or hidden ones
+ aIdx.Assign( *pNd->EndOfSectionNode(), +1 );
+ continue;
+ }
+ else if( &fnPosRegion == &fnMoveForward )
+ {
+ aIdx = *pNd;
+ SwContentNode* pCNd = pNd->GetNodes().GoNextSection( &aIdx,
+ true, !bInReadOnly );
+ if( !pCNd )
+ {
+ aIdx.Assign( *pNd->EndOfSectionNode(), +1 );
+ continue;
+ }
+ rCurrentCursor.GetPoint()->SetContent( 0 );
+ }
+ else
+ {
+ aIdx = *pNd->EndOfSectionNode();
+ SwContentNode* pCNd = SwNodes::GoPrevSection( &aIdx,
+ true, !bInReadOnly );
+ if( !pCNd )
+ {
+ ++aIdx;
+ continue;
+ }
+ rCurrentCursor.GetPoint()->SetContent( pCNd->Len() );
+ }
+ rCurrentCursor.GetPoint()->Assign( aIdx );
+ return true;
+ }
+ } while( true );
+
+ // the flow is such that it is not possible to get here
+ return false;
+}
+
+bool GotoCurrRegionAndSkip( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosRegion,
+ bool bInReadOnly )
+{
+ SwNode& rCurrNd = rCurrentCursor.GetPointNode();
+ SwSectionNode* pNd = rCurrNd.FindSectionNode();
+ if( !pNd )
+ return false;
+
+ SwPosition* pPos = rCurrentCursor.GetPoint();
+ const sal_Int32 nCurrCnt = pPos->GetContentIndex();
+ bool bMoveBackward = &fnPosRegion == &fnMoveBackward;
+
+ do {
+ SwContentNode* pCNd;
+ if( bMoveBackward ) // to the end of the section
+ {
+ SwNodeIndex aIdx( *pNd->EndOfSectionNode() );
+ pCNd = SwNodes::GoPrevSection( &aIdx, true, !bInReadOnly );
+ if( !pCNd )
+ return false;
+ pPos->Assign( aIdx );
+ }
+ else
+ {
+ SwNodeIndex aIdx( *pNd );
+ pCNd = pNd->GetNodes().GoNextSection( &aIdx, true, !bInReadOnly );
+ if( !pCNd )
+ return false;
+ pPos->Assign( aIdx );
+ }
+
+ pPos->SetContent( bMoveBackward ? pCNd->Len() : 0 );
+
+ if( &pPos->GetNode() != &rCurrNd ||
+ pPos->GetContentIndex() != nCurrCnt )
+ // there was a change
+ return true;
+
+ // try also the parent of this section
+ SwSection* pParent = pNd->GetSection().GetParent();
+ pNd = pParent ? pParent->GetFormat()->GetSectionNode() : nullptr;
+ } while( pNd );
+ return false;
+}
+
+bool SwCursor::MoveRegion( SwWhichRegion fnWhichRegion, SwMoveFnCollection const & fnPosRegion )
+{
+ SwCursorSaveState aSaveState( *this );
+ return !dynamic_cast<SwTableCursor*>(this) &&
+ (*fnWhichRegion)( *this, fnPosRegion, IsReadOnlyAvailable() ) &&
+ !IsSelOvr() &&
+ (GetPoint()->GetNodeIndex() != m_vSavePos.back().nNode ||
+ GetPoint()->GetContentIndex() != m_vSavePos.back().nContent);
+}
+
+bool SwCursorShell::MoveRegion( SwWhichRegion fnWhichRegion, SwMoveFnCollection const & fnPosRegion )
+{
+ SwCallLink aLk( *this ); // watch Cursor-Moves;call Link if needed
+ bool bRet = !m_pTableCursor && m_pCurrentCursor->MoveRegion( fnWhichRegion, fnPosRegion );
+ if( bRet )
+ UpdateCursor();
+ return bRet;
+}
+
+bool SwCursor::GotoRegion( std::u16string_view rName )
+{
+ bool bRet = false;
+ const SwSectionFormats& rFormats = GetDoc().GetSections();
+ for( SwSectionFormats::size_type n = rFormats.size(); n; )
+ {
+ const SwSectionFormat* pFormat = rFormats[ --n ];
+ const SwSection* pSect = pFormat->GetSection();
+ if( pSect && pSect->GetSectionName() == rName )
+ {
+ const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx();
+ if( pIdx && pIdx->GetNode().GetNodes().IsDocNodes() )
+ {
+ // area in normal nodes array
+ SwCursorSaveState aSaveState( *this );
+
+ GetPoint()->Assign( *pIdx );
+ Move( fnMoveForward, GoInContent );
+ bRet = !IsSelOvr();
+ }
+ }
+ }
+ return bRet;
+}
+
+bool SwCursorShell::GotoRegion( std::u16string_view rName )
+{
+ SwCallLink aLk( *this ); // watch Cursor-Moves;call Link if needed
+ bool bRet = !m_pTableCursor && m_pCurrentCursor->GotoRegion( rName );
+ if( bRet )
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
+ SwCursorShell::READONLY );
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/trvltbl.cxx b/sw/source/core/crsr/trvltbl.cxx
new file mode 100644
index 0000000000..689457d028
--- /dev/null
+++ b/sw/source/core/crsr/trvltbl.cxx
@@ -0,0 +1,926 @@
+/* -*- 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 <hintids.hxx>
+#include <crsrsh.hxx>
+#include <doc.hxx>
+#include <cntfrm.hxx>
+#include <editsh.hxx>
+#include <pam.hxx>
+#include <swtable.hxx>
+#include <frmfmt.hxx>
+#include <viscrs.hxx>
+#include "callnk.hxx"
+#include <tabfrm.hxx>
+#include <ndtxt.hxx>
+#include <shellres.hxx>
+#include <cellfrm.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <osl/diagnose.h>
+#include <svx/srchdlg.hxx>
+
+/// set cursor into next/previous cell
+bool SwCursorShell::GoNextCell( bool bAppendLine )
+{
+ bool bRet = false;
+ const SwTableNode* pTableNd = nullptr;
+
+ if( IsTableMode() || nullptr != ( pTableNd = IsCursorInTable() ))
+ {
+ SwCursor* pCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ bRet = true;
+
+ // Check if we have to move the cursor to a covered cell before
+ // proceeding:
+ const SwNode* pTableBoxStartNode = pCursor->GetPointNode().FindTableBoxStartNode();
+ const SwTableBox* pTableBox = nullptr;
+
+ if ( pCursor->GetCursorRowSpanOffset() )
+ {
+ pTableBox = pTableBoxStartNode->GetTableBox();
+ if (pTableBox && pTableBox->getRowSpan() > 1)
+ {
+ if ( !pTableNd )
+ pTableNd = IsCursorInTable();
+ assert (pTableNd);
+ pTableBox = & pTableBox->FindEndOfRowSpan( pTableNd->GetTable(),
+ o3tl::narrowing<sal_uInt16>(pTableBox->getRowSpan() + pCursor->GetCursorRowSpanOffset() ) );
+ pTableBoxStartNode = pTableBox->GetSttNd();
+ }
+ }
+
+ SwNodeIndex aCellStt( *pTableBoxStartNode->EndOfSectionNode(), 1 );
+
+ // if there is another StartNode after the EndNode of a cell then
+ // there is another cell
+ if( !aCellStt.GetNode().IsStartNode() )
+ {
+ if( pCursor->HasMark() || !bAppendLine )
+ bRet = false;
+ else if (pTableNd)
+ {
+ // if there is no list anymore then create new one
+ if ( !pTableBox )
+ pTableBox = pTableNd->GetTable().GetTableBox(
+ pCursor->GetPoint()->GetNode().
+ StartOfSectionIndex() );
+
+ OSL_ENSURE( pTableBox, "Box is not in this table" );
+ SwSelBoxes aBoxes;
+
+ // the document might change; w/o Action views would not be notified
+ static_cast<SwEditShell*>(this)->StartAllAction();
+ bRet = mxDoc->InsertRow( SwTable::SelLineFromBox( pTableBox, aBoxes, false ));
+ static_cast<SwEditShell*>(this)->EndAllAction();
+ }
+ }
+ bRet = bRet && pCursor->GoNextCell();
+ if( bRet )
+ UpdateCursor();
+ }
+ return bRet;
+}
+
+bool SwCursorShell::GoPrevCell()
+{
+ bool bRet = false;
+ if( IsTableMode() || IsCursorInTable() )
+ {
+ SwCursor* pCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ bRet = pCursor->GoPrevCell();
+ if( bRet )
+ UpdateCursor(); // update current cursor
+ }
+ return bRet;
+}
+
+static const SwFrame* lcl_FindMostUpperCellFrame( const SwFrame* pFrame )
+{
+ while ( pFrame &&
+ ( !pFrame->IsCellFrame() ||
+ !pFrame->GetUpper()->GetUpper()->IsTabFrame() ||
+ pFrame->GetUpper()->GetUpper()->GetUpper()->IsInTab() ) )
+ {
+ pFrame = pFrame->GetUpper();
+ }
+ return pFrame;
+}
+
+bool SwCursorShell::SelTableRowOrCol( bool bRow, bool bRowSimple )
+{
+ // check if the current cursor's SPoint/Mark are in a table
+ SwFrame *pFrame = GetCurrFrame();
+ if( !pFrame || !pFrame->IsInTab() )
+ return false;
+
+ const SwTabFrame* pTabFrame = pFrame->FindTabFrame();
+ const SwTabFrame* pMasterTabFrame = pTabFrame->IsFollow() ? pTabFrame->FindMaster( true ) : pTabFrame;
+ const SwTable* pTable = pTabFrame->GetTable();
+
+ CurrShell aCurr( this );
+
+ const SwTableBox* pStt = nullptr;
+ const SwTableBox* pEnd = nullptr;
+
+ // search box based on layout
+ SwSelBoxes aBoxes;
+ SwTableSearchType eType = bRow ? SwTableSearchType::Row : SwTableSearchType::Col;
+ const bool bCheckProtected = !IsReadOnlyAvailable();
+
+ if( bCheckProtected )
+ eType = static_cast<SwTableSearchType>(eType | SwTableSearchType::Protect);
+
+ if ( !bRowSimple )
+ {
+ GetTableSel( *this, aBoxes, eType );
+
+ if( aBoxes.empty() )
+ return false;
+
+ pStt = aBoxes[0];
+ pEnd = aBoxes.back();
+ }
+ // #i32329# Enhanced table selection
+ else if ( pTable->IsNewModel() )
+ {
+ const SwShellCursor *pCursor = GetCursor_();
+ SwTable::SearchType eSearchType = bRow ? SwTable::SEARCH_ROW : SwTable::SEARCH_COL;
+ pTable->CreateSelection( *pCursor, aBoxes, eSearchType, bCheckProtected );
+ if( aBoxes.empty() )
+ return false;
+
+ pStt = aBoxes[0];
+ pEnd = aBoxes.back();
+
+ m_eEnhancedTableSel = eSearchType;
+ }
+ else
+ {
+ const SwShellCursor *pCursor = GetCursor_();
+ const SwFrame* pStartFrame = pFrame;
+ const SwContentNode *pCNd = pCursor->GetMarkContentNode();
+ std::pair<Point, bool> const tmp(pCursor->GetMkPos(), true);
+ const SwFrame* pEndFrame = pCNd
+ ? pCNd->getLayoutFrame(GetLayout(), nullptr, &tmp)
+ : nullptr;
+
+ if ( bRow )
+ {
+ pStartFrame = lcl_FindMostUpperCellFrame( pStartFrame );
+ pEndFrame = lcl_FindMostUpperCellFrame( pEndFrame );
+ }
+
+ if ( !pStartFrame || !pEndFrame )
+ return false;
+
+ const bool bVert = pFrame->ImplFindTabFrame()->IsVertical();
+
+ // If we select upwards it is sufficient to set pStt and pEnd
+ // to the first resp. last box of the selection obtained from
+ // GetTableSel. However, selecting downwards requires the frames
+ // located at the corners of the selection. This does not work
+ // for column selections in vertical tables:
+ const bool bSelectUp = ( bVert && !bRow ) ||
+ *pCursor->GetPoint() <= *pCursor->GetMark();
+ SwCellFrames aCells;
+ GetTableSel( static_cast<const SwCellFrame*>(pStartFrame),
+ static_cast<const SwCellFrame*>(pEndFrame),
+ aBoxes, bSelectUp ? nullptr : &aCells, eType );
+
+ if( aBoxes.empty() || ( !bSelectUp && 4 != aCells.size() ) )
+ return false;
+
+ if ( bSelectUp )
+ {
+ pStt = aBoxes[0];
+ pEnd = aBoxes.back();
+ }
+ else
+ {
+ // will become point of table cursor
+ pStt = aCells[bVert ? 0 : (bRow ? 2 : 1)]->GetTabBox();
+ // will become mark of table cursor
+ pEnd = aCells[bVert ? 3 : (bRow ? 1 : 2)]->GetTabBox();
+ }
+ }
+
+ // if no table cursor exists, create one
+ if( !m_pTableCursor )
+ {
+ m_pTableCursor = new SwShellTableCursor( *this, *m_pCurrentCursor->GetPoint() );
+ m_pCurrentCursor->DeleteMark();
+ m_pCurrentCursor->SwSelPaintRects::Hide();
+ }
+
+ m_pTableCursor->DeleteMark();
+
+ // set start and end of a column
+ m_pTableCursor->GetPoint()->Assign( *pEnd->GetSttNd()->EndOfSectionNode() );
+ m_pTableCursor->Move( fnMoveBackward, GoInContent );
+ m_pTableCursor->SetMark();
+ m_pTableCursor->GetPoint()->Assign( *pStt->GetSttNd()->EndOfSectionNode() );
+ m_pTableCursor->Move( fnMoveBackward, GoInContent );
+
+ // set PtPos 'close' to the reference table, otherwise we might get problems
+ // with the repeated headlines check in UpdateCursor():
+ if ( !bRow )
+ m_pTableCursor->GetPtPos() = pMasterTabFrame->IsVertical()
+ ? pMasterTabFrame->getFrameArea().TopRight()
+ : pMasterTabFrame->getFrameArea().TopLeft();
+
+ UpdateCursor();
+ return true;
+}
+
+bool SwCursorShell::SelTable()
+{
+ // check if the current cursor's SPoint/Mark are in a table
+ SwFrame *pFrame = GetCurrFrame();
+ if( !pFrame->IsInTab() )
+ return false;
+
+ const SwTabFrame *pTableFrame = pFrame->ImplFindTabFrame();
+ const SwTabFrame* pMasterTabFrame = pTableFrame->IsFollow() ? pTableFrame->FindMaster( true ) : pTableFrame;
+ const SwTableNode* pTableNd = pTableFrame->GetTable()->GetTableNode();
+
+ CurrShell aCurr( this );
+
+ if( !m_pTableCursor )
+ {
+ m_pTableCursor = new SwShellTableCursor( *this, *m_pCurrentCursor->GetPoint() );
+ m_pCurrentCursor->DeleteMark();
+ m_pCurrentCursor->SwSelPaintRects::Hide();
+ }
+
+ m_pTableCursor->DeleteMark();
+ m_pTableCursor->GetPoint()->Assign( *pTableNd );
+ m_pTableCursor->Move( fnMoveForward, GoInContent );
+ m_pTableCursor->SetMark();
+ // set MkPos 'close' to the master table, otherwise we might get problems
+ // with the repeated headlines check in UpdateCursor():
+ m_pTableCursor->GetMkPos() = pMasterTabFrame->IsVertical() ? pMasterTabFrame->getFrameArea().TopRight() : pMasterTabFrame->getFrameArea().TopLeft();
+ m_pTableCursor->GetPoint()->Assign( *pTableNd->EndOfSectionNode() );
+ m_pTableCursor->Move( fnMoveBackward, GoInContent );
+ UpdateCursor();
+ return true;
+}
+
+bool SwCursorShell::SelTableBox()
+{
+ // if we're in a table, create a table cursor, and select the cell
+ // that the current cursor's point resides in
+
+ // search for start node of our table box. If not found, exit really
+ const SwStartNode* pStartNode =
+ m_pCurrentCursor->GetPoint()->GetNode().FindTableBoxStartNode();
+
+#if OSL_DEBUG_LEVEL > 0
+ // the old code checks whether we're in a table by asking the
+ // frame. This should yield the same result as searching for the
+ // table box start node, right?
+ SwFrame *pFrame = GetCurrFrame();
+ OSL_ENSURE( !pFrame->IsInTab() == !(pStartNode != nullptr),
+ "Schroedinger's table: We're in a box, and also we aren't." );
+#endif
+ if( pStartNode == nullptr )
+ return false;
+
+ CurrShell aCurr( this );
+
+ // create a table cursor, if there isn't one already
+ if( !m_pTableCursor )
+ {
+ m_pTableCursor = new SwShellTableCursor( *this, *m_pCurrentCursor->GetPoint() );
+ m_pCurrentCursor->DeleteMark();
+ m_pCurrentCursor->SwSelPaintRects::Hide();
+ }
+
+ // select the complete box with our shiny new m_pTableCursor
+ // 1. delete mark, and move point to first content node in box
+ m_pTableCursor->DeleteMark();
+ m_pTableCursor->GetPoint()->Assign( *pStartNode );
+ m_pTableCursor->Move( fnMoveForward, GoInNode );
+
+ // 2. set mark, and move point to last content node in box
+ m_pTableCursor->SetMark();
+ m_pTableCursor->GetPoint()->Assign( *(pStartNode->EndOfSectionNode()) );
+ m_pTableCursor->Move( fnMoveBackward, GoInNode );
+
+ // 3. exchange
+ m_pTableCursor->Exchange();
+
+ // with some luck, UpdateCursor() will now update everything that
+ // needs updating
+ UpdateCursor();
+
+ return true;
+}
+
+// TODO: provide documentation
+/** get the next non-protected cell inside a table
+
+ @param[in,out] rIdx is on a table node
+ @param bInReadOnly ???
+
+ @return <false> if no suitable cell could be found, otherwise <rIdx> points
+ to content in a suitable cell and <true> is returned.
+*/
+static bool lcl_FindNextCell( SwNodeIndex& rIdx, bool bInReadOnly )
+{
+ // check protected cells
+ SwNodeIndex aTmp( rIdx, 2 ); // TableNode + StartNode
+
+ // the resulting cell should be in that table:
+ const SwTableNode* pTableNd = rIdx.GetNode().GetTableNode();
+
+ if ( !pTableNd )
+ {
+ OSL_FAIL( "lcl_FindNextCell not celled with table start node!" );
+ return false;
+ }
+
+ const SwNode* pTableEndNode = pTableNd->EndOfSectionNode();
+
+ SwNodes& rNds = aTmp.GetNode().GetNodes();
+ SwContentNode* pCNd = aTmp.GetNode().GetContentNode();
+
+ // no content node => go to next content node
+ if( !pCNd )
+ pCNd = rNds.GoNext( &aTmp );
+
+ // robust
+ if ( !pCNd )
+ return false;
+
+ SwContentFrame* pFrame = pCNd->getLayoutFrame( pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() );
+
+ if ( nullptr == pFrame || pCNd->FindTableNode() != pTableNd ||
+ (!bInReadOnly && pFrame->IsProtected() ) )
+ {
+ // we are not located inside a 'valid' cell. We have to continue searching...
+
+ // skip behind current section. This might be the end of the table cell
+ // or behind an inner section or...
+ aTmp.Assign( *pCNd->EndOfSectionNode(), 1 );
+
+ // loop to find a suitable cell...
+ for( ;; )
+ {
+ SwNode* pNd = &aTmp.GetNode();
+
+ // we break this loop if we reached the end of the table.
+ // to make this code even more robust, we also break if we are
+ // already behind the table end node:
+ if( pNd == pTableEndNode || /*robust: */ pNd->GetIndex() > pTableEndNode->GetIndex() )
+ return false;
+
+ // ok, get the next content node:
+ pCNd = aTmp.GetNode().GetContentNode();
+ if( nullptr == pCNd )
+ pCNd = rNds.GoNext( &aTmp );
+
+ // robust:
+ if ( !pCNd )
+ return false;
+
+ // check if we have found a suitable table cell:
+ pFrame = pCNd->getLayoutFrame( pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() );
+
+ if ( nullptr != pFrame && pCNd->FindTableNode() == pTableNd &&
+ (bInReadOnly || !pFrame->IsProtected() ) )
+ {
+ // finally, we have found a suitable table cell => set index and return
+ rIdx = *pCNd;
+ return true;
+ }
+
+ // continue behind the current section:
+ aTmp.Assign( *pCNd->EndOfSectionNode(), +1 );
+ }
+ }
+ rIdx = *pCNd;
+ return true;
+}
+
+/// see lcl_FindNextCell()
+static bool lcl_FindPrevCell( SwNodeIndex& rIdx, bool bInReadOnly )
+{
+ SwNodeIndex aTmp( rIdx, -2 ); // TableNode + EndNode
+
+ const SwNode* pTableEndNode = &rIdx.GetNode();
+ const SwTableNode* pTableNd = pTableEndNode->StartOfSectionNode()->GetTableNode();
+
+ if ( !pTableNd )
+ {
+ OSL_FAIL( "lcl_FindPrevCell not celled with table start node!" );
+ return false;
+ }
+
+ SwContentNode* pCNd = aTmp.GetNode().GetContentNode();
+
+ if( !pCNd )
+ pCNd = SwNodes::GoPrevious( &aTmp );
+
+ if ( !pCNd )
+ return false;
+
+ SwContentFrame* pFrame = pCNd->getLayoutFrame( pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() );
+
+ if( nullptr == pFrame || pCNd->FindTableNode() != pTableNd ||
+ (!bInReadOnly && pFrame->IsProtected() ))
+ {
+ // skip before current section
+ aTmp.Assign( *pCNd->StartOfSectionNode(), -1 );
+ for( ;; )
+ {
+ SwNode* pNd = &aTmp.GetNode();
+
+ if( pNd == pTableNd || pNd->GetIndex() < pTableNd->GetIndex() )
+ return false;
+
+ pCNd = aTmp.GetNode().GetContentNode();
+ if( nullptr == pCNd )
+ pCNd = SwNodes::GoPrevious( &aTmp );
+
+ if ( !pCNd )
+ return false;
+
+ pFrame = pCNd->getLayoutFrame( pCNd->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() );
+
+ if( nullptr != pFrame && pCNd->FindTableNode() == pTableNd &&
+ (bInReadOnly || !pFrame->IsProtected() ) )
+ {
+ rIdx = *pCNd;
+ return true; // ok, not protected
+ }
+ aTmp.Assign( *pCNd->StartOfSectionNode(), -1 );
+ }
+ }
+ rIdx = *pCNd;
+ return true;
+}
+
+bool GotoPrevTable( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosTable,
+ bool bInReadOnly )
+{
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
+
+ SwNodeIndex aIdx( rCurrentCursor.GetPoint()->GetNode() );
+
+ SwTableNode* pTableNd = aIdx.GetNode().FindTableNode();
+ if( pTableNd )
+ {
+ // #i26532#: If we are inside a table, we may not go backward to the
+ // table start node, because we would miss any tables inside this table.
+ SwTableNode* pInnerTableNd = nullptr;
+ SwNodeIndex aTmpIdx( aIdx );
+ while( aTmpIdx.GetIndex() &&
+ nullptr == ( pInnerTableNd = aTmpIdx.GetNode().StartOfSectionNode()->GetTableNode()) )
+ --aTmpIdx;
+
+ if( pInnerTableNd == pTableNd )
+ aIdx.Assign( *pTableNd, -1 );
+ }
+
+ SwNodeIndex aOldIdx = aIdx;
+ SwNodeOffset nLastNd(rCurrentCursor.GetDoc().GetNodes().Count() - 1);
+ do {
+ while( aIdx.GetIndex() &&
+ nullptr == ( pTableNd = aIdx.GetNode().StartOfSectionNode()->GetTableNode()) )
+ {
+ --aIdx;
+ if ( aIdx == aOldIdx )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+ }
+
+ if ( !aIdx.GetIndex() )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped );
+ aIdx = nLastNd;
+ continue;
+ }
+
+ {
+ if( &fnPosTable == &fnMoveForward ) // at the beginning?
+ {
+ aIdx = *aIdx.GetNode().StartOfSectionNode();
+ if( !lcl_FindNextCell( aIdx, bInReadOnly ))
+ {
+ // skip table
+ aIdx.Assign( *pTableNd, -1 );
+ continue;
+ }
+ }
+ else
+ {
+ // check protected cells
+ if( !lcl_FindNextCell( aIdx, bInReadOnly ))
+ {
+ // skip table
+ aIdx.Assign( *pTableNd, -1 );
+ continue;
+ }
+ }
+
+ SwTextNode* pTextNode = aIdx.GetNode().GetTextNode();
+ if ( pTextNode )
+ {
+ rCurrentCursor.GetPoint()->Assign(*pTextNode, &fnPosTable == &fnMoveBackward ?
+ pTextNode->Len() :
+ 0 );
+ }
+ return true;
+ }
+ } while( true );
+
+ return false;
+}
+
+bool GotoNextTable( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosTable,
+ bool bInReadOnly )
+{
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );
+
+ SwNodeIndex aIdx( rCurrentCursor.GetPoint()->GetNode() );
+ SwTableNode* pTableNd = aIdx.GetNode().FindTableNode();
+
+ if( pTableNd )
+ aIdx.Assign( *pTableNd->EndOfSectionNode(), 1 );
+
+ SwNodeIndex aOldIdx = aIdx;
+ SwNodeOffset nLastNd(rCurrentCursor.GetDoc().GetNodes().Count() - 1);
+ do {
+ while( aIdx.GetIndex() < nLastNd &&
+ nullptr == ( pTableNd = aIdx.GetNode().GetTableNode()) )
+ {
+ ++aIdx;
+ if ( aIdx == aOldIdx )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
+ return false;
+ }
+ }
+
+ if ( aIdx.GetIndex() == nLastNd )
+ {
+ SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped );
+ aIdx = SwNodeOffset(0);
+ continue;
+ }
+
+ assert( pTableNd ); // coverity, should never be nullptr
+
+ if( &fnPosTable == &fnMoveForward ) // at the beginning?
+ {
+ if( !lcl_FindNextCell( aIdx, bInReadOnly ))
+ {
+ // skip table
+ aIdx.Assign( *pTableNd->EndOfSectionNode(), + 1 );
+ continue;
+ }
+ }
+ else
+ {
+ aIdx = *aIdx.GetNode().EndOfSectionNode();
+ // check protected cells
+ if( !lcl_FindNextCell( aIdx, bInReadOnly ))
+ {
+ // skip table
+ aIdx.Assign( *pTableNd->EndOfSectionNode(), + 1 );
+ continue;
+ }
+ }
+
+ SwTextNode* pTextNode = aIdx.GetNode().GetTextNode();
+ if ( pTextNode )
+ {
+ rCurrentCursor.GetPoint()->Assign(*pTextNode, &fnPosTable == &fnMoveBackward ?
+ pTextNode->Len() :
+ 0 );
+ }
+ return true;
+
+ } while( true );
+
+ // the flow is such that it is not possible to get there
+
+ return false;
+}
+
+bool GotoCurrTable( SwPaM& rCurrentCursor, SwMoveFnCollection const & fnPosTable,
+ bool bInReadOnly )
+{
+ SwTableNode* pTableNd = rCurrentCursor.GetPoint()->GetNode().FindTableNode();
+ if( !pTableNd )
+ return false;
+
+ SwTextNode* pTextNode = nullptr;
+ if( &fnPosTable == &fnMoveBackward ) // to the end of the table
+ {
+ SwNodeIndex aIdx( *pTableNd->EndOfSectionNode() );
+ if( !lcl_FindPrevCell( aIdx, bInReadOnly ))
+ return false;
+ pTextNode = aIdx.GetNode().GetTextNode();
+ }
+ else
+ {
+ SwNodeIndex aIdx( *pTableNd );
+ if( !lcl_FindNextCell( aIdx, bInReadOnly ))
+ return false;
+ pTextNode = aIdx.GetNode().GetTextNode();
+ }
+
+ if ( pTextNode )
+ {
+ rCurrentCursor.GetPoint()->Assign(*pTextNode, &fnPosTable == &fnMoveBackward ?
+ pTextNode->Len() :
+ 0 );
+ }
+
+ return true;
+}
+
+bool SwCursor::MoveTable( SwWhichTable fnWhichTable, SwMoveFnCollection const & fnPosTable )
+{
+ bool bRet = false;
+ SwTableCursor* pTableCursor = dynamic_cast<SwTableCursor*>(this);
+
+ if( pTableCursor || !HasMark() )
+ {
+ SwCursorSaveState aSaveState( *this );
+ bRet = (*fnWhichTable)( *this, fnPosTable, IsReadOnlyAvailable() ) &&
+ !IsSelOvr( SwCursorSelOverFlags::CheckNodeSection |
+ SwCursorSelOverFlags::Toggle );
+ }
+ return bRet;
+}
+
+bool SwCursorShell::MoveTable( SwWhichTable fnWhichTable, SwMoveFnCollection const & fnPosTable )
+{
+ SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed
+
+ SwShellCursor* pCursor = m_pTableCursor ? m_pTableCursor : m_pCurrentCursor;
+ bool bCheckPos;
+ bool bRet;
+ SwNodeOffset nPtNd(0);
+ sal_Int32 nPtCnt = 0;
+
+ if ( !m_pTableCursor && m_pCurrentCursor->HasMark() )
+ {
+ // switch to table mode
+ m_pTableCursor = new SwShellTableCursor( *this, *m_pCurrentCursor->GetPoint() );
+ m_pCurrentCursor->DeleteMark();
+ m_pCurrentCursor->SwSelPaintRects::Hide();
+ m_pTableCursor->SetMark();
+ pCursor = m_pTableCursor;
+ bCheckPos = false;
+ }
+ else
+ {
+ bCheckPos = true;
+ nPtNd = pCursor->GetPoint()->GetNodeIndex();
+ nPtCnt = pCursor->GetPoint()->GetContentIndex();
+ }
+
+ bRet = pCursor->MoveTable( fnWhichTable, fnPosTable );
+
+ if( bRet )
+ {
+ // #i45028# - set "top" position for repeated headline rows
+ pCursor->GetPtPos() = Point();
+
+ UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY);
+
+ if( bCheckPos &&
+ pCursor->GetPoint()->GetNodeIndex() == nPtNd &&
+ pCursor->GetPoint()->GetContentIndex() == nPtCnt )
+ bRet = false;
+ }
+ return bRet;
+}
+
+bool SwCursorShell::IsTableComplexForChart()
+{
+ bool bRet = false;
+
+ // Here we may trigger table formatting so we better do that inside an action
+ StartAction();
+ const SwTableNode* pTNd = m_pCurrentCursor->GetPoint()->GetNode().FindTableNode();
+ if( pTNd )
+ {
+ // in a table; check if table or section is balanced
+ OUString sSel;
+ if( m_pTableCursor )
+ sSel = GetBoxNms();
+ bRet = pTNd->GetTable().IsTableComplexForChart( sSel );
+ }
+ EndAction();
+
+ return bRet;
+}
+
+OUString SwCursorShell::GetBoxNms() const
+{
+ OUString sNm;
+ const SwPosition* pPos;
+ SwFrame* pFrame;
+
+ if( IsTableMode() )
+ {
+ SwContentNode *pCNd = m_pTableCursor->Start()->GetNode().GetContentNode();
+ pFrame = pCNd ? pCNd->getLayoutFrame( GetLayout() ) : nullptr;
+ if( !pFrame )
+ return sNm;
+
+ do {
+ pFrame = pFrame->GetUpper();
+ } while ( pFrame && !pFrame->IsCellFrame() );
+
+ OSL_ENSURE( pFrame, "no frame for this box" );
+
+ if( !pFrame )
+ return sNm;
+
+ sNm = static_cast<SwCellFrame*>(pFrame)->GetTabBox()->GetName() + ":";
+ pPos = m_pTableCursor->End();
+ }
+ else
+ {
+ const SwTableNode* pTableNd = IsCursorInTable();
+ if( !pTableNd )
+ return sNm;
+ pPos = GetCursor()->GetPoint();
+ }
+
+ SwContentNode* pCNd = pPos->GetNode().GetContentNode();
+ pFrame = pCNd ? pCNd->getLayoutFrame( GetLayout() ) : nullptr;
+
+ if( pFrame )
+ {
+ do {
+ pFrame = pFrame->GetUpper();
+ } while ( pFrame && !pFrame->IsCellFrame() );
+
+ if( pFrame )
+ sNm += static_cast<SwCellFrame*>(pFrame)->GetTabBox()->GetName();
+ }
+ return sNm;
+}
+
+bool SwCursorShell::GotoTable( const OUString& rName )
+{
+ SwCallLink aLk( *this ); // watch Cursor-Moves
+ bool bRet = !m_pTableCursor && m_pCurrentCursor->GotoTable( rName );
+ if( bRet )
+ {
+ m_pCurrentCursor->GetPtPos() = Point();
+ UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
+ SwCursorShell::READONLY );
+ }
+ return bRet;
+}
+
+bool SwCursorShell::CheckTableBoxContent( const SwPosition* pPos )
+{
+ if( !m_pBoxIdx || !m_pBoxPtr || IsSelTableCells() || !IsAutoUpdateCells() )
+ return false;
+
+ // check if box content is consistent with given box format, reset if not
+ SwTableBox* pChkBox = nullptr;
+ SwStartNode* pSttNd = nullptr;
+ if( !pPos )
+ {
+ // get stored position
+ if (nullptr != (pSttNd = m_pBoxIdx->GetNode().GetStartNode()) &&
+ SwTableBoxStartNode == pSttNd->GetStartNodeType() &&
+ m_pBoxPtr == pSttNd->FindTableNode()->GetTable().
+ GetTableBox( m_pBoxIdx->GetIndex() ) )
+ pChkBox = m_pBoxPtr;
+ }
+ else
+ {
+ pSttNd = pPos->GetNode().FindSttNodeByType( SwTableBoxStartNode );
+ if( pSttNd)
+ pChkBox = pSttNd->FindTableNode()->GetTable().GetTableBox( pSttNd->GetIndex() );
+ }
+
+ // box has more than one paragraph
+ if( pChkBox && pSttNd->GetIndex() + SwNodeOffset(2) != pSttNd->EndOfSectionIndex() )
+ pChkBox = nullptr;
+
+ // destroy pointer before next action starts
+ if( !pPos && !pChkBox )
+ ClearTableBoxContent();
+
+ // cursor not anymore in this section?
+ if( pChkBox && !pPos &&
+ ( m_pCurrentCursor->HasMark() || m_pCurrentCursor->GetNext() != m_pCurrentCursor ||
+ pSttNd->GetIndex() + 1 == m_pCurrentCursor->GetPoint()->GetNodeIndex() ))
+ pChkBox = nullptr;
+
+ // Did the content of a box change at all? This is important if e.g. Undo
+ // could not restore the content properly.
+ if( pChkBox )
+ {
+ const SwTextNode* pNd = GetDoc()->GetNodes()[
+ pSttNd->GetIndex() + 1 ]->GetTextNode();
+ if( !pNd ||
+ ( pNd->GetText() == SwViewShell::GetShellRes()->aCalc_Error &&
+ SfxItemState::SET == pChkBox->GetFrameFormat()->
+ GetItemState( RES_BOXATR_FORMULA )) )
+ pChkBox = nullptr;
+ }
+
+ if( pChkBox )
+ {
+ // destroy pointer before next action starts
+ ClearTableBoxContent();
+ StartAction();
+ GetDoc()->ChkBoxNumFormat( *pChkBox, true );
+ EndAction();
+ }
+
+ return nullptr != pChkBox;
+}
+
+void SwCursorShell::SaveTableBoxContent( const SwPosition* pPos )
+{
+ if( IsSelTableCells() || !IsAutoUpdateCells() )
+ return ;
+
+ if( !pPos )
+ pPos = m_pCurrentCursor->GetPoint();
+
+ SwStartNode* pSttNd = pPos->GetNode().FindSttNodeByType( SwTableBoxStartNode );
+
+ bool bCheckBox = false;
+ if( pSttNd && m_pBoxIdx )
+ {
+ if( pSttNd == &m_pBoxIdx->GetNode() )
+ pSttNd = nullptr;
+ else
+ bCheckBox = true;
+ }
+ else
+ bCheckBox = nullptr != m_pBoxIdx;
+
+ if( bCheckBox )
+ {
+ // check m_pBoxIdx
+ SwPosition aPos( *m_pBoxIdx );
+ CheckTableBoxContent( &aPos );
+ }
+
+ if( pSttNd )
+ {
+ m_pBoxPtr = pSttNd->FindTableNode()->GetTable().GetTableBox( pSttNd->GetIndex() );
+
+ if( m_pBoxIdx )
+ *m_pBoxIdx = *pSttNd;
+ else
+ m_pBoxIdx = new SwNodeIndex( *pSttNd );
+ }
+}
+
+void SwCursorShell::ClearTableBoxContent()
+{
+ delete m_pBoxIdx;
+ m_pBoxIdx = nullptr;
+ m_pBoxPtr = nullptr;
+}
+
+bool SwCursorShell::EndAllTableBoxEdit()
+{
+ bool bRet = false;
+ for(SwViewShell& rSh : GetRingContainer())
+ {
+ if( auto pCursorShell = dynamic_cast<SwCursorShell *>(&rSh) )
+ bRet |= pCursorShell->CheckTableBoxContent(
+ pCursorShell->m_pCurrentCursor->GetPoint() );
+
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/crsr/viscrs.cxx b/sw/source/core/crsr/viscrs.cxx
new file mode 100644
index 0000000000..0385ce85f7
--- /dev/null
+++ b/sw/source/core/crsr/viscrs.cxx
@@ -0,0 +1,1258 @@
+/* -*- 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_feature_desktop.h>
+
+#include <vcl/weld.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <viewopt.hxx>
+#include <frmtool.hxx>
+#include <viscrs.hxx>
+#include <crsrsh.hxx>
+#include <doc.hxx>
+#include <swtable.hxx>
+#include <viewimp.hxx>
+#include <dview.hxx>
+#include <rootfrm.hxx>
+#include <txtfrm.hxx>
+#include <ndtxt.hxx>
+#include <txtfld.hxx>
+#include <scriptinfo.hxx>
+#include <view.hxx>
+#include <IDocumentLayoutAccess.hxx>
+
+#include <svx/sdr/overlay/overlaymanager.hxx>
+#include <svx/sdrpaintwindow.hxx>
+#include <svx/srchdlg.hxx>
+#include <svx/sdr/overlay/overlayselection.hxx>
+#include "overlayrangesoutline.hxx"
+
+#include <memory>
+
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <comphelper/lok.hxx>
+#include <sfx2/lokhelper.hxx>
+#include <boost/property_tree/json_parser.hpp>
+#include <comphelper/string.hxx>
+#include <osl/diagnose.h>
+#include <paintfrm.hxx>
+#include <PostItMgr.hxx>
+#include <SwGrammarMarkUp.hxx>
+#include <docsh.hxx>
+#include <svtools/optionsdrawinglayer.hxx>
+#include <o3tl/string_view.hxx>
+#include <tools/json_writer.hxx>
+#include <cellfrm.hxx>
+#include <wrtsh.hxx>
+#include <textcontentcontrol.hxx>
+#include <dropdowncontentcontrolbutton.hxx>
+#include <datecontentcontrolbutton.hxx>
+#include <FrameControlsManager.hxx>
+
+// Here static members are defined. They will get changed on alteration of the
+// MapMode. This is done so that on ShowCursor the same size does not have to be
+// expensively determined again and again.
+
+tools::Long SwSelPaintRects::s_nPixPtX = 0;
+tools::Long SwSelPaintRects::s_nPixPtY = 0;
+MapMode* SwSelPaintRects::s_pMapMode = nullptr;
+
+// Starting from here: classes / methods for the non-text-cursor
+SwVisibleCursor::SwVisibleCursor( const SwCursorShell * pCShell )
+ : m_pCursorShell( pCShell )
+ , m_nPageLastTime(0)
+{
+ pCShell->GetWin()->SetCursor( &m_aTextCursor );
+ m_bIsVisible = m_aTextCursor.IsVisible();
+ m_bIsDragCursor = false;
+ m_aTextCursor.SetWidth( 0 );
+}
+
+SwVisibleCursor::~SwVisibleCursor()
+{
+ if( m_bIsVisible && m_aTextCursor.IsVisible() )
+ m_aTextCursor.Hide();
+
+ m_pCursorShell->GetWin()->SetCursor( nullptr );
+}
+
+void SwVisibleCursor::Show()
+{
+ if( !m_bIsVisible )
+ {
+ m_bIsVisible = true;
+
+ // display at all?
+ if( m_pCursorShell->VisArea().Overlaps( m_pCursorShell->m_aCharRect ) || comphelper::LibreOfficeKit::isActive() )
+ SetPosAndShow(nullptr);
+ }
+}
+
+void SwVisibleCursor::Hide()
+{
+ if( m_bIsVisible )
+ {
+ m_bIsVisible = false;
+
+ if( m_aTextCursor.IsVisible() ) // Shouldn't the flags be in effect?
+ m_aTextCursor.Hide();
+ }
+}
+
+namespace
+{
+
+// Build JSON message to be sent to Online
+OString buildHyperlinkJSON(const OUString& sText, const OUString& sLink)
+{
+ boost::property_tree::ptree aTree;
+ aTree.put("text", sText);
+ aTree.put("link", sLink);
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, aTree, false);
+
+ return OString(o3tl::trim(aStream.str()));
+}
+
+}
+
+void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell)
+{
+ SwRect aRect;
+ tools::Long nTmpY = m_pCursorShell->m_aCursorHeight.getY();
+ if( 0 > nTmpY )
+ {
+ nTmpY = -nTmpY;
+ m_aTextCursor.SetOrientation( 900_deg10 );
+ aRect = SwRect( m_pCursorShell->m_aCharRect.Pos(),
+ Size( m_pCursorShell->m_aCharRect.Height(), nTmpY ) );
+ aRect.Pos().setX(aRect.Pos().getX() + m_pCursorShell->m_aCursorHeight.getX());
+ if( m_pCursorShell->IsOverwriteCursor() )
+ aRect.Pos().setY(aRect.Pos().getY() + aRect.Width());
+ }
+ else
+ {
+ m_aTextCursor.SetOrientation();
+ aRect = SwRect( m_pCursorShell->m_aCharRect.Pos(),
+ Size( m_pCursorShell->m_aCharRect.Width(), nTmpY ) );
+ aRect.Pos().setY(aRect.Pos().getY() + m_pCursorShell->m_aCursorHeight.getX());
+ }
+
+ // check if cursor should show the current cursor bidi level
+ m_aTextCursor.SetDirection();
+ const SwCursor* pTmpCursor = m_pCursorShell->GetCursor_();
+
+ if ( pTmpCursor && !m_pCursorShell->IsOverwriteCursor() )
+ {
+ SwNode& rNode = pTmpCursor->GetPoint()->GetNode();
+ if( rNode.IsTextNode() )
+ {
+ const SwTextNode& rTNd = *rNode.GetTextNode();
+ const SwFrame* pFrame = rTNd.getLayoutFrame(m_pCursorShell->GetLayout(), nullptr, nullptr);
+ if ( pFrame )
+ {
+ const SwScriptInfo* pSI = static_cast<const SwTextFrame*>(pFrame)->GetScriptInfo();
+ // cursor level has to be shown
+ if ( pSI && pSI->CountDirChg() > 1 )
+ {
+ m_aTextCursor.SetDirection(
+ ( pTmpCursor->GetCursorBidiLevel() % 2 ) ?
+ CursorDirection::RTL :
+ CursorDirection::LTR );
+ }
+ if ( pFrame->IsRightToLeft() )
+ {
+ const OutputDevice *pOut = m_pCursorShell->GetOut();
+ if ( pOut )
+ {
+ tools::Long nSize = pOut->GetSettings().GetStyleSettings().GetCursorSize();
+ Size aSize( nSize, nSize );
+ aSize = pOut->PixelToLogic( aSize );
+ aRect.Left( aRect.Left() - aSize.Width() );
+ }
+ }
+ }
+ }
+ }
+
+ if( aRect.Height())
+ {
+ ::SwCalcPixStatics( m_pCursorShell->GetOut() );
+
+ // Disable pixel alignment when tiled rendering, so that twip values of
+ // the cursor don't depend on statics.
+ if (!comphelper::LibreOfficeKit::isActive())
+ ::SwAlignRect( aRect, static_cast<SwViewShell const *>(m_pCursorShell), m_pCursorShell->GetOut() );
+ }
+ if( !m_pCursorShell->IsOverwriteCursor() || m_bIsDragCursor ||
+ m_pCursorShell->IsSelection() )
+ aRect.Width( 0 );
+
+ m_aTextCursor.SetSize( aRect.SSize() );
+
+ m_aTextCursor.SetPos( aRect.Pos() );
+
+ bool bPostItActive = false;
+ SwView* pView = dynamic_cast<SwView*>(m_pCursorShell->GetSfxViewShell());
+ if (pView)
+ {
+ if (SwPostItMgr* pPostItMgr = pView->GetPostItMgr())
+ bPostItActive = pPostItMgr->GetActiveSidebarWin() != nullptr;
+ }
+
+ if (comphelper::LibreOfficeKit::isActive() && !bPostItActive)
+ {
+ // notify about page number change (if that happened)
+ sal_uInt16 nPage, nVirtPage;
+ // bCalcFrame=false is important to avoid calculating the layout when
+ // we're in the middle of doing that already.
+ const_cast<SwCursorShell*>(m_pCursorShell)->GetPageNum(nPage, nVirtPage, /*bAtCursorPos=*/true, /*bCalcFrame=*/false);
+ if (nPage != m_nPageLastTime)
+ {
+ m_nPageLastTime = nPage;
+ OString aPayload = OString::number(nPage - 1);
+ m_pCursorShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload);
+ }
+
+ // This may get called often, so instead of sending data on each update, just notify
+ // that there's been an update, and the other side will pull the data using
+ // getLOKPayload() when it decides to.
+ m_aLastLOKRect = aRect;
+ if (pViewShell)
+ {
+ if (pViewShell == m_pCursorShell->GetSfxViewShell())
+ {
+ SfxLokHelper::notifyUpdatePerViewId(pViewShell, LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR);
+ }
+ else
+ {
+ SfxLokHelper::notifyUpdatePerViewId(pViewShell, m_pCursorShell->GetSfxViewShell(), pViewShell,
+ LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
+ }
+ }
+ else
+ {
+ SfxLokHelper::notifyUpdatePerViewId(m_pCursorShell->GetSfxViewShell(), SfxViewShell::Current(),
+ m_pCursorShell->GetSfxViewShell(), LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR);
+ SfxLokHelper::notifyOtherViewsUpdatePerViewId(m_pCursorShell->GetSfxViewShell(), LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
+ }
+ }
+
+ if ( m_pCursorShell->IsCursorReadonly() && !m_pCursorShell->GetViewOptions()->IsSelectionInReadonly() )
+ return;
+
+ if ( m_pCursorShell->GetDrawView() )
+ const_cast<SwDrawView*>(static_cast<const SwDrawView*>(m_pCursorShell->GetDrawView()))->SetAnimationEnabled(
+ !m_pCursorShell->IsSelection() );
+
+ sal_uInt16 nStyle = m_bIsDragCursor ? CURSOR_SHADOW : 0;
+ if( nStyle != m_aTextCursor.GetStyle() )
+ {
+ m_aTextCursor.SetStyle( nStyle );
+ m_aTextCursor.SetWindow( m_bIsDragCursor ? m_pCursorShell->GetWin() : nullptr );
+ }
+
+ m_aTextCursor.Show();
+}
+
+std::optional<OString> SwVisibleCursor::getLOKPayload(int nType, int nViewId) const
+{
+ assert(nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR || nType == LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ SwRect aRect = m_aLastLOKRect;
+
+ // notify about the cursor position & size
+ tools::Rectangle aSVRect(aRect.Pos().getX(), aRect.Pos().getY(), aRect.Pos().getX() + aRect.SSize().Width(), aRect.Pos().getY() + aRect.SSize().Height());
+ OString sRect = aSVRect.toString();
+
+ if(nType == LOK_CALLBACK_INVALIDATE_VIEW_CURSOR)
+ return SfxLokHelper::makePayloadJSON(m_pCursorShell->GetSfxViewShell(), nViewId, "rectangle", sRect);
+
+ // is cursor at a misspelled word ?
+ bool bIsWrong = false;
+ SwView* pView = dynamic_cast<SwView*>(m_pCursorShell->GetSfxViewShell());
+ if (pView && pView->GetWrtShellPtr())
+ {
+ const SwViewOption* pVOpt = pView->GetWrtShell().GetViewOptions();
+ if(pVOpt && pVOpt->IsOnlineSpell())
+ {
+ SwPaM* pCursor = m_pCursorShell->GetCursor();
+ SwPosition aPos(*pCursor->GetPoint());
+ Point aPt = aRect.Pos();
+ SwCursorMoveState eTmpState(CursorMoveState::SetOnlyText);
+ SwTextNode *pNode = nullptr;
+ if (m_pCursorShell->GetLayout()->GetModelPositionForViewPoint(&aPos, aPt, &eTmpState))
+ pNode = aPos.GetNode().GetTextNode();
+ if (pNode && !pNode->IsInProtectSect())
+ {
+ sal_Int32 nBegin = aPos.GetContentIndex();
+ sal_Int32 nLen = 1;
+
+ SwWrongList *pWrong = pNode->GetWrong();
+ if (!pWrong)
+ pWrong = pNode->GetGrammarCheck();
+ if (pWrong)
+ bIsWrong = pWrong->InWrongWord(nBegin,nLen) && !pNode->IsSymbolAt(nBegin);
+ }
+ }
+ }
+
+ OString sHyperlink;
+ SwContentAtPos aContentAtPos(IsAttrAtPos::InetAttr);
+ bool bIsSelection = m_pCursorShell->IsSelection();
+
+ if (const_cast<SwCursorShell*>(m_pCursorShell)->GetContentAtPos(aRect.Pos(), aContentAtPos))
+ {
+ const SwFormatINetFormat* pItem = static_cast<const SwFormatINetFormat*>(aContentAtPos.aFnd.pAttr);
+ sHyperlink = buildHyperlinkJSON(aContentAtPos.sStr, pItem->GetValue());
+ }
+ else if (bIsSelection)
+ {
+ SwWrtShell* pShell = m_pCursorShell->GetDoc()->GetDocShell()->GetWrtShell();
+
+ if (pShell)
+ {
+ SfxItemSetFixed<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT>
+ aSet(m_pCursorShell->GetSfxViewShell()->GetPool());
+ pShell->GetCurAttr(aSet);
+ if(SfxItemState::SET <= aSet.GetItemState( RES_TXTATR_INETFMT ))
+ {
+ sHyperlink = buildHyperlinkJSON(m_pCursorShell->GetSelText(),
+ aSet.GetItem(RES_TXTATR_INETFMT)->GetValue());
+ }
+ }
+ }
+
+ return SfxLokHelper::makeVisCursorInvalidation(nViewId, sRect, bIsWrong, sHyperlink);
+ }
+ else
+ abort();
+}
+
+const vcl::Cursor& SwVisibleCursor::GetTextCursor() const
+{
+ return m_aTextCursor;
+}
+
+SwSelPaintRects::SwSelPaintRects( const SwCursorShell& rCSh )
+ : m_pCursorShell( &rCSh )
+#if HAVE_FEATURE_DESKTOP
+ , m_bShowTextInputFieldOverlay(true)
+ , m_bShowContentControlOverlay(true)
+#endif
+{
+}
+
+SwSelPaintRects::~SwSelPaintRects()
+{
+ Hide();
+ m_pContentControlButton.disposeAndClear();
+}
+
+void SwSelPaintRects::swapContent(SwSelPaintRects& rSwap)
+{
+ SwRects::swap(rSwap);
+
+#if HAVE_FEATURE_DESKTOP
+ // #i75172# also swap m_pCursorOverlay
+ std::swap(m_pCursorOverlay, rSwap.m_pCursorOverlay);
+ std::swap(m_bShowTextInputFieldOverlay, rSwap.m_bShowTextInputFieldOverlay);
+ std::swap(m_pTextInputFieldOverlay, rSwap.m_pTextInputFieldOverlay);
+ std::swap(m_bShowContentControlOverlay, rSwap.m_bShowContentControlOverlay);
+ std::swap(m_pContentControlOverlay, rSwap.m_pContentControlOverlay);
+#endif
+}
+
+void SwSelPaintRects::Hide()
+{
+#if HAVE_FEATURE_DESKTOP
+ m_pCursorOverlay.reset();
+ m_pTextInputFieldOverlay.reset();
+ m_pContentControlOverlay.reset();
+#endif
+
+ SwRects::clear();
+}
+
+/**
+ * Return a layout rectangle (typically with minimal width) that represents a
+ * cursor at rPosition.
+ *
+ * @param rPoint layout position as a hint about what layout frame contains
+ * rPosition (there might be multiple frames for a single node)
+ * @param rPosition the doc model position (paragraph / character index)
+ */
+static SwRect lcl_getLayoutRect(const Point& rPoint, const SwPosition& rPosition)
+{
+ const SwContentNode* pNode = rPosition.GetNode().GetContentNode();
+ std::pair<Point, bool> const tmp(rPoint, true);
+ const SwContentFrame* pFrame = pNode->getLayoutFrame(
+ pNode->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
+ &rPosition, &tmp);
+ SwRect aRect;
+ pFrame->GetCharRect(aRect, rPosition);
+ return aRect;
+}
+
+void SwShellCursor::FillStartEnd(SwRect& rStart, SwRect& rEnd) const
+{
+ const SwShellCursor* pCursor = GetShell()->getShellCursor(false);
+ rStart = lcl_getLayoutRect(pCursor->GetSttPos(), *pCursor->Start());
+ rEnd = lcl_getLayoutRect(pCursor->GetEndPos(), *pCursor->End());
+}
+
+void SwSelPaintRects::Show(std::vector<OString>* pSelectionRectangles)
+{
+ SdrView *const pView = const_cast<SdrView*>(m_pCursorShell->GetDrawView());
+
+ if(!(pView && pView->PaintWindowCount()))
+ return;
+
+ // reset rects
+ SwRects::clear();
+ FillRects();
+
+#if HAVE_FEATURE_DESKTOP
+ // get new rects
+ std::vector< basegfx::B2DRange > aNewRanges;
+ aNewRanges.reserve(size());
+ for(size_type a = 0; a < size(); ++a)
+ {
+ const SwRect aNextRect((*this)[a]);
+ const tools::Rectangle aPntRect(aNextRect.SVRect());
+
+ aNewRanges.emplace_back(
+ aPntRect.Left(), aPntRect.Top(),
+ aPntRect.Right() + 1, aPntRect.Bottom() + 1);
+ }
+
+ if (m_pCursorOverlay)
+ {
+ if(!aNewRanges.empty())
+ {
+ m_pCursorOverlay->setRanges(std::move(aNewRanges));
+ }
+ else
+ {
+ m_pCursorOverlay.reset();
+ }
+ }
+ else if(!empty())
+ {
+ SdrPaintWindow* pCandidate = pView->GetPaintWindow(0);
+ const rtl::Reference< sdr::overlay::OverlayManager >& xTargetOverlay = pCandidate->GetOverlayManager();
+
+ if (xTargetOverlay.is())
+ {
+ // get the system's highlight color
+ const Color aHighlight(SvtOptionsDrawinglayer::getHilightColor());
+
+ // create correct selection
+ m_pCursorOverlay.reset( new sdr::overlay::OverlaySelection(
+ sdr::overlay::OverlayType::Transparent,
+ aHighlight,
+ std::move(aNewRanges),
+ true) );
+
+ xTargetOverlay->add(*m_pCursorOverlay);
+ }
+ }
+
+ HighlightInputField();
+ HighlightContentControl();
+#endif
+
+ // Tiled editing does not expose the draw and writer cursor, it just
+ // talks about "the" cursor at the moment. As long as that's true,
+ // don't say anything about the Writer cursor till a draw object is
+ // being edited.
+ if (!comphelper::LibreOfficeKit::isActive() || pView->GetTextEditObject())
+ return;
+
+ // If pSelectionRectangles is set, we're just collecting the text selections -> don't emit start/end.
+ if (!empty() && !pSelectionRectangles)
+ {
+ SwRect aStartRect;
+ SwRect aEndRect;
+ FillStartEnd(aStartRect, aEndRect);
+
+ if (aStartRect.HasArea())
+ SfxLokHelper::notifyUpdate(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_SELECTION_START);
+ if (aEndRect.HasArea())
+ SfxLokHelper::notifyUpdate(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_SELECTION_END);
+ }
+
+ std::vector<OString> aRect;
+ aRect.reserve(size());
+ for (size_type i = 0; i < size(); ++i)
+ {
+ const SwRect& rRect = (*this)[i];
+ aRect.push_back(rRect.SVRect().toString());
+ }
+ OString sRect = comphelper::string::join("; ", aRect);
+ if (!pSelectionRectangles)
+ {
+ SfxLokHelper::notifyUpdate(GetShell()->GetSfxViewShell(),LOK_CALLBACK_TEXT_SELECTION);
+ SfxLokHelper::notifyOtherViewsUpdatePerViewId(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_VIEW_SELECTION);
+ }
+ else
+ pSelectionRectangles->push_back(sRect);
+}
+
+std::optional<OString> SwSelPaintRects::getLOKPayload(int nType, int nViewId) const
+{
+ switch( nType )
+ {
+ case LOK_CALLBACK_TEXT_SELECTION_START:
+ case LOK_CALLBACK_TEXT_SELECTION_END:
+ {
+ // The selection may be a complex polygon, emit the logical
+ // start/end cursor rectangle of the selection as separate
+ // events, if there is a real selection.
+ // This can be used to easily show selection handles on the
+ // client side.
+ SwRect aStartRect;
+ SwRect aEndRect;
+ FillStartEnd(aStartRect, aEndRect);
+
+ // no selection rect
+ if (!size())
+ return {};
+
+ if( nType == LOK_CALLBACK_TEXT_SELECTION_START )
+ {
+ if (aStartRect.HasArea())
+ return aStartRect.SVRect().toString();
+ return {};
+ }
+ else // LOK_CALLBACK_TEXT_SELECTION_END
+ {
+ if (aEndRect.HasArea())
+ return aEndRect.SVRect().toString();
+ return {};
+ }
+ }
+ break;
+ case LOK_CALLBACK_TEXT_SELECTION:
+ case LOK_CALLBACK_TEXT_VIEW_SELECTION:
+ {
+ std::vector<OString> aRect;
+ aRect.reserve(size());
+ for (size_type i = 0; i < size(); ++i)
+ {
+ const SwRect& rRect = (*this)[i];
+ aRect.push_back(rRect.SVRect().toString());
+ }
+ OString sRect = comphelper::string::join("; ", aRect);
+ if( nType == LOK_CALLBACK_TEXT_SELECTION )
+ return sRect;
+ else // LOK_CALLBACK_TEXT_VIEW_SELECTION
+ return SfxLokHelper::makePayloadJSON(GetShell()->GetSfxViewShell(), nViewId, "selection", sRect);
+ }
+ break;
+ }
+ abort();
+}
+
+void SwSelPaintRects::HighlightInputField()
+{
+ std::vector< basegfx::B2DRange > aInputFieldRanges;
+
+ if (m_bShowTextInputFieldOverlay)
+ {
+ SwTextInputField* pCurTextInputFieldAtCursor =
+ dynamic_cast<SwTextInputField*>(SwCursorShell::GetTextFieldAtPos( GetShell()->GetCursor()->Start(), ::sw::GetTextAttrMode::Expand));
+ if ( pCurTextInputFieldAtCursor != nullptr )
+ {
+ SwTextNode* pTextNode = pCurTextInputFieldAtCursor->GetpTextNode();
+ std::unique_ptr<SwShellCursor> pCursorForInputTextField(
+ new SwShellCursor( *GetShell(), SwPosition( *pTextNode, pCurTextInputFieldAtCursor->GetStart() ) ) );
+ pCursorForInputTextField->SetMark();
+ pCursorForInputTextField->GetMark()->Assign(*pTextNode, *(pCurTextInputFieldAtCursor->End()) );
+
+ pCursorForInputTextField->FillRects();
+ SwRects* pRects = pCursorForInputTextField.get();
+ for (const SwRect & rNextRect : *pRects)
+ {
+ const tools::Rectangle aPntRect(rNextRect.SVRect());
+
+ aInputFieldRanges.emplace_back(
+ aPntRect.Left(), aPntRect.Top(),
+ aPntRect.Right() + 1, aPntRect.Bottom() + 1);
+ }
+ }
+ }
+
+ if ( !aInputFieldRanges.empty() )
+ {
+ if (m_pTextInputFieldOverlay != nullptr)
+ {
+ m_pTextInputFieldOverlay->setRanges( std::move(aInputFieldRanges) );
+ }
+ else
+ {
+ SdrView* pView = const_cast<SdrView*>(GetShell()->GetDrawView());
+ SdrPaintWindow* pCandidate = pView->GetPaintWindow(0);
+ const rtl::Reference<sdr::overlay::OverlayManager>& xTargetOverlay = pCandidate->GetOverlayManager();
+
+ if (xTargetOverlay.is())
+ {
+ // use system's highlight color with decreased luminance as highlight color
+ Color aHighlight(SvtOptionsDrawinglayer::getHilightColor());
+ aHighlight.DecreaseLuminance( 128 );
+
+ m_pTextInputFieldOverlay.reset( new sw::overlay::OverlayRangesOutline(
+ aHighlight, std::move(aInputFieldRanges) ) );
+ xTargetOverlay->add( *m_pTextInputFieldOverlay );
+ }
+ }
+ }
+ else
+ {
+ m_pTextInputFieldOverlay.reset();
+ }
+}
+
+void SwSelPaintRects::HighlightContentControl()
+{
+ std::vector<basegfx::B2DRange> aContentControlRanges;
+ std::vector<OString> aLOKRectangles;
+ SwRect aFirstPortionPaintArea;
+ SwRect aLastPortionPaintArea;
+ bool bRTL = false;
+ std::shared_ptr<SwContentControl> pContentControl;
+
+ if (m_bShowContentControlOverlay)
+ {
+ const SwPosition* pStart = GetShell()->GetCursor()->Start();
+ SwTextNode* pTextNode = pStart->GetNode().GetTextNode();
+ SwTextContentControl* pCurContentControlAtCursor = nullptr;
+ if (pTextNode)
+ {
+ // GetTextAttrMode::Parent because this way we highlight when the user will type inside the
+ // content control, not outside of it.
+ SwTextAttr* pAttr = pTextNode->GetTextAttrAt(
+ pStart->GetContentIndex(), RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent);
+ if (pAttr)
+ {
+ pCurContentControlAtCursor = static_txtattr_cast<SwTextContentControl*>(pAttr);
+ }
+ }
+ if (pCurContentControlAtCursor)
+ {
+ auto pCursorForContentControl = std::make_unique<SwShellCursor>(
+ *GetShell(), SwPosition(*pTextNode, pCurContentControlAtCursor->GetStart()));
+ pCursorForContentControl->SetMark();
+ pCursorForContentControl->GetMark()->Assign(
+ *pTextNode, *(pCurContentControlAtCursor->End()));
+
+ pCursorForContentControl->FillRects();
+ SwRects* pRects = pCursorForContentControl.get();
+ for (const auto& rRect : *pRects)
+ {
+ tools::Rectangle aRect(rRect.SVRect());
+
+ aContentControlRanges.emplace_back(aRect.Left(), aRect.Top(), aRect.Right() + 1,
+ aRect.Bottom() + 1);
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ aLOKRectangles.push_back(aRect.toString());
+ }
+ }
+
+ if (!pRects->empty())
+ {
+ aFirstPortionPaintArea = (*pRects)[0];
+ aLastPortionPaintArea = (*pRects)[pRects->size() - 1];
+ }
+ pContentControl = pCurContentControlAtCursor->GetContentControl().GetContentControl();
+
+ // The layout knows if the text node is RTL (either set directly, or inherited from the
+ // environment).
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aFrames(*pTextNode);
+ SwTextFrame* pFrame = aFrames.First();
+ if (pFrame)
+ {
+ bRTL = pFrame->IsRightToLeft();
+ }
+ }
+ }
+
+ auto pWrtShell = dynamic_cast<const SwWrtShell*>(GetShell());
+ if (!aContentControlRanges.empty())
+ {
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ OString aPayload = comphelper::string::join("; ", aLOKRectangles);
+ tools::JsonWriter aJson;
+ aJson.put("action", "show");
+ aJson.put("rectangles", aPayload);
+
+ if (pContentControl && (pContentControl->GetComboBox() || pContentControl->GetDropDown()))
+ {
+ tools::ScopedJsonWriterArray aItems = aJson.startArray("items");
+ for (const auto& rItem : pContentControl->GetListItems())
+ {
+ aJson.putSimpleValue(rItem.ToString());
+ }
+ }
+
+ if (pContentControl && pContentControl->GetDate())
+ {
+ aJson.put("date", "true");
+ }
+
+ if (pContentControl && !pContentControl->GetAlias().isEmpty())
+ {
+ aJson.put("alias", pContentControl->GetAlias());
+ }
+
+ OString pJson(aJson.finishAndGetAsOString());
+ GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CONTENT_CONTROL, pJson);
+ }
+ if (m_pContentControlOverlay)
+ {
+ m_pContentControlOverlay->setRanges(std::move(aContentControlRanges));
+ }
+ else
+ {
+ SdrView* pView = const_cast<SdrView*>(GetShell()->GetDrawView());
+ SdrPaintWindow* pCandidate = pView->GetPaintWindow(0);
+ const rtl::Reference<sdr::overlay::OverlayManager>& xTargetOverlay
+ = pCandidate->GetOverlayManager();
+
+ if (xTargetOverlay.is())
+ {
+ // Use the system's highlight color with decreased luminance as highlight color.
+ Color aHighlight(SvtOptionsDrawinglayer::getHilightColor());
+ aHighlight.DecreaseLuminance(128);
+
+ m_pContentControlOverlay.reset(new sw::overlay::OverlayRangesOutline(
+ aHighlight, std::move(aContentControlRanges)));
+ xTargetOverlay->add(*m_pContentControlOverlay);
+ }
+ }
+
+ if (pContentControl && (pContentControl->GetComboBox() || pContentControl->GetDropDown()))
+ {
+ if (pWrtShell)
+ {
+ auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
+ if (m_pContentControlButton
+ && m_pContentControlButton->GetContentControl() != pContentControl)
+ {
+ m_pContentControlButton.disposeAndClear();
+ }
+ if (!m_pContentControlButton)
+ {
+ m_pContentControlButton = VclPtr<SwDropDownContentControlButton>::Create(
+ &rEditWin, pContentControl);
+ }
+ m_pContentControlButton->SetRTL(bRTL);
+ if (bRTL)
+ {
+ m_pContentControlButton->CalcPosAndSize(aFirstPortionPaintArea);
+ }
+ else
+ {
+ m_pContentControlButton->CalcPosAndSize(aLastPortionPaintArea);
+ }
+ m_pContentControlButton->Show();
+ }
+ }
+ if (pContentControl && pContentControl->GetDate())
+ {
+ if (pWrtShell)
+ {
+ auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
+ if (m_pContentControlButton
+ && m_pContentControlButton->GetContentControl() != pContentControl)
+ {
+ m_pContentControlButton.disposeAndClear();
+ }
+ if (!m_pContentControlButton)
+ {
+ m_pContentControlButton = VclPtr<SwDateContentControlButton>::Create(
+ &rEditWin, pContentControl, pWrtShell->GetDoc()->GetNumberFormatter());
+ }
+ m_pContentControlButton->CalcPosAndSize(aLastPortionPaintArea);
+ m_pContentControlButton->Show();
+ }
+ }
+
+ if (pWrtShell)
+ {
+ auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
+ SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager();
+ if (pContentControl && !pContentControl->GetAlias().isEmpty())
+ {
+ Point aTopLeftPixel = rEditWin.LogicToPixel(aFirstPortionPaintArea.TopLeft());
+ rMngr.SetContentControlAliasButton(pContentControl.get(), aTopLeftPixel);
+ }
+ else
+ {
+ rMngr.HideControls(FrameControlType::ContentControl);
+ }
+ }
+ }
+ else
+ {
+ if (comphelper::LibreOfficeKit::isActive() && m_pContentControlOverlay)
+ {
+ tools::JsonWriter aJson;
+ aJson.put("action", "hide");
+ OString pJson(aJson.finishAndGetAsOString());
+ GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CONTENT_CONTROL, pJson);
+ }
+ m_pContentControlOverlay.reset();
+
+ if (m_pContentControlButton)
+ {
+ m_pContentControlButton.disposeAndClear();
+ }
+
+ if (pWrtShell)
+ {
+ auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
+ SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager();
+ rMngr.HideControls(FrameControlType::ContentControl);
+ }
+ }
+}
+
+const VclPtr<SwContentControlButton>& SwSelPaintRects::GetContentControlButton() const
+{
+ return m_pContentControlButton;
+}
+
+void SwSelPaintRects::Invalidate( const SwRect& rRect )
+{
+ size_type nSz = size();
+ if( !nSz )
+ return;
+
+ SwRegionRects aReg( GetShell()->VisArea() );
+ aReg.assign( begin(), end() );
+ aReg -= rRect;
+ SwRects::erase( begin(), begin() + nSz );
+ SwRects::insert( begin(), aReg.begin(), aReg.end() );
+
+ // If the selection is to the right or at the bottom, outside the
+ // visible area, it is never aligned on one pixel at the right/bottom.
+ // This has to be determined here and if that is the case the
+ // rectangle has to be expanded.
+ if( !(GetShell()->m_bVisPortChgd && 0 != ( nSz = size())) )
+ return;
+
+ SwSelPaintRects::Get1PixelInLogic( *GetShell() );
+ iterator it = begin();
+ for( ; nSz--; ++it )
+ {
+ SwRect& rRectIt = *it;
+ if( rRectIt.Right() == GetShell()->m_aOldRBPos.X() )
+ rRectIt.AddRight( s_nPixPtX );
+ if( rRectIt.Bottom() == GetShell()->m_aOldRBPos.Y() )
+ rRectIt.AddBottom( s_nPixPtY );
+ }
+}
+
+// check current MapMode of the shell and set possibly the static members.
+// Optional set the parameters pX, pY
+void SwSelPaintRects::Get1PixelInLogic( const SwViewShell& rSh,
+ tools::Long* pX, tools::Long* pY )
+{
+ const OutputDevice* pOut = rSh.GetWin()->GetOutDev();
+ if ( ! pOut )
+ pOut = rSh.GetOut();
+
+ const MapMode& rMM = pOut->GetMapMode();
+ if (s_pMapMode->GetMapUnit() != rMM.GetMapUnit() ||
+ s_pMapMode->GetScaleX() != rMM.GetScaleX() ||
+ s_pMapMode->GetScaleY() != rMM.GetScaleY())
+ {
+ *s_pMapMode = rMM;
+ Size aTmp( 1, 1 );
+ aTmp = pOut->PixelToLogic( aTmp );
+ s_nPixPtX = aTmp.Width();
+ s_nPixPtY = aTmp.Height();
+ }
+ if( pX )
+ *pX = s_nPixPtX;
+ if( pY )
+ *pY = s_nPixPtY;
+}
+
+SwShellCursor::SwShellCursor(
+ const SwCursorShell& rCShell,
+ const SwPosition &rPos )
+ : SwCursor(rPos,nullptr)
+ , SwSelPaintRects(rCShell)
+ , m_pInitialPoint(SwPaM::GetPoint())
+{}
+
+SwShellCursor::SwShellCursor(
+ const SwCursorShell& rCShell,
+ const SwPosition &rPos,
+ const Point& rPtPos,
+ SwPaM* pRing )
+ : SwCursor(rPos, pRing)
+ , SwSelPaintRects(rCShell)
+ , m_MarkPt(rPtPos)
+ , m_PointPt(rPtPos)
+ , m_pInitialPoint(SwPaM::GetPoint())
+{}
+
+SwShellCursor::SwShellCursor( SwShellCursor& rICursor )
+ : SwCursor(rICursor, &rICursor)
+ , SwSelPaintRects(*rICursor.GetShell())
+ , m_MarkPt(rICursor.GetMkPos())
+ , m_PointPt(rICursor.GetPtPos())
+ , m_pInitialPoint(SwPaM::GetPoint())
+{}
+
+SwShellCursor::~SwShellCursor()
+{}
+
+bool SwShellCursor::IsReadOnlyAvailable() const
+{
+ return GetShell()->IsReadOnlyAvailable();
+}
+
+void SwShellCursor::SetMark()
+{
+ if (SwPaM::GetPoint() == m_pInitialPoint)
+ m_MarkPt = m_PointPt;
+ else
+ m_PointPt = m_MarkPt;
+ SwPaM::SetMark();
+}
+
+void SwShellCursor::FillRects()
+{
+ // calculate the new rectangles
+ if( HasMark() &&
+ GetPoint()->GetNode().IsContentNode() &&
+ GetPoint()->GetNode().GetContentNode()->getLayoutFrame( GetShell()->GetLayout() ) &&
+ (GetMark()->GetNode() == GetPoint()->GetNode() ||
+ (GetMark()->GetNode().IsContentNode() &&
+ GetMark()->GetNode().GetContentNode()->getLayoutFrame( GetShell()->GetLayout() ) ) ))
+ {
+ GetShell()->GetLayout()->CalcFrameRects(*this, *this);
+ }
+}
+
+void SwShellCursor::Show(SfxViewShell const * pViewShell)
+{
+ std::vector<OString> aSelectionRectangles;
+ for(SwPaM& rPaM : GetRingContainer())
+ {
+ SwShellCursor* pShCursor = dynamic_cast<SwShellCursor*>(&rPaM);
+ if(pShCursor)
+ pShCursor->SwSelPaintRects::Show(&aSelectionRectangles);
+ }
+
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ std::vector<OString> aRect;
+ for (const OString & rSelectionRectangle : aSelectionRectangles)
+ {
+ if (rSelectionRectangle.isEmpty())
+ continue;
+ aRect.push_back(rSelectionRectangle);
+ }
+ OString sRect = comphelper::string::join("; ", aRect);
+ if (pViewShell)
+ {
+ // Just notify pViewShell about our existing selection.
+ if (pViewShell != GetShell()->GetSfxViewShell())
+ SfxLokHelper::notifyOtherView(GetShell()->GetSfxViewShell(), pViewShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRect);
+ }
+ else
+ {
+ GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRect);
+ SfxLokHelper::notifyOtherViews(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRect);
+ }
+}
+
+// This rectangle gets painted anew, therefore the SSelection in this
+// area is invalid.
+void SwShellCursor::Invalidate( const SwRect& rRect )
+{
+ for(SwPaM& rPaM : GetRingContainer())
+ {
+ SwShellCursor* pShCursor = dynamic_cast<SwShellCursor*>(&rPaM);
+ // skip any non SwShellCursor objects in the ring
+ // see also: SwAutoFormat::DeleteSel()
+ if(pShCursor)
+ pShCursor->SwSelPaintRects::Invalidate(rRect);
+ }
+}
+
+void SwShellCursor::Hide()
+{
+ for(SwPaM& rPaM : GetRingContainer())
+ {
+ SwShellCursor* pShCursor = dynamic_cast<SwShellCursor*>(&rPaM);
+ if(pShCursor)
+ pShCursor->SwSelPaintRects::Hide();
+ }
+}
+
+SwCursor* SwShellCursor::Create( SwPaM* pRing ) const
+{
+ return new SwShellCursor( *GetShell(), *GetPoint(), GetPtPos(), pRing );
+}
+
+short SwShellCursor::MaxReplaceArived()
+{
+ short nRet = RET_YES;
+ SvxSearchDialog* pDlg = SwView::GetSearchDialog();
+ if( pDlg )
+ {
+ // Terminate old actions. The table-frames get constructed and
+ // a SSelection can be created.
+ std::vector<sal_uInt16> vActionCounts;
+ for(SwViewShell& rShell : const_cast< SwCursorShell* >( GetShell() )->GetRingContainer())
+ {
+ sal_uInt16 nActCnt = 0;
+ while(rShell.ActionPend())
+ {
+ rShell.EndAction();
+ ++nActCnt;
+ }
+ vActionCounts.push_back(nActCnt);
+ }
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pDlg->getDialog(), "modules/swriter/ui/asksearchdialog.ui"));
+ std::unique_ptr<weld::MessageDialog> xDialog(xBuilder->weld_message_dialog("AskSearchDialog"));
+ nRet = xDialog->run();
+ auto pActionCount = vActionCounts.begin();
+ for(SwViewShell& rShell : const_cast< SwCursorShell* >( GetShell() )->GetRingContainer())
+ {
+ while(*pActionCount)
+ {
+ rShell.StartAction();
+ --(*pActionCount);
+ }
+ ++pActionCount;
+ }
+ }
+ else
+ // otherwise from the Basic, and then switch to RET_YES
+ nRet = RET_YES;
+
+ return nRet;
+}
+
+void SwShellCursor::SaveTableBoxContent( const SwPosition* pPos )
+{
+ const_cast<SwCursorShell*>(GetShell())->SaveTableBoxContent( pPos );
+}
+
+bool SwShellCursor::UpDown( bool bUp, sal_uInt16 nCnt )
+{
+ // tdf#124603 trigger pending spell checking of the node
+ if ( nCnt == 1 )
+ {
+ SwTextNode* pNode = GetPoint()->GetNode().GetTextNode();
+ if( pNode && sw::WrongState::PENDING == pNode->GetWrongDirty() )
+ {
+ SwWrtShell* pShell = pNode->GetDoc().GetDocShell()->GetWrtShell();
+ if ( pShell && !pShell->IsSelection() && !pShell->IsSelFrameMode() )
+ {
+ const SwViewOption* pVOpt = pShell->GetViewOptions();
+ if ( pVOpt && pVOpt->IsOnlineSpell() )
+ {
+ const bool bOldViewLock = pShell->IsViewLocked();
+ pShell->LockView( true );
+
+ SwTextFrame* pFrame(
+ static_cast<SwTextFrame*>(pNode->getLayoutFrame(GetShell()->GetLayout())));
+ SwRect aRepaint(pFrame->AutoSpell_(*pNode, 0));
+ if (aRepaint.HasArea())
+ pShell->InvalidateWindows(aRepaint);
+
+ pShell->LockView( bOldViewLock );
+ }
+ }
+ }
+ }
+
+ return SwCursor::UpDown( bUp, nCnt,
+ &GetPtPos(), GetShell()->GetUpDownX(),
+ *GetShell()->GetLayout());
+}
+
+// if <true> than the cursor can be set to the position.
+bool SwShellCursor::IsAtValidPos( bool bPoint ) const
+{
+ if( GetShell() && ( GetShell()->IsAllProtect() ||
+ GetShell()->GetViewOptions()->IsReadonly() ||
+ ( GetShell()->Imp()->GetDrawView() &&
+ GetShell()->Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() )))
+ return true;
+
+ return SwCursor::IsAtValidPos( bPoint );
+}
+
+SwShellTableCursor::SwShellTableCursor( const SwCursorShell& rCursorSh,
+ const SwPosition& rPos )
+ : SwCursor(rPos,nullptr), SwShellCursor(rCursorSh, rPos), SwTableCursor(rPos)
+{
+}
+
+SwShellTableCursor::SwShellTableCursor( const SwCursorShell& rCursorSh,
+ const SwPosition& rMkPos, const Point& rMkPt,
+ const SwPosition& rPtPos, const Point& rPtPt )
+ : SwCursor(rPtPos,nullptr), SwShellCursor(rCursorSh, rPtPos), SwTableCursor(rPtPos)
+{
+ SetMark();
+ *GetMark() = rMkPos;
+ GetMkPos() = rMkPt;
+ GetPtPos() = rPtPt;
+}
+
+SwShellTableCursor::~SwShellTableCursor() {}
+
+void SwShellTableCursor::SetMark() { SwShellCursor::SetMark(); }
+
+SwCursor* SwShellTableCursor::Create( SwPaM* pRing ) const
+{
+ return SwShellCursor::Create( pRing );
+}
+
+short SwShellTableCursor::MaxReplaceArived()
+{
+ return SwShellCursor::MaxReplaceArived();
+}
+
+void SwShellTableCursor::SaveTableBoxContent( const SwPosition* pPos )
+{
+ SwShellCursor::SaveTableBoxContent( pPos );
+}
+
+void SwShellTableCursor::FillRects()
+{
+ // Calculate the new rectangles. If the cursor is still "parked" do nothing
+ if (m_SelectedBoxes.empty() || m_bParked || !GetPoint()->GetNodeIndex())
+ return;
+
+ bool bStart = true;
+ SwRegionRects aReg( comphelper::LibreOfficeKit::isActive()
+ ? GetShell()->getIDocumentLayoutAccess().GetCurrentLayout()->getFrameArea()
+ : GetShell()->VisArea() );
+ SwNodes& rNds = GetDoc().GetNodes();
+ SwFrame* pEndFrame = nullptr;
+ for (size_t n = 0; n < m_SelectedBoxes.size(); ++n)
+ {
+ const SwStartNode* pSttNd = m_SelectedBoxes[n]->GetSttNd();
+ const SwTableNode* pSelTableNd = pSttNd->FindTableNode();
+
+ SwNodeIndex aIdx( *pSttNd );
+ SwContentNode* pCNd = rNds.GoNextSection( &aIdx, true, false );
+
+ // table in table
+ // (see also lcl_FindTopLevelTable in unoobj2.cxx for a different
+ // version to do this)
+ const SwTableNode* pCurTableNd = pCNd ? pCNd->FindTableNode() : nullptr;
+ while ( pSelTableNd != pCurTableNd && pCurTableNd )
+ {
+ aIdx = pCurTableNd->EndOfSectionIndex();
+ pCNd = rNds.GoNextSection( &aIdx, true, false );
+ pCurTableNd = pCNd->FindTableNode();
+ }
+
+ if( !pCNd )
+ continue;
+
+ std::pair<Point, bool> const tmp(GetSttPos(), false);
+ SwFrame* pFrame = pCNd->getLayoutFrame(GetShell()->GetLayout(), nullptr, &tmp);
+ while( pFrame && !pFrame->IsCellFrame() )
+ pFrame = pFrame->GetUpper();
+
+ OSL_ENSURE( pFrame, "Node not in a table" );
+
+ while ( pFrame )
+ {
+ if( aReg.GetOrigin().Overlaps( pFrame->getFrameArea() ) )
+ {
+ aReg -= pFrame->getFrameArea();
+ if (bStart)
+ {
+ bStart = false;
+ m_aStart = SwRect(pFrame->getFrameArea().Left(), pFrame->getFrameArea().Top(), 1, pFrame->getFrameArea().Height());
+ }
+ }
+
+ pEndFrame = pFrame;
+ pFrame = pFrame->GetNextCellLeaf();
+ }
+ }
+ if (pEndFrame)
+ m_aEnd = SwRect(pEndFrame->getFrameArea().Right(), pEndFrame->getFrameArea().Top(), 1, pEndFrame->getFrameArea().Height());
+ aReg.Invert();
+ insert( begin(), aReg.begin(), aReg.end() );
+}
+
+void SwShellTableCursor::FillStartEnd(SwRect& rStart, SwRect& rEnd) const
+{
+ rStart = m_aStart;
+ rEnd = m_aEnd;
+}
+
+// Check if the SPoint is within the Table-SSelection.
+bool SwShellTableCursor::Contains( const Point& rPt ) const
+{
+ // Calculate the new rectangles. If the cursor is still "parked" do nothing
+ if (m_SelectedBoxes.empty() || m_bParked || !GetPoint()->GetNodeIndex())
+ return false;
+
+ SwNodes& rNds = GetDoc().GetNodes();
+ for (size_t n = 0; n < m_SelectedBoxes.size(); ++n)
+ {
+ SwNodeIndex aIdx( *m_SelectedBoxes[n]->GetSttNd() );
+ SwContentNode* pCNd = rNds.GoNextSection( &aIdx, true, false );
+ if( !pCNd )
+ continue;
+
+ std::pair<Point, bool> const tmp(GetPtPos(), true);
+ SwFrame* pFrame = pCNd->getLayoutFrame(GetShell()->GetLayout(), nullptr, &tmp);
+ while( pFrame && !pFrame->IsCellFrame() )
+ pFrame = pFrame->GetUpper();
+ OSL_ENSURE( pFrame, "Node not in a table" );
+ if( pFrame && pFrame->getFrameArea().Contains( rPt ) )
+ return true;
+
+ for ( SwCellFrame* pCellFrame = static_cast<SwCellFrame*>(pFrame); pCellFrame; pCellFrame = pCellFrame->GetFollowCell() )
+ {
+ if( pCellFrame->getFrameArea().Contains( rPt ) )
+ return true;
+ }
+ }
+ return false;
+}
+
+bool SwShellTableCursor::IsAtValidPos( bool bPoint ) const
+{
+ return SwShellCursor::IsAtValidPos( bPoint );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */