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 <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>
57 #include <viewopt.hxx>
61 #include <IDocumentUndoRedo.hxx>
62 #include <SwUndoField.hxx>
64 #include "ShadowOverlayObject.hxx"
65 #include "AnchorOverlayObject.hxx"
66 #include "OverlayRanges.hxx"
67 #include "SidebarTxtControl.hxx"
68 #include "SidebarWinAcc.hxx"
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
,
92 SwSidebarItem
& rSidebarItem
,
93 SwFormatField
* aField
)
94 : InterimItemWindow(&rEditWin
, "modules/swriter/ui/annotation.ui", "Annotation")
96 , mrView(rEditWin
.GetView())
97 , mnDeleteEventId(nullptr)
98 , meSidebarPosition(sw::sidebarwindows::SidebarPosition::NONE
)
100 , mbAnchorRectChanged(false)
101 , mbResolvedStateUpdated(false)
103 , mLayoutStatus(SwPostItHelper::INVISIBLE
)
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
);
118 mpShadow
->setVisible(false);
121 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
122 mrMgr
.ConnectSidebarWinToFrame( *(mrSidebarItem
.maLayoutInfo
.mpAnchorFrame
),
123 mrSidebarItem
.GetFormatField(),
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()
138 void SwAnnotationWin::dispose()
140 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
141 mrMgr
.DisconnectSidebarWinFromFrame( *(mrSidebarItem
.maLayoutInfo
.mpAnchorFrame
),
146 mxSidebarTextControlWin
.reset();
147 mxSidebarTextControl
.reset();
149 mxMetadataAuthor
.reset();
150 mxMetadataResolved
.reset();
151 mxMetadataDate
.reset();
152 mxVScrollbar
.reset();
157 mpTextRangeOverlay
.reset();
159 mxMenuButton
.reset();
162 Application::RemoveUserEvent(mnDeleteEventId
);
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() );
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
) );
201 GetOutlinerView()->GetEditView().SetSelection(aOrigSelection
);
203 GetOutlinerView()->ShowCursor();
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();
220 mxMetadataResolved
->show();
222 mxMetadataResolved
->hide();
224 if(IsResolved() != oldState
)
225 mbResolvedStateUpdated
= true;
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();
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
);
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 );
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())
293 SwAnnotationWin
* pSidebarWin
= mrMgr
.GetNextPostIt(KEY_PAGEDOWN
, pTopNote
);
295 while (pSidebarWin
&& pSidebarWin
->GetTopReplyNote() == TopNote
)
297 pTopNote
= pSidebarWin
;
298 if (!pTopNote
->IsResolved())
300 pSidebarWin
= mrMgr
.GetNextPostIt(KEY_PAGEDOWN
, pSidebarWin
);
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)
327 // #i98686# if we have several views, all notes should update their text
328 if(mbResolvedStateUpdated
)
329 mpFormatField
->Broadcast(SwFormatFieldHint( nullptr, SwFormatFieldHintWhich::RESOLVED
));
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
)))
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
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();
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())
408 while ( pField
&& ( pField
->Which()== SwFieldIds::Postit
) )
411 pTextAttr
= pTextField
->GetTextNode().GetTextAttrForCharAt(
412 aPosition
.GetContentIndex() + aCount
,
413 RES_TXTATR_ANNOTATION
);
415 ? const_cast<SwField
*>(pTextAttr
->GetFormatField().GetField())
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())
427 //collect our old meta data
428 SwAnnotationWin
* pWin
= mrMgr
.GetNextPostIt(KEY_PAGEUP
, this);
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)
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());
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
)
487 GetOutlinerView()->InsertText(aText
);
491 SvxLanguageItem
SwAnnotationWin::GetLanguage() const
493 // set initial language for outliner
494 SvtScriptType nScriptType
= SvtLanguageOptions::GetScriptTypeOfLanguage( mpField
->GetLanguage() );
495 sal_uInt16 nLangWhichId
= 0;
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
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: */