/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <config_wasm_strip.h>
#include <AnnotationWin.hxx>
#include <PostItMgr.hxx>
#include <strings.hrc>
#include <uiobject.hxx>
#include <vcl/svapp.hxx>
#include <vcl/uitest/logger.hxx>
#include <vcl/uitest/eventdescription.hxx>
#include <svl/undo.hxx>
#include <svtools/svparser.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/syslocale.hxx>
#include <svl/languageoptions.hxx>
#include <osl/diagnose.h>
#include <editeng/eeitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/langitem.hxx>
#include <editeng/editund2.hxx>
#include <editeng/editview.hxx>
#include <editeng/outliner.hxx>
#include <editeng/editeng.hxx>
#include <editeng/editobj.hxx>
#include <editeng/outlobj.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/random.hxx>
#include <docufld.hxx>
#include <txtfld.hxx>
#include <ndtxt.hxx>
#include <view.hxx>
#include <viewopt.hxx>
#include <wrtsh.hxx>
#include <docsh.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <SwUndoField.hxx>
#include <edtwin.hxx>
#include "ShadowOverlayObject.hxx"
#include "AnchorOverlayObject.hxx"
#include "OverlayRanges.hxx"
#include "SidebarTxtControl.hxx"
#include "SidebarWinAcc.hxx"
#include <memory>
namespace{
void collectUIInformation( const OUString& aevent , const OUString& aID )
{
EventDescription aDescription;
aDescription.aID = aID;
aDescription.aParameters = {{"" , ""}};
aDescription.aAction = aevent;
aDescription.aParent = "MainWindow";
aDescription.aKeyWord = "SwEditWinUIObject";
UITestLogger::getInstance().logEvent(aDescription);
}
}
namespace SwPostItHelper {
void ImportHTML(Outliner& rOutliner, const OUString& rHtml)
{
OString sHtmlContent(rHtml.toUtf8());
SvMemoryStream aHTMLStream(const_cast<char*>(sHtmlContent.getStr()),
sHtmlContent.getLength(), StreamMode::READ);
SvKeyValueIteratorRef xValues(new SvKeyValueIterator);
// Insert newlines for divs, not normally done, so to keep things simple
// only enable that for this case.
xValues->Append(SvKeyValue("newline-on-div", "true"));
rOutliner.Read(aHTMLStream, "", EETextFormat::Html, xValues.get());
}
}
namespace sw::annotation {
// see AnnotationContents in sd for something similar
SwAnnotationWin::SwAnnotationWin( SwEditWin& rEditWin,
SwPostItMgr& aMgr,
SwSidebarItem& rSidebarItem,
SwFormatField* aField )
: InterimItemWindow(&rEditWin, u"modules/swriter/ui/annotation.ui"_ustr, u"Annotation"_ustr)
, mrMgr(aMgr)
, mrView(rEditWin.GetView())
, mnDeleteEventId(nullptr)
, meSidebarPosition(sw::sidebarwindows::SidebarPosition::NONE)
, mPageBorder(0)
, mbAnchorRectChanged(false)
, mbResolvedStateUpdated(false)
, mbMouseOver(false)
, mLayoutStatus(SwPostItHelper::INVISIBLE)
, mbReadonly(false)
, mbIsFollow(false)
, mrSidebarItem(rSidebarItem)
, mpAnchorFrame(rSidebarItem.maLayoutInfo.mpAnchorFrame)
, mpFormatField(aField)
, mpField( static_cast<SwPostItField*>(aField->GetField()))
{
set_id("Comment"+OUString::number(mpField->GetPostItId()));
m_xContainer->connect_mouse_move(LINK(this, SwAnnotationWin, MouseMoveHdl));
mpShadow = sidebarwindows::ShadowOverlayObject::CreateShadowOverlayObject( mrView );
if ( mpShadow )
{
mpShadow->setVisible(false);
}
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
if (rSidebarItem.maLayoutInfo.mpAnchorFrame)
{
mrMgr.ConnectSidebarWinToFrame( *(rSidebarItem.maLayoutInfo.mpAnchorFrame),
mrSidebarItem.GetFormatField(),
*this );
}
#endif
if (SupportsDoubleBuffering())
// When double-buffering, allow parents to paint on our area. That's
// necessary when parents paint the complete buffer.
SetParentClipMode(ParentClipMode::NoClip);
}
SwAnnotationWin::~SwAnnotationWin()
{
disposeOnce();
}
void SwAnnotationWin::dispose()
{
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
mrMgr.DisconnectSidebarWinFromFrame( *(mrSidebarItem.maLayoutInfo.mpAnchorFrame),
*this );
#endif
Disable();
mxSidebarTextControlWin.reset();
mxSidebarTextControl.reset();
mxMetadataAuthor.reset();
mxMetadataResolved.reset();
mxMetadataDate.reset();
mxVScrollbar.reset();
mpAnchor.reset();
mpShadow.reset();
mpTextRangeOverlay.reset();
mxMenuButton.reset();
if (mnDeleteEventId)
Application::RemoveUserEvent(mnDeleteEventId);
mpOutliner.reset();
mpOutlinerView.reset();
InterimItemWindow::dispose();
}
void SwAnnotationWin::SetPostItText()
{
//If the cursor was visible, then make it visible again after
//changing text, e.g. fdo#33599
vcl::Cursor *pCursor = GetOutlinerView()->GetEditView().GetCursor();
bool bCursorVisible = pCursor && pCursor->IsVisible();
//If the new text is the same as the old text, keep the same insertion
//point .e.g. fdo#33599
mpField = static_cast<SwPostItField*>(mpFormatField->GetField());
OUString sNewText = mpField->GetPar2();
bool bTextUnchanged = sNewText == mpOutliner->GetEditEngine().GetText();
ESelection aOrigSelection(GetOutlinerView()->GetEditView().GetSelection());
// get text from SwPostItField and insert into our textview
mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() );
mpOutliner->EnableUndo( false );
if( mpField->GetTextObject() )
mpOutliner->SetText( *mpField->GetTextObject() );
else
{
mpOutliner->Clear();
GetOutlinerView()->SetStyleSheet(SwResId(STR_POOLCOLL_COMMENT));
GetOutlinerView()->InsertText(sNewText);
}
mpOutliner->ClearModifyFlag();
mpOutliner->GetUndoManager().Clear();
mpOutliner->EnableUndo( true );
mpOutliner->SetModifyHdl( LINK( this, SwAnnotationWin, ModifyHdl ) );
if (bTextUnchanged)
GetOutlinerView()->GetEditView().SetSelection(aOrigSelection);
if (bCursorVisible)
GetOutlinerView()->ShowCursor();
Invalidate();
}
void SwAnnotationWin::GeneratePostItName()
{
if (mpField && mpField->GetName().isEmpty())
{
mpField->SetName(sw::mark::MarkBase::GenerateNewName(u"__Annotation__"));
}
}
void SwAnnotationWin::SetResolved(bool resolved)
{
bool oldState = IsResolved();
static_cast<SwPostItField*>(mpFormatField->GetField())->SetResolved(resolved);
if (SwWrtShell* pWrtShell = mrView.GetWrtShellPtr())
{
const SwViewOption* pVOpt = pWrtShell->GetViewOptions();
mrSidebarItem.mbShow = !IsResolved() || (pVOpt->IsResolvedPostIts());
}
mpTextRangeOverlay.reset();
if(IsResolved())
mxMetadataResolved->show();
else
mxMetadataResolved->hide();
if(IsResolved() != oldState)
mbResolvedStateUpdated = true;
UpdateData();
Invalidate();
collectUIInformation(u"SETRESOLVED"_ustr,get_id());
}
void SwAnnotationWin::ToggleResolved()
{
SetResolved(!IsResolved());
}
void SwAnnotationWin::ToggleResolvedForThread()
{
auto pTop = GetTopReplyNote();
pTop->ToggleResolved();
mrMgr.UpdateResolvedStatus(pTop);
mrMgr.LayoutPostIts();
}
sal_uInt32 SwAnnotationWin::GetParaId()
{
auto pField = static_cast<SwPostItField*>(mpFormatField->GetField());
auto nParaId = pField->GetParaId();
if (nParaId == 0)
{
// The parent annotation does not have a paraId. This happens when the annotation was just
// created, and not imported. paraIds are regenerated upon export, thus this new paraId
// is only generated so that children annotations can refer to it
nParaId = CreateUniqueParaId();
pField->SetParaId(nParaId);
}
return nParaId;
}
sal_uInt32 SwAnnotationWin::CreateUniqueParaId()
{
return comphelper::rng::uniform_uint_distribution(0, std::numeric_limits<sal_uInt32>::max());
}
void SwAnnotationWin::DeleteThread()
{
// Go to the top and delete each comment one by one
SwAnnotationWin* topNote = GetTopReplyNote();
for (SwAnnotationWin* current = topNote;;)
{
SwAnnotationWin* next = mrMgr.GetNextPostIt(KEY_PAGEDOWN, current);
current->mnDeleteEventId = Application::PostUserEvent( LINK( current, SwAnnotationWin, DeleteHdl), nullptr, true );
if (!next || next->GetTopReplyNote() != topNote)
return;
current = next;
}
}
bool SwAnnotationWin::IsResolved() const
{
return static_cast<SwPostItField*>(mpFormatField->GetField())->GetResolved();
}
bool SwAnnotationWin::IsThreadResolved()
{
/// First Get the top note
// then iterate downwards checking resolved status
SwAnnotationWin* topNote = GetTopReplyNote();
for (SwAnnotationWin* current = topNote;;)
{
if (!current->IsResolved())
return false;
current = mrMgr.GetNextPostIt(KEY_PAGEDOWN, current);
if (!current || current->GetTopReplyNote() != topNote)
return true;
}
}
bool SwAnnotationWin::IsRootNote() const
{
return static_cast<SwPostItField*>(mpFormatField->GetField())->GetParentPostItId() == 0;
}
void SwAnnotationWin::SetAsRoot()
{
if (!IsRootNote())
{
SwPostItField* pPostIt = static_cast<SwPostItField*>(mpFormatField->GetField());
pPostIt->SetParentId(0);
pPostIt->SetParentPostItId(0);
pPostIt->SetParentName("");
mrMgr.MoveSubthreadToRoot(this);
mpFormatField->Broadcast(SwFormatFieldHint(nullptr, SwFormatFieldHintWhich::CHANGED));
}
}
void SwAnnotationWin::UpdateData()
{
if ( mpOutliner->IsModified() || mbResolvedStateUpdated )
{
IDocumentUndoRedo & rUndoRedo(
mrView.GetDocShell()->GetDoc()->GetIDocumentUndoRedo());
std::unique_ptr<SwField> pOldField;
if (rUndoRedo.DoesUndo())
{
pOldField = mpField->Copy();
}
mpField->SetPar2(mpOutliner->GetEditEngine().GetText());
mpField->SetTextObject(mpOutliner->CreateParaObject());
if (rUndoRedo.DoesUndo())
{
SwTextField *const pTextField = mpFormatField->GetTextField();
SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() );
rUndoRedo.AppendUndo(
std::make_unique<SwUndoFieldFromDoc>(aPosition, *pOldField, *mpField, true));
}
// so we get a new layout of notes (anchor position is still the same and we would otherwise not get one)
mrMgr.SetLayout();
// #i98686# if we have several views, all notes should update their text
if(mbResolvedStateUpdated)
mpFormatField->Broadcast(SwFormatFieldHint( nullptr, SwFormatFieldHintWhich::RESOLVED));
else
mpFormatField->Broadcast(SwFormatFieldHint( nullptr, SwFormatFieldHintWhich::CHANGED));
mrView.GetDocShell()->SetModified();
}
mpOutliner->ClearModifyFlag();
mpOutliner->GetUndoManager().Clear();
mbResolvedStateUpdated = false;
}
void SwAnnotationWin::Delete()
{
collectUIInformation(u"DELETE"_ustr,get_id());
SwWrtShell* pWrtShell = mrView.GetWrtShellPtr();
if (!(pWrtShell && pWrtShell->GotoField(*mpFormatField)))
return;
if ( mrMgr.GetActiveSidebarWin() == this)
{
mrMgr.SetActiveSidebarWin(nullptr);
// if the note is empty, the previous line will send a delete event, but we are already there
if (mnDeleteEventId)
{
Application::RemoveUserEvent(mnDeleteEventId);
mnDeleteEventId = nullptr;
}
}
// we delete the field directly, the Mgr cleans up the PostIt by listening
GrabFocusToDocument();
pWrtShell->ClearMark();
pWrtShell->DelRight();
}
void SwAnnotationWin::GotoPos()
{
mrView.GetDocShell()->GetWrtShell()->GotoField(*mpFormatField);
}
sal_uInt32 SwAnnotationWin::MoveCaret()
{
// if this is an answer, do not skip over all following ones, but insert directly behind the current one
// but when just leaving a note, skip all following ones as well to continue typing
return mrMgr.IsAnswer()
? 1
: 1 + CountFollowing();
}
// counts how many SwPostItField we have right after the current one
sal_uInt32 SwAnnotationWin::CountFollowing()
{
SwTextField* pTextField = mpFormatField->GetTextField();
SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() );
for (sal_Int32 n = 1;; ++n)
{
SwTextAttr * pTextAttr = pTextField->GetTextNode().GetTextAttrForCharAt(
aPosition.GetContentIndex() + n,
RES_TXTATR_ANNOTATION );
if (!pTextAttr)
return n - 1;
const SwField* pField = pTextAttr->GetFormatField().GetField();
if (!pField || pField->Which() != SwFieldIds::Postit)
return n - 1;
}
}
void SwAnnotationWin::InitAnswer(OutlinerParaObject const & rText)
{
// If tiled annotations is off in lok case, skip adding additional reply text.
if (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations())
return;
//collect our old meta data
SwAnnotationWin* pWin = mrMgr.GetNextPostIt(KEY_PAGEUP, this);
if (!pWin)
return;
const SvtSysLocale aSysLocale;
const LocaleDataWrapper& rLocalData = aSysLocale.GetLocaleData();
SwRewriter aRewriter;
aRewriter.AddRule(UndoArg1, pWin->GetAuthor());
const OUString aText = aRewriter.Apply(SwResId(STR_REPLY))
+ " (" + rLocalData.getDate( pWin->GetDate())
+ ", " + rLocalData.getTime( pWin->GetTime(), false)
+ "): \"";
GetOutlinerView()->InsertText(aText);
// insert old, selected text or "..."
// TODO: iterate over all paragraphs, not only first one to find out if it is empty
if (rText.GetTextObject().HasText(0))
GetOutlinerView()->GetEditView().InsertText(rText.GetTextObject());
else
GetOutlinerView()->InsertText(u"..."_ustr);
GetOutlinerView()->InsertText(u"\"\n"_ustr);
GetOutlinerView()->SetSelection(ESelection(0,0,EE_PARA_ALL,EE_TEXTPOS_ALL));
SfxItemSet aAnswerSet( mrView.GetDocShell()->GetPool() );
aAnswerSet.Put(SvxFontHeightItem(200,80,EE_CHAR_FONTHEIGHT));
aAnswerSet.Put(SvxPostureItem(ITALIC_NORMAL,EE_CHAR_ITALIC));
GetOutlinerView()->SetAttribs(aAnswerSet);
GetOutlinerView()->SetSelection(ESelection(EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT,EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT));
//remove all attributes and reset our standard ones
GetOutlinerView()->GetEditView().RemoveAttribsKeepLanguages(true);
// let's insert an undo step so the initial text can be easily deleted
// but do not use UpdateData() directly, would set modified state again and reentrance into Mgr
mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() );
IDocumentUndoRedo & rUndoRedo(
mrView.GetDocShell()->GetDoc()->GetIDocumentUndoRedo());
std::unique_ptr<SwField> pOldField;
if (rUndoRedo.DoesUndo())
{
pOldField = mpField->Copy();
}
mpField->SetPar2(mpOutliner->GetEditEngine().GetText());
mpField->SetTextObject(mpOutliner->CreateParaObject());
if (rUndoRedo.DoesUndo())
{
SwTextField *const pTextField = mpFormatField->GetTextField();
SwPosition aPosition( pTextField->GetTextNode(), pTextField->GetStart() );
rUndoRedo.AppendUndo(
std::make_unique<SwUndoFieldFromDoc>(aPosition, *pOldField, *mpField, true));
}
mpOutliner->SetModifyHdl( LINK( this, SwAnnotationWin, ModifyHdl ) );
mpOutliner->ClearModifyFlag();
mpOutliner->GetUndoManager().Clear();
}
void SwAnnotationWin::UpdateText(const OUString& aText)
{
mpOutliner->Clear();
GetOutlinerView()->InsertText(aText);
UpdateData();
}
void SwAnnotationWin::UpdateHTML(const OUString& rHtml)
{
mpOutliner->Clear();
SwPostItHelper::ImportHTML(*mpOutliner, rHtml);
UpdateData();
}
OString SwAnnotationWin::GetSimpleHtml() const
{
return GetOutlinerView()->GetEditView().GetSimpleHtml();
}
bool SwAnnotationWin::IsReadOnlyOrProtected() const
{
return mbReadonly ||
GetLayoutStatus() == SwPostItHelper::DELETED ||
( mpFormatField && mpFormatField->IsProtect() );
}
OUString SwAnnotationWin::GetAuthor() const
{
return mpField->GetPar1();
}
Date SwAnnotationWin::GetDate() const
{
return mpField->GetDate();
}
tools::Time SwAnnotationWin::GetTime() const
{
return mpField->GetTime();
}
FactoryFunction SwAnnotationWin::GetUITestFactory() const
{
return CommentUIObject::create;
}
} // end of namespace sw::annotation
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'Read' is required to be utilized.