Add a comment to clarify what kind of inputs the class handles
[LibreOffice.git] / sw / source / uibase / docvw / AnnotationWin.cxx
blob7ca518b6b5f7962f391227d1388fe14a9bd1de51
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 <svtools/svparser.hxx>
36 #include <unotools/localedatawrapper.hxx>
37 #include <unotools/syslocale.hxx>
38 #include <svl/languageoptions.hxx>
39 #include <osl/diagnose.h>
41 #include <editeng/eeitem.hxx>
42 #include <editeng/postitem.hxx>
43 #include <editeng/fhgtitem.hxx>
44 #include <editeng/langitem.hxx>
45 #include <editeng/editund2.hxx>
47 #include <editeng/editview.hxx>
48 #include <editeng/outliner.hxx>
49 #include <editeng/editeng.hxx>
50 #include <editeng/editobj.hxx>
51 #include <editeng/outlobj.hxx>
53 #include <comphelper/lok.hxx>
54 #include <comphelper/random.hxx>
55 #include <docufld.hxx>
56 #include <txtfld.hxx>
57 #include <ndtxt.hxx>
58 #include <view.hxx>
59 #include <viewopt.hxx>
60 #include <wrtsh.hxx>
61 #include <docsh.hxx>
62 #include <doc.hxx>
63 #include <IDocumentUndoRedo.hxx>
64 #include <SwUndoField.hxx>
65 #include <edtwin.hxx>
66 #include "ShadowOverlayObject.hxx"
67 #include "AnchorOverlayObject.hxx"
68 #include "OverlayRanges.hxx"
69 #include "SidebarTxtControl.hxx"
70 #include "SidebarWinAcc.hxx"
72 #include <memory>
74 namespace{
76 void collectUIInformation( const OUString& aevent , const OUString& aID )
78 EventDescription aDescription;
79 aDescription.aID = aID;
80 aDescription.aParameters = {{"" , ""}};
81 aDescription.aAction = aevent;
82 aDescription.aParent = "MainWindow";
83 aDescription.aKeyWord = "SwEditWinUIObject";
84 UITestLogger::getInstance().logEvent(aDescription);
89 namespace SwPostItHelper {
91 void ImportHTML(Outliner& rOutliner, const OUString& rHtml)
93 OString sHtmlContent(rHtml.toUtf8());
94 SvMemoryStream aHTMLStream(const_cast<char*>(sHtmlContent.getStr()),
95 sHtmlContent.getLength(), StreamMode::READ);
96 SvKeyValueIteratorRef xValues(new SvKeyValueIterator);
97 // Insert newlines for divs, not normally done, so to keep things simple
98 // only enable that for this case.
99 xValues->Append(SvKeyValue("newline-on-div", "true"));
100 xValues->Append(SvKeyValue("content-type", "text/html;charset=utf-8"));
101 rOutliner.Read(aHTMLStream, "", EETextFormat::Html, xValues.get());
106 namespace sw::annotation {
108 // see AnnotationContents in sd for something similar
109 SwAnnotationWin::SwAnnotationWin( SwEditWin& rEditWin,
110 SwPostItMgr& aMgr,
111 SwAnnotationItem& rSidebarItem,
112 SwFormatField* aField )
113 : InterimItemWindow(&rEditWin, u"modules/swriter/ui/annotation.ui"_ustr, u"Annotation"_ustr)
114 , mrMgr(aMgr)
115 , mrView(rEditWin.GetView())
116 , mnDeleteEventId(nullptr)
117 , meSidebarPosition(sw::sidebarwindows::SidebarPosition::NONE)
118 , mPageBorder(0)
119 , mbAnchorRectChanged(false)
120 , mbResolvedStateUpdated(false)
121 , mbMouseOver(false)
122 , mLayoutStatus(SwPostItHelper::INVISIBLE)
123 , mbReadonly(false)
124 , mbIsFollow(false)
125 , mpSidebarItem(&rSidebarItem)
126 , mpAnchorFrame(rSidebarItem.maLayoutInfo.mpAnchorFrame)
127 , mpFormatField(aField)
128 , mpField( static_cast<SwPostItField*>(aField->GetField()))
130 set_id("Comment"+OUString::number(mpField->GetPostItId()));
132 m_xContainer->connect_mouse_move(LINK(this, SwAnnotationWin, MouseMoveHdl));
134 mpShadow = sidebarwindows::ShadowOverlayObject::CreateShadowOverlayObject( mrView );
135 if ( mpShadow )
137 mpShadow->setVisible(false);
140 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
141 if (rSidebarItem.maLayoutInfo.mpAnchorFrame)
143 mrMgr.ConnectSidebarWinToFrame( *(rSidebarItem.maLayoutInfo.mpAnchorFrame),
144 mpSidebarItem->GetFormatField(),
145 *this );
147 #endif
149 if (SupportsDoubleBuffering())
150 // When double-buffering, allow parents to paint on our area. That's
151 // necessary when parents paint the complete buffer.
152 SetParentClipMode(ParentClipMode::NoClip);
155 SwAnnotationWin::~SwAnnotationWin()
157 disposeOnce();
160 void SwAnnotationWin::dispose()
162 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
163 mrMgr.DisconnectSidebarWinFromFrame( *(mpSidebarItem->maLayoutInfo.mpAnchorFrame),
164 *this );
165 #endif
166 Disable();
168 mxSidebarTextControlWin.reset();
169 mxSidebarTextControl.reset();
171 mxMetadataAuthor.reset();
172 mxMetadataResolved.reset();
173 mxMetadataDate.reset();
174 mxVScrollbar.reset();
176 mpAnchor.reset();
177 mpShadow.reset();
179 mpTextRangeOverlay.reset();
181 mxMenuButton.reset();
183 if (mnDeleteEventId)
184 Application::RemoveUserEvent(mnDeleteEventId);
186 mpOutliner.reset();
187 mpOutlinerView.reset();
189 InterimItemWindow::dispose();
192 void SwAnnotationWin::SetPostItText()
194 //If the cursor was visible, then make it visible again after
195 //changing text, e.g. fdo#33599
196 vcl::Cursor *pCursor = GetOutlinerView()->GetEditView().GetCursor();
197 bool bCursorVisible = pCursor && pCursor->IsVisible();
199 //If the new text is the same as the old text, keep the same insertion
200 //point .e.g. fdo#33599
201 mpField = static_cast<SwPostItField*>(mpFormatField->GetField());
202 OUString sNewText = mpField->GetPar2();
203 bool bTextUnchanged = sNewText == mpOutliner->GetEditEngine().GetText();
204 ESelection aOrigSelection(GetOutlinerView()->GetEditView().GetSelection());
206 // get text from SwPostItField and insert into our textview
207 mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() );
208 mpOutliner->EnableUndo( false );
209 if( mpField->GetTextObject() )
210 mpOutliner->SetText( *mpField->GetTextObject() );
211 else
213 mpOutliner->Clear();
214 GetOutlinerView()->SetStyleSheet(SwResId(STR_POOLCOLL_COMMENT));
215 GetOutlinerView()->InsertText(sNewText);
218 mpOutliner->ClearModifyFlag();
219 mpOutliner->GetUndoManager().Clear();
220 mpOutliner->EnableUndo( true );
221 mpOutliner->SetModifyHdl( LINK( this, SwAnnotationWin, ModifyHdl ) );
222 if (bTextUnchanged)
223 GetOutlinerView()->GetEditView().SetSelection(aOrigSelection);
224 if (bCursorVisible)
225 GetOutlinerView()->ShowCursor();
226 Invalidate();
229 void SwAnnotationWin::GeneratePostItName()
231 if (mpField && mpField->GetName().isEmpty())
233 mpField->SetName(sw::mark::MarkBase::GenerateNewName(u"__Annotation__"));
237 void SwAnnotationWin::SetResolved(bool resolved)
239 bool oldState = IsResolved();
240 static_cast<SwPostItField*>(mpFormatField->GetField())->SetResolved(resolved);
241 if (SwWrtShell* pWrtShell = mrView.GetWrtShellPtr())
243 const SwViewOption* pVOpt = pWrtShell->GetViewOptions();
244 mpSidebarItem->mbShow = !IsResolved() || (pVOpt->IsResolvedPostIts());
247 mpTextRangeOverlay.reset();
249 if(IsResolved())
250 mxMetadataResolved->show();
251 else
252 mxMetadataResolved->hide();
254 if(IsResolved() != oldState)
255 mbResolvedStateUpdated = true;
256 UpdateData();
257 Invalidate();
258 collectUIInformation(u"SETRESOLVED"_ustr,get_id());
261 void SwAnnotationWin::ToggleResolved()
263 SetResolved(!IsResolved());
266 void SwAnnotationWin::ToggleResolvedForThread()
268 auto pTop = GetTopReplyNote();
269 pTop->ToggleResolved();
270 mrMgr.UpdateResolvedStatus(pTop);
271 mrMgr.LayoutPostIts();
274 sal_uInt32 SwAnnotationWin::GetParaId()
276 auto pField = static_cast<SwPostItField*>(mpFormatField->GetField());
277 auto nParaId = pField->GetParaId();
278 if (nParaId == 0)
280 // The parent annotation does not have a paraId. This happens when the annotation was just
281 // created, and not imported. paraIds are regenerated upon export, thus this new paraId
282 // is only generated so that children annotations can refer to it
283 nParaId = CreateUniqueParaId();
284 pField->SetParaId(nParaId);
286 return nParaId;
289 sal_uInt32 SwAnnotationWin::CreateUniqueParaId()
291 return comphelper::rng::uniform_uint_distribution(0, std::numeric_limits<sal_uInt32>::max());
294 void SwAnnotationWin::DeleteThread()
296 // Go to the top and delete each comment one by one
297 SwAnnotationWin* topNote = GetTopReplyNote();
298 for (SwAnnotationWin* current = topNote;;)
300 SwAnnotationWin* next = mrMgr.GetNextPostIt(KEY_PAGEDOWN, current);
301 current->mnDeleteEventId = Application::PostUserEvent( LINK( current, SwAnnotationWin, DeleteHdl), nullptr, true );
302 if (!next || next->GetTopReplyNote() != topNote)
303 return;
304 current = next;
308 bool SwAnnotationWin::IsResolved() const
310 return static_cast<SwPostItField*>(mpFormatField->GetField())->GetResolved();
313 bool SwAnnotationWin::IsThreadResolved()
315 /// First Get the top note
316 // then iterate downwards checking resolved status
317 SwAnnotationWin* topNote = GetTopReplyNote();
318 for (SwAnnotationWin* current = topNote;;)
320 if (!current->IsResolved())
321 return false;
322 current = mrMgr.GetNextPostIt(KEY_PAGEDOWN, current);
323 if (!current || current->GetTopReplyNote() != topNote)
324 return true;
328 bool SwAnnotationWin::IsRootNote() const
330 return static_cast<SwPostItField*>(mpFormatField->GetField())->GetParentPostItId() == 0;
333 void SwAnnotationWin::SetAsRoot()
335 if (!IsRootNote())
337 SwPostItField* pPostIt = static_cast<SwPostItField*>(mpFormatField->GetField());
338 pPostIt->SetParentId(0);
339 pPostIt->SetParentPostItId(0);
340 pPostIt->SetParentName("");
341 mrMgr.MoveSubthreadToRoot(this);
342 mpFormatField->Broadcast(SwFormatFieldHint(nullptr, SwFormatFieldHintWhich::CHANGED));
346 void SwAnnotationWin::UpdateData()
348 if ( mpOutliner->IsModified() || mbResolvedStateUpdated )
350 IDocumentUndoRedo & rUndoRedo(
351 mrView.GetDocShell()->GetDoc()->GetIDocumentUndoRedo());
352 std::unique_ptr<SwField> pOldField;
353 if (rUndoRedo.DoesUndo())
355 pOldField = mpField->Copy();
357 mpField->SetPar2(mpOutliner->GetEditEngine().GetText());
358 mpField->SetTextObject(mpOutliner->CreateParaObject());
359 if (rUndoRedo.DoesUndo())
361 SwTextField *const pTextField = mpFormatField->GetTextField();
362 SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() );
363 rUndoRedo.AppendUndo(
364 std::make_unique<SwUndoFieldFromDoc>(aPosition, *pOldField, *mpField, true));
366 // so we get a new layout of notes (anchor position is still the same and we would otherwise not get one)
367 mrMgr.SetLayout();
368 // #i98686# if we have several views, all notes should update their text
369 if(mbResolvedStateUpdated)
370 mpFormatField->Broadcast(SwFormatFieldHint( nullptr, SwFormatFieldHintWhich::RESOLVED));
371 else
372 mpFormatField->Broadcast(SwFormatFieldHint( nullptr, SwFormatFieldHintWhich::CHANGED));
373 mrView.GetDocShell()->SetModified();
375 mpOutliner->ClearModifyFlag();
376 mpOutliner->GetUndoManager().Clear();
377 mbResolvedStateUpdated = false;
380 void SwAnnotationWin::Delete()
382 collectUIInformation(u"DELETE"_ustr,get_id());
383 SwWrtShell* pWrtShell = mrView.GetWrtShellPtr();
384 if (!(pWrtShell && pWrtShell->GotoField(*mpFormatField)))
385 return;
387 if ( mrMgr.GetActiveSidebarWin() == this)
389 mrMgr.SetActiveSidebarWin(nullptr);
390 // if the note is empty, the previous line will send a delete event, but we are already there
391 if (mnDeleteEventId)
393 Application::RemoveUserEvent(mnDeleteEventId);
394 mnDeleteEventId = nullptr;
397 // we delete the field directly, the Mgr cleans up the PostIt by listening
398 GrabFocusToDocument();
399 pWrtShell->ClearMark();
400 pWrtShell->DelRight();
403 void SwAnnotationWin::GotoPos()
405 mrView.GetDocShell()->GetWrtShell()->GotoField(*mpFormatField);
408 sal_uInt32 SwAnnotationWin::MoveCaret()
410 // if this is an answer, do not skip over all following ones, but insert directly behind the current one
411 // but when just leaving a note, skip all following ones as well to continue typing
412 return mrMgr.IsAnswer()
414 : 1 + CountFollowing();
417 // counts how many SwPostItField we have right after the current one
418 sal_uInt32 SwAnnotationWin::CountFollowing()
420 SwTextField* pTextField = mpFormatField->GetTextField();
421 SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() );
423 for (sal_Int32 n = 1;; ++n)
425 SwTextAttr * pTextAttr = pTextField->GetTextNode().GetTextAttrForCharAt(
426 aPosition.GetContentIndex() + n,
427 RES_TXTATR_ANNOTATION );
428 if (!pTextAttr)
429 return n - 1;
430 const SwField* pField = pTextAttr->GetFormatField().GetField();
431 if (!pField || pField->Which() != SwFieldIds::Postit)
432 return n - 1;
436 void SwAnnotationWin::InitAnswer(OutlinerParaObject const & rText)
438 // If tiled annotations is off in lok case, skip adding additional reply text.
439 if (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations())
440 return;
442 //collect our old meta data
443 SwAnnotationWin* pWin = mrMgr.GetNextPostIt(KEY_PAGEUP, this);
444 if (!pWin)
445 return;
447 const SvtSysLocale aSysLocale;
448 const LocaleDataWrapper& rLocalData = aSysLocale.GetLocaleData();
449 SwRewriter aRewriter;
450 aRewriter.AddRule(UndoArg1, pWin->GetAuthor());
451 const OUString aText = aRewriter.Apply(SwResId(STR_REPLY))
452 + " (" + rLocalData.getDate( pWin->GetDate())
453 + ", " + rLocalData.getTime( pWin->GetTime(), false)
454 + "): \"";
455 GetOutlinerView()->InsertText(aText);
457 // insert old, selected text or "..."
458 // TODO: iterate over all paragraphs, not only first one to find out if it is empty
459 if (rText.GetTextObject().HasText(0))
460 GetOutlinerView()->GetEditView().InsertText(rText.GetTextObject());
461 else
462 GetOutlinerView()->InsertText(u"..."_ustr);
463 GetOutlinerView()->InsertText(u"\"\n"_ustr);
465 GetOutlinerView()->SetSelection(ESelection::All());
466 SfxItemSet aAnswerSet( mrView.GetDocShell()->GetPool() );
467 aAnswerSet.Put(SvxFontHeightItem(200,80,EE_CHAR_FONTHEIGHT));
468 aAnswerSet.Put(SvxPostureItem(ITALIC_NORMAL,EE_CHAR_ITALIC));
469 GetOutlinerView()->SetAttribs(aAnswerSet);
470 GetOutlinerView()->SetSelection(ESelection::AtEnd());
472 //remove all attributes and reset our standard ones
473 GetOutlinerView()->GetEditView().RemoveAttribsKeepLanguages(true);
474 // let's insert an undo step so the initial text can be easily deleted
475 // but do not use UpdateData() directly, would set modified state again and reentrance into Mgr
476 mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() );
477 IDocumentUndoRedo & rUndoRedo(
478 mrView.GetDocShell()->GetDoc()->GetIDocumentUndoRedo());
479 std::unique_ptr<SwField> pOldField;
480 if (rUndoRedo.DoesUndo())
482 pOldField = mpField->Copy();
484 mpField->SetPar2(mpOutliner->GetEditEngine().GetText());
485 mpField->SetTextObject(mpOutliner->CreateParaObject());
486 if (rUndoRedo.DoesUndo())
488 SwTextField *const pTextField = mpFormatField->GetTextField();
489 SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() );
490 rUndoRedo.AppendUndo(
491 std::make_unique<SwUndoFieldFromDoc>(aPosition, *pOldField, *mpField, true));
493 mpOutliner->SetModifyHdl( LINK( this, SwAnnotationWin, ModifyHdl ) );
494 mpOutliner->ClearModifyFlag();
495 mpOutliner->GetUndoManager().Clear();
498 void SwAnnotationWin::UpdateText(const OUString& aText)
500 mpOutliner->Clear();
501 GetOutlinerView()->InsertText(aText);
502 UpdateData();
505 void SwAnnotationWin::UpdateHTML(const OUString& rHtml)
507 mpOutliner->Clear();
508 SwPostItHelper::ImportHTML(*mpOutliner, rHtml);
509 UpdateData();
512 OString SwAnnotationWin::GetSimpleHtml() const
514 return GetOutlinerView()->GetEditView().GetSimpleHtml();
517 bool SwAnnotationWin::IsReadOnlyOrProtected() const
519 return mbReadonly ||
520 GetLayoutStatus() == SwPostItHelper::DELETED ||
521 ( mpFormatField && mpFormatField->IsProtect() );
524 OUString SwAnnotationWin::GetAuthor() const
526 return mpField->GetPar1();
529 Date SwAnnotationWin::GetDate() const
531 return mpField->GetDate();
534 tools::Time SwAnnotationWin::GetTime() const
536 return mpField->GetTime();
539 FactoryFunction SwAnnotationWin::GetUITestFactory() const
541 return CommentUIObject::create;
544 } // end of namespace sw::annotation
546 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */