Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / source / uibase / docvw / AnnotationWin.cxx
blob3d350048055b8b2e6294294d686f8eb3cb82bb46
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_wasm_strip.h>
22 #include <AnnotationWin.hxx>
24 #include <PostItMgr.hxx>
26 #include <strings.hrc>
28 #include <uiobject.hxx>
30 #include <vcl/svapp.hxx>
31 #include <vcl/uitest/logger.hxx>
32 #include <vcl/uitest/eventdescription.hxx>
34 #include <svl/undo.hxx>
35 #include <unotools/localedatawrapper.hxx>
36 #include <unotools/syslocale.hxx>
37 #include <svl/languageoptions.hxx>
38 #include <osl/diagnose.h>
40 #include <editeng/eeitem.hxx>
41 #include <editeng/postitem.hxx>
42 #include <editeng/fhgtitem.hxx>
43 #include <editeng/langitem.hxx>
45 #include <editeng/editview.hxx>
46 #include <editeng/outliner.hxx>
47 #include <editeng/editeng.hxx>
48 #include <editeng/editobj.hxx>
49 #include <editeng/outlobj.hxx>
51 #include <comphelper/lok.hxx>
52 #include <comphelper/random.hxx>
53 #include <docufld.hxx>
54 #include <txtfld.hxx>
55 #include <ndtxt.hxx>
56 #include <view.hxx>
57 #include <viewopt.hxx>
58 #include <wrtsh.hxx>
59 #include <docsh.hxx>
60 #include <doc.hxx>
61 #include <IDocumentUndoRedo.hxx>
62 #include <SwUndoField.hxx>
63 #include <edtwin.hxx>
64 #include "ShadowOverlayObject.hxx"
65 #include "AnchorOverlayObject.hxx"
66 #include "OverlayRanges.hxx"
67 #include "SidebarTxtControl.hxx"
68 #include "SidebarWinAcc.hxx"
70 #include <memory>
72 namespace{
74 void collectUIInformation( const OUString& aevent , const OUString& aID )
76 EventDescription aDescription;
77 aDescription.aID = aID;
78 aDescription.aParameters = {{"" , ""}};
79 aDescription.aAction = aevent;
80 aDescription.aParent = "MainWindow";
81 aDescription.aKeyWord = "SwEditWinUIObject";
82 UITestLogger::getInstance().logEvent(aDescription);
87 namespace sw::annotation {
89 // see AnnotationContents in sd for something similar
90 SwAnnotationWin::SwAnnotationWin( SwEditWin& rEditWin,
91 SwPostItMgr& aMgr,
92 SwSidebarItem& rSidebarItem,
93 SwFormatField* aField )
94 : InterimItemWindow(&rEditWin, "modules/swriter/ui/annotation.ui", "Annotation")
95 , mrMgr(aMgr)
96 , mrView(rEditWin.GetView())
97 , mnDeleteEventId(nullptr)
98 , meSidebarPosition(sw::sidebarwindows::SidebarPosition::NONE)
99 , mPageBorder(0)
100 , mbAnchorRectChanged(false)
101 , mbResolvedStateUpdated(false)
102 , mbMouseOver(false)
103 , mLayoutStatus(SwPostItHelper::INVISIBLE)
104 , mbReadonly(false)
105 , mbIsFollow(false)
106 , mrSidebarItem(rSidebarItem)
107 , mpAnchorFrame(rSidebarItem.maLayoutInfo.mpAnchorFrame)
108 , mpFormatField(aField)
109 , mpField( static_cast<SwPostItField*>(aField->GetField()))
111 set_id("Comment"+OUString::number(mpField->GetPostItId()));
113 m_xContainer->connect_mouse_move(LINK(this, SwAnnotationWin, MouseMoveHdl));
115 mpShadow = sidebarwindows::ShadowOverlayObject::CreateShadowOverlayObject( mrView );
116 if ( mpShadow )
118 mpShadow->setVisible(false);
121 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
122 mrMgr.ConnectSidebarWinToFrame( *(mrSidebarItem.maLayoutInfo.mpAnchorFrame),
123 mrSidebarItem.GetFormatField(),
124 *this );
125 #endif
127 if (SupportsDoubleBuffering())
128 // When double-buffering, allow parents to paint on our area. That's
129 // necessary when parents paint the complete buffer.
130 SetParentClipMode(ParentClipMode::NoClip);
133 SwAnnotationWin::~SwAnnotationWin()
135 disposeOnce();
138 void SwAnnotationWin::dispose()
140 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
141 mrMgr.DisconnectSidebarWinFromFrame( *(mrSidebarItem.maLayoutInfo.mpAnchorFrame),
142 *this );
143 #endif
144 Disable();
146 mxSidebarTextControlWin.reset();
147 mxSidebarTextControl.reset();
149 mxMetadataAuthor.reset();
150 mxMetadataResolved.reset();
151 mxMetadataDate.reset();
152 mxVScrollbar.reset();
154 mpAnchor.reset();
155 mpShadow.reset();
157 mpTextRangeOverlay.reset();
159 mxMenuButton.reset();
161 if (mnDeleteEventId)
162 Application::RemoveUserEvent(mnDeleteEventId);
164 mpOutliner.reset();
165 mpOutlinerView.reset();
167 InterimItemWindow::dispose();
170 void SwAnnotationWin::SetPostItText()
172 //If the cursor was visible, then make it visible again after
173 //changing text, e.g. fdo#33599
174 vcl::Cursor *pCursor = GetOutlinerView()->GetEditView().GetCursor();
175 bool bCursorVisible = pCursor && pCursor->IsVisible();
177 //If the new text is the same as the old text, keep the same insertion
178 //point .e.g. fdo#33599
179 mpField = static_cast<SwPostItField*>(mpFormatField->GetField());
180 OUString sNewText = mpField->GetPar2();
181 bool bTextUnchanged = sNewText == mpOutliner->GetEditEngine().GetText();
182 ESelection aOrigSelection(GetOutlinerView()->GetEditView().GetSelection());
184 // get text from SwPostItField and insert into our textview
185 mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() );
186 mpOutliner->EnableUndo( false );
187 if( mpField->GetTextObject() )
188 mpOutliner->SetText( *mpField->GetTextObject() );
189 else
191 mpOutliner->Clear();
192 GetOutlinerView()->SetAttribs(DefaultItem());
193 GetOutlinerView()->InsertText(sNewText);
196 mpOutliner->ClearModifyFlag();
197 mpOutliner->GetUndoManager().Clear();
198 mpOutliner->EnableUndo( true );
199 mpOutliner->SetModifyHdl( LINK( this, SwAnnotationWin, ModifyHdl ) );
200 if (bTextUnchanged)
201 GetOutlinerView()->GetEditView().SetSelection(aOrigSelection);
202 if (bCursorVisible)
203 GetOutlinerView()->ShowCursor();
204 Invalidate();
207 void SwAnnotationWin::SetResolved(bool resolved)
209 bool oldState = IsResolved();
210 static_cast<SwPostItField*>(mpFormatField->GetField())->SetResolved(resolved);
211 if (SwWrtShell* pWrtShell = mrView.GetWrtShellPtr())
213 const SwViewOption* pVOpt = pWrtShell->GetViewOptions();
214 mrSidebarItem.mbShow = !IsResolved() || (pVOpt->IsResolvedPostIts());
217 mpTextRangeOverlay.reset();
219 if(IsResolved())
220 mxMetadataResolved->show();
221 else
222 mxMetadataResolved->hide();
224 if(IsResolved() != oldState)
225 mbResolvedStateUpdated = true;
226 UpdateData();
227 Invalidate();
228 collectUIInformation("SETRESOLVED",get_id());
231 void SwAnnotationWin::ToggleResolved()
233 SetResolved(!IsResolved());
236 void SwAnnotationWin::ToggleResolvedForThread()
238 GetTopReplyNote()->ToggleResolved();
239 mrMgr.UpdateResolvedStatus(GetTopReplyNote());
240 mrMgr.LayoutPostIts();
243 sal_uInt32 SwAnnotationWin::GetParaId()
245 auto pField = static_cast<SwPostItField*>(mpFormatField->GetField());
246 auto nParaId = pField->GetParaId();
247 if (nParaId == 0)
249 // The parent annotation does not have a paraId. This happens when the annotation was just
250 // created, and not imported. paraIds are regenerated upon export, thus this new paraId
251 // is only generated so that children annotations can refer to it
252 nParaId = CreateUniqueParaId();
253 pField->SetParaId(nParaId);
255 return nParaId;
258 sal_uInt32 SwAnnotationWin::CreateUniqueParaId()
260 return comphelper::rng::uniform_uint_distribution(0, std::numeric_limits<sal_uInt32>::max());
263 void SwAnnotationWin::DeleteThread()
265 // Go to the top and delete each comment one by one
266 SwAnnotationWin *current, *topNote;
267 current = topNote = GetTopReplyNote();
268 SwAnnotationWin* next = mrMgr.GetNextPostIt(KEY_PAGEDOWN, current);
270 while(next && next->GetTopReplyNote() == topNote)
272 current->mnDeleteEventId = Application::PostUserEvent( LINK( current, SwAnnotationWin, DeleteHdl), nullptr, true );
273 current = next;
274 next = mrMgr.GetNextPostIt(KEY_PAGEDOWN, current);
276 current->mnDeleteEventId = Application::PostUserEvent( LINK( current, SwAnnotationWin, DeleteHdl), nullptr, true );
279 bool SwAnnotationWin::IsResolved() const
281 return static_cast<SwPostItField*>(mpFormatField->GetField())->GetResolved();
284 bool SwAnnotationWin::IsThreadResolved()
286 /// First Get the top note
287 // then iterate downwards checking resolved status
288 SwAnnotationWin *pTopNote, *TopNote;
289 pTopNote = TopNote = GetTopReplyNote();
290 if (!pTopNote->IsResolved())
291 return false;
293 SwAnnotationWin* pSidebarWin = mrMgr.GetNextPostIt(KEY_PAGEDOWN, pTopNote);
295 while (pSidebarWin && pSidebarWin->GetTopReplyNote() == TopNote)
297 pTopNote = pSidebarWin;
298 if (!pTopNote->IsResolved())
299 return false;
300 pSidebarWin = mrMgr.GetNextPostIt(KEY_PAGEDOWN, pSidebarWin);
302 return true;
305 void SwAnnotationWin::UpdateData()
307 if ( mpOutliner->IsModified() || mbResolvedStateUpdated )
309 IDocumentUndoRedo & rUndoRedo(
310 mrView.GetDocShell()->GetDoc()->GetIDocumentUndoRedo());
311 std::unique_ptr<SwField> pOldField;
312 if (rUndoRedo.DoesUndo())
314 pOldField = mpField->Copy();
316 mpField->SetPar2(mpOutliner->GetEditEngine().GetText());
317 mpField->SetTextObject(mpOutliner->CreateParaObject());
318 if (rUndoRedo.DoesUndo())
320 SwTextField *const pTextField = mpFormatField->GetTextField();
321 SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() );
322 rUndoRedo.AppendUndo(
323 std::make_unique<SwUndoFieldFromDoc>(aPosition, *pOldField, *mpField, true));
325 // so we get a new layout of notes (anchor position is still the same and we would otherwise not get one)
326 mrMgr.SetLayout();
327 // #i98686# if we have several views, all notes should update their text
328 if(mbResolvedStateUpdated)
329 mpFormatField->Broadcast(SwFormatFieldHint( nullptr, SwFormatFieldHintWhich::RESOLVED));
330 else
331 mpFormatField->Broadcast(SwFormatFieldHint( nullptr, SwFormatFieldHintWhich::CHANGED));
332 mrView.GetDocShell()->SetModified();
334 mpOutliner->ClearModifyFlag();
335 mpOutliner->GetUndoManager().Clear();
336 mbResolvedStateUpdated = false;
339 void SwAnnotationWin::Delete()
341 collectUIInformation("DELETE",get_id());
342 SwWrtShell* pWrtShell = mrView.GetWrtShellPtr();
343 if (!(pWrtShell && pWrtShell->GotoField(*mpFormatField)))
344 return;
346 if ( mrMgr.GetActiveSidebarWin() == this)
348 mrMgr.SetActiveSidebarWin(nullptr);
349 // if the note is empty, the previous line will send a delete event, but we are already there
350 if (mnDeleteEventId)
352 Application::RemoveUserEvent(mnDeleteEventId);
353 mnDeleteEventId = nullptr;
356 // we delete the field directly, the Mgr cleans up the PostIt by listening
357 GrabFocusToDocument();
358 pWrtShell->ClearMark();
359 pWrtShell->DelRight();
362 void SwAnnotationWin::GotoPos()
364 mrView.GetDocShell()->GetWrtShell()->GotoField(*mpFormatField);
367 sal_uInt32 SwAnnotationWin::MoveCaret()
369 // if this is an answer, do not skip over all following ones, but insert directly behind the current one
370 // but when just leaving a note, skip all following ones as well to continue typing
371 return mrMgr.IsAnswer()
373 : 1 + CountFollowing();
376 // returns a non-zero postit parent id, if exists, otherwise 0 for root comments
377 sal_uInt32 SwAnnotationWin::CalcParent()
379 SwTextField* pTextField = mpFormatField->GetTextField();
380 SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() );
381 SwTextAttr * const pTextAttr =
382 pTextField->GetTextNode().GetTextAttrForCharAt(
383 aPosition.GetContentIndex() - 1,
384 RES_TXTATR_ANNOTATION );
385 const SwField* pField = pTextAttr ? pTextAttr->GetFormatField().GetField() : nullptr;
386 sal_uInt32 nParentId = 0;
387 if (pField && pField->Which() == SwFieldIds::Postit)
389 const SwPostItField* pPostItField = static_cast<const SwPostItField*>(pField);
390 nParentId = pPostItField->GetPostItId();
392 return nParentId;
395 // counts how many SwPostItField we have right after the current one
396 sal_uInt32 SwAnnotationWin::CountFollowing()
398 sal_uInt32 aCount = 1; // we start with 1, so we have to subtract one at the end again
399 SwTextField* pTextField = mpFormatField->GetTextField();
400 SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() );
402 SwTextAttr * pTextAttr = pTextField->GetTextNode().GetTextAttrForCharAt(
403 aPosition.GetContentIndex() + 1,
404 RES_TXTATR_ANNOTATION );
405 SwField* pField = pTextAttr
406 ? const_cast<SwField*>(pTextAttr->GetFormatField().GetField())
407 : nullptr;
408 while ( pField && ( pField->Which()== SwFieldIds::Postit ) )
410 aCount++;
411 pTextAttr = pTextField->GetTextNode().GetTextAttrForCharAt(
412 aPosition.GetContentIndex() + aCount,
413 RES_TXTATR_ANNOTATION );
414 pField = pTextAttr
415 ? const_cast<SwField*>(pTextAttr->GetFormatField().GetField())
416 : nullptr;
418 return aCount - 1;
421 void SwAnnotationWin::InitAnswer(OutlinerParaObject const & rText)
423 // If tiled annotations is off in lok case, skip adding additional reply text.
424 if (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations())
425 return;
427 //collect our old meta data
428 SwAnnotationWin* pWin = mrMgr.GetNextPostIt(KEY_PAGEUP, this);
429 if (!pWin)
430 return;
432 const SvtSysLocale aSysLocale;
433 const LocaleDataWrapper& rLocalData = aSysLocale.GetLocaleData();
434 SwRewriter aRewriter;
435 aRewriter.AddRule(UndoArg1, pWin->GetAuthor());
436 const OUString aText = aRewriter.Apply(SwResId(STR_REPLY))
437 + " (" + rLocalData.getDate( pWin->GetDate())
438 + ", " + rLocalData.getTime( pWin->GetTime(), false)
439 + "): \"";
440 GetOutlinerView()->InsertText(aText);
442 // insert old, selected text or "..."
443 // TODO: iterate over all paragraphs, not only first one to find out if it is empty
444 if (!rText.GetTextObject().GetText(0).isEmpty())
445 GetOutlinerView()->GetEditView().InsertText(rText.GetTextObject());
446 else
447 GetOutlinerView()->InsertText("...");
448 GetOutlinerView()->InsertText("\"\n");
450 GetOutlinerView()->SetSelection(ESelection(0,0,EE_PARA_ALL,EE_TEXTPOS_ALL));
451 SfxItemSet aAnswerSet( mrView.GetDocShell()->GetPool() );
452 aAnswerSet.Put(SvxFontHeightItem(200,80,EE_CHAR_FONTHEIGHT));
453 aAnswerSet.Put(SvxPostureItem(ITALIC_NORMAL,EE_CHAR_ITALIC));
454 GetOutlinerView()->SetAttribs(aAnswerSet);
455 GetOutlinerView()->SetSelection(ESelection(EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT,EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT));
457 //remove all attributes and reset our standard ones
458 GetOutlinerView()->GetEditView().RemoveAttribsKeepLanguages(true);
459 GetOutlinerView()->SetAttribs(DefaultItem());
460 // lets insert an undo step so the initial text can be easily deleted
461 // but do not use UpdateData() directly, would set modified state again and reentrance into Mgr
462 mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() );
463 IDocumentUndoRedo & rUndoRedo(
464 mrView.GetDocShell()->GetDoc()->GetIDocumentUndoRedo());
465 std::unique_ptr<SwField> pOldField;
466 if (rUndoRedo.DoesUndo())
468 pOldField = mpField->Copy();
470 mpField->SetPar2(mpOutliner->GetEditEngine().GetText());
471 mpField->SetTextObject(mpOutliner->CreateParaObject());
472 if (rUndoRedo.DoesUndo())
474 SwTextField *const pTextField = mpFormatField->GetTextField();
475 SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() );
476 rUndoRedo.AppendUndo(
477 std::make_unique<SwUndoFieldFromDoc>(aPosition, *pOldField, *mpField, true));
479 mpOutliner->SetModifyHdl( LINK( this, SwAnnotationWin, ModifyHdl ) );
480 mpOutliner->ClearModifyFlag();
481 mpOutliner->GetUndoManager().Clear();
484 void SwAnnotationWin::UpdateText(const OUString& aText)
486 mpOutliner->Clear();
487 GetOutlinerView()->InsertText(aText);
488 UpdateData();
491 SvxLanguageItem SwAnnotationWin::GetLanguage() const
493 // set initial language for outliner
494 SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( mpField->GetLanguage() );
495 sal_uInt16 nLangWhichId = 0;
496 switch (nScriptType)
498 case SvtScriptType::LATIN : nLangWhichId = EE_CHAR_LANGUAGE ; break;
499 case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break;
500 case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break;
501 default: OSL_FAIL("GetLanguage: wrong script type");
503 return SvxLanguageItem(mpField->GetLanguage(),nLangWhichId);
506 bool SwAnnotationWin::IsReadOnlyOrProtected() const
508 return mbReadonly ||
509 GetLayoutStatus() == SwPostItHelper::DELETED ||
510 ( mpFormatField && mpFormatField->IsProtect() );
513 OUString SwAnnotationWin::GetAuthor() const
515 return mpField->GetPar1();
518 Date SwAnnotationWin::GetDate() const
520 return mpField->GetDate();
523 tools::Time SwAnnotationWin::GetTime() const
525 return mpField->GetTime();
528 FactoryFunction SwAnnotationWin::GetUITestFactory() const
530 return CommentUIObject::create;
533 } // end of namespace sw::annotation
535 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */