diff options
Diffstat (limited to 'sw/source/core/crsr')
28 files changed, 18372 insertions, 0 deletions
diff --git a/sw/source/core/crsr/BlockCursor.cxx b/sw/source/core/crsr/BlockCursor.cxx new file mode 100644 index 000000000..5c986ab86 --- /dev/null +++ b/sw/source/core/crsr/BlockCursor.cxx @@ -0,0 +1,32 @@ +/* -*- 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 000000000..28b09e0e8 --- /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 000000000..d5c44f121 --- /dev/null +++ b/sw/source/core/crsr/DateFormFieldButton.cxx @@ -0,0 +1,97 @@ +/* -*- 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 <bookmrk.hxx> +#include <vcl/floatwin.hxx> +#include <vcl/calendar.hxx> +#include <tools/date.hxx> +#include <svl/zforlist.hxx> + +namespace +{ +class SwDatePickerDialog : public FloatingWindow +{ +private: + VclPtr<Calendar> m_pCalendar; + sw::mark::DateFieldmark* m_pFieldmark; + SvNumberFormatter* m_pNumberFormatter; + + DECL_LINK(ImplSelectHdl, Calendar*, void); + +public: + SwDatePickerDialog(SwEditWin* parent, sw::mark::DateFieldmark* pFieldmark, + SvNumberFormatter* pNumberFormatter); + virtual ~SwDatePickerDialog() override; + virtual void dispose() override; +}; +} + +SwDatePickerDialog::SwDatePickerDialog(SwEditWin* parent, sw::mark::DateFieldmark* pFieldmark, + SvNumberFormatter* pNumberFormatter) + : FloatingWindow(parent, WB_BORDER | WB_SYSTEMWINDOW | WB_NOSHADOW) + , m_pCalendar(VclPtr<Calendar>::Create(this, WB_TABSTOP)) + , m_pFieldmark(pFieldmark) + , m_pNumberFormatter(pNumberFormatter) +{ + if (m_pFieldmark != nullptr) + { + std::pair<bool, double> aResult = m_pFieldmark->GetCurrentDate(); + if (aResult.first) + { + const Date& rNullDate = m_pNumberFormatter->GetNullDate(); + m_pCalendar->SetCurDate(rNullDate + sal_Int32(aResult.second)); + } + } + m_pCalendar->SetSelectHdl(LINK(this, SwDatePickerDialog, ImplSelectHdl)); + m_pCalendar->SetOutputSizePixel(m_pCalendar->CalcWindowSizePixel()); + m_pCalendar->Show(); + SetOutputSizePixel(m_pCalendar->GetSizePixel()); +} + +SwDatePickerDialog::~SwDatePickerDialog() { disposeOnce(); } + +void SwDatePickerDialog::dispose() +{ + m_pCalendar.clear(); + FloatingWindow::dispose(); +} + +IMPL_LINK(SwDatePickerDialog, ImplSelectHdl, Calendar*, pCalendar, void) +{ + if (!pCalendar->IsTravelSelect()) + { + if (m_pFieldmark != nullptr) + { + const Date& rNullDate = m_pNumberFormatter->GetNullDate(); + double dDate = pCalendar->GetFirstSelectedDate() - rNullDate; + m_pFieldmark->SetCurrentDate(dDate); + } + EndPopupMode(); + } +} + +DateFormFieldButton::DateFormFieldButton(SwEditWin* pEditWin, sw::mark::DateFieldmark& rFieldmark, + SvNumberFormatter* pNumberFormatter) + : FormFieldButton(pEditWin, rFieldmark) + , m_pNumberFormatter(pNumberFormatter) +{ +} + +DateFormFieldButton::~DateFormFieldButton() { disposeOnce(); } + +void DateFormFieldButton::InitPopup() +{ + sw::mark::DateFieldmark* pDateFieldmark = dynamic_cast<sw::mark::DateFieldmark*>(&m_rFieldmark); + m_pFieldPopup = VclPtr<SwDatePickerDialog>::Create(static_cast<SwEditWin*>(GetParent()), + pDateFieldmark, m_pNumberFormatter); +} + +/* 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 000000000..a110ac3f0 --- /dev/null +++ b/sw/source/core/crsr/DropDownFormFieldButton.cxx @@ -0,0 +1,167 @@ +/* -*- 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 <bookmrk.hxx> +#include <vcl/floatwin.hxx> +#include <vcl/InterimItemWindow.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> + +namespace +{ +class SwFieldListBox final : public InterimItemWindow +{ +private: + std::unique_ptr<weld::TreeView> m_xTreeView; + +public: + SwFieldListBox(vcl::Window* pParent) + : InterimItemWindow(pParent, "modules/swriter/ui/formdropdown.ui", "FormDropDown") + , m_xTreeView(m_xBuilder->weld_tree_view("list")) + { + } + weld::TreeView& get_widget() { return *m_xTreeView; } + virtual ~SwFieldListBox() override { disposeOnce(); } + virtual void dispose() override + { + m_xTreeView.reset(); + InterimItemWindow::dispose(); + } +}; + +/** + * 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. + */ +class SwFieldDialog : public FloatingWindow +{ +private: + VclPtr<SwFieldListBox> m_xListBox; + sw::mark::IFieldmark* m_pFieldmark; + + DECL_LINK(MyListBoxHandler, weld::TreeView&, bool); + +public: + SwFieldDialog(SwEditWin* parent, sw::mark::IFieldmark* fieldBM, long nMinListWidth); + virtual ~SwFieldDialog() override; + virtual void dispose() override; +}; +} + +SwFieldDialog::SwFieldDialog(SwEditWin* parent, sw::mark::IFieldmark* fieldBM, long nMinListWidth) + : FloatingWindow(parent, WB_BORDER | WB_SYSTEMWINDOW) + , m_xListBox(VclPtr<SwFieldListBox>::Create(this)) + , m_pFieldmark(fieldBM) +{ + weld::TreeView& rTreeView = m_xListBox->get_widget(); + + if (fieldBM != nullptr) + { + const sw::mark::IFieldmark::parameter_map_t* const pParameters = fieldBM->GetParameters(); + + OUString sListKey = ODF_FORMDROPDOWN_LISTENTRY; + sw::mark::IFieldmark::parameter_map_t::const_iterator pListEntries + = pParameters->find(sListKey); + css::uno::Sequence<OUString> vListEntries; + if (pListEntries != pParameters->end()) + { + pListEntries->second >>= vListEntries; + for (OUString const& i : std::as_const(vListEntries)) + rTreeView.append_text(i); + } + + if (!vListEntries.hasElements()) + { + rTreeView.append_text(SwResId(STR_DROP_DOWN_EMPTY_LIST)); + } + + // Select the current one + OUString sResultKey = ODF_FORMDROPDOWN_RESULT; + sw::mark::IFieldmark::parameter_map_t::const_iterator pResult + = pParameters->find(sResultKey); + if (pResult != pParameters->end()) + { + sal_Int32 nSelection = -1; + pResult->second >>= nSelection; + rTreeView.set_cursor(nSelection); + rTreeView.select(nSelection); + } + } + + auto nHeight = rTreeView.get_height_rows( + std::min<int>(Application::GetSettings().GetStyleSettings().GetListBoxMaximumLineCount(), + rTreeView.n_children())); + rTreeView.set_size_request(-1, nHeight); + Size lbSize(rTreeView.get_preferred_size()); + lbSize.AdjustWidth(4); + lbSize.AdjustHeight(4); + lbSize.setWidth(std::max(lbSize.Width(), nMinListWidth)); + m_xListBox->SetSizePixel(lbSize); + rTreeView.connect_row_activated(LINK(this, SwFieldDialog, MyListBoxHandler)); + m_xListBox->Show(); + + rTreeView.grab_focus(); + + SetSizePixel(lbSize); +} + +SwFieldDialog::~SwFieldDialog() { disposeOnce(); } + +void SwFieldDialog::dispose() +{ + m_xListBox.disposeAndClear(); + FloatingWindow::dispose(); +} + +IMPL_LINK(SwFieldDialog, MyListBoxHandler, weld::TreeView&, rBox, bool) +{ + OUString sSelection = rBox.get_selected_text(); + if (sSelection == SwResId(STR_DROP_DOWN_EMPTY_LIST)) + { + EndPopupMode(); + return true; + } + + sal_Int32 nSelection = rBox.get_selected_index(); + if (nSelection >= 0) + { + OUString sKey = ODF_FORMDROPDOWN_RESULT; + (*m_pFieldmark->GetParameters())[sKey] <<= nSelection; + m_pFieldmark->Invalidate(); + SwView& rView = static_cast<SwEditWin*>(GetParent())->GetView(); + rView.GetDocShell()->SetModified(); + } + + EndPopupMode(); + + return true; +} + +DropDownFormFieldButton::DropDownFormFieldButton(SwEditWin* pEditWin, + sw::mark::DropDownFieldmark& rFieldmark) + : FormFieldButton(pEditWin, rFieldmark) +{ +} + +DropDownFormFieldButton::~DropDownFormFieldButton() { disposeOnce(); } + +void DropDownFormFieldButton::InitPopup() +{ + m_pFieldPopup = VclPtr<SwFieldDialog>::Create(static_cast<SwEditWin*>(GetParent()), + &m_rFieldmark, GetSizePixel().Width()); +} + +/* 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 000000000..43d8ff6e0 --- /dev/null +++ b/sw/source/core/crsr/FormFieldButton.cxx @@ -0,0 +1,151 @@ +/* -*- 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 <bookmrk.hxx> +#include <vcl/floatwin.hxx> +#include <vcl/event.hxx> + +FormFieldButton::FormFieldButton(SwEditWin* pEditWin, sw::mark::Fieldmark& rFieldmark) + : MenuButton(pEditWin, WB_DIALOGCONTROL) + , m_rFieldmark(rFieldmark) +{ + assert(GetParent()); + assert(dynamic_cast<SwEditWin*>(GetParent())); +} + +FormFieldButton::~FormFieldButton() { disposeOnce(); } + +void FormFieldButton::dispose() +{ + m_pFieldPopup.disposeAndClear(); + MenuButton::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::MouseButtonUp(const MouseEvent&) +{ + assert(GetParent()); + + Point aPixPos = GetPosPixel(); + aPixPos.AdjustY(GetSizePixel().Height()); + + // sets m_pFieldPopup + InitPopup(); + + m_pFieldPopup->SetPopupModeEndHdl(LINK(this, DropDownFormFieldButton, FieldPopupModeEndHdl)); + + tools::Rectangle aRect(GetParent()->OutputToScreenPixel(aPixPos), Size(0, 0)); + m_pFieldPopup->StartPopupMode(aRect, FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus); + Invalidate(); +} + +IMPL_LINK_NOARG(FormFieldButton, FieldPopupModeEndHdl, FloatingWindow*, void) +{ + m_pFieldPopup.disposeAndClear(); + 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_pFieldPopup ? 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.getWidth() - 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 = MenuButton::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 000000000..0fc6d16da --- /dev/null +++ b/sw/source/core/crsr/annotationmark.cxx @@ -0,0 +1,97 @@ +/* -*- 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 <calbck.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("__Annotation__") ); + } + } + + AnnotationMark::~AnnotationMark() + { + } + + void AnnotationMark::InitDoc(SwDoc* const io_pDoc, + sw::mark::InsertMode const, SwPosition const*const) + { + SwTextNode *pTextNode = GetMarkEnd().nNode.GetNode().GetTextNode(); + assert(pTextNode); + SwTextField *const pTextField = pTextNode->GetFieldTextAttrAt( + GetMarkEnd().nContent.GetIndex()-1, true); + 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_pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + io_pDoc->GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoInsBookmark>(*this) ); + } + io_pDoc->getIDocumentState().SetModified(); + } + + const SwFormatField* AnnotationMark::GetAnnotationFormatField() const + { + SwDoc* pDoc = GetMarkPos().GetDoc(); + assert(pDoc != nullptr); + + const auto sName = GetName(); + SwFieldType* pType = pDoc->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/bookmrk.cxx b/sw/source/core/crsr/bookmrk.cxx new file mode 100644 index 000000000..bd7e5d48f --- /dev/null +++ b/sw/source/core/crsr/bookmrk.cxx @@ -0,0 +1,1068 @@ +/* -*- 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 <bookmrk.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 <UndoBookmark.hxx> +#include <unobookmark.hxx> +#include <xmloff/odffields.hxx> +#include <libxml/xmlwriter.h> +#include <comphelper/random.hxx> +#include <comphelper/anytostring.hxx> +#include <sal/log.hxx> +#include <svl/zforlist.hxx> +#include <edtwin.hxx> +#include <DateFormFieldButton.hxx> +#include <DropDownFormFieldButton.hxx> +#include <DocumentContentOperationsManager.hxx> +#include <comphelper/lok.hxx> +#include <view.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <wrtsh.hxx> +#include <rtl/strbuf.hxx> +#include <sfx2/lokhelper.hxx> +#include <strings.hrc> + +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.nNode.GetNodes()); + sal_uLong const nStartNode(rStartPos.nNode.GetIndex()); + sal_uLong const nEndNode(rEndPos.nNode.GetIndex()); + int nFields(0); + std::optional<SwPosition> ret; + for (sal_uLong n = nEndNode; nStartNode <= n; --n) + { + SwNode *const pNode(rNodes[n]); + if (pNode->IsTextNode()) + { + SwTextNode & rTextNode(*pNode->GetTextNode()); + sal_Int32 const nStart(n == nStartNode + ? rStartPos.nContent.GetIndex() + 1 + : 0); + sal_Int32 const nEnd(n == nEndNode + // subtract 1 to ignore the end char + ? rEndPos.nContent.GetIndex() - 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 = SwPosition(rTextNode, i - 1); +#ifndef DBG_UTIL + return *ret; +#endif + } + break; + } + } + } + else if (pNode->IsEndNode()) + { + assert(nStartNode <= pNode->StartOfSectionIndex()); + // fieldmark cannot overlap node section + n = pNode->StartOfSectionIndex(); + } + else + { + assert(pNode->IsNoTextNode()); + } + } + 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.nNode.GetNode().GetTextNode(); + if(pTextNode == nullptr && rPos.nContent.GetIndex() > 0) + { + SAL_INFO( + "sw.core", + "illegal position: " << rPos.nContent.GetIndex() + << " without proper TextNode"); + rPos.nContent.Assign(nullptr, 0); + } + else if(pTextNode != nullptr && rPos.nContent.GetIndex() > pTextNode->Len()) + { + SAL_INFO( + "sw.core", + "illegal position: " << rPos.nContent.GetIndex() + << " is beyond " << pTextNode->Len()); + rPos.nContent.Assign(pTextNode, pTextNode->Len()); + } + } + + void lcl_AssertFieldMarksSet(Fieldmark const * const pField, + const sal_Unicode aStartMark, + const sal_Unicode aEndMark) + { + if (aEndMark != CH_TXT_ATR_FORMELEMENT) + { + SwPosition const& rStart(pField->GetMarkStart()); + assert(rStart.nNode.GetNode().GetTextNode()->GetText()[rStart.nContent.GetIndex()] == aStartMark); (void) rStart; (void) aStartMark; + SwPosition const sepPos(sw::mark::FindFieldSep(*pField)); + assert(sepPos.nNode.GetNode().GetTextNode()->GetText()[sepPos.nContent.GetIndex()] == CH_TXT_ATR_FIELDSEP); (void) sepPos; + } + SwPosition const& rEnd(pField->GetMarkEnd()); + assert(rEnd.nNode.GetNode().GetTextNode()->GetText()[rEnd.nContent.GetIndex() - 1] == aEndMark); (void) rEnd; + } + + void lcl_SetFieldMarks(Fieldmark* const pField, + SwDoc* const io_pDoc, + const sal_Unicode aStartMark, + const sal_Unicode aEndMark, + SwPosition const*const pSepPos) + { + io_pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr); + OUString startChar(aStartMark); + if (aEndMark != CH_TXT_ATR_FORMELEMENT + && pField->GetMarkStart() == pField->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 = pField->GetMarkStart(); + if (aEndMark != CH_TXT_ATR_FORMELEMENT) + { + SwPaM aStartPaM(start); + io_pDoc->getIDocumentContentOperations().InsertString(aStartPaM, startChar); + start.nContent -= 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 + pField->SetMarkStartPos( start ); + SwPosition& rEnd = pField->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_pDoc->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.nContent; + } + } + assert(pSepPos == nullptr || (start < *pSepPos && *pSepPos <= rEnd)); + } + else + { + assert(pSepPos == nullptr); + } + + SwPosition& rEnd = pField->GetMarkEnd(); + if (aEndMark && startChar.getLength() == 1) + { + SwPaM aEndPaM(rEnd); + io_pDoc->getIDocumentContentOperations().InsertString(aEndPaM, OUString(aEndMark)); + ++rEnd.nContent; + } + lcl_AssertFieldMarksSet(pField, aStartMark, aEndMark); + + io_pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr); + }; + + void lcl_RemoveFieldMarks(Fieldmark const * const pField, + SwDoc* const io_pDoc, + const sal_Unicode aStartMark, + const sal_Unicode aEndMark) + { + io_pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::UI_REPLACE, nullptr); + + const SwPosition& rStart = pField->GetMarkStart(); + SwTextNode const*const pStartTextNode = rStart.nNode.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(*pField)); + io_pDoc->GetDocumentContentOperationsManager().DeleteDummyChar(rStart, aStartMark); + io_pDoc->GetDocumentContentOperationsManager().DeleteDummyChar(sepPos, CH_TXT_ATR_FIELDSEP); + } + + const SwPosition& rEnd = pField->GetMarkEnd(); + SwTextNode *const pEndTextNode = rEnd.nNode.GetNode().GetTextNode(); + assert(pEndTextNode); + const sal_Int32 nEndPos = (rEnd == rStart) + ? rEnd.nContent.GetIndex() + : rEnd.nContent.GetIndex() - 1; + assert(pEndTextNode->GetText()[nEndPos] == aEndMark); + SwPosition const aEnd(*pEndTextNode, nEndPos); + io_pDoc->GetDocumentContentOperationsManager().DeleteDummyChar(aEnd, aEndMark); + + io_pDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::UI_REPLACE, nullptr); + }; + + auto InvalidatePosition(SwPosition const& rPos) -> void + { + SwUpdateAttr const hint(rPos.nContent.GetIndex(), rPos.nContent.GetIndex(), 0); + rPos.nNode.GetNode().GetTextNode()->NotifyClients(nullptr, &hint); + } +} + +namespace sw::mark +{ + MarkBase::MarkBase(const SwPaM& aPaM, + const OUString& rName) + : m_pPos1(new SwPosition(*(aPaM.GetPoint()))) + , m_aName(rName) + { + m_pPos1->nContent.SetMark(this); + lcl_FixPosition(*m_pPos1); + if (aPaM.HasMark() && (*aPaM.GetMark() != *aPaM.GetPoint())) + { + MarkBase::SetOtherMarkPos(*(aPaM.GetMark())); + lcl_FixPosition(*m_pPos2); + } + } + + // 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) + { + std::make_unique<SwPosition>(rNewPos).swap(m_pPos1); + m_pPos1->nContent.SetMark(this); + } + + void MarkBase::SetOtherMarkPos(const SwPosition& rNewPos) + { + std::make_unique<SwPosition>(rNewPos).swap(m_pPos2); + m_pPos2->nContent.SetMark(this); + } + + OUString MarkBase::ToString( ) const + { + return "Mark: ( Name, [ Node1, Index1 ] ): ( " + m_aName + ", [ " + + OUString::number( GetMarkPos().nNode.GetIndex( ) ) + ", " + + OUString::number( GetMarkPos().nContent.GetIndex( ) ) + " ] )"; + } + + void MarkBase::dumpAsXml(xmlTextWriterPtr pWriter) const + { + xmlTextWriterStartElement(pWriter, BAD_CAST("MarkBase")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(m_aName.toUtf8().getStr())); + xmlTextWriterStartElement(pWriter, BAD_CAST("markPos")); + GetMarkPos().dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + if (IsExpanded()) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("otherMarkPos")); + GetOtherMarkPos().dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + } + xmlTextWriterEndElement(pWriter); + } + + MarkBase::~MarkBase() + { } + + OUString MarkBase::GenerateNewName(const OUString& 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::Modify( const SfxPoolItem *pOld, const SfxPoolItem *pNew ) + { + NotifyClients(pOld, pNew); + if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which())) + { // invalidate cached uno object + SetXBookmark(uno::Reference<text::XTextContent>(nullptr)); + } + } + + auto MarkBase::InvalidateFrames() -> void + { + } + + NavigatorReminder::NavigatorReminder(const SwPaM& rPaM) + : MarkBase(rPaM, MarkBase::GenerateNewName("__NavigatorReminder__")) + { } + + UnoMark::UnoMark(const SwPaM& aPaM) + : MarkBase(aPaM, MarkBase::GenerateNewName("__UnoMark__")) + { } + + DdeBookmark::DdeBookmark(const SwPaM& aPaM) + : MarkBase(aPaM, MarkBase::GenerateNewName("__DdeLink__")) + { } + + void DdeBookmark::SetRefObject(SwServerObject* pObj) + { + m_aRefObj = pObj; + } + + void DdeBookmark::DeregisterFromDoc(SwDoc* const pDoc) + { + if(m_aRefObj.is()) + pDoc->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) + , ::sfx2::Metadatable() + , m_aCode(rCode) + , m_bHidden(false) + { + m_aName = rName; + } + + void Bookmark::InitDoc(SwDoc* const io_pDoc, + sw::mark::InsertMode const, SwPosition const*const) + { + if (io_pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + io_pDoc->GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoInsBookmark>(*this)); + } + io_pDoc->getIDocumentState().SetModified(); + InvalidateFrames(); + } + + void Bookmark::DeregisterFromDoc(SwDoc* const io_pDoc) + { + DdeBookmark::DeregisterFromDoc(io_pDoc); + + if (io_pDoc->GetIDocumentUndoRedo().DoesUndo()) + { + io_pDoc->GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoDeleteBookmark>(*this)); + } + io_pDoc->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; + InvalidateFrames(); + } + } + + ::sfx2::IXmlIdRegistry& Bookmark::GetRegistry() + { + SwDoc *const pDoc( GetMarkPos().GetDoc() ); + assert(pDoc); + return pDoc->GetXmlIdRegistry(); + } + + bool Bookmark::IsInClipboard() const + { + SwDoc *const pDoc( GetMarkPos().GetDoc() ); + assert(pDoc); + return pDoc->IsClipBoard(); + } + + bool Bookmark::IsInUndo() const + { + return false; + } + + bool Bookmark::IsInContent() const + { + SwDoc *const pDoc( GetMarkPos().GetDoc() ); + assert(pDoc); + return !pDoc->IsInHeaderFooter( GetMarkPos().nNode ); + } + + uno::Reference< rdf::XMetadatable > Bookmark::MakeUnoObject() + { + SwDoc *const pDoc( GetMarkPos().GetDoc() ); + assert(pDoc); + const uno::Reference< rdf::XMetadatable> xMeta( + SwXBookmark::CreateXBookmark(*pDoc, this), uno::UNO_QUERY); + return xMeta; + } + + Fieldmark::Fieldmark(const SwPaM& rPaM) + : MarkBase(rPaM, MarkBase::GenerateNewName("__Fieldmark__")) + { + if(!IsExpanded()) + SetOtherMarkPos(GetMarkPos()); + } + + void Fieldmark::SetMarkStartPos( const SwPosition& rNewStartPos ) + { + if ( GetMarkPos( ) <= GetOtherMarkPos( ) ) + return SetMarkPos( rNewStartPos ); + else + return SetOtherMarkPos( rNewStartPos ); + } + + void Fieldmark::SetMarkEndPos( const SwPosition& rNewEndPos ) + { + if ( GetMarkPos( ) <= GetOtherMarkPos( ) ) + return SetOtherMarkPos( rNewEndPos ); + else + return SetMarkPos( rNewEndPos ); + } + + OUString Fieldmark::ToString( ) const + { + return "Fieldmark: ( Name, Type, [ Nd1, Id1 ], [ Nd2, Id2 ] ): ( " + m_aName + ", " + + m_aFieldname + ", [ " + OUString::number( GetMarkPos().nNode.GetIndex( ) ) + + ", " + OUString::number( GetMarkPos( ).nContent.GetIndex( ) ) + " ], [" + + OUString::number( GetOtherMarkPos().nNode.GetIndex( ) ) + ", " + + OUString::number( GetOtherMarkPos( ).nContent.GetIndex( ) ) + " ] ) "; + } + + 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 + { + xmlTextWriterStartElement(pWriter, BAD_CAST("Fieldmark")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldname"), BAD_CAST(m_aFieldname.toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fieldHelptext"), BAD_CAST(m_aFieldHelptext.toUtf8().getStr())); + MarkBase::dumpAsXml(pWriter); + xmlTextWriterStartElement(pWriter, BAD_CAST("parameters")); + for (auto& rParam : m_vParams) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("parameter")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(rParam.first.toUtf8().getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(comphelper::anyToString(rParam.second).toUtf8().getStr())); + xmlTextWriterEndElement(pWriter); + } + xmlTextWriterEndElement(pWriter); + xmlTextWriterEndElement(pWriter); + } + + TextFieldmark::TextFieldmark(const SwPaM& rPaM, const OUString& rName) + : Fieldmark(rPaM) + { + if ( !rName.isEmpty() ) + m_aName = rName; + } + + void TextFieldmark::InitDoc(SwDoc* const io_pDoc, + sw::mark::InsertMode const eMode, SwPosition const*const pSepPos) + { + if (eMode == sw::mark::InsertMode::New) + { + lcl_SetFieldMarks(this, io_pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND, pSepPos); + // no need to invalidate text frames here, the insertion of the + // CH_TXT_ATR already invalidates + } + else + { + lcl_AssertFieldMarksSet(this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); + } + } + + void TextFieldmark::ReleaseDoc(SwDoc* const pDoc) + { + IDocumentUndoRedo & rIDUR(pDoc->GetIDocumentUndoRedo()); + if (rIDUR.DoesUndo()) + { + rIDUR.AppendUndo(std::make_unique<SwUndoDelTextFieldmark>(*this)); + } + ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes + lcl_RemoveFieldMarks(this, pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); + } + + NonTextFieldmark::NonTextFieldmark(const SwPaM& rPaM) + : Fieldmark(rPaM) + { } + + void NonTextFieldmark::InitDoc(SwDoc* const io_pDoc, + sw::mark::InsertMode const eMode, SwPosition const*const pSepPos) + { + assert(pSepPos == nullptr); + if (eMode == sw::mark::InsertMode::New) + { + lcl_SetFieldMarks(this, io_pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT, pSepPos); + + // For some reason the end mark is moved from 1 by the Insert: + // we don't want this for checkboxes + SwPosition aNewEndPos = GetMarkEnd(); + aNewEndPos.nContent--; + SetMarkEndPos( aNewEndPos ); + } + else + { + lcl_AssertFieldMarksSet(this, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT); + } + } + + void NonTextFieldmark::ReleaseDoc(SwDoc* const pDoc) + { + IDocumentUndoRedo & rIDUR(pDoc->GetIDocumentUndoRedo()); + if (rIDUR.DoesUndo()) + { + rIDUR.AppendUndo(std::make_unique<SwUndoDelNoTextFieldmark>(*this)); + } + ::sw::UndoGuard const ug(rIDUR); // prevent SwUndoDeletes + lcl_RemoveFieldMarks(this, pDoc, + CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FORMELEMENT); + } + + + CheckboxFieldmark::CheckboxFieldmark(const SwPaM& rPaM) + : NonTextFieldmark(rPaM) + { } + + void CheckboxFieldmark::SetChecked(bool checked) + { + if ( IsChecked() != checked ) + { + (*GetParameters())[OUString(ODF_FORMCHECKBOX_RESULT)] <<= checked; + // mark document as modified + SwDoc *const pDoc( GetMarkPos().GetDoc() ); + if ( pDoc ) + pDoc->getIDocumentState().SetModified(); + } + } + + bool CheckboxFieldmark::IsChecked() const + { + bool bResult = false; + parameter_map_t::const_iterator pResult = GetParameters()->find(OUString(ODF_FORMCHECKBOX_RESULT)); + if(pResult != GetParameters()->end()) + pResult->second >>= bResult; + return bResult; + } + + FieldmarkWithDropDownButton::FieldmarkWithDropDownButton(const SwPaM& rPaM) + : NonTextFieldmark(rPaM) + , m_pButton(nullptr) + { + } + + FieldmarkWithDropDownButton::~FieldmarkWithDropDownButton() + { + m_pButton.disposeAndClear(); + } + + void FieldmarkWithDropDownButton::HideButton() + { + if(m_pButton) + m_pButton->Show(false); + } + + void FieldmarkWithDropDownButton::RemoveButton() + { + if(m_pButton) + m_pButton.disposeAndClear(); + } + + DropDownFieldmark::DropDownFieldmark(const SwPaM& rPaM) + : FieldmarkWithDropDownButton(rPaM) + { + } + + DropDownFieldmark::~DropDownFieldmark() + { + SendLOKMessage("hide"); + } + + 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(); + SendLOKMessage("show"); + } + } + + void DropDownFieldmark::HideButton() + { + SendLOKMessage("hide"); + FieldmarkWithDropDownButton::HideButton(); + } + + void DropDownFieldmark::RemoveButton() + { + SendLOKMessage("hide"); + FieldmarkWithDropDownButton::RemoveButton(); + } + + void DropDownFieldmark::SetPortionPaintArea(const SwRect& rPortionPaintArea) + { + m_aPortionPaintArea = rPortionPaintArea; + if(m_pButton) + { + m_pButton->Show(); + m_pButton->CalcPosAndSize(m_aPortionPaintArea); + SendLOKMessage("show"); + } + } + + void DropDownFieldmark::SendLOKMessage(const OString& sAction) + { + const SfxViewShell* pViewShell = SfxViewShell::Current(); + if (pViewShell && pViewShell->isLOKMobilePhone()) + { + return; + } + + if (comphelper::LibreOfficeKit::isActive()) + { + if (!m_pButton) + return; + + SwEditWin* pEditWin = dynamic_cast<SwEditWin*>(m_pButton->GetParent()); + if (!pEditWin) + return; + + OStringBuffer sPayload; + if (sAction == "show") + { + if(m_aPortionPaintArea.IsEmpty()) + return; + + sPayload = OStringLiteral("{\"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 + OUString sResultKey = ODF_FORMDROPDOWN_RESULT; + auto pSelectedItemIter = pParameters->find(sResultKey); + 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) + "\"}}"); + } + else + { + sPayload = "{\"action\": \"hide\", \"type\": \"drop-down\"}"; + } + if (sPayload.toString() != m_sLastSentLOKMsg) { + m_sLastSentLOKMsg = sPayload.toString(); + pEditWin->GetView().GetWrtShell().GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_FORM_FIELD_BUTTON, m_sLastSentLOKMsg.getStr()); + } + } + } + + DateFieldmark::DateFieldmark(const SwPaM& rPaM) + : FieldmarkWithDropDownButton(rPaM) + , m_pNumberFormatter(nullptr) + , m_pDocumentContentOperationsManager(nullptr) + { + } + + DateFieldmark::~DateFieldmark() + { + } + + void DateFieldmark::InitDoc(SwDoc* const io_pDoc, + sw::mark::InsertMode eMode, SwPosition const*const pSepPos) + { + m_pNumberFormatter = io_pDoc->GetNumberFormatter(); + m_pDocumentContentOperationsManager = &io_pDoc->GetDocumentContentOperationsManager(); + if (eMode == sw::mark::InsertMode::New) + { + lcl_SetFieldMarks(this, io_pDoc, 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* const pDoc) + { + IDocumentUndoRedo & rIDUR(pDoc->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, pDoc, CH_TXT_ATR_FIELDSTART, CH_TXT_ATR_FIELDEND); + } + + 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().nNode.GetNode().GetTextNode(); + SwPosition const sepPos(sw::mark::FindFieldSep(*this)); + const sal_Int32 nStart(sepPos.nContent.GetIndex()); + const sal_Int32 nEnd (GetMarkEnd().nContent.GetIndex()); + + 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().nNode.GetNode().GetTextNode(); + SwPosition const sepPos(sw::mark::FindFieldSep(*this)); + const sal_Int32 nStart(sepPos.nContent.GetIndex()); + const sal_Int32 nEnd (GetMarkEnd().nContent.GetIndex()); + + if(nStart + 1 < pTextNode->GetText().getLength() && nEnd <= pTextNode->GetText().getLength() && + nEnd > nStart + 2) + { + SwPaM aFieldPam(GetMarkStart().nNode, nStart + 1, + GetMarkStart().nNode, nEnd - 1); + m_pDocumentContentOperationsManager->ReplaceRange(aFieldPam, sNewContent, false); + } + else + { + SwPaM aFieldStartPam(GetMarkStart().nNode, 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) + { + 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) + { + 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 000000000..f6c277036 --- /dev/null +++ b/sw/source/core/crsr/callnk.cxx @@ -0,0 +1,247 @@ +/* -*- 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 <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> + +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()->nNode.GetNode(); + m_nNode = rNd.GetIndex(); + m_nContent = pCursor->GetPoint()->nContent.GetIndex(); + 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; + } +} + +static void lcl_notifyRow(const SwContentNode* pNode, SwCursorShell & rShell) +{ + if ( !pNode ) + return; + + SwFrame *const pMyFrame = pNode->getLayoutFrame( rShell.GetLayout() ); + if ( !pMyFrame ) + return; + + // We need to emulated a change of the row height in order + // to have the complete row redrawn + SwRowFrame *const pRow = pMyFrame->FindRowFrame(); + if ( !pRow ) + return; + + const SwTableLine* pLine = pRow->GetTabLine( ); + + if (rShell.IsTableMode() || (rShell.StartsWithTable() && rShell.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; + } + + SwFormatFrameSize aSize = pLine->GetFrameFormat()->GetFrameSize(); + pRow->ModifyNotification(nullptr, &aSize); +} + +SwCallLink::~SwCallLink() COVERITY_NOEXCEPT_FALSE +{ + 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->GetContentNode(); + if( !pCNd ) + return; + + lcl_notifyRow(pCNd, m_rShell); + + const SwDoc *pDoc=m_rShell.GetDoc(); + const SwContentNode *pNode = nullptr; + if ( pDoc && m_nNode < pDoc->GetNodes( ).Count( ) ) + { + pNode = pDoc->GetNodes()[m_nNode]->GetContentNode(); + } + lcl_notifyRow(pNode, m_rShell); + + sal_Int32 nCmp, nCurrentContent = pCurrentCursor->GetPoint()->nContent.GetIndex(); + SwNodeType nNdWhich = pCNd->GetNodeType(); + sal_uLong nCurrentNode = pCurrentCursor->GetPoint()->nNode.GetIndex(); + + // 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() ) + { + 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() ); + } +} + +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 000000000..241491917 --- /dev/null +++ b/sw/source/core/crsr/callnk.hxx @@ -0,0 +1,48 @@ +/* -*- 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/solar.h> +#include <ndtyp.hxx> + +class SwCursorShell; +class SwTextNode; +class SwRootFrame; + +class SwCallLink +{ +public: + SwCursorShell & m_rShell; + sal_uLong m_nNode; + sal_Int32 m_nContent; + SwNodeType m_nNodeType; + long m_nLeftFramePos; + bool m_bHasSelection; + + explicit SwCallLink( SwCursorShell & rSh ); + ~SwCallLink() COVERITY_NOEXCEPT_FALSE; + + static long getLayoutFrame( const SwRootFrame*, SwTextNode const & rNd, sal_Int32 nCntPos, bool bCalcFrame ); +}; + +#endif // INCLUDED_SW_SOURCE_CORE_CRSR_CALLNK_HXX + +/* 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 000000000..2311cf8d9 --- /dev/null +++ b/sw/source/core/crsr/crbm.cxx @@ -0,0 +1,318 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <crsrsh.hxx> +#include <ndtxt.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <IMark.hxx> +#include <swcrsr.hxx> +#include <IDocumentMarkAccess.hxx> +#include <IDocumentSettingAccess.hxx> + +using namespace std; + +namespace +{ + struct CursorStateHelper + { + explicit CursorStateHelper(SwCursorShell const & rShell) + : m_pCursor(rShell.GetSwCursor()) + , 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.IsHideRedlines()) + { + return false; + } + SwTextNode const& rNode(*rMark.GetMarkPos().nNode.GetNode().GetTextNode()); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + rNode.getLayoutFrame(&rLayout))); + if (!pFrame) + { + return true; + } + if (rMark.IsExpanded()) + { + SwTextFrame const*const pOtherFrame(static_cast<SwTextFrame const*>( + rMark.GetOtherMarkPos().nNode.GetNode().GetTextNode()->getLayoutFrame(&rLayout))); + return pFrame == pOtherFrame + && pFrame->MapModelToViewPos(rMark.GetMarkPos()) + == pFrame->MapModelToViewPos(rMark.GetOtherMarkPos()); + } + else + { + if (rMark.GetMarkPos().nContent.GetIndex() == rNode.Len()) + { // at end of node: never deleted (except if node deleted) + return rNode.GetRedlineMergeFlag() == SwNode::Merge::Hidden; + } + else + { // check character following mark pos + return pFrame->MapModelToViewPos(rMark.GetMarkPos()) + == pFrame->MapModelToView(&rNode, rMark.GetMarkPos().nContent.GetIndex() + 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()->GetPoint()); + return getIDocumentMarkAccess()->getFieldmarkFor(pos); +} + +::sw::mark::IFieldmark* SwCursorShell::GetFieldmarkAfter() +{ + SwPosition pos(*GetCursor()->GetPoint()); + return getIDocumentMarkAccess()->getFieldmarkAfter(pos); +} + +::sw::mark::IFieldmark* SwCursorShell::GetFieldmarkBefore() +{ + SwPosition pos(*GetCursor()->GetPoint()); + return getIDocumentMarkAccess()->getFieldmarkBefore(pos); +} + +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()->nContent; + --aCursorSt.m_pCursor->GetMark()->nContent; + + 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 000000000..9fbba6de0 --- /dev/null +++ b/sw/source/core/crsr/crossrefbookmark.cxx @@ -0,0 +1,93 @@ +/* -*- 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> + +namespace +{ + const char CrossRefNumItemBookmark_NamePrefix[] = "__RefNumPara__"; +} + +namespace sw::mark +{ + CrossRefBookmark::CrossRefBookmark(const SwPaM& rPaM, + const vcl::KeyCode& rCode, + const OUString& rName, + const OUString& 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_pPos2); + } + + void CrossRefBookmark::SetMarkPos(const SwPosition& rNewPos) + { + assert(rNewPos.nNode.GetNode().GetTextNode() && + "<sw::mark::CrossRefBookmark::SetMarkPos(..)>" + " - new bookmark position for cross-reference bookmark doesn't mark text node"); + assert(rNewPos.nContent.GetIndex() == 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, IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix()+"_Toc") + { } + + bool CrossRefHeadingBookmark::IsLegalName(const OUString& rName) + { + return rName.match(IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix()); + } + + CrossRefNumItemBookmark::CrossRefNumItemBookmark(const SwPaM& rPaM, + const vcl::KeyCode& rCode, + const OUString& rName) + : CrossRefBookmark(rPaM, rCode, rName, CrossRefNumItemBookmark_NamePrefix) + { } + + bool CrossRefNumItemBookmark::IsLegalName(const OUString& rName) + { + return rName.match(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 000000000..7a0c3b881 --- /dev/null +++ b/sw/source/core/crsr/crsrsh.cxx @@ -0,0 +1,3814 @@ +/* -*- 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/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 <IGrammarContact.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 <boost/property_tree/json_parser.hpp> + +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 ) +{ + const SwPosition *pStt = pCurrentCursor->Start(), + *pEnd = pCurrentCursor->GetPoint() == pStt ? pCurrentCursor->GetMark() : pCurrentCursor->GetPoint(); + + SwPaM *pTmpDel = nullptr, + *pTmp = pCurrentCursor->GetNext(); + + // Search the complete ring + while( pTmp != pCurrentCursor ) + { + const SwPosition *pTmpStt = pTmp->Start(), + *pTmpEnd = pTmp->GetPoint() == pTmpStt ? + pTmp->GetMark() : pTmp->GetPoint(); + 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()); + + // 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()). + */ +SwPaM & SwCursorShell::CreateNewShellCursor() +{ + if (HasSelection()) + { + (void) CreateCursor(); // n.b. returns old cursor + } + return *GetCursor(); +} + +/** + * Return the current shell cursor + * @return - returns current `SwPaM` shell cursor + */ +SwPaM & SwCursorShell::GetCurrentShellCursor() +{ + return *GetCursor(); +} + +/** + * Return pointer to the current shell cursor + * @return - returns pointer to current `SwPaM` shell cursor + */ +SwPaM* SwCursorShell::GetCursor( bool bMakeTableCursor ) const +{ + if( m_pTableCursor ) + { + if( bMakeTableCursor && m_pTableCursor->IsCursorMovedUpdate() ) + { + //don't re-create 'parked' cursors + const SwContentNode* pCNd; + if( m_pTableCursor->GetPoint()->nNode.GetIndex() && + m_pTableCursor->GetMark()->nNode.GetIndex() ) + { + pCNd = m_pTableCursor->GetContentNode(); + if( pCNd && pCNd->getLayoutFrame( GetLayout() ) ) + { + pCNd = m_pTableCursor->GetContentNode(false); + 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()->nNode.GetNode(); + m_nCurrentNode = rNd.GetIndex(); + m_nCurrentContent = m_pCurrentCursor->GetPoint()->nContent.GetIndex(); + 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, const bool DoSetPosX ) +{ + comphelper::FlagRestorationGuard g(mbSelectAll, StartsWithTable() && ExtendedSelectedAll()); + bool bVis = m_bSVCursorVis; + + sal_uInt16 eFlags = SwCursorShell::CHKRANGE; + if ( !DoSetPosX ) + eFlags |= SwCursorShell::UPDOWN; + + + // Idle-formatting? + if( bIdleEnd && Imp()->GetRegion() ) + { + 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; + } + + 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, true ); + --m_nCursorMove; +#ifdef DBG_UTIL + if( !m_nCursorMove ) + m_bInCMvVisportChgd = false; +#endif +} + +bool SwCursorShell::LeftRight( bool bLeft, sal_uInt16 nCnt, sal_uInt16 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()->nNode.GetNode().IsTextNode() + && static_cast<SwTextFrame const*>( + pShellCursor->GetPoint()->nNode.GetNode().GetTextNode()->getLayoutFrame(GetLayout()) + )->MapModelToViewPos(*pShellCursor->GetPoint()) == TextFrameIndex(0) + && !pShellCursor->IsInFrontOfLabel() + && !pShellCursor->HasMark() + && nullptr != (pTextNd = sw::GetParaPropsNode(*GetLayout(), pShellCursor->GetPoint()->nNode)) + && 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()); + 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) + { + 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()->nNode); + + if ( pTextNd ) + { + 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 ) +{ + SET_CURR_SHELL( 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( static_cast<sal_uInt16>(eUpdateMode) ); + } + } + return bRet; +} + +bool SwCursorShell::LRMargin( bool bLeft, bool bAPI) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + SET_CURR_SHELL( 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_()->GetNode().GetTextNode(); + assert(sw::GetParaPropsNode(*GetLayout(), GetCursor_()->GetPoint()->nNode) == 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; +} + +void SwCursorShell::ExtendedSelectAll(bool bFootnotes) +{ + SwNodes& rNodes = GetDoc()->GetNodes(); + SwPosition* pPos = m_pCurrentCursor->GetPoint(); + pPos->nNode = bFootnotes ? rNodes.GetEndOfPostIts() : rNodes.GetEndOfAutotext(); + pPos->nContent.Assign( rNodes.GoNext( &pPos->nNode ), 0 ); + pPos = m_pCurrentCursor->GetMark(); + pPos->nNode = rNodes.GetEndOfContent(); + SwContentNode* pCNd = SwNodes::GoPrevious( &pPos->nNode ); + pPos->nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 ); +} + +bool SwCursorShell::ExtendedSelectedAll() +{ + SwNodes& rNodes = GetDoc()->GetNodes(); + SwNodeIndex nNode = rNodes.GetEndOfAutotext(); + SwContentNode* pStart = rNodes.GoNext(&nNode); + + nNode = rNodes.GetEndOfContent(); + SwContentNode* pEnd = SwNodes::GoPrevious(&nNode); + + if (!pStart || !pEnd) + return false; + + SwPosition aStart(*pStart, 0); + SwPosition aEnd(*pEnd, pEnd->Len()); + SwShellCursor* pShellCursor = getShellCursor(false); + return aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End(); +} + +bool SwCursorShell::StartsWithTable() +{ + SwNodes& rNodes = GetDoc()->GetNodes(); + SwNodeIndex nNode(rNodes.GetEndOfExtras()); + SwContentNode* pContentNode = rNodes.GoNext(&nNode); + return pContentNode->FindTableNode(); +} + +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 + SET_CURR_SHELL( this ); + + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + Point& rPt = m_pCurrentCursor->GetPtPos(); + std::pair<Point, bool> tmp(rPt, false); + SwContentFrame * pFrame = m_pCurrentCursor->GetContentNode()-> + 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->GetContentNode(); + 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->GetContentNode(); + 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( const SwNodeIndex& rIdx, Point& rPt ) +{ + SwFrame* pFrame = nullptr; + SwContentNode* pCNd = rIdx.GetNode().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()->nNode, aPt ); + if( pFrame && pbInHeader ) + *pbInHeader = pFrame->IsHeaderFrame(); + return nullptr != pFrame; +} + +int SwCursorShell::SetCursor( const Point &rLPt, bool bOnlyText, bool bBlock ) +{ + SET_CURR_SHELL( 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()->nNode); + + 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.nNode, aPt ); + if( IsTableMode() && !pFrame && aPos.nNode.GetNode().StartOfSectionNode() == + pCursor->GetPoint()->nNode.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().IsInside( rCurrentCursorPt )) + return bRet; + } + else if( aPos.nNode.GetNode().IsContentNode() ) + { + // in the same frame? + std::pair<Point, bool> tmp(m_aCharRect.Pos(), false); + SwFrame* pOld = static_cast<SwContentNode&>(aPos.nNode.GetNode()).getLayoutFrame( + GetLayout(), nullptr, &tmp); + tmp.first = aPt; + SwFrame* pNew = static_cast<SwContentNode&>(aPos.nNode.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.nNode, pCursor->GetMark()->nNode, true ) + || ( pFrame && !pFrame->getFrameArea().IsInside( 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 ) +{ + SET_CURR_SHELL( this ); + + // check if the SPoint is in a table selection + if( m_pTableCursor ) + return m_pTableCursor->IsInside( 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 SwPaM *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()->nNode == m_pCurrentCursor->GetMark()->nNode) + { + return true; + } + if (GetLayout()->IsHideRedlines()) + { + SwContentFrame const*const pFrame(GetCurrFrame(false)); + auto const n(m_pCurrentCursor->GetMark()->nNode.GetIndex()); + return FrameContainsNode(*pFrame, n); + } + return false; +} + +bool SwCursorShell::IsSttPara() const +{ + if (GetLayout()->IsHideRedlines()) + { + SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->nNode.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()->nContent == 0; +} + +bool SwCursorShell::IsEndPara() const +{ + if (GetLayout()->IsHideRedlines()) + { + SwTextNode const*const pNode(m_pCurrentCursor->GetPoint()->nNode.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()->nContent == m_pCurrentCursor->GetContentNode()->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()->nNode); +} + +bool SwCursorShell::IsCursorInFootnote() const +{ + SwStartNodeType aStartNodeType = m_pCurrentCursor->GetNode().StartOfSectionNode()->GetStartNodeType(); + return aStartNodeType == SwStartNodeType::SwFootnoteStartNode; +} + +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 ) +{ + SET_CURR_SHELL( 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 ) +{ + SET_CURR_SHELL( 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() +{ + SET_CURR_SHELL(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 ) +{ + SET_CURR_SHELL( 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() +{ + SET_CURR_SHELL( 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(pFrame->getFrameArea().Left()); + aBuf.append(", "); + aBuf.append(pFrame->getFrameArea().Top()); + aBuf.append(", "); + aBuf.append(pFrame->getFrameArea().Width()); + aBuf.append(", "); + aBuf.append(pFrame->getFrameArea().Height()); + aBuf.append("; "); + } + 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; + + SET_CURR_SHELL( 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; + + SET_CURR_SHELL( 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; +} + +bool SwCursorShell::GoNextPrevCursorSetSearchLabel(const bool bNext) +{ + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + + if( !m_pCurrentCursor->IsMultiSelection() ) + { + if( !m_pCurrentCursor->HasMark() ) + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + return false; + } + + return bNext ? GoNextCursor() : GoPrevCursor(); +} + +void SwCursorShell::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect) +{ + comphelper::FlagRestorationGuard g(mbSelectAll, StartsWithTable() && ExtendedSelectedAll()); + SET_CURR_SHELL( 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.IsOver( 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 ) +{ + SET_CURR_SHELL( 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 move somewhere else (e.g. when + deleting a border). The new position is calculated from its current position + in the layout. +*/ +void SwCursorShell::UpdateCursorPos() +{ + SET_CURR_SHELL( this ); + ++mnStartAction; + SwShellCursor* pShellCursor = getShellCursor( true ); + Size aOldSz( GetDocSize() ); + + if( isInHiddenTextFrame(pShellCursor) ) + { + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_bSetInReadOnly = IsReadOnlyAvailable(); + GetLayout()->GetModelPositionForViewPoint( pShellCursor->GetPoint(), pShellCursor->GetPtPos(), + &aTmpState ); + pShellCursor->DeleteMark(); + } + IGrammarContact *pGrammarContact = GetDoc() ? GetDoc()->getGrammarContact() : nullptr; + if( pGrammarContact ) + pGrammarContact->updateCursorPosition( *m_pCurrentCursor->GetPoint() ); + --mnStartAction; + if( aOldSz != GetDocSize() ) + SizeChgNotify(); +} + +// #i65475# - if Point/Mark in hidden sections, move them out +static bool lcl_CheckHiddenSection( SwNodeIndex& rIdx ) +{ + bool bOk = true; + const SwSectionNode* pSectNd = rIdx.GetNode().FindSectionNode(); + if( pSectNd && pSectNd->GetSection().IsHiddenFlag() ) + { + SwNodeIndex aTmp( *pSectNd ); + const SwNode* pFrameNd = + rIdx.GetNodes().FindPrvNxtFrameNode( aTmp, pSectNd->EndOfSectionNode() ); + bOk = pFrameNd != nullptr; + SAL_WARN_IF(!bOk, "sw.core", "found no Node with Frames"); + rIdx = aTmp; + } + return bOk; +} + +/// Try to set the cursor to the next visible content node. +static void lcl_CheckHiddenPara( SwPosition& rPos ) +{ + SwNodeIndex aTmp( rPos.nNode ); + 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 = SwPosition( aTmp, SwIndex( pTextNd, 0 ) ); +} + +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(); + } +}; + +} + +void SwCursorShell::UpdateCursor( sal_uInt16 eFlags, bool bIdleEnd ) +{ + SET_CURR_SHELL( this ); + ClearUpCursors(); + + if (ActionPend()) + { + if ( eFlags & SwCursorShell::READONLY ) + m_bIgnoreReadonly = true; + return; // if not then no update + } + + SwNotifyAccAboutInvalidTextSelections aInvalidateTextSelections( *this ); + + 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 && + mxDoc->IsIdxInTable( pTstCursor->GetPoint()->nNode ) && + ( m_pTableCursor || + pTstCursor->GetNode().StartOfSectionNode() != + pTstCursor->GetNode( false ).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->nNode ); + lcl_CheckHiddenSection( pITmpCursor->GetMark()->nNode ); + + // 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->nNode.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->GetContentNode( false )-> + 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 (Imp()->IsAccessible()) + Imp()->InvalidateAccessibleCursorPosition( pTableFrame ); + 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->GetNode().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()->nNode.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()->nNode.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->GetContentNode()->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 ) + { + do + { + CalcLayout(); + std::pair<Point, bool> const tmp(pShellCursor->GetPtPos(), false); + pFrame = pShellCursor->GetContentNode()->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 (Imp()->IsAccessible()) + Imp()->InvalidateAccessibleCursorPosition( pFrame ); + + // 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() +{ + SwWrtShell* pShell = GetDoc()->GetDocShell()->GetWrtShell(); + if (!pShell) + return; + + SwFrame* pCurrentFrame = GetCurrFrame(); + SelectionType eType = pShell->GetSelectionType(); + + boost::property_tree::ptree aRootTree; + + if (pCurrentFrame && (eType & SelectionType::Table) && pCurrentFrame->IsInTab()) + { + const SwRect& rPageRect = pShell->GetAnyCurRect(CurRectType::Page, nullptr); + + boost::property_tree::ptree aTableColumns; + { + SwTabCols aTabCols; + pShell->GetTabCols(aTabCols); + + const int nColumnOffset = aTabCols.GetLeftMin() + rPageRect.Left(); + + aTableColumns.put("left", aTabCols.GetLeft()); + aTableColumns.put("right", aTabCols.GetRight()); + aTableColumns.put("tableOffset", nColumnOffset); + + boost::property_tree::ptree aEntries; + for (size_t i = 0; i < aTabCols.Count(); ++i) + { + auto const & rEntry = aTabCols.GetEntry(i); + boost::property_tree::ptree aTableColumnEntry; + aTableColumnEntry.put("position", rEntry.nPos); + aTableColumnEntry.put("min", rEntry.nMin); + aTableColumnEntry.put("max", rEntry.nMax); + aTableColumnEntry.put("hidden", rEntry.bHidden); + aEntries.push_back(std::make_pair("", aTableColumnEntry)); + } + aTableColumns.push_back(std::make_pair("entries", aEntries)); + } + + boost::property_tree::ptree aTableRows; + { + SwTabCols aTabRows; + pShell->GetTabRows(aTabRows); + + const int nRowOffset = aTabRows.GetLeftMin() + rPageRect.Top(); + + aTableRows.put("left", aTabRows.GetLeft()); + aTableRows.put("right", aTabRows.GetRight()); + aTableRows.put("tableOffset", nRowOffset); + + boost::property_tree::ptree aEntries; + for (size_t i = 0; i < aTabRows.Count(); ++i) + { + auto const & rEntry = aTabRows.GetEntry(i); + boost::property_tree::ptree aTableRowEntry; + aTableRowEntry.put("position", rEntry.nPos); + aTableRowEntry.put("min", rEntry.nMin); + aTableRowEntry.put("max", rEntry.nMax); + aTableRowEntry.put("hidden", rEntry.bHidden); + aEntries.push_back(std::make_pair("", aTableRowEntry)); + } + aTableRows.push_back(std::make_pair("entries", aEntries)); + } + + aRootTree.add_child("columns", aTableColumns); + aRootTree.add_child("rows", aTableRows); + } + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aRootTree); + GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TABLE_SELECTED, aStream.str().c_str()); +} + +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.GetContentNode()->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 ) ) + { + 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) +{ + SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + + // 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) + { + SwCursorSaveState aSaveState( *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 ) ) + { + 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()->nNode, + m_pCurrentCursor->GetPoint()->nNode, 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() ) + { + SET_CURR_SHELL( 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; + + SET_CURR_SHELL( 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 ) + { + m_bSVCursorVis = true; + m_pCurrentCursor->SetShowTextInputFieldOverlay( true ); + + if (comphelper::LibreOfficeKit::isActive()) + { + OString aPayload = OString::boolean(m_bSVCursorVis); + GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload.getStr()); + SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); + } + + UpdateCursor(); + } +} + +void SwCursorShell::HideCursor() +{ + if( !m_bBasicHideCursor ) + { + m_bSVCursorVis = false; + // possibly reverse selected areas!! + SET_CURR_SHELL( this ); + m_pCurrentCursor->SetShowTextInputFieldOverlay( false ); + m_pVisibleCursor->Hide(); + + if (comphelper::LibreOfficeKit::isActive()) + { + OString aPayload = OString::boolean(m_bSVCursorVis); + GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload.getStr()); + SfxLokHelper::notifyOtherViews(GetSfxViewShell(), LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", aPayload); + } + } +} + +void SwCursorShell::ShellLoseFocus() +{ + if( !m_bBasicHideCursor ) + HideCursors(); + m_bHasFocus = false; +} + +void SwCursorShell::ShellGetFocus() +{ + m_bHasFocus = true; + if( !m_bBasicHideCursor && VisArea().Width() ) + { + UpdateCursor( static_cast<sal_uInt16>( SwCursorShell::CHKRANGE ) ); + ShowCursors( m_bSVCursorVis ); + } +} + +/** Get current frame in which the cursor is positioned. */ +SwContentFrame *SwCursorShell::GetCurrFrame( const bool bCalcFrame ) const +{ + SET_CURR_SHELL( const_cast<SwCursorShell*>(this) ); + SwContentFrame *pRet = nullptr; + SwContentNode *pNd = m_pCurrentCursor->GetContentNode(); + 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::Modify( const SfxPoolItem* pOld, const SfxPoolItem* pNew ) +{ + const sal_uInt16 nWhich = pOld ? + pOld->Which() : + pNew ? + pNew->Which() : + sal::static_int_cast<sal_uInt16>(RES_MSG_BEGIN); + + if( m_bCallChgLnk && + ( nWhich < RES_MSG_BEGIN || nWhich >= RES_MSG_END || + 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( m_aGrfArrivedLnk.IsSet() && + ( RES_GRAPHIC_ARRIVED == nWhich || RES_GRAPHIC_SWAPIN == nWhich )) + m_aGrfArrivedLnk.Call( *this ); +} + +/** 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()->IsHideRedlines()) + { + SwContentFrame const*const pFrame(GetCurrFrame(false)); + if (FrameContainsNode(*pFrame, m_pCurrentCursor->GetMark()->nNode.GetIndex())) + { + OUStringBuffer buf; + SwPosition const*const pStart(m_pCurrentCursor->Start()); + SwPosition const*const pEnd(m_pCurrentCursor->End()); + for (sal_uLong i = pStart->nNode.GetIndex(); i <= pEnd->nNode.GetIndex(); ++i) + { + SwNode const& rNode(*pStart->nNode.GetNodes()[i]); + assert(!rNode.IsEndNode()); + if (rNode.IsStartNode()) + { + i = rNode.EndOfSectionIndex(); + } + else if (rNode.IsTextNode()) + { + sal_Int32 const nStart(i == pStart->nNode.GetIndex() + ? pStart->nContent.GetIndex() + : 0); + sal_Int32 const nEnd(i == pEnd->nNode.GetIndex() + ? pEnd->nContent.GetIndex() + : 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()->nNode.GetIndex() == + m_pCurrentCursor->GetMark()->nNode.GetIndex() ) + { + SwTextNode* pTextNd = m_pCurrentCursor->GetNode().GetTextNode(); + if( pTextNd ) + { + const sal_Int32 nStt = m_pCurrentCursor->Start()->nContent.GetIndex(); + aText = pTextNd->GetExpandText(GetLayout(), nStt, + m_pCurrentCursor->End()->nContent.GetIndex() - 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, 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->nNode.GetNode().GetTextNode(); + if( !pTextNd ) + return 0; + + const sal_Int32 nPos = pPos->nContent.GetIndex(); + 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->nNode.GetNode().GetTextNode(); + assert(pTextNd); + + sal_Int32 nPos = pPos->nContent.GetIndex(); + 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->nContent = 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 ) +{ + SET_CURR_SHELL( 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.nNode.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() ); +} + +/** 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()->nContent.GetIndex() ) + 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()->nNode; +} + +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()->nNode && + pCNd->Len() == m_pCurrentCursor->GetPoint()->nContent.GetIndex(); +} + +/** 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 ) +{ + const SwPosition *pStt = pDelRg->Start(), + *pEnd = pDelRg->GetPoint() == pStt ? pDelRg->GetMark() : pDelRg->GetPoint(); + + SwPaM *pTmpDel = nullptr, *pTmp = *ppDelRing; + + // search over the whole ring + bool bGoNext; + do { + + if (!pTmp) + break; + + const SwPosition *pTmpStt = pTmp->Start(), + *pTmpEnd = pTmp->GetPoint() == pTmpStt ? + pTmp->GetMark() : pTmp->GetPoint(); + // 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()->nContent.Assign(nullptr, 0); + pTmpDel->GetPoint()->nNode = 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 SwNodeIndex &rIdx ) +{ + SwNode *pNode = &rIdx.GetNode(); + + // create a new PaM + std::unique_ptr<SwPaM> pNew( new SwPaM( *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) + pNew->GetPoint()->nNode = *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) + pNew->GetPoint()->nNode = *pNode->EndOfSectionNode()->StartOfSectionNode(); + } + else + pNew->GetPoint()->nNode = *pNode->StartOfSectionNode(); + pNew->SetMark(); + pNew->GetPoint()->nNode = *pNode->EndOfSectionNode(); + + // take care of all shells + for(SwViewShell& rTmp : GetRingContainer()) + { + if( dynamic_cast<const SwCursorShell *>(&rTmp) != nullptr) + { + SwCursorShell* pSh = static_cast<SwCursorShell*>(&rTmp); + if (pSh->m_pStackCursor) + pSh->ParkPams(pNew.get(), &pSh->m_pStackCursor); + + pSh->ParkPams( pNew.get(), &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()->nNode.GetNode().FindTableNode(); + if ( pTableNd ) + { + pTCursor->GetPoint()->nContent.Assign(nullptr, 0); + pTCursor->GetPoint()->nNode = 0; + pTCursor->DeleteMark(); + pSh->m_pCurrentCursor->GetPoint()->nNode = *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 ) + , SwModify() + , 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_sMarkedListId() + , m_nMarkedListLevel( 0 ) + , m_oldColFrame(nullptr) +{ + SET_CURR_SHELL( this ); + // only keep the position of the current cursor of the copy shell + m_pCurrentCursor = new SwShellCursor( *this, *(rShell.m_pCurrentCursor->GetPoint()) ); + m_pCurrentCursor->GetContentNode()->Add( this ); + + m_bAllProtect = m_bVisPortChgd = m_bChgCallFlag = m_bInCMvVisportChgd = + m_bGCAttr = m_bIgnoreReadonly = m_bSelTableCells = m_bBasicHideCursor = + m_bOverwriteCursor = false; + 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 ) + , SwModify() + , 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_sMarkedListId() + , m_nMarkedListLevel( 0 ) + , m_oldColFrame(nullptr) +{ + SET_CURR_SHELL( 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, SwIndex( 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_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()->nNode.GetIndex() + 10 < + pPam->End()->nNode.GetIndex(); +} + +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 ); + 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 + SwNodeIndex& rNdIdx = m_pCurrentCursor->GetPoint()->nNode; + sal_uLong nNdIdx = rNdIdx.GetIndex(); // keep backup + SwNodes& rNds = mxDoc->GetNodes(); + SwContentNode* pCNd = rNdIdx.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()->nNode = rNds.GetEndOfContent(); + aPam.GetPoint()->nNode = *pCNd->EndOfSectionNode(); + + bool bFirst = false; + if( nullptr == (pCNd = ::GetNode( aPam, bFirst, fnMoveForward ))) + { + aPam.GetMark()->nNode = *rNds.GetEndOfPostIts().StartOfSectionNode(); + pCNd = ::GetNode( aPam, bFirst, fnMoveBackward ); + } + + if( !pCNd ) // should *never* happen + { + rNdIdx = nNdIdx; // back to old node + return false; + } + *m_pCurrentCursor->GetPoint() = *aPam.GetPoint(); + } + else if( bOnlyText && pCNd && pCNd->IsNoTextNode() ) + { + // set to beginning of document + rNdIdx = mxDoc->GetNodes().GetEndOfExtras(); + m_pCurrentCursor->GetPoint()->nContent.Assign( mxDoc->GetNodes().GoNext( + &rNdIdx ), 0 ); + nNdIdx = rNdIdx.GetIndex(); + } + + bool bOk = true; + + // #i9059# cursor may not stand in protected cells + // (unless cursor in protected areas is OK.) + const SwTableNode* pTableNode = rNdIdx.GetNode().FindTableNode(); + if( !IsReadOnlyAvailable() && + pTableNode != nullptr && rNdIdx.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( rNdIdx.GetNode(), 0 ); + while( aPam.GetNode().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.GetNode().IsProtect() ) + { + SwPaM aTmpPaM( rNdIdx.GetNode(), 0 ); + aPam = aTmpPaM; + while( aPam.GetNode().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.GetNode().IsProtect() ) + { + *m_pCurrentCursor->GetPoint() = *aPam.GetPoint(); + } + } + + // in a protected frame + const SwSectionNode* pSectNd = rNdIdx.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( &rNdIdx, + true, !IsReadOnlyAvailable() ); + else + pCNd = SwNodes::GoPrevSection( &rNdIdx, + 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 && rNdIdx.GetIndex() < 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; + rNdIdx = nNdIdx; + } + } + } + if( bOk ) + { + pCNd = rNdIdx.GetNode().GetContentNode(); + const sal_Int32 nContent = rNdIdx.GetIndex() < nNdIdx ? pCNd->Len() : 0; + m_pCurrentCursor->GetPoint()->nContent.Assign( pCNd, nContent ); + } + else + { + pCNd = rNdIdx.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() const +{ + 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 ) + { + bRet = m_pTableCursor->HasReadOnlyBoxSel() + || m_pTableCursor->HasReadonlySel( GetViewOptions()->IsFormView() ); + } + else + { + for(const SwPaM& rCursor : m_pCurrentCursor->GetRingContainer()) + { + if( rCursor.HasReadonlySel( GetViewOptions()->IsFormView() ) ) + { + bRet = true; + break; + } + } + } + } + return bRet; +} + +bool SwCursorShell::IsSelFullPara() const +{ + bool bRet = false; + + if( m_pCurrentCursor->GetPoint()->nNode.GetIndex() == + m_pCurrentCursor->GetMark()->nNode.GetIndex() && !m_pCurrentCursor->IsMultiSelection() ) + { + sal_Int32 nStt = m_pCurrentCursor->GetPoint()->nContent.GetIndex(); + sal_Int32 nEnd = m_pCurrentCursor->GetMark()->nContent.GetIndex(); + if( nStt > nEnd ) + { + sal_Int32 nTmp = nStt; + nStt = nEnd; + nEnd = nTmp; + } + const SwContentNode* pCNd = m_pCurrentCursor->GetContentNode(); + 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, the hidden range +/// is selected. +bool SwCursorShell::SelectHiddenRange() +{ + bool bRet = false; + if ( !GetViewOptions()->IsShowHiddenChar() && !m_pCurrentCursor->HasMark() ) + { + SwPosition& rPt = *m_pCurrentCursor->GetPoint(); + const SwTextNode* pNode = rPt.nNode.GetNode().GetTextNode(); + if ( pNode ) + { + const sal_Int32 nPos = rPt.nContent.GetIndex(); + + // check if nPos is in hidden range + sal_Int32 nHiddenStart; + sal_Int32 nHiddenEnd; + SwScriptInfo::GetBoundsOfHiddenRange( *pNode, nPos, nHiddenStart, nHiddenEnd ); + if ( COMPLETE_STRING != nHiddenStart ) + { + // make selection: + m_pCurrentCursor->SetMark(); + m_pCurrentCursor->GetMark()->nContent = nHiddenEnd; + bRet = true; + } + } + } + + return bRet; +} + +sal_uLong 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_uLong nRet = m_pCurrentCursor->Find_Text(rSearchOpt, bSearchInNotes, eStart, eEnd, + bCancel, eRng, bReplace, GetLayout()); + if( nRet || bCancel ) + UpdateCursor(); + return nRet; +} + +sal_uLong 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_uLong nRet = m_pCurrentCursor->FindFormat(rFormatColl, eStart, eEnd, bCancel, eRng, + pReplFormat ); + if( nRet ) + UpdateCursor(); + return nRet; +} + +sal_uLong 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_uLong 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(); + SwPaM* 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. +*/ +bool sw_PosOk(const SwPosition & aPos) +{ + return nullptr != aPos.nNode.GetNode().GetContentNode() && + aPos.nContent.GetIdxReg(); +} + +/** + 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( !sw_PosOk( *pStartCursor->GetPoint() ) ) + { + SwNodes & aNodes = GetDoc()->GetNodes(); + const SwNode* pStart = lcl_NodeContext( pStartCursor->GetPoint()->nNode.GetNode() ); + SwNodeIndex aIdx( pStartCursor->GetPoint()->nNode ); + SwNode * pNode = SwNodes::GoPrevious(&aIdx); + if( pNode == nullptr || lcl_NodeContext( *pNode ) != pStart ) + 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 +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwCursorShell")); + + SwViewShell::dumpAsXml(pWriter); + + xmlTextWriterStartElement(pWriter, BAD_CAST("m_pCurrentCursor")); + for (const SwPaM& rPaM : m_pCurrentCursor->GetRingContainer()) + rPaM.dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + + 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 ); + } + } + } + + 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 + SwIndex aIndex( &rNode, nBegin ); + SwPosition aStartPos( rNode, aIndex ); + + // create SwPosition for nEndIndex + SwPosition aEndPos( aStartPos ); + aEndPos.nContent = nBegin + nLen; + + const uno::Reference<text::XTextRange> 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.nNode.GetNode().GetTextNode(); + if ( pNode && !pNode->IsInProtectSect() ) + { + const SwWrongList *pSmartTagList = pNode->GetSmartTags(); + if ( pSmartTagList ) + { + sal_Int32 nCurrent = aPos.nContent.GetIndex(); + sal_Int32 nBegin = nCurrent; + sal_Int32 nLen = 1; + + if (pSmartTagList->InWrongWord(nBegin, nLen) && !pNode->IsSymbolAt(nBegin)) + { + 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.nNode.GetNode().GetTextNode(); + if( !pNode ) + return; + pSmartTagList = pNode->GetSmartTags(); + if( !pSmartTagList ) + return; + if( !pNode->IsInProtectSect() ) + { + sal_Int32 nBegin = aPos.nContent.GetIndex(); + sal_Int32 nLen = 1; + + if (pSmartTagList->InWrongWord(nBegin, nLen) && !pNode->IsSymbolAt(nBegin)) + { + // 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()->nContent.GetIndex(); + RightMargin(); + const sal_Int32 nLineEnd = GetCursor()->GetPoint()->nContent.GetIndex(); + 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.nContent = 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(); + SwIndex& rContent = GetCursor()->GetPoint()->nContent; + rContent = nWordStart; + SwRect aStartRect; + SwCursorMoveState aState; + aState.m_bRealWidth = true; + SwContentNode* pContentNode = pCursor->GetContentNode(); + std::pair<Point, bool> const tmp(rPt, false); + SwContentFrame *pContentFrame = pContentNode->getLayoutFrame( + GetLayout(), pCursor->GetPoint(), &tmp); + + pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState ); + rContent = 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 000000000..4dbb5e732 --- /dev/null +++ b/sw/source/core/crsr/crstrvl.cxx @@ -0,0 +1,2592 @@ +/* -*- 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 <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> + +using namespace ::com::sun::star; + +void SwCursorShell::MoveCursorToNum() +{ + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + if( ActionPend() ) + return; + SET_CURR_SHELL( 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->GetContentNode()->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 ) + { + SET_CURR_SHELL( 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 ) + { + 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 ) + { + SwCursor *pTmpCursor = getShellCursor( true ); + SET_CURR_SHELL( 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; + } + else + pFrame = nullptr; + } + else + pFrame = nullptr; + return nullptr != pFrame; +} + +bool SwCursorShell::SetCursorInHdFt( size_t nDescNo, bool bInHeader ) +{ + bool bRet = false; + SwDoc *pMyDoc = GetDoc(); + const SwPageDesc* pDesc = nullptr; + + SET_CURR_SHELL( 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 ) + { + // check if the attribute exists + const SwFormatContent* pCnt = nullptr; + if( bInHeader ) + { + // mirrored pages? ignore for now + const SwFormatHeader& rHd = pDesc->GetMaster().GetHeader(); + if( rHd.GetHeaderFormat() ) + pCnt = &rHd.GetHeaderFormat()->GetContent(); + } + else + { + const SwFormatFooter& rFt = pDesc->GetMaster().GetFooter(); + if( rFt.GetFooterFormat() ) + pCnt = &rFt.GetFooterFormat()->GetContent(); + } + + if( pCnt && pCnt->GetContentIdx() ) + { + 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)) + { + // 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.nNode = *pCNd; + rPos.nContent.Assign( pCNd, 0 ); + + bRet = !m_pCurrentCursor->IsSelOvr(); + if( bRet ) + UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE | + SwCursorShell::READONLY ); + } + } + } + return bRet; +} + +/// jump to the next index +bool SwCursorShell::GotoNextTOXBase( const OUString* pName ) +{ + bool bRet = false; + + 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()->nNode < pSectNd->GetIndex() + && (!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 ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + m_pCurrentCursor->GetPoint()->nNode = *pFnd; + m_pCurrentCursor->GetPoint()->nContent.Assign( pFnd, 0 ); + 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 ) +{ + bool bRet = false; + + 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()->nNode > pSectNd->EndOfSectionIndex() + && (!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 ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + m_pCurrentCursor->GetPoint()->nNode = *pFnd; + m_pCurrentCursor->GetPoint()->nContent.Assign( pFnd, 0 ); + bRet = !m_pCurrentCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + } + return bRet; +} + +/// jump to index of TOXMark +void SwCursorShell::GotoTOXMarkBase() +{ + bool bRet = false; + + SwTOXMarks aMarks; + sal_uInt16 nCnt = SwDoc::GetCurTOXMark( *m_pCurrentCursor->GetPoint(), aMarks ); + if( nCnt ) + { + // Take the 1. and get the index type. Search in its dependency list + // for the actual index + const SwTOXType* pType = aMarks[0]->GetTOXType(); + SwIterator<SwTOXBase,SwTOXType> aIter( *pType ); + + for( SwTOXBase* pTOX = aIter.First(); pTOX; pTOX = aIter.Next() ) + { + auto pTOXBaseSection = dynamic_cast<const SwTOXBaseSection*>( pTOX); + if( !pTOXBaseSection ) + continue; + auto pSectFormat = pTOXBaseSection->GetFormat(); + if( !pSectFormat ) + continue; + const SwSectionNode* pSectNd = pSectFormat->GetSectionNode(); + if (!pSectNd) + continue; + SwNodeIndex aIdx( *pSectNd, 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = GetDoc()->GetNodes().GoNext( &aIdx ); + if( !pCNd ) + continue; + if( pCNd->EndOfSectionIndex() >= pSectNd->EndOfSectionIndex() ) + continue; + const SwContentFrame* pCFrame = pCNd->getLayoutFrame( GetLayout() ); + if( pCFrame && ( IsReadOnlyAvailable() || !pCFrame->IsProtected() ) ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + assert(pCFrame->IsTextFrame()); + *m_pCurrentCursor->GetPoint() = + static_cast<SwTextFrame const*>(pCFrame) + ->MapViewToModelPos(TextFrameIndex(0)); + bRet = !m_pCurrentCursor->IsInProtectTable() && + !m_pCurrentCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + break; + } + } + } +} + +/// 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.nNode = 0; + SetGetExpField aFndGEF( aFndPos ), aCurGEF( rPos ); + + { + const SwNode* pSttNd = rPos.nNode.GetNode().FindTableBoxStartNode(); + if( pSttNd ) + { + const SwTableBox* pTBox = pSttNd->FindTableNode()->GetTable(). + GetTableBox( pSttNd->GetIndex() ); + if( pTBox ) + aCurGEF = SetGetExpField( *pTBox ); + } + } + + if( rPos.nNode < GetDoc()->GetNodes().GetEndOfExtras() ) + { + // also at collection use only the first frame + std::pair<Point, bool> const tmp(aPt, false); + aCurGEF.SetBodyPos( *rPos.nNode.GetNode().GetContentNode()->getLayoutFrame( GetLayout(), + &rPos, &tmp) ); + } + { + sal_uInt32 nMaxItems = GetDoc()->GetAttrPool().GetItemCount2( RES_BOXATR_FORMULA ); + + if( nMaxItems > 0 ) + { + sal_uInt8 nMaxDo = 2; + do { + for (const SfxPoolItem* pItem : GetDoc()->GetAttrPool().GetItemSurrogates(RES_BOXATR_FORMULA)) + { + 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() ) ) + { + const SwContentFrame* pCFrame; + SwNodeIndex aIdx( *pTBox->GetSttNd() ); + const SwContentNode* pCNd = GetDoc()->GetNodes().GoNext( &aIdx ); + std::pair<Point, bool> const tmp(aPt, false); + if (pCNd) + { + 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.nNode = 0; + rPos.nContent = 0; + aCurGEF = SetGetExpField( rPos ); + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped ); + } + else + { + aCurGEF = SetGetExpField( SwPosition( GetDoc()->GetNodes().GetEndOfContent() ) ); + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped ); + } + } + } while( !bFnd && --nMaxDo ); + } + } + + if( bFnd ) + { + SET_CURR_SHELL( 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 ); + } + else + { + rPos = aOldPos; + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + } + + 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.nNode = 0; + SetGetExpField aFndGEF( aFndPos ), aCurGEF( rPos ); + + if( rPos.nNode.GetIndex() < GetDoc()->GetNodes().GetEndOfExtras().GetIndex() ) + { + // also at collection use only the first frame + std::pair<Point, bool> const tmp(aPt, false); + aCurGEF.SetBodyPos( *rPos.nNode.GetNode(). + GetContentNode()->getLayoutFrame(GetLayout(), &rPos, &tmp)); + } + + { + const SwTextNode* pTextNd; + const SwTextTOXMark* pTextTOX; + sal_uInt32 nMaxItems = GetDoc()->GetAttrPool().GetItemCount2( RES_TXTATR_TOXMARK ); + + if( nMaxItems > 0 ) + { + do { + for (const SfxPoolItem* pItem : GetDoc()->GetAttrPool().GetItemSurrogates(RES_TXTATR_TOXMARK)) + { + 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() )) + { + SwNodeIndex aNdIndex( *pTextNd ); // UNIX needs this object + SetGetExpField aCmp( aNdIndex, *pTextTOX ); + aCmp.SetBodyPos( *pCFrame ); + + if( bNext ? ( aCurGEF < aCmp && aCmp < aFndGEF ) + : ( aCmp < aCurGEF && aFndGEF < aCmp )) + { + aFndGEF = aCmp; + bFnd = true; + } + } + } + if( !bFnd ) + { + if ( bNext ) + { + rPos.nNode = 0; + rPos.nContent = 0; + aCurGEF = SetGetExpField( rPos ); + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped ); + } + else + { + aCurGEF = SetGetExpField( SwPosition( GetDoc()->GetNodes().GetEndOfContent() ) ); + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped ); + } + } + } while ( !bFnd ); + } + else + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + } + + if( bFnd ) + { + SET_CURR_SHELL( 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 ) +{ + SET_CURR_SHELL( 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.nNode = rNewMark.GetTextTOXMark()->GetTextNode(); + rPos.nContent.Assign( rPos.nNode.GetNode().GetContentNode(), + 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( SwNodeIndex( 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::unique_ptr<SetGetExpField> pSrch; + std::unique_ptr<SwIndex> pIndex; + if (-1 == nContentOffset) + { + pSrch.reset(new SetGetExpField(rPos.nNode, pTextField, &rPos.nContent)); + } + else + { + pIndex.reset(new SwIndex(rPos.nNode.GetNode().GetContentNode(), nContentOffset)); + pSrch.reset(new SetGetExpField(rPos.nNode, pTextField, pIndex.get())); + } + + if (rPos.nNode.GetIndex() < pTextNode->GetNodes().GetEndOfExtras().GetIndex()) + { + // also at collection use only the first frame + Point aPt; + std::pair<Point, bool> const tmp(aPt, false); + pSrch->SetBodyPos(*pTextNode->getLayoutFrame(pLayout, &rPos, &tmp)); + } + + SetGetExpFields::const_iterator it = rSrtLst.lower_bound(pSrch.get()); + + o_rFound = (it != rSrtLst.end()) && (**it == *pSrch); + 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(); + for( size_t i=0; i < nSize; ++i ) + { + pFieldType = rFieldTypes[ i ].get(); + if( 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.nNode.GetNode().GetTextNode(); + OSL_ENSURE( pTNd, "No ContentNode" ); + + SwTextField * pTextField = pTNd->GetFieldTextAttrAt( rPos.nContent.GetIndex(), true ); + 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.nContent.GetIndex(), + 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.nContent.GetIndex() != 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; + + SET_CURR_SHELL( 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::GotoFormatField( const SwFormatField& rField ) +{ + bool bRet = false; + SwTextField const*const pTextField(rField.GetTextField()); + if (pTextField + && (!GetLayout()->IsHideRedlines() + || !sw::IsFieldDeletedInModel( + GetDoc()->getIDocumentRedlineAccess(), *pTextField))) + { + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + + SwCursor* pCursor = getShellCursor( true ); + SwCursorSaveState aSaveState( *pCursor ); + + SwTextNode* pTNd = pTextField->GetpTextNode(); + pCursor->GetPoint()->nNode = *pTNd; + pCursor->GetPoint()->nContent.Assign( pTNd, pTextField->GetStart() ); + + bRet = !pCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + } + return bRet; +} + +SwTextField * SwCursorShell::GetTextFieldAtPos( + const SwPosition* pPos, + const bool bIncludeInputFieldAtStart ) +{ + SwTextField* pTextField = nullptr; + + SwTextNode * const pNode = pPos->nNode.GetNode().GetTextNode(); + if ( pNode != nullptr ) + { + pTextField = pNode->GetFieldTextAttrAt( pPos->nContent.GetIndex(), bIncludeInputFieldAtStart ); + } + + return pTextField; +} + +SwTextField* SwCursorShell::GetTextFieldAtCursor( + const SwPaM* pCursor, + const bool bIncludeInputFieldAtStart ) +{ + SwTextField* pFieldAtCursor = nullptr; + + SwTextField* pTextField = GetTextFieldAtPos( pCursor->Start(), bIncludeInputFieldAtStart ); + if ( pTextField != nullptr + && pCursor->Start()->nNode == pCursor->End()->nNode ) + { + const sal_Int32 nTextFieldLength = + pTextField->End() != nullptr + ? *(pTextField->End()) - pTextField->GetStart() + : 1; + if ( ( pCursor->End()->nContent.GetIndex() - pCursor->Start()->nContent.GetIndex() ) <= nTextFieldLength ) + { + pFieldAtCursor = pTextField; + } + } + + return pFieldAtCursor; +} + +SwField* SwCursorShell::GetFieldAtCursor( + const SwPaM *const pCursor, + const bool bIncludeInputFieldAtStart) +{ + SwTextField *const pField(GetTextFieldAtCursor(pCursor, bIncludeInputFieldAtStart)); + 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, true))) + return true; + } + return false; +} + +bool SwCursorShell::PosInsideInputField( const SwPosition& rPos ) +{ + return dynamic_cast<const SwTextInputField*>(GetTextFieldAtPos( &rPos, false )) != 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, true )); + 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, true )); + 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 ); + + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *pCursor ); + + const SwNodes& rNds = GetDoc()->GetNodes(); + SwTextNode* pTextNd = rNds.GetOutLineNds()[ nIdx ]->GetTextNode(); + pCursor->GetPoint()->nNode = *pTextNd; + pCursor->GetPoint()->nContent.Assign( pTextNd, 0 ); + + if( !pCursor->IsSelOvr() ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); +} + +bool SwCursorShell::GotoOutline( const OUString& rName ) +{ + SwCursor* pCursor = getShellCursor( true ); + + SET_CURR_SHELL( 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->GetNode()); + 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 ); + } + + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *pCursor ); + pCursor->GetPoint()->nNode = *pNd; + pCursor->GetPoint()->nContent.Assign( pNd->GetTextNode(), 0 ); + + 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->GetNode()); + SwOutlineNodes::size_type nPos; + bool bRet = false; + (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) + { + if (nStartPos < nPos) + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped ); + } + else + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty ); + } + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *pCursor ); + pCursor->GetPoint()->nNode = *pNd; + pCursor->GetPoint()->nContent.Assign( pNd->GetTextNode(), 0 ); + + bRet = !pCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + } + else + { + SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound ); + } + return bRet; +} + +/// search "outline position" before previous outline node at given level +SwOutlineNodes::size_type SwCursorShell::GetOutlinePos( sal_uInt8 nLevel ) +{ + SwPaM* pCursor = getShellCursor( true ); + const SwNodes& rNds = GetDoc()->GetNodes(); + + SwNode* pNd = &(pCursor->GetNode()); + 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) + { + return nPos; + } + } + return SwOutlineNodes::npos; // no more left +} + +bool 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 false; + + SET_CURR_SHELL( 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()->nNode = *pSttNd; + m_pCurrentCursor->GetPoint()->nContent.Assign( pSttNd->GetContentNode(), 0 ); + m_pCurrentCursor->SetMark(); + m_pCurrentCursor->GetPoint()->nNode = *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); + return bRet; +} + +/// jump to reference marker +bool SwCursorShell::GotoRefMark( const OUString& rRefMark, sal_uInt16 nSubType, + sal_uInt16 nSeqNo ) +{ + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + sal_Int32 nPos = -1; + SwTextNode* pTextNd = SwGetRefFieldType::FindAnchor( GetDoc(), rRefMark, + nSubType, nSeqNo, &nPos, nullptr, GetLayout()); + if( pTextNd && pTextNd->GetNodes().IsDocNodes() ) + { + m_pCurrentCursor->GetPoint()->nNode = *pTextNd; + m_pCurrentCursor->GetPoint()->nContent.Assign( pTextNd, nPos ); + + if( !m_pCurrentCursor->IsSelOvr() ) + { + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return true; + } + } + return false; +} + +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 ) +{ + SET_CURR_SHELL( this ); + bool bRet = false; + + if( !IsTableMode() ) + { + Point aPt( rPt ); + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + + SwTextNode* pTextNd; + SwContentFrame *pFrame(nullptr); + SwTextAttr* pTextAttr; + 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.nNode.GetNode().GetTextNode(); + + const SwNodes& rNds = GetDoc()->GetNodes(); + if( pTextNd + && IsAttrAtPos::Outline & rContentAtPos.eContentAtPos + && !rNds.GetOutLineNds().empty() ) + { + const SwTextNode* pONd = pTextNd->FindOutlineNodeOfLevel(MAXLEVEL-1, GetLayout()); + if( pONd ) + { + rContentAtPos.eContentAtPos = IsAttrAtPos::Outline; + rContentAtPos.sStr = sw::GetExpandTextMerged(GetLayout(), *pONd, true, false, ExpandMode::ExpandFootnote); + 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.nNode); + + Size aSizeLogic(aTmpState.m_nInNumPortionOffset, 0); + Size aSizePixel = GetWin()->LogicToPixel(aSizeLogic); + rContentAtPos.nDist = aSizePixel.Width(); + } + else if( bCursorFoundExact && pTextNd ) + { + if( !aTmpState.m_bPosCorr ) + { + if ( IsAttrAtPos::SmartTag & rContentAtPos.eContentAtPos + && !aTmpState.m_bFootnoteNoInfo ) + { + const SwWrongList* pSmartTagList = pTextNd->GetSmartTags(); + sal_Int32 nCurrent = aPos.nContent.GetIndex(); + 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.nContent.GetIndex() ); + 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->getFieldmarkFor( aPos ); + if (bCursorFoundExact && pFieldBookmark) + { + rContentAtPos.eContentAtPos = IsAttrAtPos::FormControl; + rContentAtPos.aFnd.pFieldmark = pFieldBookmark; + 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.nContent.GetIndex(), RES_TXTATR_FTN )) ) + { + bRet = true; + if( bSetCursor ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + m_pCurrentCursor->GetPoint()->nNode = *static_cast<SwTextFootnote*>(pTextAttr)->GetStartNode(); + SwContentNode* pCNd = GetDoc()->GetNodes().GoNextSection( + &m_pCurrentCursor->GetPoint()->nNode, + true, !IsReadOnlyAvailable() ); + + if( pCNd ) + { + m_pCurrentCursor->GetPoint()->nContent.Assign( pCNd, 0 ); + 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.nContent.GetIndex(), 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.nContent.GetIndex(), 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 ) + { + pTextAttr = pTextNd->GetTextAttrAt( + aPos.nContent.GetIndex(), 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::TableBoxFml & rContentAtPos.eContentAtPos +#ifdef DBG_UTIL + || IsAttrAtPos::TableBoxValue & rContentAtPos.eContentAtPos +#endif + ) ) + { + const SwTableNode* pTableNd; + const SwTableBox* pBox; + const SwStartNode* pSttNd = pTextNd->FindTableBoxStartNode(); + const SfxPoolItem* pItem; + if( pSttNd && nullptr != ( pTableNd = pTextNd->FindTableNode()) && + nullptr != ( pBox = pTableNd->GetTable().GetTableBox( + pSttNd->GetIndex() )) && +#ifdef DBG_UTIL + ( SfxItemState::SET == pBox->GetFrameFormat()->GetItemState( + RES_BOXATR_FORMULA, false, &pItem ) || + SfxItemState::SET == pBox->GetFrameFormat()->GetItemState( + RES_BOXATR_VALUE, false, &pItem )) +#else + SfxItemState::SET == pBox->GetFrameFormat()->GetItemState( + RES_BOXATR_FORMULA, false, &pItem ) +#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().IsInside( 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( RES_BOXATR_VALUE == pItem->Which() ) + rContentAtPos.eContentAtPos = IsAttrAtPos::TableBoxValue; + else +#endif + const_cast<SwTableBoxFormula*>(static_cast<const 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.nContent.GetIndex(); + SfxItemSet aSet( GetDoc()->GetAttrPool(), svl::Items<POOLATTR_BEGIN, + POOLATTR_END - 1>{} ); + 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( aPos.nNode.GetIndex()); + rContentAtPos.sStr += ":"; + rContentAtPos.sStr += OUString::number( aPos.nContent.GetIndex()); + 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.toString(); + } + } + 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 +{ + const SwPostItField* pPostItField = nullptr; + + if ( !IsTableMode() ) + { + const SwPosition* pCursorPos = GetCursor_()->GetPoint(); + const SwTextNode* pTextNd = pCursorPos->nNode.GetNode().GetTextNode(); + if ( pTextNd ) + { + SwTextAttr* pTextAttr = pTextNd->GetFieldTextAttrAt( pCursorPos->nContent.GetIndex() ); + 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 +{ + bool bRet = false; + const SwTextNode* pNd = nullptr; + if (pFndTextAttr && (eContentAtPos == IsAttrAtPos::Ftn)) + { + const SwTextFootnote* pTextFootnote = static_cast<const SwTextFootnote*>(pFndTextAttr); + if(pTextFootnote->GetStartNode()) + { + SwStartNode* pSttNd = pTextFootnote->GetStartNode()->GetNode().GetStartNode(); + SwPaM aTemp( *pSttNd ); + aTemp.Move(fnMoveForward, GoInNode); + SwContentNode* pContentNode = aTemp.GetContentNode(); + if(pContentNode && pContentNode->IsTextNode()) + pNd = pContentNode->GetTextNode(); + } + } + if(pNd) + { + 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::SelectText( const sal_Int32 nStart, + const sal_Int32 nEnd ) +{ + SET_CURR_SHELL( this ); + bool bRet = false; + + SwCallLink aLk( *this ); + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + SwPosition& rPos = *m_pCurrentCursor->GetPoint(); + m_pCurrentCursor->DeleteMark(); + rPos.nContent = nStart; + m_pCurrentCursor->SetMark(); + rPos.nContent = nEnd; + + if( !m_pCurrentCursor->IsSelOvr() ) + { + UpdateCursor(); + bRet = true; + } + + return bRet; +} + +bool SwCursorShell::SelectTextAttr( sal_uInt16 nWhich, + bool bExpand, + const SwTextAttr* pTextAttr ) +{ + SET_CURR_SHELL( this ); + bool bRet = false; + + if( !IsTableMode() ) + { + if( !pTextAttr ) + { + SwPosition& rPos = *m_pCurrentCursor->GetPoint(); + SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode(); + pTextAttr = pTextNd + ? pTextNd->GetTextAttrAt(rPos.nContent.GetIndex(), + nWhich, + bExpand ? SwTextNode::EXPAND : SwTextNode::DEFAULT) + : nullptr; + } + + if( pTextAttr ) + { + const sal_Int32* pEnd = pTextAttr->End(); + bRet = SelectText( pTextAttr->GetStart(), ( pEnd ? *pEnd : pTextAttr->GetStart() + 1 ) ); + } + } + return bRet; +} + +bool SwCursorShell::GotoINetAttr( const SwTextINetFormat& rAttr ) +{ + bool bRet = false; + if( rAttr.GetpTextNode() ) + { + SwCursor* pCursor = getShellCursor( true ); + + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *pCursor ); + + pCursor->GetPoint()->nNode = *rAttr.GetpTextNode(); + pCursor->GetPoint()->nContent.Assign( const_cast<SwTextNode*>(rAttr.GetpTextNode()), + rAttr.GetStart() ); + bRet = !pCursor->IsSelOvr(); + if( bRet ) + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + } + return bRet; +} + +const SwFormatINetFormat* SwCursorShell::FindINetAttr( const OUString& rName ) const +{ + return mxDoc->FindINetAttr( rName ); +} + +bool SwCursorShell::GetShadowCursorPos( const Point& rPt, SwFillMode eFillMode, + SwRect& rRect, sal_Int16& rOrient ) +{ + + SET_CURR_SHELL( this ); + bool bRet = false; + + if (!IsTableMode() && !HasSelection() + && GetDoc()->GetIDocumentUndoRedo().DoesUndo()) + { + Point aPt( rPt ); + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + + SwFillCursorPos aFPos( eFillMode ); + SwCursorMoveState aTmpState( &aFPos ); + + if( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ) && + !aPos.nNode.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 ) +{ + SET_CURR_SHELL( this ); + bool bRet = false; + + if (!IsTableMode() && !HasSelection() + && GetDoc()->GetIDocumentUndoRedo().DoesUndo()) + { + Point aPt( rPt ); + SwPosition aPos( *m_pCurrentCursor->GetPoint() ); + + SwFillCursorPos aFPos( eFillMode ); + SwCursorMoveState aTmpState( &aFPos ); + + if( GetLayout()->GetModelPositionForViewPoint( &aPos, aPt, &aTmpState ) ) + { + SwCallLink aLk( *this ); // watch Cursor-Moves + StartAction(); + + SwContentNode* pCNd = aPos.nNode.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.nNode, 1 ); + while( aEnd.GetNode().IsEndNode() && + &aEnd.GetNode() != + pSectNd->EndOfSectionNode() ) + ++aEnd; + + if( aEnd.GetNode().IsEndNode() && + pCNd->Len() == aPos.nContent.GetIndex() ) + aPos.nNode = *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.nNode.GetNode().GetContentNode() )) + { + SfxItemSet aSet( + GetDoc()->GetAttrPool(), + svl::Items< + RES_PARATR_ADJUST, RES_PARATR_ADJUST, + RES_LR_SPACE, RES_LR_SPACE>{}); + SvxLRSpaceItem aLR( static_cast<const SvxLRSpaceItem&>( + pCNd->GetAttr( RES_LR_SPACE ) ) ); + aLR.SetTextLeft( aFPos.nTabCnt ); + aLR.SetTextFirstLineOffset( 0 ); + aSet.Put( aLR ); + + const SvxAdjustItem& rAdj = static_cast<const SvxAdjustItem&>(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(); + + bRet = true; + } + } + return bRet; +} + +const SwRangeRedline* SwCursorShell::SelNextRedline() +{ + const SwRangeRedline* pFnd = nullptr; + if( !IsTableMode() ) + { + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + // ensure point is at the end so alternating SelNext/SelPrev works + NormalizePam(false); + 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() +{ + const SwRangeRedline* pFnd = nullptr; + if( !IsTableMode() ) + { + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); // watch Cursor-Moves + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + // ensure point is at the start so alternating SelNext/SelPrev works + NormalizePam(true); + 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 ) + { + *m_pCurrentCursor->GetPoint() = *pFnd->Start(); + + SwNodeIndex* pIdx = &m_pCurrentCursor->GetPoint()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + SwContentNode* pCNd = GetDoc()->GetNodes().GoNextSection( pIdx, + true, IsReadOnlyAvailable() ); + if( pCNd ) + { + if( *pIdx <= pFnd->End()->nNode ) + m_pCurrentCursor->GetPoint()->nContent.Assign( pCNd, 0 ); + else + pFnd = nullptr; + } + } + + if( pFnd && bSelect ) + { + m_pCurrentCursor->SetMark(); + if( RedlineType::FmtColl == pFnd->GetType() ) + { + SwContentNode* pCNd = pIdx->GetNode().GetContentNode(); + m_pCurrentCursor->GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + m_pCurrentCursor->GetMark()->nContent.Assign( pCNd, 0 ); + } + else + *m_pCurrentCursor->GetPoint() = *pFnd->End(); + + pIdx = &m_pCurrentCursor->GetPoint()->nNode; + if( !pIdx->GetNode().IsContentNode() ) + { + SwContentNode* pCNd = SwNodes::GoPrevSection( pIdx, + true, IsReadOnlyAvailable() ); + if( pCNd ) + { + if( *pIdx >= m_pCurrentCursor->GetMark()->nNode ) + m_pCurrentCursor->GetPoint()->nContent.Assign( pCNd, 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() ) + { + SET_CURR_SHELL( this ); + + const SwRedlineTable& rTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + const SwRangeRedline* pTmp = rTable[ nArrPos ]; + sal_uInt16 nSeqNo = pTmp->GetSeqNo(); + if( nSeqNo && bSelect ) + { + 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(); + SwPosition* pCStt = pCur->Start(), *pCEnd = pCur->End(); + while( pCur != pNextPam ) + { + const SwPosition *pNStt = pNextPam->Start(), + *pNEnd = pNextPam->End(); + + 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 ); + } + else + pFnd = GotoRedline_( nArrPos, bSelect ); + } + return pFnd; +} + +bool SwCursorShell::SelectNxtPrvHyperlink( bool bNext ) +{ + SwNodes& rNds = GetDoc()->GetNodes(); + const SwNode* pBodyEndNd = &rNds.GetEndOfContent(); + const SwNode* pBodySttNd = pBodyEndNd->StartOfSectionNode(); + sal_uLong 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(); + SwContentFrame* pFrame; + std::pair<Point, bool> tmp(aPt, true); + if (pCNd) + { + 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; + SwPosition aTmpPos( *pTextNd ); + SetGetExpField aPos( aTmpPos.nNode, rAttr ); + SwContentFrame* pFrame; + if (pTextNd->GetIndex() < nBodySttNdIdx) + { + std::pair<Point, bool> tmp(aPt, true); + 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 + { + const SwFrameFormats* pFormats = GetDoc()->GetSpzFrameFormats(); + for( SwFrameFormats::size_type n = 0, nEnd = pFormats->size(); n < nEnd; ++n ) + { + SwFlyFrameFormat* pFormat = static_cast<SwFlyFrameFormat*>((*pFormats)[ n ]); + 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 ? + bool bRet = false; + const SwTextINetFormat* pFndAttr = aCmpPos.GetINetFormat(); + const SwFlyFrameFormat* pFndFormat = aCmpPos.GetFlyFormat(); + if( pFndAttr || pFndFormat ) + { + SET_CURR_SHELL( this ); + SwCallLink aLk( *this ); + + // found a text attribute ? + if( pFndAttr ) + { + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + aCmpPos.GetPosOfContent( *m_pCurrentCursor->GetPoint() ); + m_pCurrentCursor->DeleteMark(); + m_pCurrentCursor->SetMark(); + m_pCurrentCursor->GetPoint()->nContent = *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 000000000..565456676 --- /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/findattr.cxx b/sw/source/core/crsr/findattr.cxx new file mode 100644 index 000000000..a2486ab29 --- /dev/null +++ b/sw/source/core/crsr/findattr.cxx @@ -0,0 +1,1444 @@ +/* -*- 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 <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 static_cast<const SvxFontItem&>(rItem1).GetFamilyName() == + static_cast<const SvxFontItem&>(rItem2).GetFamilyName(); + + case RES_CHRATR_COLOR: + return static_cast<const SvxColorItem&>(rItem1).GetValue().IsRGBEqual( + static_cast<const SvxColorItem&>(rItem2).GetValue() ); + case RES_PAGEDESC: + ::std::optional<sal_uInt16> const oNumOffset1 = + static_cast<const SwFormatPageDesc&>(rItem1).GetNumOffset(); + ::std::optional<sal_uInt16> const oNumOffset2 = + static_cast<const SwFormatPageDesc&>(rItem2).GetNumOffset(); + + if (oNumOffset1 != oNumOffset2) + return false; + + return static_cast<const SwFormatPageDesc&>(rItem1).GetPageDesc() == static_cast<const SwFormatPageDesc&>(rItem2).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()->nContent.GetIndex(); + else + nContentPos = rPam.GetPoint()->nContent.GetIndex(); + bool bTstEnd = rPam.GetPoint()->nNode == rPam.GetMark()->nNode; + + SwContentNode* pCNd = rPam.GetContentNode(); + rPam.GetPoint()->nContent.Assign( pCNd, nStart ); + rPam.SetMark(); // Point == GetMark + + // Point points to end of search area or end of attribute + if( pEnd ) + { + if( bTstEnd && *pEnd > nContentPos ) + rPam.GetPoint()->nContent = nContentPos; + else + rPam.GetPoint()->nContent = *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()->nContent.GetIndex(); + + 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.GetWhichByPos( aIter.GetFirstPos() ); + m_nArrLen = m_aComapeSet.GetWhichByPos( 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()->nContent.GetIndex(); + m_nNodeEnd = rPam.GetPoint()->nNode == rPam.GetMark()->nNode + ? rPam.GetMark()->nContent.GetIndex() + : rTextNd.GetText().getLength(); + } + else + { + m_nNodeEnd = rPam.GetPoint()->nContent.GetIndex(); + m_nNodeStart = rPam.GetPoint()->nNode == rPam.GetMark()->nNode + ? rPam.GetMark()->nContent.GetIndex() + : 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.GetWhichByPos( 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 + { + if( RES_TXTATR_END <= (nWhich = pItem->Which() )) + 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::unique_ptr<SfxWhichIter> pIter; + 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 ) + { + pIter.reset(new SfxWhichIter( *pSet )); + nWhch = pIter->FirstWhich(); + while( nWhch && + SfxItemState::SET != pSet->GetItemState( nWhch, true, &pTmpItem ) ) + nWhch = pIter->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( pIter ) + { + assert(pSet && "otherwise no pIter"); + nWhch = pIter->NextWhich(); + while( nWhch && + SfxItemState::SET != pSet->GetItemState( nWhch, true, &pTmpItem ) ) + nWhch = pIter->NextWhich(); + if( !nWhch ) + break; + } + else + break; + } + pIter.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::unique_ptr<SfxWhichIter> pIter; + 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 ) + { + pIter.reset( new SfxWhichIter( *pSet ) ); + nWhch = pIter->FirstWhich(); + while( nWhch && + SfxItemState::SET != pSet->GetItemState( nWhch, true, &pTmpItem ) ) + nWhch = pIter->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( pIter ) + { + assert(pSet && "otherwise no pIter"); + nWhch = pIter->NextWhich(); + while( nWhch && + SfxItemState::SET != pSet->GetItemState( nWhch, true, &pTmpItem ) ) + nWhch = pIter->NextWhich(); + if( !nWhch ) + break; + } + else + break; + } + pIter.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 + if( (nSttPos = rCmpArr.Start()) > (nEndPos = rCmpArr.End()) ) + 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 + if( (nSttPos = rCmpArr.Start()) > (nEndPos = rCmpArr.End()) ) + 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.GetWhichByPos( 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::unique_ptr<SwPaM> pPam(sw::MakeRegion(fnMove, rRegion)); + + 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 + ? pPam->GetPoint()->nContent.GetIndex() == pPam->GetContentNode()->Len() + : !pPam->GetPoint()->nContent.GetIndex() ) + { + if( !(*fnMove.fnNds)( &pPam->GetPoint()->nNode, false )) + { + return false; + } + SwContentNode *pNd = pPam->GetContentNode(); + pPam->GetPoint()->nContent.Assign( pNd, bSrchForward ? 0 : pNd->Len() ); + } + + while (nullptr != (pNode = ::GetNode(*pPam, 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() < pPam->GetPoint()->nNode.GetIndex() + || (pAttrNode->GetIndex() == pPam->GetPoint()->nNode.GetIndex() + && pAttr->GetStart() < pPam->GetPoint()->nContent.GetIndex()) + || pAttr->Which() != nWhich)); + } + else + { + sw::MergedAttrIterReverse iter(*pFrame); + do + { + pAttr = iter.PrevAttr(&pAttrNode); + } + while (pAttr + && (pPam->GetPoint()->nNode.GetIndex() < pAttrNode->GetIndex() + || (pPam->GetPoint()->nNode.GetIndex() == pAttrNode->GetIndex() + && pPam->GetPoint()->nContent.GetIndex() <= pAttr->GetStart()) + || pAttr->Which() != nWhich)); + } + if (pAttr) + { + assert(pAttrNode); + pPam->GetPoint()->nNode = *pAttrNode; + lcl_SetAttrPam(*pPam, pAttr->GetStart(), pAttr->End(), bSrchForward); + bFound = true; + break; + } + } + else if (!pLayout && pNode->GetTextNode()->HasHints() && + lcl_SearchAttr(*pNode->GetTextNode(), *pPam, rAttr, fnMove)) + { + bFound = true; + } + if (bFound) + { + // set to the values of the attribute + rSearchPam.SetMark(); + *rSearchPam.GetPoint() = *pPam->GetPoint(); + *rSearchPam.GetMark() = *pPam->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(); + pNode->MakeEndIndex( &rSearchPam.GetPoint()->nContent ); + 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::unique_ptr<SwPaM> pPam(sw::MakeRegion(fnMove, rRegion)); + + 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 ); + SfxItemSet aOtherSet( rSearchPam.GetDoc()->GetAttrPool(), + svl::Items<RES_PARATR_BEGIN, RES_GRFATR_END-1>{} ); + 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 + ? pPam->GetPoint()->nContent.GetIndex() == pPam->GetContentNode()->Len() + : !pPam->GetPoint()->nContent.GetIndex() ) ) + { + if( !(*fnMove.fnNds)( &pPam->GetPoint()->nNode, false )) + { + return false; + } + SwContentNode *pNd = pPam->GetContentNode(); + pPam->GetPoint()->nContent.Assign( pNd, bSrchForward ? 0 : pNd->Len() ); + } + + while (nullptr != (pNode = ::GetNode(*pPam, 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(*pPam->Start()); + SwPosition const& rEnd(*pPam->End()); + // no extents? fall back to searching index 0 of propsnode + // to find its node items + if (pMergedPara->extents.empty()) + { + if (rStart.nNode.GetIndex() <= rPropsNode.GetIndex() + && rPropsNode.GetIndex() <= rEnd.nNode.GetIndex()) + { + SwPaM tmp(rPropsNode, 0, rPropsNode, 0); + bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, tmp); + if (bFound) + { + *pPam = 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.nNode.GetIndex() + || rEnd.nNode.GetIndex() < rExtent.pNode->GetIndex()) + { + continue; + } + sal_Int32 const nStart(rExtent.pNode == &rStart.nNode.GetNode() + ? rStart.nContent.GetIndex() + : 0); + if (rExtent.nEnd <= nStart) + { + continue; + } + sal_Int32 const nEnd(rExtent.pNode == &rEnd.nNode.GetNode() + ? rEnd.nContent.GetIndex() + : 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) + { + *pPam = tmp; + break; + } + } + } + } + else + { + bFound = (*fnSearch)(*pNode->GetTextNode(), aCmpArr, *pPam); + } + if (bFound) + { + // set to the values of the attribute + rSearchPam.SetMark(); + *rSearchPam.GetPoint() = *pPam->GetPoint(); + *rSearchPam.GetMark() = *pPam->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() = *pPam->GetPoint(); + rSearchPam.SetMark(); + *rSearchPam.GetMark() = pFrame->MapViewToModelPos( + TextFrameIndex(bSrchForward ? pFrame->GetText().getLength() : 0)); + } + else + { + *rSearchPam.GetPoint() = *pPam->GetPoint(); + rSearchPam.SetMark(); + if (bSrchForward) + { + pNode->MakeEndIndex( &rSearchPam.GetPoint()->nContent ); + } + else + { + pNode->MakeStartIndex( &rSearchPam.GetPoint()->nContent ); + } + } + 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); + SwIndex& rSttCntIdx = rCursor.Start()->nContent; + const sal_Int32 nSttCnt = rSttCntIdx.GetIndex(); + + // 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 ); + } + rSttCntIdx = 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_uLong 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* pDoc = GetDoc(); + Link<bool,void> aLnk( pDoc->GetOle2Link() ); + pDoc->SetOle2Link( Link<bool,void>() ); + + bool bReplace = ( pSearchOpt && ( !pSearchOpt->replaceString.isEmpty() || + !rSet.Count() ) ) || + (pReplSet && pReplSet->Count()); + bool const bStartUndo = pDoc->GetIDocumentUndoRedo().DoesUndo() && bReplace; + if (bStartUndo) + { + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::REPLACE, nullptr ); + } + + SwFindParaAttr aSwFindParaAttr( rSet, bNoCollections, pSearchOpt, + pReplSet, *this, pLayout ); + + sal_uLong nRet = FindAll( aSwFindParaAttr, nStart, nEnd, eFndRngs, bCancel ); + pDoc->SetOle2Link( aLnk ); + if( nRet && bReplace ) + pDoc->getIDocumentState().SetModified(); + + if (bStartUndo) + { + pDoc->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 000000000..8385e0a4c --- /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_uLong 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* pDoc = GetDoc(); + Link<bool,void> aLnk( pDoc->GetOle2Link() ); + pDoc->SetOle2Link( Link<bool,void>() ); + + bool const bStartUndo = + pDoc->GetIDocumentUndoRedo().DoesUndo() && pReplFormatColl; + if (bStartUndo) + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, rFormatColl.GetName()); + aRewriter.AddRule(UndoArg2, SwResId(STR_YIELDS)); + aRewriter.AddRule(UndoArg3, pReplFormatColl->GetName()); + + pDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_REPLACE_STYLE, + &aRewriter ); + } + + SwFindParaFormatColl aSwFindParaFormatColl(rFormatColl, pReplFormatColl, pLayout); + + sal_uLong nRet = FindAll( aSwFindParaFormatColl, nStart, nEnd, eFndRngs, bCancel ); + pDoc->SetOle2Link( aLnk ); + + if( nRet && pReplFormatColl ) + pDoc->getIDocumentState().SetModified(); + + if (bStartUndo) + { + pDoc->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 000000000..a04850284 --- /dev/null +++ b/sw/source/core/crsr/findfmt.cxx @@ -0,0 +1,97 @@ +/* -*- 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::unique_ptr<SwPaM> pPam(MakeRegion( fnMove, rRegion )); + + // if at beginning/end then move it out of the node + if( bSrchForward + ? pPam->GetPoint()->nContent.GetIndex() == pPam->GetContentNode()->Len() + : !pPam->GetPoint()->nContent.GetIndex() ) + { + if( !(*fnMove.fnNds)( &pPam->GetPoint()->nNode, false )) + { + return false; + } + SwContentNode *pNd = pPam->GetPoint()->nNode.GetNode().GetContentNode(); + pPam->GetPoint()->nContent.Assign( pNd, bSrchForward ? 0 : pNd->Len() ); + } + + bool bFirst = true; + SwContentNode* pNode; + while (nullptr != (pNode = ::GetNode(*pPam, 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() = *pPam->GetPoint(); + rSearchPam.SetMark(); + *rSearchPam.GetMark() = pFrame->MapViewToModelPos( + TextFrameIndex(bSrchForward ? pFrame->GetText().getLength() : 0)); + } + else + { + *rSearchPam.GetPoint() = *pPam->GetPoint(); + rSearchPam.SetMark(); + pNode->MakeEndIndex( &rSearchPam.GetPoint()->nContent ); + rSearchPam.GetMark()->nContent = 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 000000000..1e9ecef2c --- /dev/null +++ b/sw/source/core/crsr/findtxt.cxx @@ -0,0 +1,1183 @@ +/* -*- 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 <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) + { +#if BOOST_VERSION < 105600 + m_oMergedIter.reset(*pFrame); +#else + m_oMergedIter.emplace(*pFrame); +#endif + } + } + + 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_FTN: + 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::unique_ptr<SwPaM> pPam = sw::MakeRegion(fnMove, rRegion); + const bool bSrchForward = &fnMove == &fnMoveForward; + SwNodeIndex& rNdIdx = pPam->GetPoint()->nNode; + SwIndex& rContentIdx = pPam->GetPoint()->nContent; + + // 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(*pPam, 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, pPam->GetMark()->nNode.GetIndex()) + : rNdIdx == pPam->GetMark()->nNode) + { + if (pLayout) + { + nEnd.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->GetMark())); + } + else + { + nEnd.SetModelIndex(pPam->GetMark()->nContent.GetIndex()); + } + } + else + { + if (bSrchForward) + { + nEnd = nTextLen; + } + else + { + if (pLayout) + { + nEnd.SetFrameIndex(TextFrameIndex(0)); + } + else + { + nEnd.SetModelIndex(0); + } + } + } + AmbiguousIndex nStart; + if (pLayout) + { + nStart.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->GetPoint())); + } + else + { + nStart.SetModelIndex(rContentIdx.GetIndex()); + } + + /* #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 SwPosition* pPosition = pFrameFormat->GetAnchor().GetContentAnchor(); + if (!pPosition || (pLayout + ? !FrameContainsNode(*pFrame, pPosition->nNode.GetIndex()) + : pPosition->nNode.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()->nNode = rTextNode; + aPaM.GetPoint()->nContent.Assign( + aPaM.GetPoint()->nNode.GetNode().GetTextNode(), + nStart.GetModelIndex()); + } + aPaM.SetMark(); + if (pLayout) + { + aPaM.GetMark()->nNode = (pFrame->GetMergedPara() + ? *pFrame->GetMergedPara()->pLastNode + : rTextNode) + .GetIndex() + 1; + } + else + { + aPaM.GetMark()->nNode = rTextNode.GetIndex() + 1; + } + aPaM.GetMark()->nContent.Assign(aPaM.GetMark()->nNode.GetNode().GetTextNode(), 0); + if (pNode->GetDoc()->getIDocumentDrawModelAccess().Search(aPaM, *xSearchItem) && pSdrView) + { + if (SdrObject* pObject = pSdrView->GetTextEditObject()) + { + if (SwFrameFormat* pFrameFormat = FindFrameFormat(pObject)) + { + const SwPosition* pPosition = pFrameFormat->GetAnchor().GetContentAnchor(); + if (pPosition) + { + // Set search position to the shape's anchor point. + *rSearchPam.GetPoint() = *pPosition; + rSearchPam.GetPoint()->nContent.Assign(pPosition->nNode.GetNode().GetContentNode(), 0); + 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, + pPam.get() ); + 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, + pPam.get() ); + } + 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; + SwNodeIndex& rNdIdx = pPam->GetPoint()->nNode; + 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("\\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( utl::TextSearch::UpgradeToSearchOptions2( 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()->nContent = nStart.GetModelIndex(); + rSearchPam.GetPoint()->nContent = 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()->nContent = bChkParaEnd ? nTextLen.GetModelIndex() : 0; + } + rSearchPam.SetMark(); + const SwNode *const pSttNd = bSrchForward + ? &rSearchPam.GetPoint()->nNode.GetNode() // end of the frame + : &rNdIdx.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 != &rNdIdx.GetNode()) && + rSearchPam.Move(fnMoveForward, GoInContent) && + (!bSrchForward || pSttNd != &rSearchPam.GetPoint()->nNode.GetNode()) && + 1 == std::abs(static_cast<int>(rSearchPam.GetPoint()->nNode.GetIndex() - + rSearchPam.GetMark()->nNode.GetIndex()))) + { + // 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( utl::TextSearch::UpgradeToSearchOptions2(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); + SwIndex& rSttCntIdx = rCursor.Start()->nContent; + const sal_Int32 nSttCnt = rSttCntIdx.GetIndex(); + // 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()->nContent = nSttCnt; + return FIND_NO_RING; + } + return bFnd ? FIND_FOUND : FIND_NOT_FOUND; +} + +bool SwFindParaText::IsReplaceMode() const +{ + return m_bReplace; +} + +sal_uLong 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* pDoc = GetDoc(); + Link<bool,void> aLnk( pDoc->GetOle2Link() ); + pDoc->SetOle2Link( Link<bool,void>() ); + + bool const bStartUndo = pDoc->GetIDocumentUndoRedo().DoesUndo() && bReplace; + if (bStartUndo) + { + pDoc->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_uLong nRet = FindAll( aSwFindParaText, nStart, nEnd, eFndRngs, bCancel ); + pDoc->SetOle2Link( aLnk ); + if( nRet && bReplace ) + pDoc->getIDocumentState().SetModified(); + + if (bStartUndo) + { + SwRewriter rewriter(MakeUndoReplaceRewriter( + nRet, rSearchOpt.searchString, rSearchOpt.replaceString)); + pDoc->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()->nNode == ranges.front()->GetMark()->nNode); + 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->GetContentNode(); + SwContentNode const*const pMarkTextNode = pPam->GetContentNode(false); + 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()->nNode.GetIndex()) + : pTextNode == pMarkTextNode)) + { + utl::TextSearch aSText( utl::TextSearch::UpgradeToSearchOptions2( rSearchOpt) ); + SearchResult aResult; + OUString aReplaceStr( rSearchOpt.replaceString ); + if (bParaEnd) + { + OUString const aStr("\\n"); + aResult.subRegExpressions = 1; + aResult.startOffset.realloc(1); + aResult.endOffset.realloc(1); + aResult.startOffset[0] = 0; + aResult.endOffset[0] = aStr.getLength(); + aSText.ReplaceBackReferences( aReplaceStr, aStr, aResult ); + xRet = aReplaceStr; + } + else + { + OUString const aStr(pLayout + ? pFrame->GetText() + : pTextNode->GetTextNode()->GetText()); + AmbiguousIndex nStart; + AmbiguousIndex nEnd; + if (pLayout) + { + nStart.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->Start())); + nEnd.SetFrameIndex(pFrame->MapModelToViewPos(*pPam->End())); + } + else + { + nStart.SetModelIndex(pPam->Start()->nContent.GetIndex()); + nEnd.SetModelIndex(pPam->End()->nContent.GetIndex()); + } + 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 000000000..1ec83370e --- /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()); + const basegfx::B2DPolyPolygon aPolyPolygon(impCombineRangesToPolyPolygon(getRanges())); + const drawinglayer::primitive2d::Primitive2DReference aOutline( + new drawinglayer::primitive2d::PolyPolygonHairlinePrimitive2D( + aPolyPolygon, + aRGBColor)); + + aRetval.resize(1); + aRetval[0] = aOutline; + } + + return aRetval; + } + + OverlayRangesOutline::OverlayRangesOutline( + const Color& rColor, + const std::vector< basegfx::B2DRange >& rRanges ) + : sdr::overlay::OverlayObject(rColor) + , maRanges(rRanges) + { + // no AA for highlight overlays + allowAntiAliase(false); + } + + OverlayRangesOutline::~OverlayRangesOutline() + { + if( getOverlayManager() ) + { + getOverlayManager()->remove(*this); + } + } + + void OverlayRangesOutline::setRanges(const std::vector< basegfx::B2DRange >& rNew) + { + if(rNew != maRanges) + { + maRanges = 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 000000000..3621f7fe6 --- /dev/null +++ b/sw/source/core/crsr/overlayrangesoutline.hxx @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#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 +{ + namespace 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, + const 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(const std::vector< basegfx::B2DRange >& rNew); + }; + } // end of namespace overlay +} // end of namespace sw + +#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 000000000..d1fccd357 --- /dev/null +++ b/sw/source/core/crsr/pam.cxx @@ -0,0 +1,1171 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <string_view> + +#include <tools/gen.hxx> +#include <editeng/protitem.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 <xmloff/odffields.hxx> +#include <rtl/ustrbuf.hxx> + +#include <editsh.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 SwIndex & rContent ) + : nNode( rNodeIndex ), nContent( rContent ) +{ +} + +SwPosition::SwPosition( const SwNodeIndex & rNodeIndex ) + : nNode( rNodeIndex ), nContent( nNode.GetNode().GetContentNode() ) +{ +} + +SwPosition::SwPosition( const SwNode& rNode ) + : nNode( rNode ), nContent( nNode.GetNode().GetContentNode() ) +{ +} + +SwPosition::SwPosition( SwContentNode & rNode, const sal_Int32 nOffset ) + : nNode( rNode ), nContent( &rNode, nOffset ) +{ +} + +bool SwPosition::operator<(const SwPosition &rPos) const +{ + if( nNode < rPos.nNode ) + return true; + if( nNode == rPos.nNode ) + { + // note that positions with text node but no SwIndex registered are + // created for text frames anchored at para (see SwXFrame::getAnchor()) + SwIndexReg const*const pThisReg(nContent.GetIdxReg()); + SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg()); + if (pThisReg && pOtherReg) + { + return (nContent < rPos.nContent); + } + else // by convention position with no index is smaller + { + return pOtherReg != nullptr; + } + } + return false; +} + +bool SwPosition::operator>(const SwPosition &rPos) const +{ + if(nNode > rPos.nNode ) + return true; + if( nNode == rPos.nNode ) + { + // note that positions with text node but no SwIndex registered are + // created for text frames anchored at para (see SwXFrame::getAnchor()) + SwIndexReg const*const pThisReg(nContent.GetIdxReg()); + SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg()); + if (pThisReg && pOtherReg) + { + return (nContent > rPos.nContent); + } + else // by convention position with no index is smaller + { + return pThisReg != nullptr; + } + } + return false; +} + +bool SwPosition::operator<=(const SwPosition &rPos) const +{ + if(nNode < rPos.nNode ) + return true; + if( nNode == rPos.nNode ) + { + // note that positions with text node but no SwIndex registered are + // created for text frames anchored at para (see SwXFrame::getAnchor()) + SwIndexReg const*const pThisReg(nContent.GetIdxReg()); + SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg()); + if (pThisReg && pOtherReg) + { + return (nContent <= rPos.nContent); + } + else // by convention position with no index is smaller + { + return pThisReg == nullptr; + } + } + return false; +} + +bool SwPosition::operator>=(const SwPosition &rPos) const +{ + if(nNode > rPos.nNode ) + return true; + if( nNode == rPos.nNode ) + { + // note that positions with text node but no SwIndex registered are + // created for text frames anchored at para (see SwXFrame::getAnchor()) + SwIndexReg const*const pThisReg(nContent.GetIdxReg()); + SwIndexReg const*const pOtherReg(rPos.nContent.GetIdxReg()); + if (pThisReg && pOtherReg) + { + return (nContent >= rPos.nContent); + } + else // by convention position with no index is smaller + { + return pOtherReg == nullptr; + } + } + return false; +} + +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 nNode.GetNode().GetDoc(); +} + +void SwPosition::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwPosition")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nNode"), BAD_CAST(OString::number(nNode.GetIndex()).getStr())); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nContent"), BAD_CAST(OString::number(nContent.GetIndex()).getStr())); + xmlTextWriterEndElement(pWriter); +} + +std::ostream &operator <<(std::ostream& s, const SwPosition& position) +{ + return s << "SwPosition (node " << position.nNode.GetIndex() << ", offset " << position.nContent.GetIndex() << ")"; +} + +namespace { + +enum CHKSECTION { Chk_Both, Chk_One, Chk_None }; + +} + +static CHKSECTION lcl_TstIdx( sal_uLong nSttIdx, sal_uLong nEndIdx, const SwNode& rEndNd ) +{ + sal_uLong 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, sal_uLong nStt, sal_uLong 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; + } + + sal_uLong 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 SwNodeIndex& rStt, + const SwNodeIndex& rEnd, bool bChkSection ) +{ + const SwNodes& rNds = rStt.GetNodes(); + sal_uLong 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, SwIndex * pIdx, sal_uInt16 nMode ) +{ + if( pNd->IsContentNode() ) + return static_cast<SwContentNode*>(pNd)->GoNext( pIdx, nMode ); + return false; +} + +bool GoPrevious( SwNode* pNd, SwIndex * pIdx, sal_uInt16 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 && 1 != aIdx.GetIndex() - pIdx->GetIndex() && + !CheckNodesRange( *pIdx, aIdx, 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 && 1 != pIdx->GetIndex() - aIdx.GetIndex() && + !CheckNodesRange( *pIdx, aIdx, true ) ) + pNd = nullptr; + else + *pIdx = aIdx; + } + return pNd; +} + +SwPaM::SwPaM( const SwPosition& rPos, SwPaM* pRing ) + : Ring( pRing ) + , m_Bound1( rPos ) + , m_Bound2( rPos.nNode.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, + long nMarkOffset, long 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.nNode.GetNode().GetContentNode(), 0 ); + m_Bound2.nContent.Assign( m_Bound2.nNode.GetNode().GetContentNode(), 0 ); +} + +SwPaM::SwPaM( const SwNode& rMark, const SwNode& rPoint, + long nMarkOffset, long 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.nNode.GetNode().GetContentNode(), 0 ); + m_Bound2.nContent.Assign( m_Bound2.nNode.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->nNode.GetNode().GetContentNode(), + nPointContent); + m_pMark ->nContent.Assign( m_pMark ->nNode.GetNode().GetContentNode(), + nMarkContent ); +} + +SwPaM::SwPaM( const SwNode& rNode, sal_Int32 nContent, SwPaM* pRing ) + : Ring( pRing ) + , m_Bound1( rNode ) + , m_Bound2( m_Bound1.nNode.GetNode().GetNodes() ) // default initialize + , m_pPoint( &m_Bound1 ) + , m_pMark( &m_Bound1 ) + , m_bIsInFrontOfLabel( false ) +{ + m_pPoint->nContent.Assign( m_pPoint->nNode.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() {} + +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; +} + +#ifdef DBG_UTIL +void SwPaM::Exchange() +{ + if (m_pPoint != m_pMark) + { + SwPosition *pTmp = m_pPoint; + m_pPoint = m_pMark; + m_pMark = pTmp; + } +} +#endif + +/// 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. + + @return Newly created range, in Ring with parameter pOrigRg. +*/ +std::unique_ptr<SwPaM> MakeRegion(SwMoveFnCollection const & fnMove, + const SwPaM & rOrigRg) +{ + std::unique_ptr<SwPaM> pPam; + { + pPam.reset(new SwPaM(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( (pPam->GetMark()->*fnMove.fnCmpOp)( *pPam->GetPoint() ) ) + pPam->Exchange(); + } + return pPam; +} + +} // 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->nNode.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 ) const +{ + bool bRet = false; + + const SwContentNode* pNd = GetPoint()->nNode.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()->nNode.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 ) + { + sal_uLong nSttIdx = GetMark()->nNode.GetIndex(), + nEndIdx = GetPoint()->nNode.GetIndex(); + if( nEndIdx <= nSttIdx ) + { + sal_uLong nTmp = nSttIdx; + nSttIdx = nEndIdx; + nEndIdx = nTmp; + } + + // If a protected section should be between nodes, then the + // selection needs to contain already x nodes. + // (TextNd, SectNd, TextNd, EndNd, TextNd ) + if( nSttIdx + 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?" ); + sal_uLong nIdx = rContent.GetContentIdx()->GetIndex(); + if( nSttIdx <= nIdx && nEndIdx >= nIdx && + rContent.GetContentIdx()->GetNode().GetNodes().IsDocNodes() ) + { + bRet = true; + break; + } + } + } + } + } + } + + const SwDoc *pDoc = GetDoc(); + // Legacy text/combo/checkbox: never return read-only when inside these form fields. + const IDocumentMarkAccess* pMarksAccess = pDoc->getIDocumentMarkAccess(); + sw::mark::IFieldmark* pA = GetPoint() ? pMarksAccess->getFieldmarkFor( *GetPoint( ) ) : nullptr; + sw::mark::IFieldmark* pB = GetMark() ? pMarksAccess->getFieldmarkFor( *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 (!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 && pDoc->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) + { + // Paragraph Signatures and Classification fields are read-only. + if (pDoc && pDoc->GetEditShell()) + bRet = pDoc->GetEditShell()->IsCursorInParagraphMetadataField(); + } + + if (!bRet && + pDoc->getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_BOOKMARKS)) + { + if (pDoc->getIDocumentMarkAccess()->isBookmarkDeleted(*this)) + { + return true; + } + } + if (!bRet && + pDoc->getIDocumentSettingAccess().get(DocumentSettingId::PROTECT_FIELDS)) + { + SwPosition const& rStart(*Start()); + SwPosition const& rEnd(*End()); + for (SwNodeIndex n = rStart.nNode; n <= rEnd.nNode; ++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.nNode && pHint->GetStart() < rStart.nContent.GetIndex()) + { + continue; // before selection + } + if (n == rEnd.nNode && rEnd.nContent.GetIndex() <= 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; + } + } + } + } + } + } + + 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.GetContentNode(); + 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.nNode.GetNodes(); + + // go to next/previous ContentNode + while( true ) + { + if (i_pLayout && aPos.nNode.GetNode().IsTextNode()) + { + auto const fal(sw::GetFirstAndLastNode(*pLayout, aPos.nNode)); + aPos.nNode = bSrchForward ? *fal.second : *fal.first; + } + + pNd = bSrchForward + ? rNodes.GoNextSection( &aPos.nNode, true, !bInReadOnly ) + : SwNodes::GoPrevSection( &aPos.nNode, true, !bInReadOnly ); + if( pNd ) + { + aPos.nContent.Assign( pNd, ::GetSttOrEnd( bSrchForward,*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->nNode.GetNodes(); + pPos->nNode = *rNodes.GetEndOfContent().StartOfSectionNode(); + // we always need to find a ContentNode! + SwContentNode* pCNd = rNodes.GoNext( &pPos->nNode ); + if( pCNd ) + pCNd->MakeStartIndex( &pPos->nContent ); +} + +void GoEndDoc( SwPosition * pPos ) +{ + SwNodes& rNodes = pPos->nNode.GetNodes(); + pPos->nNode = rNodes.GetEndOfContent(); + SwContentNode* pCNd = GoPreviousNds( &pPos->nNode, true ); + if( pCNd ) + pCNd->MakeEndIndex( &pPos->nContent ); +} + +void GoStartSection( SwPosition * pPos ) +{ + // jump to section's beginning + SwNodes& rNodes = pPos->nNode.GetNodes(); + sal_uInt16 nLevel = SwNodes::GetSectionLevel( pPos->nNode ); + if( pPos->nNode < rNodes.GetEndOfContent().StartOfSectionIndex() ) + nLevel--; + do { SwNodes::GoStartOfSection( &pPos->nNode ); } while( nLevel-- ); + + // already on a ContentNode + pPos->nNode.GetNode().GetContentNode()->MakeStartIndex( &pPos->nContent ); +} + +/// go to the end of the current base section +void GoEndSection( SwPosition * pPos ) +{ + // jump to section's beginning/end + SwNodes& rNodes = pPos->nNode.GetNodes(); + sal_uInt16 nLevel = SwNodes::GetSectionLevel( pPos->nNode ); + if( pPos->nNode < rNodes.GetEndOfContent().StartOfSectionIndex() ) + nLevel--; + do { SwNodes::GoEndOfSection( &pPos->nNode ); } while( nLevel-- ); + + // now on an EndNode, thus to the previous ContentNode + if( GoPreviousNds( &pPos->nNode, true ) ) + pPos->nNode.GetNode().GetContentNode()->MakeEndIndex( &pPos->nContent ); +} + +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.fnNds)( &rPam.GetPoint()->nNode, true ); + if( pNd ) + rPam.GetPoint()->nContent.Assign( pNd, + ::GetSttOrEnd( &fnMove == &fnMoveForward, *pNd ) ); + return pNd; +} + +bool GoInContent( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(), + &rPam.GetPoint()->nContent, CRSR_SKIP_CHARS )) + return true; + return GoInNode( rPam, fnMove ); +} + +bool GoInContentCells( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(), + &rPam.GetPoint()->nContent, CRSR_SKIP_CELLS )) + return true; + return GoInNode( rPam, fnMove ); +} + +bool GoInContentSkipHidden( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(), + &rPam.GetPoint()->nContent, CRSR_SKIP_CHARS | CRSR_SKIP_HIDDEN ) ) + return true; + return GoInNode( rPam, fnMove ); +} + +bool GoInContentCellsSkipHidden( SwPaM & rPam, SwMoveFnCollection const & fnMove ) +{ + if( (*fnMove.fnNd)( &rPam.GetPoint()->nNode.GetNode(), + &rPam.GetPoint()->nContent, CRSR_SKIP_CELLS | CRSR_SKIP_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.nNode.GetNode().GetContentNode(); + rPos.nContent.Assign( pNd, + ::GetSttOrEnd( &aPosPara == &fnMoveForward, *pNd ) ); + return true; + } + return false; +} + +bool GoCurrPara( SwPaM & rPam, SwMoveFnCollection const & aPosPara ) +{ + SwPosition& rPos = *rPam.GetPoint(); + SwContentNode * pNd = rPos.nNode.GetNode().GetContentNode(); + if( pNd ) + { + const sal_Int32 nOld = rPos.nContent.GetIndex(); + const sal_Int32 nNew = &aPosPara == &fnMoveForward ? 0 : pNd->Len(); + // if already at beginning/end then to the next/previous + if( nOld != nNew ) + { + rPos.nContent.Assign( pNd, nNew ); + return true; + } + } + // move node to next/previous ContentNode + if( ( &aPosPara==&fnParaStart && nullptr != ( pNd = + GoPreviousNds( &rPos.nNode, true ))) || + ( &aPosPara==&fnParaEnd && nullptr != ( pNd = + GoNextNds( &rPos.nNode, true ))) ) + { + rPos.nContent.Assign( pNd, + ::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.nNode.GetNode().GetContentNode(); + rPos.nContent.Assign( pNd, + ::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.nNode ); + SwContentNode *pNd; + if( nullptr == ( pNd = rPos.nNode.GetNode().GetContentNode()) && + nullptr == ( pNd = (*fnMove.fnNds)( &rPos.nNode, true )) ) + { + rPos = aSavePos; // do not change cursor + return false; + } + + rPos.nContent.Assign( pNd, + ::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()->nContent.GetIndex() + : 0; + const sal_Int32 nEnd = bIsEndNode + ? End()->nContent.GetIndex() + : aTmpStr.getLength(); + + aResult.append(std::u16string_view(aTmpStr).substr(nStart, nEnd-nStart)); + } + else + { + aResult.append(aTmpStr); + } + } + + if (bIsEndNode) + { + break; + } + + ++aNodeIndex; + bIsStartNode = false; + } + + return aResult.makeStringAndClear(); +} + +void SwPaM::InvalidatePaM() +{ + for (SwNodeIndex index = Start()->nNode; index <= End()->nNode; ++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()->nContent.GetIndex() : 0); + // this should work even for length of 0 + SwUpdateAttr const aHint( + nStart, + index == End()->nNode + ? End()->nContent.GetIndex() - nStart + : pTextNode->Len() - nStart, + 0); + pTextNode->CallSwClientNotify(sw::LegacyModifyHint(&aHint, &aHint)); + } + // other node types not invalidated + } +} + +void SwPaM::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("SwPaM")); + + xmlTextWriterStartElement(pWriter, BAD_CAST("point")); + GetPoint()->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + + if (HasMark()) + { + xmlTextWriterStartElement(pWriter, BAD_CAST("mark")); + GetMark()->dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); + } + + 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 000000000..bbc975acf --- /dev/null +++ b/sw/source/core/crsr/paminit.cxx @@ -0,0 +1,61 @@ +/* -*- 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> + +static const SwMoveFnCollection aFwrd = { + /* fnNd */ &GoNext, + /* fnNds */ &GoNextNds, + /* fnDoc */ &GoEndDoc, + /* fnSections */ &GoEndSection, + /* fnCmpOp */ &SwPosition::operator<, + /* fnGetHint */ &GetFrwrdTextHint, + /* fnSearch */ &utl::TextSearch::SearchForward, + /* fnSection */ &SwNodes::GoStartOfSection +}; + +static const SwMoveFnCollection aBwrd = { + /* fnNd */ &GoPrevious, + /* fnNds */ &GoPreviousNds, + /* fnDoc */ &GoStartDoc, + /* fnSections */ &GoStartSection, + /* fnCmpOp */ &SwPosition::operator>, + /* fnGetHint */ &GetBkwrdTextHint, + /* fnSearch */ &utl::TextSearch::SearchBackward, + /* fnSection */ &SwNodes::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 000000000..e1237d8cf --- /dev/null +++ b/sw/source/core/crsr/swcrsr.cxx @@ -0,0 +1,2569 @@ +/* -*- 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 <bookmrk.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> + +using namespace ::com::sun::star::i18n; + +static 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) + { + if( ( bBack = (nStt > nEnd )) ) + { + sal_uLong n = nStt; nStt = nEnd; nEnd = n; + } + ::StartProgress( STR_STATSTR_SEARCH, nStt, nEnd ); + } + + explicit PercentHdl( const SwPaM& rPam ) + : pDSh( rPam.GetDoc()->GetDocShell() ) + { + sal_uLong nStt, nEnd; + if( rPam.GetPoint()->nNode == rPam.GetMark()->nNode ) + { + bNodeIdx = false; + nStt = rPam.GetMark()->nContent.GetIndex(); + nEnd = rPam.GetPoint()->nContent.GetIndex(); + } + else + { + bNodeIdx = true; + nStt = rPam.GetMark()->nNode.GetIndex(); + nEnd = rPam.GetPoint()->nNode.GetIndex(); + } + nActPos = nStt; + if( ( bBack = (nStt > nEnd )) ) + { + sal_uLong n = nStt; nStt = nEnd; nEnd = n; + } + ::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_uLong nPos; + if( bNodeIdx ) + nPos = rPos.nNode.GetIndex(); + else + nPos = rPos.nContent.GetIndex(); + ::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()->nNode.GetIndex() < + 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, GetPoint()->nNode, true )) + { + GetPoint()->nNode = aOldPos; + GetPoint()->nContent.Assign( GetContentNode(), GetSavePos()->nContent ); + return true; + } + } + return SwCursor::IsSelOvrCheck(eFlags); +} + +namespace +{ + const SwTextAttr* InputFieldAtPos(SwPosition const *pPos) + { + SwTextNode* pTextNd = pPos->nNode.GetNode().GetTextNode(); + if (!pTextNd) + return nullptr; + return pTextNd->GetTextAttrAt(pPos->nContent.GetIndex(), RES_TXTATR_INPUTFIELD, SwTextNode::PARENT); + } +} + +bool SwCursor::IsSelOvr( SwCursorSelOverFlags eFlags ) +{ + SwDoc* pDoc = GetDoc(); + SwNodes& rNds = pDoc->GetNodes(); + + bool bSkipOverHiddenSections = IsSkipOverHiddenSections(); + bool bSkipOverProtectSections = IsSkipOverProtectSections(); + + if ( IsSelOvrCheck( eFlags ) ) + { + return true; + } + + if (m_vSavePos.back().nNode != GetPoint()->nNode.GetIndex() && + // (1997) in UI-ReadOnly everything is allowed + ( !pDoc->GetDocShell() || !pDoc->GetDocShell()->IsReadOnlyUI() )) + { + // check new sections + SwNodeIndex& rPtIdx = GetPoint()->nNode; + const SwSectionNode* pSectNd = rPtIdx.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( rPtIdx ); + sal_Int32 nContentPos = m_vSavePos.back().nContent; + bool bGoNxt = m_vSavePos.back().nNode < rPtIdx.GetIndex(); + SwContentNode* pCNd = bGoNxt + ? rNds.GoNextSection( &rPtIdx, bSkipOverHiddenSections, bSkipOverProtectSections) + : SwNodes::GoPrevSection( &rPtIdx, bSkipOverHiddenSections, bSkipOverProtectSections); + if( !pCNd && ( SwCursorSelOverFlags::EnableRevDirection & eFlags )) + { + bGoNxt = !bGoNxt; + pCNd = bGoNxt ? rNds.GoNextSection( &rPtIdx, bSkipOverHiddenSections, bSkipOverProtectSections) + : SwNodes::GoPrevSection( &rPtIdx, bSkipOverHiddenSections, bSkipOverProtectSections); + } + + bool bIsValidPos = nullptr != pCNd; + const bool bValidNodesRange = bIsValidPos && + ::CheckNodesRange( rPtIdx, aIdx, true ); + if( !bValidNodesRange ) + { + rPtIdx = m_vSavePos.back().nNode; + if( nullptr == ( pCNd = rPtIdx.GetNode().GetContentNode() ) ) + { + bIsValidPos = false; + nContentPos = 0; + rPtIdx = aIdx; + if( nullptr == ( pCNd = rPtIdx.GetNode().GetContentNode() ) ) + { + // then to the beginning of the document + rPtIdx = rNds.GetEndOfExtras(); + pCNd = rNds.GoNext( &rPtIdx ); + } + } + } + + // register ContentIndex: + const sal_Int32 nTmpPos = bIsValidPos ? (bGoNxt ? 0 : pCNd->Len()) : nContentPos; + GetPoint()->nContent.Assign( pCNd, nTmpPos ); + if( !bIsValidPos || !bValidNodesRange || + IsInProtectTable( true ) ) + return true; + } + + // is there a protected section in the section? + if( HasMark() && bSkipOverProtectSections) + { + sal_uLong nSttIdx = GetMark()->nNode.GetIndex(), + nEndIdx = GetPoint()->nNode.GetIndex(); + if( nEndIdx <= nSttIdx ) + { + sal_uLong nTmp = nSttIdx; + nSttIdx = nEndIdx; + nEndIdx = nTmp; + } + + const SwSectionFormats& rFormats = pDoc->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?" ); + sal_uLong 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()->nNode.GetNode(); + if( pNd->IsContentNode() && !dynamic_cast<SwUnoCursor*>(this) ) + { + const SwContentFrame* pFrame = static_cast<const SwContentNode*>(pNd)->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ); + 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 + SwNodeIndex& rPtIdx = GetPoint()->nNode; + bool bGoNxt = m_vSavePos.back().nNode < rPtIdx.GetIndex(); + for (;;) + { + pFrame = bGoNxt ? pFrame->GetNextContentFrame() : pFrame->GetPrevContentFrame(); + 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( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ); + while ( pFrame && 0 == pFrame->getFrameArea().Height() ) + { + pFrame = bGoNxt ? pFrame->GetNextContentFrame() + : pFrame->GetPrevContentFrame(); + } + } + + 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 + rPtIdx = *pCNd; + // assign corresponding ContentIndex + const sal_Int32 nTmpPos = bGoNxt ? 0 : pCNd->Len(); + GetPoint()->nContent.Assign( pCNd, nTmpPos ); + } + + + if (rPtIdx.GetIndex() == m_vSavePos.back().nNode + && GetPoint()->nContent.GetIndex() == 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 ) + { + 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()->nNode, GetPoint()->nNode, true )) + { + DeleteMark(); + RestoreSavePos(); + return true; // we need a frame + } + + pNd = &GetMark()->nNode.GetNode(); + if( pNd->IsContentNode() + && !static_cast<const SwContentNode*>(pNd)->getLayoutFrame( pDoc->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 sal_uLong nRefNodeIdx = + ( SwCursorSelOverFlags::Toggle & eFlags ) + ? m_vSavePos.back().nNode + : GetMark()->nNode.GetIndex(); + const sal_Int32 nRefContentIdx = + ( SwCursorSelOverFlags::Toggle & eFlags ) + ? m_vSavePos.back().nContent + : GetMark()->nContent.GetIndex(); + const bool bIsForwardSelection = + nRefNodeIdx < GetPoint()->nNode.GetIndex() + || ( nRefNodeIdx == GetPoint()->nNode.GetIndex() + && nRefContentIdx < GetPoint()->nContent.GetIndex() ); + + if ( pInputFieldTextAttrAtPoint != nullptr ) + { + const sal_Int32 nNewPointPos = + bIsForwardSelection ? *(pInputFieldTextAttrAtPoint->End()) : pInputFieldTextAttrAtPoint->GetStart(); + SwTextNode* pTextNdAtPoint = GetPoint()->nNode.GetNode().GetTextNode(); + GetPoint()->nContent.Assign( pTextNdAtPoint, nNewPointPos ); + } + + if ( pInputFieldTextAttrAtMark != nullptr ) + { + const sal_Int32 nNewMarkPos = + bIsForwardSelection ? pInputFieldTextAttrAtMark->GetStart() : *(pInputFieldTextAttrAtMark->End()); + SwTextNode* pTextNdAtMark = GetMark()->nNode.GetNode().GetTextNode(); + GetMark()->nContent.Assign( pTextNdAtMark, nNewMarkPos ); + } + } + } + + const SwTableNode* pPtNd = GetPoint()->nNode.GetNode().FindTableNode(); + const SwTableNode* pMrkNd = GetMark()->nNode.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()->nNode.GetIndex() < + ((SwCursorSelOverFlags::Toggle & eFlags) + ? m_vSavePos.back().nNode : GetMark()->nNode.GetIndex()); + + do { // loop for table after table + sal_uLong nSEIdx = pPtNd->EndOfSectionIndex(); + sal_uLong nSttEndTable = nSEIdx + 1; + + if( bSelTop ) + nSttEndTable = rNds[ nSEIdx ]->StartOfSectionIndex() - 1; + + GetPoint()->nNode = nSttEndTable; + const SwNode* pMyNd = &(GetNode()); + + if( pMyNd->IsSectionNode() || ( pMyNd->IsEndNode() && + pMyNd->StartOfSectionNode()->IsSectionNode() ) ) + { + pMyNd = bSelTop + ? SwNodes::GoPrevSection( &GetPoint()->nNode,true,false ) + : rNds.GoNextSection( &GetPoint()->nNode,true,false ); + + /* #i12312# Handle failure of Go{Prev|Next}Section */ + if ( nullptr == pMyNd) + break; + + if( nullptr != ( pPtNd = pMyNd->FindTableNode() )) + continue; + } + + // we permit these + if( pMyNd->IsContentNode() && + ::CheckNodesRange( GetMark()->nNode, + GetPoint()->nNode, 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()->nContent.Assign( pCNd, 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 = GetContentNode(); + 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()->nNode.GetIndex()) + 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()->nNode.GetIndex()) + { + // search next valid box + // if there is another StartNode after the EndNode of a cell then + // there is another cell + SwNodeIndex aCellStt( *GetNode().FindTableBoxStartNode()->EndOfSectionNode(), 1 ); + bool bProt = true; +GoNextCell: + for (;;) { + if( !aCellStt.GetNode().IsStartNode() ) + break; + ++aCellStt; + if( nullptr == ( pCNd = aCellStt.GetNode().GetContentNode() )) + pCNd = aCellStt.GetNodes().GoNext( &aCellStt ); + bProt = pCNd->IsProtect(); + if( !bProt ) + break; + aCellStt.Assign( *pCNd->FindTableBoxStartNode()->EndOfSectionNode(), 1 ); + } + +SetNextCursor: + if( !bProt ) // found free cell + { + GetPoint()->nNode = aCellStt; + SwContentNode* pTmpCNd = GetContentNode(); + if( pTmpCNd ) + { + GetPoint()->nContent.Assign( pTmpCNd, 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( *GetNode().FindTableBoxStartNode(), -1 ); + SwNode* pNd; + bool bProt = true; +GoPrevCell: + for (;;) { + pNd = &aCellStt.GetNode(); + if( !pNd->IsEndNode() ) + break; + aCellStt.Assign( *pNd->StartOfSectionNode(), +1 ); + if( nullptr == ( pCNd = aCellStt.GetNode().GetContentNode() )) + pCNd = pNd->GetNodes().GoNext( &aCellStt ); + bProt = pCNd->IsProtect(); + if( !bProt ) + break; + aCellStt.Assign( *pNd->FindTableBoxStartNode(), -1 ); + } + +SetPrevCursor: + if( !bProt ) // found free cell + { + GetPoint()->nNode = aCellStt; + SwContentNode* pTmpCNd = GetContentNode(); + if( pTmpCNd ) + { + GetPoint()->nContent.Assign( pTmpCNd, 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* pDoc = GetDoc(); + const SwPosition* pPos = bPoint ? GetPoint() : GetMark(); + const SwNode* pNd = &pPos->nNode.GetNode(); + + if( pNd->IsContentNode() && !static_cast<const SwContentNode*>(pNd)->getLayoutFrame( pDoc->getIDocumentLayoutAccess().GetCurrentLayout() ) && + !dynamic_cast<const SwUnoCursor*>(this) ) + { + return false; + } + + // #i45129# - in UI-ReadOnly everything is allowed + if( !pDoc->GetDocShell() || !pDoc->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_uLong lcl_FindSelection( SwFindParas& rParas, SwCursor* pCurrentCursor, + SwMoveFnCollection const & fnMove, SwCursor*& pFndRing, + SwPaM& aRegion, FindRanges eFndRngs, + bool bInReadOnly, bool& bCancel ) +{ + SwDoc* pDoc = pCurrentCursor->GetDoc(); + bool const bDoesUndo = pDoc->GetIDocumentUndoRedo().DoesUndo(); + int nFndRet = 0; + sal_uLong 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, pDoc->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) + && pDoc->GetIDocumentUndoRedo().DoesUndo() + && rParas.IsReplaceMode()) + { + short nRet = pCurrentCursor->MaxReplaceArived(); + if( RET_YES == nRet ) + { + pDoc->GetIDocumentUndoRedo().DelAllUndoObj(); + pDoc->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(); + + pDoc->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()->nNode = rSttNd; + pCNd = rNds.GoNext( &rPam.GetPoint()->nNode ); + if( !pCNd ) + return false; + pCNd->MakeStartIndex( &rPam.GetPoint()->nContent ); + } + else if( rSttNd.GetIndex() > rPam.GetPoint()->nNode.GetIndex() || + rPam.GetPoint()->nNode.GetIndex() >= rEndNd.GetIndex() ) + // not in this section + return false; + + rPam.SetMark(); + rPam.GetPoint()->nNode = rEndNd; + pCNd = SwNodes::GoPrevious( &rPam.GetPoint()->nNode ); + if( !pCNd ) + return false; + pCNd->MakeEndIndex( &rPam.GetPoint()->nContent ); + + 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()->nNode = rSttNd; + pCNd = SwNodes::GoPrevious( &rPam.GetPoint()->nNode ); + if( !pCNd ) + return false; + pCNd->MakeEndIndex( &rPam.GetPoint()->nContent ); + } + else if( rEndNd.GetIndex() > rPam.GetPoint()->nNode.GetIndex() || + rPam.GetPoint()->nNode.GetIndex() >= rSttNd.GetIndex() ) + return false; // not in this section + + rPam.SetMark(); + rPam.GetPoint()->nNode = rEndNd; + pCNd = rNds.GoNext( &rPam.GetPoint()->nNode ); + if( !pCNd ) + return false; + pCNd->MakeStartIndex( &rPam.GetPoint()->nContent ); + + 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_uLong 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_uLong 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) + if( 0 == ( nFound = lcl_FindSelection( rParas, this, fnMove, + pFndRing, aRegion, eFndRngs, + bInReadOnly, bCancel ) )) + 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()->nNode.GetIndex() ) + : lcl_MakeSelFwrd( *rNds.GetEndOfPostIts().StartOfSectionNode(), + rNds.GetEndOfExtras(), *this, + rNds.GetEndOfExtras().GetIndex() >= + GetPoint()->nNode.GetIndex() )) + { + 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.nNode = *rNds.GetEndOfContent().StartOfSectionNode(); + pCNd = rNds.GoNext( &rPos.nNode ); + break; + case SwDocPositions::End: + rPos.nNode = rNds.GetEndOfContent(); + pCNd = SwNodes::GoPrevious( &rPos.nNode ); + bIsStart = false; + break; + case SwDocPositions::OtherStart: + rPos.nNode = *rNds[ sal_uLong(0) ]; + pCNd = rNds.GoNext( &rPos.nNode ); + break; + case SwDocPositions::OtherEnd: + rPos.nNode = *rNds.GetEndOfContent().StartOfSectionNode(); + pCNd = SwNodes::GoPrevious( &rPos.nNode ); + bIsStart = false; + break; + default: + rPos = *GetPoint(); + } + + if( pCNd ) + { + rPos.nContent.Assign( pCNd, bIsStart ? 0 : pCNd->Len() ); + } +} + +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->IsHideRedlines()) + { + 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 = GetNode().GetTextNode(); + if (pTextNd) + { + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + 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 = GetNode().GetTextNode(); + if (pTextNd) + { + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + 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 = GetNode().GetTextNode(); + if (pTextNd) + { + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + 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 ? + GetContentNode() && GetPoint()->nContent == GetContentNode()->Len() : + GetPoint()->nContent.GetIndex() == 0; + + if ((pLayout != nullptr && pLayout->IsHideRedlines()) || !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 = GetNode().GetTextNode(); + if (pTextNd) + { + SwCursorSaveState aSave( *this ); + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + 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() = SwPosition(*pTextNd, nPtPos); + if( !IsSelOvr() ) + bRet = true; + } + } + return bRet; +} + +bool SwCursor::GoEndWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + SwCursorSaveState aSave( *this ); + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + 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()->nContent.GetIndex() != nPtPos ) + { + *GetPoint() = SwPosition(*pTextNd, nPtPos); + if( !IsSelOvr() ) + bRet = true; + } + } + return bRet; +} + +bool SwCursor::GoNextWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + SwCursorSaveState aSave( *this ); + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + 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() = SwPosition(*pTextNd, nPtPos); + if( !IsSelOvr() ) + bRet = true; + } + } + return bRet; +} + +bool SwCursor::GoPrevWordWT(sal_Int16 nWordType, SwRootFrame const*const pLayout) +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + SwCursorSaveState aSave( *this ); + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + 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() = SwPosition(*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 = GetNode().GetTextNode(); + if (pTextNd) + { + // Should we select the whole fieldmark? + const IDocumentMarkAccess* pMarksAccess = GetDoc()->getIDocumentMarkAccess( ); + sw::mark::IFieldmark const*const pMark(pMarksAccess->getFieldmarkFor(*GetPoint())); + if (pMark && (IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK + || IDocumentMarkAccess::GetType(*pMark) == IDocumentMarkAccess::MarkType::DATE_FIELDMARK)) + { + *GetPoint() = sw::mark::FindFieldSep(*pMark); + ++GetPoint()->nContent; // Don't select the separator + + const SwPosition& rEnd = pMark->GetMarkEnd(); + + assert(pMark->GetMarkEnd() != *GetPoint()); + SetMark(); + GetMark()->nNode = rEnd.nNode; + GetMark()->nContent = rEnd.nContent; + --GetMark()->nContent; // Don't select the end delimiter + + bRet = true; + } + else + { + bool bForward = true; + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + 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() = SwPosition(*pEndNode, nEndIndex); + if( !IsSelOvr() ) + { + SetMark(); + *GetMark() = SwPosition(*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()->nNode == pAnnotationMark->GetMarkStart().nNode && + GetMark()->nContent == pAnnotationMark->GetMarkStart().nContent; + bool bEndMatch = GetPoint()->nNode == pAnnotationMark->GetMarkEnd().nNode && + GetPoint()->nContent.GetIndex() + 1 == pAnnotationMark->GetMarkEnd().nContent.GetIndex(); + if (bStartMatch && bEndMatch) + ++GetPoint()->nContent; + } + 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()->nNode > pTextNd->GetIndex() ) + 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, OUString(CH_TXTATR_INWORD) ); + } + } + } + aRes = sNodeText; + } + return aRes; +} + +bool SwCursor::GoSentence(SentenceMoveType eMoveType, SwRootFrame const*const pLayout) +{ + bool bRet = false; + SwTextNode* pTextNd = GetNode().GetTextNode(); + if (pTextNd) + { + OUString const sNodeText(lcl_MaskDeletedRedlines(pTextNd)); + + SwCursorSaveState aSave( *this ); + sal_Int32 nPtPos = GetPoint()->nContent.GetIndex(); + + { + 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() = SwPosition(*pTextNd, nPtPos); + if( !IsSelOvr() ) + bRet = true; + } + } + return bRet; +} + +bool SwCursor::ExpandToSentenceBorders(SwRootFrame const*const pLayout) +{ + bool bRes = false; + SwTextNode* pStartNd = Start()->nNode.GetNode().GetTextNode(); + SwTextNode* pEndNd = End()->nNode.GetNode().GetTextNode(); + if (pStartNd && pEndNd) + { + if (!HasMark()) + SetMark(); + + OUString sStartText( lcl_MaskDeletedRedlines( pStartNd ) ); + OUString sEndText( pStartNd == pEndNd? sStartText : lcl_MaskDeletedRedlines( pEndNd ) ); + + SwCursorSaveState aSave( *this ); + sal_Int32 nStartPos = Start()->nContent.GetIndex(); + sal_Int32 nEndPos = End()->nContent.GetIndex(); + + { + 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 + bool bChanged = false; + if (nStartPos <= pStartNd->GetText().getLength() && nStartPos >= 0) + { + *GetMark() = SwPosition(*pStartNd, nStartPos); + bChanged = true; + } + if (nEndPos <= pEndNd->GetText().getLength() && nEndPos >= 0) + { + *GetPoint() = SwPosition(*pEndNd, nEndPos); + bChanged = true; + } + if (bChanged && !IsSelOvr()) + bRes = true; + } + return bRes; +} + +bool SwTableCursor::LeftRight( bool bLeft, sal_uInt16 nCnt, sal_uInt16 /*nMode*/, + bool /*bVisualAllowed*/, bool /*bSkipHidden*/, bool /*bInsertCursor*/, + SwRootFrame const*) +{ + 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()->nNode.GetNode(); + + if( rNode.IsTextNode() ) + { + const SwTextNode& rTNd = *rNode.GetTextNode(); + SwIndex& rIdx = GetPoint()->nContent; + sal_Int32 nPos = rIdx.GetIndex(); + + const SvtCTLOptions& rCTLOptions = SW_MOD()->GetCTLOptions(); + if ( bVisualAllowed && rCTLOptions.IsCTLFontEnabled() && + SvtCTLOptions::MOVEMENT_VISUAL == + rCTLOptions.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, sal_uInt16 nMode, + bool bVisualAllowed,bool bSkipHidden, bool bInsertCursor, + SwRootFrame const*const pLayout) +{ + // calculate cursor bidi level + SwNode& rNode = GetPoint()->nNode.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 = CRSR_SKIP_CELLS == nMode ? GoInContentCellsSkipHidden : GoInContentSkipHidden; + else + fnGo = CRSR_SKIP_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()->nNode ); + + TextFrameIndex beforeIndex(-1); + if (pFrame) + { + beforeIndex = pFrame->MapModelToViewPos(*GetPoint()); + } + + if ( !Move( fnMove, fnGo ) ) + break; + + if (pFrame) + { + SwTextFrame const* pNewFrame(static_cast<SwTextFrame const*>( + GetPoint()->nNode.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 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()->nNode.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(), + static_cast<sal_uInt16>(pTableBox->getRowSpan() + m_nRowSpanOffset)); + SwNodeIndex& rPtIdx = GetPoint()->nNode; + SwNodeIndex aNewIdx( *pTableBox->GetSttNd() ); + rPtIdx = aNewIdx; + + GetDoc()->GetNodes().GoNextSection( &rPtIdx, false, false ); + SwContentNode* pContentNode = GetContentNode(); + if ( pContentNode ) + { + GetPoint()->nContent.Assign( pContentNode, 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()->nNode.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() ); + SwNodeIndex& rPtIdx = GetPoint()->nNode; + SwNodeIndex aNewIdx( *pTableBox->GetSttNd() ); + rPtIdx = aNewIdx; + + GetDoc()->GetNodes().GoNextSection( &rPtIdx, false, false ); + SwContentNode* pContentNode = GetContentNode(); + if ( pContentNode ) + { + GetPoint()->nContent.Assign( pContentNode, bLeft ? pContentNode->Len() : 0 ); + } + } + } + --nCnt; + } + + // here come some special rules for visual cursor travelling + if ( pSttFrame ) + { + SwNode& rTmpNode = GetPoint()->nNode.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()->nNode.GetNode(); + if ( rNode.IsTextNode() ) + { + SwTextFrame const* pFrame; + const SwScriptInfo* pSI = + SwScriptInfo::GetScriptInfo( *rNode.GetTextNode(), &pFrame ); + if ( pSI ) + { + SwIndex& rIdx = GetPoint()->nContent; + const sal_Int32 nPos = rIdx.GetIndex(); + + if (nPos && nPos < rNode.GetTextNode()->GetText().getLength()) + { + 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, 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 && GetNode().StartOfSectionNode() == + GetNode( false ).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 = GetContentNode()->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 = GetNode().FindTableBoxStartNode(); + OSL_ENSURE( pTableNd, "pTableCursor without SwTableNode?" ); + + if ( pTableNd ) // safety first + { + const SwNode* pEndNd = pTableNd->EndOfSectionNode(); + GetPoint()->nNode = *pEndNd; + pTableCursor->Move( fnMoveBackward, GoInNode ); + std::pair<Point, bool> const tmp(aPt, true); + pFrame = GetContentNode()->getLayoutFrame(&rLayout, GetPoint(), &tmp); + } + } + + while( nCnt && + (bUp ? pFrame->UnitUp( this, nUpDownX, bInReadOnly ) + : pFrame->UnitDown( this, nUpDownX, bInReadOnly ) ) && + CheckNodesRange( aOldPos.nNode, GetPoint()->nNode, bChkRange )) + { + std::pair<Point, bool> const tmp(aPt, true); + pFrame = GetContentNode()->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 = GetContentNode()->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 + { + // Jump to beginning or end of line when the cursor at first or last line. + SwNode& rNode = GetPoint()->nNode.GetNode(); + const sal_Int32 nOffset = bUp ? 0 : rNode.GetTextNode()->GetText().getLength(); + const SwPosition aPos(*GetContentNode(), nOffset); + + //if cursor has already been at start or end of file, + //Update cursor to change nUpDownX. + if ( aOldPos.nContent.GetIndex() == nOffset ) + { + GetDoc()->GetEditShell()->UpdateCursor(); + bRet = false; + } + else{ + *GetPoint() = aPos; // just give a new position + bRet = true; + } + + } + + 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 = GetContentNode()->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 = GetContentNode()->getLayoutFrame( + &rLayout, GetPoint(), &tmp); + if( pFrame ) + { + SwPaM aPam( *GetPoint() ); + if( !bLeft && aPam.GetPoint()->nContent.GetIndex() ) + --aPam.GetPoint()->nContent; + 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()->nNode.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 ); + SwNodeIndex& rPtIdx = GetPoint()->nNode; + + while( nCnt-- ) + { + const SwNode* pTableBoxStartNode = rPtIdx.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(), + static_cast<sal_uInt16>(pTableBox->getRowSpan() + m_nRowSpanOffset)); + SwNodeIndex aNewIdx( *pTableBox->GetSttNd() ); + rPtIdx = aNewIdx; + pTableBoxStartNode = rPtIdx.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) + rPtIdx = aCellIdx; + else + rPtIdx.Assign(*aCellIdx.GetNode().StartOfSectionNode()); + + pTableBoxStartNode = rPtIdx.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() ); + SwNodeIndex aNewIdx( *pTableBox->GetSttNd() ); + rPtIdx = aNewIdx; + } + } + + ++rPtIdx; + if( !rPtIdx.GetNode().IsContentNode() ) + GetDoc()->GetNodes().GoNextSection( &rPtIdx, true, false ); + GetPoint()->nContent.Assign( GetContentNode(), 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()->nNode = *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()->nNode.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()->nNode = *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()->nNode.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()->nContent.GetIndex() != nSttEnd ) + bShortCut = true; + } + } + else + { + if ( pNd->IsTextNode() && + pNd->GetNodes()[ pNd->GetIndex() + + (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). + sal_uLong uNodeCount = GetPoint()->nNode.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) + { + GetPoint()->nNode = m_vSavePos.back().nNode; + + sal_Int32 nIdx = 0; + if ( GetContentNode() ) + { + if (m_vSavePos.back().nContent <= GetContentNode()->Len()) + nIdx = m_vSavePos.back().nContent; + else + { + nIdx = GetContentNode()->Len(); + OSL_FAIL("SwCursor::RestoreSavePos: invalid content index"); + } + } + GetPoint()->nContent.Assign( GetContentNode(), nIdx ); + } +} + +SwTableCursor::SwTableCursor( const SwPosition &rPos ) + : SwCursor( rPos, nullptr ) +{ + m_bParked = false; + m_bChanged = false; + m_nTablePtNd = 0; + m_nTableMkNd = 0; + m_nTablePtCnt = 0; + m_nTableMkCnt = 0; +} + +SwTableCursor::~SwTableCursor() {} + +static bool +lcl_SeekEntry(const SwSelBoxes& rTmp, SwStartNode const*const pSrch, + size_t & o_rFndPos) +{ + sal_uLong 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; + SwPaM* pCur = pCurrentCursor; + do { + size_t nPos; + bool bDel = false; + pSttNd = pCur->GetPoint()->nNode.GetNode().FindTableBoxStartNode(); + if( !pCur->HasMark() || !pSttNd || + pSttNd != pCur->GetMark()->nNode.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->nNode.GetNode() ) + pPos->nNode = *pNd; + pPos->nContent.Assign( const_cast<SwContentNode*>(static_cast<const SwContentNode*>(pNd)), 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->nNode.GetNode()) + pPos->nNode = *pNd; + pPos->nContent.Assign(const_cast<SwContentNode*>(static_cast<const SwContentNode*>(pNd)), pNd ? static_cast<const SwContentNode*>(pNd)->Len() : 0); + + aTmp.erase( aTmp.begin() + nPos ); + } + else + bDel = true; + + pCur = pCur->GetNext(); + if( bDel ) + { + SwPaM* pDel = pCur->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()->nNode = *pNd; + pNew->GetPoint()->nContent.Assign( static_cast<SwContentNode*>(pNd), 0 ); + pNew->SetMark(); + + SwPosition* pPos = pNew->GetPoint(); + pPos->nNode.Assign( *pSttNd->EndOfSectionNode(), - 1 ); + pNd = &pPos->nNode.GetNode(); + if( !pNd->IsContentNode() ) + pNd = SwNodes::GoPrevSection( &pPos->nNode, true, false ); + + pPos->nContent.Assign(static_cast<SwContentNode*>(pNd), pNd ? static_cast<SwContentNode*>(pNd)->Len() : 0); + } + } + 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 = GetNode().FindTableBoxStartNode(); + const SwNode *pEnd = GetNode(false).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()->nNode.GetIndex(); + m_nTablePtNd = GetPoint()->nNode.GetIndex(); + m_nTableMkCnt = GetMark()->nContent.GetIndex(); + m_nTablePtCnt = GetPoint()->nContent.GetIndex(); + return true; +} + +/// park table cursor on the boxes' start node +void SwTableCursor::ParkCursor() +{ + // de-register index from text node + SwNode* pNd = &GetPoint()->nNode.GetNode(); + if( !pNd->IsStartNode() ) + pNd = pNd->StartOfSectionNode(); + GetPoint()->nNode = *pNd; + GetPoint()->nContent.Assign( nullptr, 0 ); + + pNd = &GetMark()->nNode.GetNode(); + if( !pNd->IsStartNode() ) + pNd = pNd->StartOfSectionNode(); + GetMark()->nNode = *pNd; + GetMark()->nContent.Assign( nullptr, 0 ); + + 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; +} + +/* 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 000000000..8e1de5ba8 --- /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 ) + { + SwContentFrame* pCnt = (*fnPosCol)( pLayFrame ); + if( pCnt ) + { + SET_CURR_SHELL( 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 000000000..e0eb9ca48 --- /dev/null +++ b/sw/source/core/crsr/trvlfnfl.cxx @@ -0,0 +1,378 @@ +/* -*- 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> + +bool SwCursorShell::CallCursorShellFN( FNCursorShell fnCursor ) +{ + 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 ) +{ + 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()->nNode.GetNode().GetTextNode(); + + SwTextAttr *const pFootnote( pTextNd + ? pTextNd->GetTextAttrForCharAt( + GetPoint()->nContent.GetIndex(), RES_TXTATR_FTN) + : nullptr); + if (pFootnote) + { + SwCursorSaveState aSaveState( *this ); + GetPoint()->nNode = *static_cast<SwTextFootnote*>(pFootnote)->GetStartNode(); + + SwContentNode* pCNd = GetDoc()->GetNodes().GoNextSection( + &GetPoint()->nNode, + true, !IsReadOnlyAvailable() ); + if( pCNd ) + { + GetPoint()->nContent.Assign( pCNd, 0 ); + bRet = !IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | + SwCursorSelOverFlags::Toggle ); + } + } + return bRet; +} + +bool SwCursorShell::GotoFootnoteText() +{ + bool bRet = CallCursorFN( &SwCursor::GotoFootnoteText ); + if( !bRet ) + { + SwTextNode* pTextNd = GetCursor_() ? + GetCursor_()->GetPoint()->nNode.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 = GetNode().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()->nNode = rTNd; + GetPoint()->nContent.Assign( &rTNd, pTextFootnote->GetStart() ); + + return !IsSelOvr( SwCursorSelOverFlags::CheckNodeSection | + SwCursorSelOverFlags::Toggle ); + } + } + } + return false; +} + +bool SwCursorShell::GotoFootnoteAnchor() +{ + // 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, sal_uLong nNd, sal_Int32 nCnt ) +{ + const sal_uLong nTNd = rFootnote.GetTextNode().GetIndex(); + return nTNd < nNd || ( nTNd == nNd && rFootnote.GetStart() <= nCnt ); +} + +static bool CmpL( const SwTextFootnote& rFootnote, sal_uLong nNd, sal_Int32 nCnt ) +{ + const sal_uLong 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()->nNode, &nPos )) + { + // there is a footnote with this index, so search also for the next one + if( nPos < rFootnoteArr.size() ) + { + sal_uLong nNdPos = GetPoint()->nNode.GetIndex(); + const sal_Int32 nCntPos = GetPoint()->nContent.GetIndex(); + + 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()->nNode = rTNd; + GetPoint()->nContent.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()->nNode, &nPos ) ) + { + // there is a footnote with this index, so search also for the next one + sal_uLong nNdPos = GetPoint()->nNode.GetIndex(); + const sal_Int32 nCntPos = GetPoint()->nContent.GetIndex(); + + 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()->nNode = rTNd; + GetPoint()->nContent.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() +{ + SET_CURR_SHELL( 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().IsInside( 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 000000000..1648caf8f --- /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()->nNode ); + SwSectionNode* pNd = aIdx.GetNode().FindSectionNode(); + if( pNd ) + aIdx.Assign( *pNd, - 1 ); + + SwNodeIndex aOldIdx = aIdx; + sal_uLong 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()->nContent.Assign( pCNd, 0 ); + } + else + { + aIdx = *pNd->EndOfSectionNode(); + SwContentNode* pCNd = SwNodes::GoPrevSection( &aIdx, + true, !bInReadOnly ); + if( !pCNd ) + { + aIdx.Assign( *pNd, - 1 ); + continue; + } + rCurrentCursor.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + } + rCurrentCursor.GetPoint()->nNode = 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()->nNode ); + SwSectionNode* pNd = aIdx.GetNode().FindSectionNode(); + if( pNd ) + aIdx.Assign( *pNd->EndOfSectionNode(), - 1 ); + + SwNodeIndex aOldIdx = aIdx; + sal_uLong 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 = 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()->nContent.Assign( pCNd, 0 ); + } + else + { + aIdx = *pNd->EndOfSectionNode(); + SwContentNode* pCNd = SwNodes::GoPrevSection( &aIdx, + true, !bInReadOnly ); + if( !pCNd ) + { + ++aIdx; + continue; + } + rCurrentCursor.GetPoint()->nContent.Assign( pCNd, pCNd->Len() ); + } + rCurrentCursor.GetPoint()->nNode = 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.GetNode(); + SwSectionNode* pNd = rCurrNd.FindSectionNode(); + if( !pNd ) + return false; + + SwPosition* pPos = rCurrentCursor.GetPoint(); + const sal_Int32 nCurrCnt = pPos->nContent.GetIndex(); + 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->nNode = aIdx; + } + else + { + SwNodeIndex aIdx( *pNd ); + pCNd = pNd->GetNodes().GoNextSection( &aIdx, true, !bInReadOnly ); + if( !pCNd ) + return false; + pPos->nNode = aIdx; + } + + pPos->nContent.Assign( pCNd, bMoveBackward ? pCNd->Len() : 0 ); + + if( &pPos->nNode.GetNode() != &rCurrNd || + pPos->nContent.GetIndex() != 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()->nNode.GetIndex() != m_vSavePos.back().nNode || + GetPoint()->nContent.GetIndex() != 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( const OUString& 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()->nNode = *pIdx; + Move( fnMoveForward, GoInContent ); + bRet = !IsSelOvr(); + } + } + } + return bRet; +} + +bool SwCursorShell::GotoRegion( const OUString& 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 000000000..cdee8e9c5 --- /dev/null +++ b/sw/source/core/crsr/trvltbl.cxx @@ -0,0 +1,927 @@ +/* -*- 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 <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->GetNode().FindTableBoxStartNode(); + const SwTableBox* pTableBox = nullptr; + + if ( pCursor->GetCursorRowSpanOffset() ) + { + pTableBox = pTableBoxStartNode->GetTableBox(); + if ( pTableBox->getRowSpan() > 1 ) + { + if ( !pTableNd ) + pTableNd = IsCursorInTable(); + assert (pTableNd); + pTableBox = & pTableBox->FindEndOfRowSpan( pTableNd->GetTable(), + static_cast<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()->nNode.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->IsInTab() ) + return false; + + const SwTabFrame* pTabFrame = pFrame->FindTabFrame(); + const SwTabFrame* pMasterTabFrame = pTabFrame->IsFollow() ? pTabFrame->FindMaster( true ) : pTabFrame; + const SwTable* pTable = pTabFrame->GetTable(); + + SET_CURR_SHELL( 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->GetContentNode( false ); + 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()->nNode = *pEnd->GetSttNd(); + m_pTableCursor->Move( fnMoveForward, GoInContent ); + m_pTableCursor->SetMark(); + m_pTableCursor->GetPoint()->nNode = *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(); + + SET_CURR_SHELL( 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()->nNode = *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()->nNode = *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()->nNode.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; + + SET_CURR_SHELL( 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()) = SwPosition( *pStartNode ); + m_pTableCursor->Move( fnMoveForward, GoInNode ); + + // 2. set mark, and move point to last content node in box + m_pTableCursor->SetMark(); + *(m_pTableCursor->GetPoint()) = SwPosition( *(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()->nNode ); + + 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; + sal_uLong 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()->nNode = *pTextNode; + rCurrentCursor.GetPoint()->nContent.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()->nNode ); + SwTableNode* pTableNd = aIdx.GetNode().FindTableNode(); + + if( pTableNd ) + aIdx.Assign( *pTableNd->EndOfSectionNode(), 1 ); + + SwNodeIndex aOldIdx = aIdx; + sal_uLong 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 = 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()->nNode = *pTextNode; + rCurrentCursor.GetPoint()->nContent.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()->nNode.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()->nNode = *pTextNode; + rCurrentCursor.GetPoint()->nContent.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; + sal_uLong 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()->nNode.GetIndex(); + nPtCnt = pCursor->GetPoint()->nContent.GetIndex(); + } + + 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()->nNode.GetIndex() == nPtNd && + pCursor->GetPoint()->nContent.GetIndex() == 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()->nNode.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()->nNode.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->nNode.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 if( nullptr != ( pSttNd = pPos->nNode.GetNode(). + FindSttNodeByType( SwTableBoxStartNode )) ) + { + pChkBox = pSttNd->FindTableNode()->GetTable().GetTableBox( pSttNd->GetIndex() ); + } + + // box has more than one paragraph + if( pChkBox && pSttNd->GetIndex() + 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()->nNode.GetIndex() )) + 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->nNode.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( dynamic_cast<const SwCursorShell *>(&rSh) != nullptr ) + bRet |= static_cast<SwCursorShell*>(&rSh)->CheckTableBoxContent( + static_cast<SwCursorShell*>(&rSh)->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 000000000..6f3a1e3b6 --- /dev/null +++ b/sw/source/core/crsr/viscrs.cxx @@ -0,0 +1,941 @@ +/* -*- 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_features.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 <paintfrm.hxx> +#include <PostItMgr.hxx> +#include <SwGrammarMarkUp.hxx> +#include <docsh.hxx> + +#include <cellfrm.hxx> +#include <wrtsh.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. + +long SwSelPaintRects::s_nPixPtX = 0; +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().IsOver( 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(aStream.str().c_str()).trim(); +} + +} + +void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell) +{ + SwRect aRect; + long nTmpY = m_pCursorShell->m_aCursorHeight.getY(); + if( 0 > nTmpY ) + { + nTmpY = -nTmpY; + m_aTextCursor.SetOrientation( 900 ); + 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()->nNode.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 ) + { + 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.getStr()); + } + + // 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(); + + // is cursor at a misspelled word ? + bool bIsWrong = false; + 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.nNode.GetNode().GetTextNode(); + if (pNode && !pNode->IsInProtectSect()) + { + sal_Int32 nBegin = aPos.nContent.GetIndex(); + sal_Int32 nLen = 1; + + SwWrongList *pWrong = nullptr; + 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) + { + SfxItemSet aSet(m_pCursorShell->GetSfxViewShell()->GetPool(), + svl::Items<RES_TXTATR_INETFMT, + RES_TXTATR_INETFMT>{}); + pShell->GetCurAttr(aSet); + if(SfxItemState::SET <= aSet.GetItemState( RES_TXTATR_INETFMT )) + { + sHyperlink = buildHyperlinkJSON(m_pCursorShell->GetSelText(), + aSet.GetItem(RES_TXTATR_INETFMT)->GetValue()); + } + } + } + + if (pViewShell) + { + if (pViewShell == m_pCursorShell->GetSfxViewShell()) + { + SfxLokHelper::notifyVisCursorInvalidation(pViewShell, sRect, bIsWrong, sHyperlink); + } + else + SfxLokHelper::notifyOtherView(m_pCursorShell->GetSfxViewShell(), pViewShell, LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", sRect); + } + else + { + SfxLokHelper::notifyVisCursorInvalidation(m_pCursorShell->GetSfxViewShell(), sRect, bIsWrong, sHyperlink); + SfxLokHelper::notifyOtherViews(m_pCursorShell->GetSfxViewShell(), LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", sRect); + } + } + + if ( !m_pCursorShell->IsCursorReadonly() || m_pCursorShell->GetViewOptions()->IsSelectionInReadonly() ) + { + 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(); + } +} + +const vcl::Cursor& SwVisibleCursor::GetTextCursor() const +{ + return m_aTextCursor; +} + +SwSelPaintRects::SwSelPaintRects( const SwCursorShell& rCSh ) + : SwRects() + , m_pCursorShell( &rCSh ) +#if HAVE_FEATURE_DESKTOP + , m_bShowTextInputFieldOverlay(true) +#endif +{ +} + +SwSelPaintRects::~SwSelPaintRects() +{ + Hide(); +} + +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); +#endif +} + +void SwSelPaintRects::Hide() +{ +#if HAVE_FEATURE_DESKTOP + m_pCursorOverlay.reset(); + m_pTextInputFieldOverlay.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.nNode.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()) + { + // 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()) + { + static_cast<sdr::overlay::OverlaySelection*>(m_pCursorOverlay.get())->setRanges(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 SvtOptionsDrawinglayer aSvtOptionsDrawinglayer; + const Color aHighlight(aSvtOptionsDrawinglayer.getHilightColor()); + + // create correct selection + m_pCursorOverlay.reset( new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Transparent, + aHighlight, + aNewRanges, + true) ); + + xTargetOverlay->add(*m_pCursorOverlay); + } + } + + HighlightInputField(); +#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()) + { + // If pSelectionRectangles is set, we're just collecting the text selections -> don't emit start/end. + if (!empty() && !pSelectionRectangles) + { + // 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); + + if (aStartRect.HasArea()) + { + OString sRect = aStartRect.SVRect().toString(); + GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_START, sRect.getStr()); + } + if (aEndRect.HasArea()) + { + OString sRect = aEndRect.SVRect().toString(); + GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_END, sRect.getStr()); + } + } + + 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) + { + GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRect.getStr()); + SfxLokHelper::notifyOtherViews(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRect); + } + else + pSelectionRectangles->push_back(sRect); + } + } +} + +void SwSelPaintRects::HighlightInputField() +{ + std::vector< basegfx::B2DRange > aInputFieldRanges; + + if (m_bShowTextInputFieldOverlay) + { + SwTextInputField* pCurTextInputFieldAtCursor = + dynamic_cast<SwTextInputField*>(SwCursorShell::GetTextFieldAtPos( GetShell()->GetCursor()->Start(), false )); + if ( pCurTextInputFieldAtCursor != nullptr ) + { + SwTextNode* pTextNode = pCurTextInputFieldAtCursor->GetpTextNode(); + std::unique_ptr<SwShellCursor> pCursorForInputTextField( + new SwShellCursor( *GetShell(), SwPosition( *pTextNode, pCurTextInputFieldAtCursor->GetStart() ) ) ); + pCursorForInputTextField->SetMark(); + pCursorForInputTextField->GetMark()->nNode = *pTextNode; + pCursorForInputTextField->GetMark()->nContent.Assign( pTextNode, *(pCurTextInputFieldAtCursor->End()) ); + + pCursorForInputTextField->FillRects(); + SwRects* pRects = static_cast<SwRects*>(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( 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 + const SvtOptionsDrawinglayer aSvtOptionsDrawinglayer; + Color aHighlight(aSvtOptionsDrawinglayer.getHilightColor()); + aHighlight.DecreaseLuminance( 128 ); + + m_pTextInputFieldOverlay.reset( new sw::overlay::OverlayRangesOutline( + aHighlight, aInputFieldRanges ) ); + xTargetOverlay->add( *m_pTextInputFieldOverlay ); + } + } + } + else + { + m_pTextInputFieldOverlay.reset(); + } +} + +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()) ) + { + 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, + long* pX, long* pY ) +{ + const OutputDevice* pOut = rSh.GetWin(); + 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()->nNode.GetNode().IsContentNode() && + GetPoint()->nNode.GetNode().GetContentNode()->getLayoutFrame( GetShell()->GetLayout() ) && + (GetMark()->nNode == GetPoint()->nNode || + (GetMark()->nNode.GetNode().IsContentNode() && + GetMark()->nNode.GetNode().GetContentNode()->getLayoutFrame( GetShell()->GetLayout() ) ) )) + GetShell()->GetLayout()->CalcFrameRects( *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()) + { + 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.getStr()); + 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 ) +{ + 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()->nNode.GetIndex()) + return; + + bool bStart = true; + SwRegionRects aReg( GetShell()->VisArea() ); + if (comphelper::LibreOfficeKit::isActive()) + aReg = GetShell()->getIDocumentLayoutAccess().GetCurrentLayout()->getFrameArea(); + 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().IsOver( 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::IsInside( const Point& rPt ) const +{ + // Calculate the new rectangles. If the cursor is still "parked" do nothing + if (m_SelectedBoxes.empty() || m_bParked || !GetPoint()->nNode.GetIndex()) + 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().IsInside( rPt ) ) + return true; + + for ( SwCellFrame* pCellFrame = static_cast<SwCellFrame*>(pFrame); pCellFrame; pCellFrame = pCellFrame->GetFollowCell() ) + { + if( pCellFrame->getFrameArea().IsInside( rPt ) ) + return true; + } + } + return false; +} + +bool SwShellTableCursor::IsAtValidPos( bool bPoint ) const +{ + return SwShellCursor::IsAtValidPos( bPoint ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |