Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / source / core / crsr / viscrs.cxx
blobda9c043c65815b2d0f9bf9ad4b860f2fd4144e08
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_feature_desktop.h>
22 #include <vcl/weld.hxx>
23 #include <vcl/svapp.hxx>
24 #include <vcl/settings.hxx>
25 #include <viewopt.hxx>
26 #include <frmtool.hxx>
27 #include <viscrs.hxx>
28 #include <crsrsh.hxx>
29 #include <doc.hxx>
30 #include <swtable.hxx>
31 #include <viewimp.hxx>
32 #include <dview.hxx>
33 #include <rootfrm.hxx>
34 #include <txtfrm.hxx>
35 #include <ndtxt.hxx>
36 #include <txtfld.hxx>
37 #include <scriptinfo.hxx>
38 #include <view.hxx>
39 #include <IDocumentLayoutAccess.hxx>
41 #include <svx/sdr/overlay/overlaymanager.hxx>
42 #include <svx/sdrpaintwindow.hxx>
43 #include <svx/srchdlg.hxx>
44 #include <svx/sdr/overlay/overlayselection.hxx>
45 #include "overlayrangesoutline.hxx"
47 #include <memory>
49 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
50 #include <comphelper/lok.hxx>
51 #include <sfx2/lokhelper.hxx>
52 #include <boost/property_tree/json_parser.hpp>
53 #include <comphelper/string.hxx>
54 #include <osl/diagnose.h>
55 #include <paintfrm.hxx>
56 #include <PostItMgr.hxx>
57 #include <SwGrammarMarkUp.hxx>
58 #include <docsh.hxx>
59 #include <svtools/optionsdrawinglayer.hxx>
60 #include <o3tl/string_view.hxx>
61 #include <tools/json_writer.hxx>
62 #include <cellfrm.hxx>
63 #include <wrtsh.hxx>
64 #include <textcontentcontrol.hxx>
65 #include <dropdowncontentcontrolbutton.hxx>
66 #include <datecontentcontrolbutton.hxx>
67 #include <FrameControlsManager.hxx>
69 // Here static members are defined. They will get changed on alteration of the
70 // MapMode. This is done so that on ShowCursor the same size does not have to be
71 // expensively determined again and again.
73 tools::Long SwSelPaintRects::s_nPixPtX = 0;
74 tools::Long SwSelPaintRects::s_nPixPtY = 0;
75 MapMode* SwSelPaintRects::s_pMapMode = nullptr;
77 // Starting from here: classes / methods for the non-text-cursor
78 SwVisibleCursor::SwVisibleCursor( const SwCursorShell * pCShell )
79 : m_pCursorShell( pCShell )
80 , m_nPageLastTime(0)
82 pCShell->GetWin()->SetCursor( &m_aTextCursor );
83 m_bIsVisible = m_aTextCursor.IsVisible();
84 m_bIsDragCursor = false;
85 m_aTextCursor.SetWidth( 0 );
88 SwVisibleCursor::~SwVisibleCursor()
90 if( m_bIsVisible && m_aTextCursor.IsVisible() )
91 m_aTextCursor.Hide();
93 m_pCursorShell->GetWin()->SetCursor( nullptr );
96 void SwVisibleCursor::Show()
98 if( !m_bIsVisible )
100 m_bIsVisible = true;
102 // display at all?
103 if( m_pCursorShell->VisArea().Overlaps( m_pCursorShell->m_aCharRect ) || comphelper::LibreOfficeKit::isActive() )
104 SetPosAndShow(nullptr);
108 void SwVisibleCursor::Hide()
110 if( m_bIsVisible )
112 m_bIsVisible = false;
114 if( m_aTextCursor.IsVisible() ) // Shouldn't the flags be in effect?
115 m_aTextCursor.Hide();
119 namespace
122 // Build JSON message to be sent to Online
123 OString buildHyperlinkJSON(const OUString& sText, const OUString& sLink)
125 boost::property_tree::ptree aTree;
126 aTree.put("text", sText);
127 aTree.put("link", sLink);
128 std::stringstream aStream;
129 boost::property_tree::write_json(aStream, aTree, false);
131 return OString(o3tl::trim(aStream.str()));
136 void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell)
138 SwRect aRect;
139 tools::Long nTmpY = m_pCursorShell->m_aCursorHeight.getY();
140 if( 0 > nTmpY )
142 nTmpY = -nTmpY;
143 m_aTextCursor.SetOrientation( 900_deg10 );
144 aRect = SwRect( m_pCursorShell->m_aCharRect.Pos(),
145 Size( m_pCursorShell->m_aCharRect.Height(), nTmpY ) );
146 aRect.Pos().setX(aRect.Pos().getX() + m_pCursorShell->m_aCursorHeight.getX());
147 if( m_pCursorShell->IsOverwriteCursor() )
148 aRect.Pos().setY(aRect.Pos().getY() + aRect.Width());
150 else
152 m_aTextCursor.SetOrientation();
153 aRect = SwRect( m_pCursorShell->m_aCharRect.Pos(),
154 Size( m_pCursorShell->m_aCharRect.Width(), nTmpY ) );
155 aRect.Pos().setY(aRect.Pos().getY() + m_pCursorShell->m_aCursorHeight.getX());
158 // check if cursor should show the current cursor bidi level
159 m_aTextCursor.SetDirection();
160 const SwCursor* pTmpCursor = m_pCursorShell->GetCursor_();
162 if ( pTmpCursor && !m_pCursorShell->IsOverwriteCursor() )
164 SwNode& rNode = pTmpCursor->GetPoint()->GetNode();
165 if( rNode.IsTextNode() )
167 const SwTextNode& rTNd = *rNode.GetTextNode();
168 const SwFrame* pFrame = rTNd.getLayoutFrame(m_pCursorShell->GetLayout(), nullptr, nullptr);
169 if ( pFrame )
171 const SwScriptInfo* pSI = static_cast<const SwTextFrame*>(pFrame)->GetScriptInfo();
172 // cursor level has to be shown
173 if ( pSI && pSI->CountDirChg() > 1 )
175 m_aTextCursor.SetDirection(
176 ( pTmpCursor->GetCursorBidiLevel() % 2 ) ?
177 CursorDirection::RTL :
178 CursorDirection::LTR );
180 if ( pFrame->IsRightToLeft() )
182 const OutputDevice *pOut = m_pCursorShell->GetOut();
183 if ( pOut )
185 tools::Long nSize = pOut->GetSettings().GetStyleSettings().GetCursorSize();
186 Size aSize( nSize, nSize );
187 aSize = pOut->PixelToLogic( aSize );
188 aRect.Left( aRect.Left() - aSize.Width() );
195 if( aRect.Height())
197 ::SwCalcPixStatics( m_pCursorShell->GetOut() );
199 // Disable pixel alignment when tiled rendering, so that twip values of
200 // the cursor don't depend on statics.
201 if (!comphelper::LibreOfficeKit::isActive())
202 ::SwAlignRect( aRect, static_cast<SwViewShell const *>(m_pCursorShell), m_pCursorShell->GetOut() );
204 if( !m_pCursorShell->IsOverwriteCursor() || m_bIsDragCursor ||
205 m_pCursorShell->IsSelection() )
206 aRect.Width( 0 );
208 m_aTextCursor.SetSize( aRect.SSize() );
210 m_aTextCursor.SetPos( aRect.Pos() );
212 bool bPostItActive = false;
213 SwView* pView = dynamic_cast<SwView*>(m_pCursorShell->GetSfxViewShell());
214 if (pView)
216 if (SwPostItMgr* pPostItMgr = pView->GetPostItMgr())
217 bPostItActive = pPostItMgr->GetActiveSidebarWin() != nullptr;
220 if (comphelper::LibreOfficeKit::isActive() && !bPostItActive)
222 // notify about page number change (if that happened)
223 sal_uInt16 nPage, nVirtPage;
224 // bCalcFrame=false is important to avoid calculating the layout when
225 // we're in the middle of doing that already.
226 const_cast<SwCursorShell*>(m_pCursorShell)->GetPageNum(nPage, nVirtPage, /*bAtCursorPos=*/true, /*bCalcFrame=*/false);
227 if (nPage != m_nPageLastTime)
229 m_nPageLastTime = nPage;
230 OString aPayload = OString::number(nPage - 1);
231 m_pCursorShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload);
234 // This may get called often, so instead of sending data on each update, just notify
235 // that there's been an update, and the other side will pull the data using
236 // getLOKPayload() when it decides to.
237 m_aLastLOKRect = aRect;
238 if (pViewShell)
240 if (pViewShell == m_pCursorShell->GetSfxViewShell())
242 SfxLokHelper::notifyUpdatePerViewId(pViewShell, LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR);
244 else
246 SfxLokHelper::notifyUpdatePerViewId(pViewShell, m_pCursorShell->GetSfxViewShell(), pViewShell,
247 LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
250 else
252 SfxLokHelper::notifyUpdatePerViewId(m_pCursorShell->GetSfxViewShell(), SfxViewShell::Current(),
253 m_pCursorShell->GetSfxViewShell(), LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR);
254 SfxLokHelper::notifyOtherViewsUpdatePerViewId(m_pCursorShell->GetSfxViewShell(), LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
258 if ( m_pCursorShell->IsCursorReadonly() && !m_pCursorShell->GetViewOptions()->IsSelectionInReadonly() )
259 return;
261 if ( m_pCursorShell->GetDrawView() )
262 const_cast<SwDrawView*>(static_cast<const SwDrawView*>(m_pCursorShell->GetDrawView()))->SetAnimationEnabled(
263 !m_pCursorShell->IsSelection() );
265 sal_uInt16 nStyle = m_bIsDragCursor ? CURSOR_SHADOW : 0;
266 if( nStyle != m_aTextCursor.GetStyle() )
268 m_aTextCursor.SetStyle( nStyle );
269 m_aTextCursor.SetWindow( m_bIsDragCursor ? m_pCursorShell->GetWin() : nullptr );
272 m_aTextCursor.Show();
275 std::optional<OString> SwVisibleCursor::getLOKPayload(int nType, int nViewId) const
277 assert(nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR || nType == LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
278 if (comphelper::LibreOfficeKit::isActive())
280 SwRect aRect = m_aLastLOKRect;
282 // notify about the cursor position & size
283 tools::Rectangle aSVRect(aRect.Pos().getX(), aRect.Pos().getY(), aRect.Pos().getX() + aRect.SSize().Width(), aRect.Pos().getY() + aRect.SSize().Height());
284 OString sRect = aSVRect.toString();
286 if(nType == LOK_CALLBACK_INVALIDATE_VIEW_CURSOR)
287 return SfxLokHelper::makePayloadJSON(m_pCursorShell->GetSfxViewShell(), nViewId, "rectangle", sRect);
289 // is cursor at a misspelled word ?
290 bool bIsWrong = false;
291 SwView* pView = dynamic_cast<SwView*>(m_pCursorShell->GetSfxViewShell());
292 if (pView && pView->GetWrtShellPtr())
294 const SwViewOption* pVOpt = pView->GetWrtShell().GetViewOptions();
295 if(pVOpt && pVOpt->IsOnlineSpell())
297 SwPaM* pCursor = m_pCursorShell->GetCursor();
298 SwPosition aPos(*pCursor->GetPoint());
299 Point aPt = aRect.Pos();
300 SwCursorMoveState eTmpState(CursorMoveState::SetOnlyText);
301 SwTextNode *pNode = nullptr;
302 if (m_pCursorShell->GetLayout()->GetModelPositionForViewPoint(&aPos, aPt, &eTmpState))
303 pNode = aPos.GetNode().GetTextNode();
304 if (pNode && !pNode->IsInProtectSect())
306 sal_Int32 nBegin = aPos.GetContentIndex();
307 sal_Int32 nLen = 1;
309 SwWrongList *pWrong = pNode->GetWrong();
310 if (!pWrong)
311 pWrong = pNode->GetGrammarCheck();
312 if (pWrong)
313 bIsWrong = pWrong->InWrongWord(nBegin,nLen) && !pNode->IsSymbolAt(nBegin);
318 OString sHyperlink;
319 SwContentAtPos aContentAtPos(IsAttrAtPos::InetAttr);
320 bool bIsSelection = m_pCursorShell->IsSelection();
322 if (const_cast<SwCursorShell*>(m_pCursorShell)->GetContentAtPos(aRect.Pos(), aContentAtPos))
324 const SwFormatINetFormat* pItem = static_cast<const SwFormatINetFormat*>(aContentAtPos.aFnd.pAttr);
325 sHyperlink = buildHyperlinkJSON(aContentAtPos.sStr, pItem->GetValue());
327 else if (bIsSelection)
329 SwWrtShell* pShell = m_pCursorShell->GetDoc()->GetDocShell()->GetWrtShell();
331 if (pShell)
333 SfxItemSetFixed<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT>
334 aSet(m_pCursorShell->GetSfxViewShell()->GetPool());
335 pShell->GetCurAttr(aSet);
336 if(SfxItemState::SET <= aSet.GetItemState( RES_TXTATR_INETFMT ))
338 sHyperlink = buildHyperlinkJSON(m_pCursorShell->GetSelText(),
339 aSet.GetItem(RES_TXTATR_INETFMT)->GetValue());
344 return SfxLokHelper::makeVisCursorInvalidation(nViewId, sRect, bIsWrong, sHyperlink);
346 else
347 abort();
350 const vcl::Cursor& SwVisibleCursor::GetTextCursor() const
352 return m_aTextCursor;
355 SwSelPaintRects::SwSelPaintRects( const SwCursorShell& rCSh )
356 : m_pCursorShell( &rCSh )
357 #if HAVE_FEATURE_DESKTOP
358 , m_bShowTextInputFieldOverlay(true)
359 , m_bShowContentControlOverlay(true)
360 #endif
364 SwSelPaintRects::~SwSelPaintRects()
366 Hide();
367 m_pContentControlButton.disposeAndClear();
370 void SwSelPaintRects::swapContent(SwSelPaintRects& rSwap)
372 SwRects::swap(rSwap);
374 #if HAVE_FEATURE_DESKTOP
375 // #i75172# also swap m_pCursorOverlay
376 std::swap(m_pCursorOverlay, rSwap.m_pCursorOverlay);
377 std::swap(m_bShowTextInputFieldOverlay, rSwap.m_bShowTextInputFieldOverlay);
378 std::swap(m_pTextInputFieldOverlay, rSwap.m_pTextInputFieldOverlay);
379 std::swap(m_bShowContentControlOverlay, rSwap.m_bShowContentControlOverlay);
380 std::swap(m_pContentControlOverlay, rSwap.m_pContentControlOverlay);
381 #endif
384 void SwSelPaintRects::Hide()
386 #if HAVE_FEATURE_DESKTOP
387 m_pCursorOverlay.reset();
388 m_pTextInputFieldOverlay.reset();
389 m_pContentControlOverlay.reset();
390 #endif
392 SwRects::clear();
396 * Return a layout rectangle (typically with minimal width) that represents a
397 * cursor at rPosition.
399 * @param rPoint layout position as a hint about what layout frame contains
400 * rPosition (there might be multiple frames for a single node)
401 * @param rPosition the doc model position (paragraph / character index)
403 static SwRect lcl_getLayoutRect(const Point& rPoint, const SwPosition& rPosition)
405 const SwContentNode* pNode = rPosition.GetNode().GetContentNode();
406 std::pair<Point, bool> const tmp(rPoint, true);
407 const SwContentFrame* pFrame = pNode->getLayoutFrame(
408 pNode->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
409 &rPosition, &tmp);
410 SwRect aRect;
411 pFrame->GetCharRect(aRect, rPosition);
412 return aRect;
415 void SwShellCursor::FillStartEnd(SwRect& rStart, SwRect& rEnd) const
417 const SwShellCursor* pCursor = GetShell()->getShellCursor(false);
418 rStart = lcl_getLayoutRect(pCursor->GetSttPos(), *pCursor->Start());
419 rEnd = lcl_getLayoutRect(pCursor->GetEndPos(), *pCursor->End());
422 void SwSelPaintRects::Show(std::vector<OString>* pSelectionRectangles)
424 SdrView *const pView = const_cast<SdrView*>(m_pCursorShell->GetDrawView());
426 if(!(pView && pView->PaintWindowCount()))
427 return;
429 // reset rects
430 SwRects::clear();
431 FillRects();
433 #if HAVE_FEATURE_DESKTOP
434 // get new rects
435 std::vector< basegfx::B2DRange > aNewRanges;
436 aNewRanges.reserve(size());
437 for(size_type a = 0; a < size(); ++a)
439 const SwRect aNextRect((*this)[a]);
440 const tools::Rectangle aPntRect(aNextRect.SVRect());
442 aNewRanges.emplace_back(
443 aPntRect.Left(), aPntRect.Top(),
444 aPntRect.Right() + 1, aPntRect.Bottom() + 1);
447 if (m_pCursorOverlay)
449 if(!aNewRanges.empty())
451 static_cast<sdr::overlay::OverlaySelection*>(m_pCursorOverlay.get())->setRanges(std::move(aNewRanges));
453 else
455 m_pCursorOverlay.reset();
458 else if(!empty())
460 SdrPaintWindow* pCandidate = pView->GetPaintWindow(0);
461 const rtl::Reference< sdr::overlay::OverlayManager >& xTargetOverlay = pCandidate->GetOverlayManager();
463 if (xTargetOverlay.is())
465 // get the system's highlight color
466 const Color aHighlight(SvtOptionsDrawinglayer::getHilightColor());
468 // create correct selection
469 m_pCursorOverlay.reset( new sdr::overlay::OverlaySelection(
470 sdr::overlay::OverlayType::Transparent,
471 aHighlight,
472 std::move(aNewRanges),
473 true) );
475 xTargetOverlay->add(*m_pCursorOverlay);
479 HighlightInputField();
480 HighlightContentControl();
481 #endif
483 // Tiled editing does not expose the draw and writer cursor, it just
484 // talks about "the" cursor at the moment. As long as that's true,
485 // don't say anything about the Writer cursor till a draw object is
486 // being edited.
487 if (!comphelper::LibreOfficeKit::isActive() || pView->GetTextEditObject())
488 return;
490 // If pSelectionRectangles is set, we're just collecting the text selections -> don't emit start/end.
491 if (!empty() && !pSelectionRectangles)
493 SwRect aStartRect;
494 SwRect aEndRect;
495 FillStartEnd(aStartRect, aEndRect);
497 if (aStartRect.HasArea())
498 SfxLokHelper::notifyUpdate(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_SELECTION_START);
499 if (aEndRect.HasArea())
500 SfxLokHelper::notifyUpdate(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_SELECTION_END);
503 std::vector<OString> aRect;
504 aRect.reserve(size());
505 for (size_type i = 0; i < size(); ++i)
507 const SwRect& rRect = (*this)[i];
508 aRect.push_back(rRect.SVRect().toString());
510 OString sRect = comphelper::string::join("; ", aRect);
511 if (!pSelectionRectangles)
513 SfxLokHelper::notifyUpdate(GetShell()->GetSfxViewShell(),LOK_CALLBACK_TEXT_SELECTION);
514 SfxLokHelper::notifyOtherViewsUpdatePerViewId(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_VIEW_SELECTION);
516 else
517 pSelectionRectangles->push_back(sRect);
520 std::optional<OString> SwSelPaintRects::getLOKPayload(int nType, int nViewId) const
522 switch( nType )
524 case LOK_CALLBACK_TEXT_SELECTION_START:
525 case LOK_CALLBACK_TEXT_SELECTION_END:
527 // The selection may be a complex polygon, emit the logical
528 // start/end cursor rectangle of the selection as separate
529 // events, if there is a real selection.
530 // This can be used to easily show selection handles on the
531 // client side.
532 SwRect aStartRect;
533 SwRect aEndRect;
534 FillStartEnd(aStartRect, aEndRect);
536 // no selection rect
537 if (!size())
538 return {};
540 if( nType == LOK_CALLBACK_TEXT_SELECTION_START )
542 if (aStartRect.HasArea())
543 return aStartRect.SVRect().toString();
544 return {};
546 else // LOK_CALLBACK_TEXT_SELECTION_END
548 if (aEndRect.HasArea())
549 return aEndRect.SVRect().toString();
550 return {};
553 break;
554 case LOK_CALLBACK_TEXT_SELECTION:
555 case LOK_CALLBACK_TEXT_VIEW_SELECTION:
557 std::vector<OString> aRect;
558 aRect.reserve(size());
559 for (size_type i = 0; i < size(); ++i)
561 const SwRect& rRect = (*this)[i];
562 aRect.push_back(rRect.SVRect().toString());
564 OString sRect = comphelper::string::join("; ", aRect);
565 if( nType == LOK_CALLBACK_TEXT_SELECTION )
566 return sRect;
567 else // LOK_CALLBACK_TEXT_VIEW_SELECTION
568 return SfxLokHelper::makePayloadJSON(GetShell()->GetSfxViewShell(), nViewId, "selection", sRect);
570 break;
572 abort();
575 void SwSelPaintRects::HighlightInputField()
577 std::vector< basegfx::B2DRange > aInputFieldRanges;
579 if (m_bShowTextInputFieldOverlay)
581 SwTextInputField* pCurTextInputFieldAtCursor =
582 dynamic_cast<SwTextInputField*>(SwCursorShell::GetTextFieldAtPos( GetShell()->GetCursor()->Start(), ::sw::GetTextAttrMode::Expand));
583 if ( pCurTextInputFieldAtCursor != nullptr )
585 SwTextNode* pTextNode = pCurTextInputFieldAtCursor->GetpTextNode();
586 std::unique_ptr<SwShellCursor> pCursorForInputTextField(
587 new SwShellCursor( *GetShell(), SwPosition( *pTextNode, pCurTextInputFieldAtCursor->GetStart() ) ) );
588 pCursorForInputTextField->SetMark();
589 pCursorForInputTextField->GetMark()->Assign(*pTextNode, *(pCurTextInputFieldAtCursor->End()) );
591 pCursorForInputTextField->FillRects();
592 SwRects* pRects = pCursorForInputTextField.get();
593 for (const SwRect & rNextRect : *pRects)
595 const tools::Rectangle aPntRect(rNextRect.SVRect());
597 aInputFieldRanges.emplace_back(
598 aPntRect.Left(), aPntRect.Top(),
599 aPntRect.Right() + 1, aPntRect.Bottom() + 1);
604 if ( !aInputFieldRanges.empty() )
606 if (m_pTextInputFieldOverlay != nullptr)
608 m_pTextInputFieldOverlay->setRanges( std::move(aInputFieldRanges) );
610 else
612 SdrView* pView = const_cast<SdrView*>(GetShell()->GetDrawView());
613 SdrPaintWindow* pCandidate = pView->GetPaintWindow(0);
614 const rtl::Reference<sdr::overlay::OverlayManager>& xTargetOverlay = pCandidate->GetOverlayManager();
616 if (xTargetOverlay.is())
618 // use system's highlight color with decreased luminance as highlight color
619 Color aHighlight(SvtOptionsDrawinglayer::getHilightColor());
620 aHighlight.DecreaseLuminance( 128 );
622 m_pTextInputFieldOverlay.reset( new sw::overlay::OverlayRangesOutline(
623 aHighlight, std::move(aInputFieldRanges) ) );
624 xTargetOverlay->add( *m_pTextInputFieldOverlay );
628 else
630 m_pTextInputFieldOverlay.reset();
634 void SwSelPaintRects::HighlightContentControl()
636 std::vector<basegfx::B2DRange> aContentControlRanges;
637 std::vector<OString> aLOKRectangles;
638 SwRect aFirstPortionPaintArea;
639 SwRect aLastPortionPaintArea;
640 bool bRTL = false;
641 std::shared_ptr<SwContentControl> pContentControl;
643 if (m_bShowContentControlOverlay)
645 const SwPosition* pStart = GetShell()->GetCursor()->Start();
646 SwTextNode* pTextNode = pStart->GetNode().GetTextNode();
647 SwTextContentControl* pCurContentControlAtCursor = nullptr;
648 if (pTextNode)
650 // GetTextAttrMode::Parent because this way we highlight when the user will type inside the
651 // content control, not outside of it.
652 SwTextAttr* pAttr = pTextNode->GetTextAttrAt(
653 pStart->GetContentIndex(), RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent);
654 if (pAttr)
656 pCurContentControlAtCursor = static_txtattr_cast<SwTextContentControl*>(pAttr);
659 if (pCurContentControlAtCursor)
661 auto pCursorForContentControl = std::make_unique<SwShellCursor>(
662 *GetShell(), SwPosition(*pTextNode, pCurContentControlAtCursor->GetStart()));
663 pCursorForContentControl->SetMark();
664 pCursorForContentControl->GetMark()->Assign(
665 *pTextNode, *(pCurContentControlAtCursor->End()));
667 pCursorForContentControl->FillRects();
668 SwRects* pRects = pCursorForContentControl.get();
669 for (const auto& rRect : *pRects)
671 tools::Rectangle aRect(rRect.SVRect());
673 aContentControlRanges.emplace_back(aRect.Left(), aRect.Top(), aRect.Right() + 1,
674 aRect.Bottom() + 1);
675 if (comphelper::LibreOfficeKit::isActive())
677 aLOKRectangles.push_back(aRect.toString());
681 if (!pRects->empty())
683 aFirstPortionPaintArea = (*pRects)[0];
684 aLastPortionPaintArea = (*pRects)[pRects->size() - 1];
686 pContentControl = pCurContentControlAtCursor->GetContentControl().GetContentControl();
688 // The layout knows if the text node is RTL (either set directly, or inherited from the
689 // environment).
690 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aFrames(*pTextNode);
691 SwTextFrame* pFrame = aFrames.First();
692 if (pFrame)
694 bRTL = pFrame->IsRightToLeft();
699 auto pWrtShell = dynamic_cast<const SwWrtShell*>(GetShell());
700 if (!aContentControlRanges.empty())
702 if (comphelper::LibreOfficeKit::isActive())
704 OString aPayload = comphelper::string::join("; ", aLOKRectangles);
705 tools::JsonWriter aJson;
706 aJson.put("action", "show");
707 aJson.put("rectangles", aPayload);
709 if (pContentControl && (pContentControl->GetComboBox() || pContentControl->GetDropDown()))
711 tools::ScopedJsonWriterArray aItems = aJson.startArray("items");
712 for (const auto& rItem : pContentControl->GetListItems())
714 aJson.putSimpleValue(rItem.ToString());
718 if (pContentControl && pContentControl->GetDate())
720 aJson.put("date", "true");
723 if (pContentControl && !pContentControl->GetAlias().isEmpty())
725 aJson.put("alias", pContentControl->GetAlias());
728 OString pJson(aJson.finishAndGetAsOString());
729 GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CONTENT_CONTROL, pJson);
731 if (m_pContentControlOverlay)
733 m_pContentControlOverlay->setRanges(std::move(aContentControlRanges));
735 else
737 SdrView* pView = const_cast<SdrView*>(GetShell()->GetDrawView());
738 SdrPaintWindow* pCandidate = pView->GetPaintWindow(0);
739 const rtl::Reference<sdr::overlay::OverlayManager>& xTargetOverlay
740 = pCandidate->GetOverlayManager();
742 if (xTargetOverlay.is())
744 // Use the system's highlight color with decreased luminance as highlight color.
745 Color aHighlight(SvtOptionsDrawinglayer::getHilightColor());
746 aHighlight.DecreaseLuminance(128);
748 m_pContentControlOverlay.reset(new sw::overlay::OverlayRangesOutline(
749 aHighlight, std::move(aContentControlRanges)));
750 xTargetOverlay->add(*m_pContentControlOverlay);
754 if (pContentControl && (pContentControl->GetComboBox() || pContentControl->GetDropDown()))
756 if (pWrtShell)
758 auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
759 if (m_pContentControlButton
760 && m_pContentControlButton->GetContentControl() != pContentControl)
762 m_pContentControlButton.disposeAndClear();
764 if (!m_pContentControlButton)
766 m_pContentControlButton = VclPtr<SwDropDownContentControlButton>::Create(
767 &rEditWin, pContentControl);
769 m_pContentControlButton->SetRTL(bRTL);
770 if (bRTL)
772 m_pContentControlButton->CalcPosAndSize(aFirstPortionPaintArea);
774 else
776 m_pContentControlButton->CalcPosAndSize(aLastPortionPaintArea);
778 m_pContentControlButton->Show();
781 if (pContentControl && pContentControl->GetDate())
783 if (pWrtShell)
785 auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
786 if (m_pContentControlButton
787 && m_pContentControlButton->GetContentControl() != pContentControl)
789 m_pContentControlButton.disposeAndClear();
791 if (!m_pContentControlButton)
793 m_pContentControlButton = VclPtr<SwDateContentControlButton>::Create(
794 &rEditWin, pContentControl, pWrtShell->GetDoc()->GetNumberFormatter());
796 m_pContentControlButton->CalcPosAndSize(aLastPortionPaintArea);
797 m_pContentControlButton->Show();
801 if (pWrtShell)
803 auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
804 SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager();
805 if (pContentControl && !pContentControl->GetAlias().isEmpty())
807 Point aTopLeftPixel = rEditWin.LogicToPixel(aFirstPortionPaintArea.TopLeft());
808 rMngr.SetContentControlAliasButton(pContentControl.get(), aTopLeftPixel);
810 else
812 rMngr.HideControls(FrameControlType::ContentControl);
816 else
818 if (comphelper::LibreOfficeKit::isActive() && m_pContentControlOverlay)
820 tools::JsonWriter aJson;
821 aJson.put("action", "hide");
822 OString pJson(aJson.finishAndGetAsOString());
823 GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CONTENT_CONTROL, pJson);
825 m_pContentControlOverlay.reset();
827 if (m_pContentControlButton)
829 m_pContentControlButton.disposeAndClear();
832 if (pWrtShell)
834 auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin());
835 SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager();
836 rMngr.HideControls(FrameControlType::ContentControl);
841 const VclPtr<SwContentControlButton>& SwSelPaintRects::GetContentControlButton() const
843 return m_pContentControlButton;
846 void SwSelPaintRects::Invalidate( const SwRect& rRect )
848 size_type nSz = size();
849 if( !nSz )
850 return;
852 SwRegionRects aReg( GetShell()->VisArea() );
853 aReg.assign( begin(), end() );
854 aReg -= rRect;
855 SwRects::erase( begin(), begin() + nSz );
856 SwRects::insert( begin(), aReg.begin(), aReg.end() );
858 // If the selection is to the right or at the bottom, outside the
859 // visible area, it is never aligned on one pixel at the right/bottom.
860 // This has to be determined here and if that is the case the
861 // rectangle has to be expanded.
862 if( !(GetShell()->m_bVisPortChgd && 0 != ( nSz = size())) )
863 return;
865 SwSelPaintRects::Get1PixelInLogic( *GetShell() );
866 iterator it = begin();
867 for( ; nSz--; ++it )
869 SwRect& rRectIt = *it;
870 if( rRectIt.Right() == GetShell()->m_aOldRBPos.X() )
871 rRectIt.AddRight( s_nPixPtX );
872 if( rRectIt.Bottom() == GetShell()->m_aOldRBPos.Y() )
873 rRectIt.AddBottom( s_nPixPtY );
877 // check current MapMode of the shell and set possibly the static members.
878 // Optional set the parameters pX, pY
879 void SwSelPaintRects::Get1PixelInLogic( const SwViewShell& rSh,
880 tools::Long* pX, tools::Long* pY )
882 const OutputDevice* pOut = rSh.GetWin()->GetOutDev();
883 if ( ! pOut )
884 pOut = rSh.GetOut();
886 const MapMode& rMM = pOut->GetMapMode();
887 if (s_pMapMode->GetMapUnit() != rMM.GetMapUnit() ||
888 s_pMapMode->GetScaleX() != rMM.GetScaleX() ||
889 s_pMapMode->GetScaleY() != rMM.GetScaleY())
891 *s_pMapMode = rMM;
892 Size aTmp( 1, 1 );
893 aTmp = pOut->PixelToLogic( aTmp );
894 s_nPixPtX = aTmp.Width();
895 s_nPixPtY = aTmp.Height();
897 if( pX )
898 *pX = s_nPixPtX;
899 if( pY )
900 *pY = s_nPixPtY;
903 SwShellCursor::SwShellCursor(
904 const SwCursorShell& rCShell,
905 const SwPosition &rPos )
906 : SwCursor(rPos,nullptr)
907 , SwSelPaintRects(rCShell)
908 , m_pInitialPoint(SwPaM::GetPoint())
911 SwShellCursor::SwShellCursor(
912 const SwCursorShell& rCShell,
913 const SwPosition &rPos,
914 const Point& rPtPos,
915 SwPaM* pRing )
916 : SwCursor(rPos, pRing)
917 , SwSelPaintRects(rCShell)
918 , m_MarkPt(rPtPos)
919 , m_PointPt(rPtPos)
920 , m_pInitialPoint(SwPaM::GetPoint())
923 SwShellCursor::SwShellCursor( SwShellCursor& rICursor )
924 : SwCursor(rICursor, &rICursor)
925 , SwSelPaintRects(*rICursor.GetShell())
926 , m_MarkPt(rICursor.GetMkPos())
927 , m_PointPt(rICursor.GetPtPos())
928 , m_pInitialPoint(SwPaM::GetPoint())
931 SwShellCursor::~SwShellCursor()
934 bool SwShellCursor::IsReadOnlyAvailable() const
936 return GetShell()->IsReadOnlyAvailable();
939 void SwShellCursor::SetMark()
941 if (SwPaM::GetPoint() == m_pInitialPoint)
942 m_MarkPt = m_PointPt;
943 else
944 m_PointPt = m_MarkPt;
945 SwPaM::SetMark();
948 void SwShellCursor::FillRects()
950 // calculate the new rectangles
951 if( HasMark() &&
952 GetPoint()->GetNode().IsContentNode() &&
953 GetPoint()->GetNode().GetContentNode()->getLayoutFrame( GetShell()->GetLayout() ) &&
954 (GetMark()->GetNode() == GetPoint()->GetNode() ||
955 (GetMark()->GetNode().IsContentNode() &&
956 GetMark()->GetNode().GetContentNode()->getLayoutFrame( GetShell()->GetLayout() ) ) ))
957 GetShell()->GetLayout()->CalcFrameRects( *this );
960 void SwShellCursor::Show(SfxViewShell const * pViewShell)
962 std::vector<OString> aSelectionRectangles;
963 for(SwPaM& rPaM : GetRingContainer())
965 SwShellCursor* pShCursor = dynamic_cast<SwShellCursor*>(&rPaM);
966 if(pShCursor)
967 pShCursor->SwSelPaintRects::Show(&aSelectionRectangles);
970 if (!comphelper::LibreOfficeKit::isActive())
971 return;
973 std::vector<OString> aRect;
974 for (const OString & rSelectionRectangle : aSelectionRectangles)
976 if (rSelectionRectangle.isEmpty())
977 continue;
978 aRect.push_back(rSelectionRectangle);
980 OString sRect = comphelper::string::join("; ", aRect);
981 if (pViewShell)
983 // Just notify pViewShell about our existing selection.
984 if (pViewShell != GetShell()->GetSfxViewShell())
985 SfxLokHelper::notifyOtherView(GetShell()->GetSfxViewShell(), pViewShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRect);
987 else
989 GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRect);
990 SfxLokHelper::notifyOtherViews(GetShell()->GetSfxViewShell(), LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRect);
994 // This rectangle gets painted anew, therefore the SSelection in this
995 // area is invalid.
996 void SwShellCursor::Invalidate( const SwRect& rRect )
998 for(SwPaM& rPaM : GetRingContainer())
1000 SwShellCursor* pShCursor = dynamic_cast<SwShellCursor*>(&rPaM);
1001 // skip any non SwShellCursor objects in the ring
1002 // see also: SwAutoFormat::DeleteSel()
1003 if(pShCursor)
1004 pShCursor->SwSelPaintRects::Invalidate(rRect);
1008 void SwShellCursor::Hide()
1010 for(SwPaM& rPaM : GetRingContainer())
1012 SwShellCursor* pShCursor = dynamic_cast<SwShellCursor*>(&rPaM);
1013 if(pShCursor)
1014 pShCursor->SwSelPaintRects::Hide();
1018 SwCursor* SwShellCursor::Create( SwPaM* pRing ) const
1020 return new SwShellCursor( *GetShell(), *GetPoint(), GetPtPos(), pRing );
1023 short SwShellCursor::MaxReplaceArived()
1025 short nRet = RET_YES;
1026 SvxSearchDialog* pDlg = SwView::GetSearchDialog();
1027 if( pDlg )
1029 // Terminate old actions. The table-frames get constructed and
1030 // a SSelection can be created.
1031 std::vector<sal_uInt16> vActionCounts;
1032 for(SwViewShell& rShell : const_cast< SwCursorShell* >( GetShell() )->GetRingContainer())
1034 sal_uInt16 nActCnt = 0;
1035 while(rShell.ActionPend())
1037 rShell.EndAction();
1038 ++nActCnt;
1040 vActionCounts.push_back(nActCnt);
1042 std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pDlg->getDialog(), "modules/swriter/ui/asksearchdialog.ui"));
1043 std::unique_ptr<weld::MessageDialog> xDialog(xBuilder->weld_message_dialog("AskSearchDialog"));
1044 nRet = xDialog->run();
1045 auto pActionCount = vActionCounts.begin();
1046 for(SwViewShell& rShell : const_cast< SwCursorShell* >( GetShell() )->GetRingContainer())
1048 while(*pActionCount)
1050 rShell.StartAction();
1051 --(*pActionCount);
1053 ++pActionCount;
1056 else
1057 // otherwise from the Basic, and then switch to RET_YES
1058 nRet = RET_YES;
1060 return nRet;
1063 void SwShellCursor::SaveTableBoxContent( const SwPosition* pPos )
1065 const_cast<SwCursorShell*>(GetShell())->SaveTableBoxContent( pPos );
1068 bool SwShellCursor::UpDown( bool bUp, sal_uInt16 nCnt )
1070 // tdf#124603 trigger pending spell checking of the node
1071 if ( nCnt == 1 )
1073 SwTextNode* pNode = GetPoint()->GetNode().GetTextNode();
1074 if( pNode && sw::WrongState::PENDING == pNode->GetWrongDirty() )
1076 SwWrtShell* pShell = pNode->GetDoc().GetDocShell()->GetWrtShell();
1077 if ( pShell && !pShell->IsSelection() && !pShell->IsSelFrameMode() )
1079 const SwViewOption* pVOpt = pShell->GetViewOptions();
1080 if ( pVOpt && pVOpt->IsOnlineSpell() )
1082 const bool bOldViewLock = pShell->IsViewLocked();
1083 pShell->LockView( true );
1085 SwTextFrame* pFrame(
1086 static_cast<SwTextFrame*>(pNode->getLayoutFrame(GetShell()->GetLayout())));
1087 SwRect aRepaint(pFrame->AutoSpell_(*pNode, 0));
1088 if (aRepaint.HasArea())
1089 pShell->InvalidateWindows(aRepaint);
1091 pShell->LockView( bOldViewLock );
1097 return SwCursor::UpDown( bUp, nCnt,
1098 &GetPtPos(), GetShell()->GetUpDownX(),
1099 *GetShell()->GetLayout());
1102 // if <true> than the cursor can be set to the position.
1103 bool SwShellCursor::IsAtValidPos( bool bPoint ) const
1105 if( GetShell() && ( GetShell()->IsAllProtect() ||
1106 GetShell()->GetViewOptions()->IsReadonly() ||
1107 ( GetShell()->Imp()->GetDrawView() &&
1108 GetShell()->Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() )))
1109 return true;
1111 return SwCursor::IsAtValidPos( bPoint );
1114 SwShellTableCursor::SwShellTableCursor( const SwCursorShell& rCursorSh,
1115 const SwPosition& rPos )
1116 : SwCursor(rPos,nullptr), SwShellCursor(rCursorSh, rPos), SwTableCursor(rPos)
1120 SwShellTableCursor::SwShellTableCursor( const SwCursorShell& rCursorSh,
1121 const SwPosition& rMkPos, const Point& rMkPt,
1122 const SwPosition& rPtPos, const Point& rPtPt )
1123 : SwCursor(rPtPos,nullptr), SwShellCursor(rCursorSh, rPtPos), SwTableCursor(rPtPos)
1125 SetMark();
1126 *GetMark() = rMkPos;
1127 GetMkPos() = rMkPt;
1128 GetPtPos() = rPtPt;
1131 SwShellTableCursor::~SwShellTableCursor() {}
1133 void SwShellTableCursor::SetMark() { SwShellCursor::SetMark(); }
1135 SwCursor* SwShellTableCursor::Create( SwPaM* pRing ) const
1137 return SwShellCursor::Create( pRing );
1140 short SwShellTableCursor::MaxReplaceArived()
1142 return SwShellCursor::MaxReplaceArived();
1145 void SwShellTableCursor::SaveTableBoxContent( const SwPosition* pPos )
1147 SwShellCursor::SaveTableBoxContent( pPos );
1150 void SwShellTableCursor::FillRects()
1152 // Calculate the new rectangles. If the cursor is still "parked" do nothing
1153 if (m_SelectedBoxes.empty() || m_bParked || !GetPoint()->GetNodeIndex())
1154 return;
1156 bool bStart = true;
1157 SwRegionRects aReg( comphelper::LibreOfficeKit::isActive()
1158 ? GetShell()->getIDocumentLayoutAccess().GetCurrentLayout()->getFrameArea()
1159 : GetShell()->VisArea() );
1160 SwNodes& rNds = GetDoc().GetNodes();
1161 SwFrame* pEndFrame = nullptr;
1162 for (size_t n = 0; n < m_SelectedBoxes.size(); ++n)
1164 const SwStartNode* pSttNd = m_SelectedBoxes[n]->GetSttNd();
1165 const SwTableNode* pSelTableNd = pSttNd->FindTableNode();
1167 SwNodeIndex aIdx( *pSttNd );
1168 SwContentNode* pCNd = rNds.GoNextSection( &aIdx, true, false );
1170 // table in table
1171 // (see also lcl_FindTopLevelTable in unoobj2.cxx for a different
1172 // version to do this)
1173 const SwTableNode* pCurTableNd = pCNd ? pCNd->FindTableNode() : nullptr;
1174 while ( pSelTableNd != pCurTableNd && pCurTableNd )
1176 aIdx = pCurTableNd->EndOfSectionIndex();
1177 pCNd = rNds.GoNextSection( &aIdx, true, false );
1178 pCurTableNd = pCNd->FindTableNode();
1181 if( !pCNd )
1182 continue;
1184 std::pair<Point, bool> const tmp(GetSttPos(), false);
1185 SwFrame* pFrame = pCNd->getLayoutFrame(GetShell()->GetLayout(), nullptr, &tmp);
1186 while( pFrame && !pFrame->IsCellFrame() )
1187 pFrame = pFrame->GetUpper();
1189 OSL_ENSURE( pFrame, "Node not in a table" );
1191 while ( pFrame )
1193 if( aReg.GetOrigin().Overlaps( pFrame->getFrameArea() ) )
1195 aReg -= pFrame->getFrameArea();
1196 if (bStart)
1198 bStart = false;
1199 m_aStart = SwRect(pFrame->getFrameArea().Left(), pFrame->getFrameArea().Top(), 1, pFrame->getFrameArea().Height());
1203 pEndFrame = pFrame;
1204 pFrame = pFrame->GetNextCellLeaf();
1207 if (pEndFrame)
1208 m_aEnd = SwRect(pEndFrame->getFrameArea().Right(), pEndFrame->getFrameArea().Top(), 1, pEndFrame->getFrameArea().Height());
1209 aReg.Invert();
1210 insert( begin(), aReg.begin(), aReg.end() );
1213 void SwShellTableCursor::FillStartEnd(SwRect& rStart, SwRect& rEnd) const
1215 rStart = m_aStart;
1216 rEnd = m_aEnd;
1219 // Check if the SPoint is within the Table-SSelection.
1220 bool SwShellTableCursor::Contains( const Point& rPt ) const
1222 // Calculate the new rectangles. If the cursor is still "parked" do nothing
1223 if (m_SelectedBoxes.empty() || m_bParked || !GetPoint()->GetNodeIndex())
1224 return false;
1226 SwNodes& rNds = GetDoc().GetNodes();
1227 for (size_t n = 0; n < m_SelectedBoxes.size(); ++n)
1229 SwNodeIndex aIdx( *m_SelectedBoxes[n]->GetSttNd() );
1230 SwContentNode* pCNd = rNds.GoNextSection( &aIdx, true, false );
1231 if( !pCNd )
1232 continue;
1234 std::pair<Point, bool> const tmp(GetPtPos(), true);
1235 SwFrame* pFrame = pCNd->getLayoutFrame(GetShell()->GetLayout(), nullptr, &tmp);
1236 while( pFrame && !pFrame->IsCellFrame() )
1237 pFrame = pFrame->GetUpper();
1238 OSL_ENSURE( pFrame, "Node not in a table" );
1239 if( pFrame && pFrame->getFrameArea().Contains( rPt ) )
1240 return true;
1242 for ( SwCellFrame* pCellFrame = static_cast<SwCellFrame*>(pFrame); pCellFrame; pCellFrame = pCellFrame->GetFollowCell() )
1244 if( pCellFrame->getFrameArea().Contains( rPt ) )
1245 return true;
1248 return false;
1251 bool SwShellTableCursor::IsAtValidPos( bool bPoint ) const
1253 return SwShellCursor::IsAtValidPos( bPoint );
1256 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */