1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
59 #include <viewopt.hxx>
63 #include <IDocumentUndoRedo.hxx>
64 #include <SwUndoField.hxx>
66 #include "ShadowOverlayObject.hxx"
67 #include "AnchorOverlayObject.hxx"
68 #include "OverlayRanges.hxx"
69 #include "SidebarTxtControl.hxx"
70 #include "SidebarWinAcc.hxx"
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
,
111 SwAnnotationItem
& rSidebarItem
,
112 SwFormatField
* aField
)
113 : InterimItemWindow(&rEditWin
, u
"modules/swriter/ui/annotation.ui"_ustr
, u
"Annotation"_ustr
)
115 , mrView(rEditWin
.GetView())
116 , mnDeleteEventId(nullptr)
117 , meSidebarPosition(sw::sidebarwindows::SidebarPosition::NONE
)
119 , mbAnchorRectChanged(false)
120 , mbResolvedStateUpdated(false)
122 , mLayoutStatus(SwPostItHelper::INVISIBLE
)
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
);
137 mpShadow
->setVisible(false);
140 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
141 if (rSidebarItem
.maLayoutInfo
.mpAnchorFrame
)
143 mrMgr
.ConnectSidebarWinToFrame( *(rSidebarItem
.maLayoutInfo
.mpAnchorFrame
),
144 mpSidebarItem
->GetFormatField(),
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()
160 void SwAnnotationWin::dispose()
162 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
163 mrMgr
.DisconnectSidebarWinFromFrame( *(mpSidebarItem
->maLayoutInfo
.mpAnchorFrame
),
168 mxSidebarTextControlWin
.reset();
169 mxSidebarTextControl
.reset();
171 mxMetadataAuthor
.reset();
172 mxMetadataResolved
.reset();
173 mxMetadataDate
.reset();
174 mxVScrollbar
.reset();
179 mpTextRangeOverlay
.reset();
181 mxMenuButton
.reset();
184 Application::RemoveUserEvent(mnDeleteEventId
);
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() );
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
) );
223 GetOutlinerView()->GetEditView().SetSelection(aOrigSelection
);
225 GetOutlinerView()->ShowCursor();
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();
250 mxMetadataResolved
->show();
252 mxMetadataResolved
->hide();
254 if(IsResolved() != oldState
)
255 mbResolvedStateUpdated
= true;
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();
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
);
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
)
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())
322 current
= mrMgr
.GetNextPostIt(KEY_PAGEDOWN
, current
);
323 if (!current
|| current
->GetTopReplyNote() != topNote
)
328 bool SwAnnotationWin::IsRootNote() const
330 return static_cast<SwPostItField
*>(mpFormatField
->GetField())->GetParentPostItId() == 0;
333 void SwAnnotationWin::SetAsRoot()
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)
368 // #i98686# if we have several views, all notes should update their text
369 if(mbResolvedStateUpdated
)
370 mpFormatField
->Broadcast(SwFormatFieldHint( nullptr, SwFormatFieldHintWhich::RESOLVED
));
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
)))
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
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
);
430 const SwField
* pField
= pTextAttr
->GetFormatField().GetField();
431 if (!pField
|| pField
->Which() != SwFieldIds::Postit
)
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())
442 //collect our old meta data
443 SwAnnotationWin
* pWin
= mrMgr
.GetNextPostIt(KEY_PAGEUP
, this);
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)
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());
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
)
501 GetOutlinerView()->InsertText(aText
);
505 void SwAnnotationWin::UpdateHTML(const OUString
& rHtml
)
508 SwPostItHelper::ImportHTML(*mpOutliner
, rHtml
);
512 OString
SwAnnotationWin::GetSimpleHtml() const
514 return GetOutlinerView()->GetEditView().GetSimpleHtml();
517 bool SwAnnotationWin::IsReadOnlyOrProtected() const
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: */