/* -*- 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 .
*/
// distance between Anchor Y and initial note position #define POSTIT_INITIAL_ANCHOR_DISTANCE 20 //distance between two postits #define POSTIT_SPACE_BETWEEN 8 #define POSTIT_MINIMUMSIZE_WITH_META 60 #define POSTIT_SCROLL_SIDEBAR_HEIGHT 20
// if we layout more often we stop, this should never happen #define MAX_LOOP_COUNT 50
// is the anchor placed in Footnote or the Footer? if( aPosAnchorA.GetNode().FindFootnoteStartNode() || aPosAnchorA.GetNode().FindFooterStartNode() )
aAnchorAInFooter = true; if( aPosAnchorB.GetNode().FindFootnoteStartNode() || aPosAnchorB.GetNode().FindFooterStartNode() )
aAnchorBInFooter = true;
// fdo#34800 // if AnchorA is in footnote, and AnchorB isn't // we do not want to change over the position if( aAnchorAInFooter && !aAnchorBInFooter ) returnfalse; // if aAnchorA is not placed in a footnote, and aAnchorB is // force a change over elseif( !aAnchorAInFooter && aAnchorBInFooter ) returntrue; // If neither or both are in the footer, compare the positions. // Since footnotes are in Inserts section of nodes array and footers // in Autotext section, all footnotes precede any footers so no need // to check that. else return aPosAnchorA < aPosAnchorB;
}
/// Emits LOK notification about one addition/removal/change of a comment void lcl_CommentNotification(const SwView* pView, const CommentNotificationType nType, const SwAnnotationItem* pItem, const sal_uInt32 nPostItId)
{ if (!comphelper::LibreOfficeKit::isActive()) return;
if (!pItem->maLayoutInfo.mPositionFromCommentAnchor)
{ // Comments on frames: anchor position is the corner position, not the whole frame.
aSVRect.SetSize(Size(0, 0));
}
aAnnotation.put("id", pField->GetPostItId());
aAnnotation.put("parentId", pField->GetParentPostItId());
aAnnotation.put("author", pField->GetPar1().toUtf8().getStr()); // Note, for just plain text we could use "text" populated by pField->GetPar2()
aAnnotation.put("html", pWin->GetSimpleHtml());
aAnnotation.put("resolved", pField->GetResolved() ? "true" : "false");
aAnnotation.put("dateTime", utl::toISO8601(pField->GetDateTime().GetUNODateTime()));
aAnnotation.put("anchorPos", aSVRect.toString());
aAnnotation.put("textRange", sRects.getStr());
aAnnotation.put("layoutStatus", pItem->mLayoutStatus);
} if (nType == CommentNotificationType::Remove && comphelper::LibreOfficeKit::isActive())
{ // Redline author is basically the author which has made the modification rather than author of the comments // This is important to know who removed the comment
aAnnotation.put("author", SwModule::get()->GetRedlineAuthor(SwModule::get()->GetRedlineAuthor()));
}
//Manages the passed in vector by automatically removing entries if they are deleted //and automatically adding entries if they appear in the document and match the //functor. // //This will completely refill in the case of a "anonymous" NULL pField stating //rather unhelpfully that "something changed" so you may process the same //Fields more than once. class FieldDocWatchingStack : public SfxListener
{
std::vector<std::unique_ptr<SwAnnotationItem>>& m_aSidebarItems;
std::vector<const SwFormatField*> m_aFormatFields;
SwDocShell& m_rDocShell;
FilterFunctor& m_rFilter;
bool isOwnFileFormat(SfxMedium* pMedium)
{ // Assume that unsaved documents are own format return !pMedium || !pMedium->GetFilter() || pMedium->GetFilter()->IsOwnFormat();
}
//make sure we get the colour yellow always, even if not the first one of comments or redlining
SwModule::get()->GetRedlineAuthor();
// collect all PostIts and redline comments that exist after loading the document // don't check for existence for any of them, don't focus them
AddPostIts(false,false); /* this code can be used once we want redline comments in the Sidebar AddRedlineComments(false,false);
*/ // we want to receive stuff like SfxHintId::DocChanged
StartListening(*mpView->GetDocShell()); // listen to stylesheet pool to update on stylesheet rename, // as EditTextObject references styles by name.
SfxStyleSheetBasePool* pStyleSheetPool = mpView->GetDocShell()->GetStyleSheetPool(); if (pStyleSheetPool)
StartListening(*static_cast<SwDocStyleSheetPool*>(pStyleSheetPool)->GetEEStyleSheetPool()); if (!mvPostItFields.empty())
{
mbWaitingForCalcRects = true;
mnEventId = Application::PostUserEvent( LINK( this, SwPostItMgr, CalcHdl) );
}
}
SwPostItMgr::~SwPostItMgr()
{ if ( mnEventId )
Application::RemoveUserEvent( mnEventId ); // forget about all our Sidebar windows
RemoveSidebarWin();
EndListeningAll();
mPages.clear();
}
bool SwPostItMgr::CheckForRemovedPostIts()
{
IDocumentRedlineAccess const& rIDRA(mpWrtShell->getIDocumentRedlineAccess()); bool bRemoved = false; auto it = mvPostItFields.begin(); while(it != mvPostItFields.end())
{ if (!(*it)->UseElement(*mpWrtShell->GetLayout(), rIDRA))
{
EndListening(const_cast<SfxBroadcaster&>(*(*it)->GetBroadcaster()));
std::unique_ptr<SwAnnotationItem> p = std::move(*it);
it = mvPostItFields.erase(it); if (GetActiveSidebarWin() == p->mpPostIt)
SetActiveSidebarWin(nullptr);
p->mpPostIt.disposeAndClear();
// make sure that no deleted items remain in page lists // todo: only remove deleted ones?! if ( mvPostItFields.empty() )
{
PreparePageContainer();
PrepareView();
} else
{ // if postits are there make sure that page lists are not empty // otherwise sudden paints can cause pain (in BorderOverPageBorder)
CalcRects();
}
returntrue;
}
SwAnnotationItem* SwPostItMgr::InsertItem(SfxBroadcaster* pItem, bool bCheckExistence, bool bFocus)
{ if (bCheckExistence)
{ for (autoconst& postItField : mvPostItFields)
{ if ( postItField->GetBroadcaster() == pItem ) return nullptr;
}
}
mbLayout = bFocus;
SwAnnotationItem* pAnnotationItem = nullptr; if (auto pSwFormatField = dynamic_cast< SwFormatField *>( pItem ))
{
IsPostitField isPostitField; if (!isPostitField(pSwFormatField)) return nullptr;
mvPostItFields.push_back(std::make_unique<SwAnnotationItem>(*pSwFormatField, bFocus));
pAnnotationItem = mvPostItFields.back().get();
}
assert(dynamic_cast< const SwFormatField *>( pItem ) && "Mgr::InsertItem: seems like new stuff was added");
StartListening(*pItem); return pAnnotationItem;
}
sw::annotation::SwAnnotationWin* SwPostItMgr::GetRemovedAnnotationWin( const SfxBroadcaster* pBroadcast )
{ auto i = std::find_if(mvPostItFields.begin(), mvPostItFields.end(),
[&pBroadcast](const std::unique_ptr<SwAnnotationItem>& pField) { return pField->GetBroadcaster() == pBroadcast; }); if (i != mvPostItFields.end())
{ return (*i)->mpPostIt;
} return nullptr;
}
void SwPostItMgr::RemoveItem( SfxBroadcaster* pBroadcast )
{
EndListening(*pBroadcast); auto i = std::find_if(mvPostItFields.begin(), mvPostItFields.end(),
[&pBroadcast](const std::unique_ptr<SwAnnotationItem>& pField) { return pField->GetBroadcaster() == pBroadcast; }); if (i != mvPostItFields.end())
{ #if ENABLE_YRS // note: (*i)->mpPostIt may be null here, if it's in hidden text - see testMissingDefaultLineColor
mpView->GetDocShell()->GetDoc()->getIDocumentState().YrsRemoveComment(
(*i)->GetAnchorPosition()); #endif
std::unique_ptr<SwAnnotationItem> p = std::move(*i); // tdf#120487 remove from list before dispose, so comment window // won't be recreated due to the entry still in the list if focus // transferring from the pPostIt triggers relayout of postits // tdf#133348 remove from list before calling SetActiveSidebarWin // so GetNextPostIt won't deal with mvPostItFields containing empty unique_ptr
mvPostItFields.erase(i); if (GetActiveSidebarWin() == p->mpPostIt)
SetActiveSidebarWin(nullptr);
p->mpPostIt.disposeAndClear();
}
mbLayout = true;
PrepareView();
}
void SwPostItMgr::Notify( SfxBroadcaster& rBC, const SfxHint& rHint )
{ if (rHint.GetId() == SfxHintId::ThisIsAnSfxEventHint)
{ const SfxEventHint& rSfxEventHint = static_cast<const SfxEventHint&>(rHint); if (rSfxEventHint.GetEventId() == SfxEventHintId::SwEventLayoutFinished)
{ if ( !mbWaitingForCalcRects && !mvPostItFields.empty())
{
mbWaitingForCalcRects = true;
mnEventId = Application::PostUserEvent( LINK( this, SwPostItMgr, CalcHdl) );
}
}
} elseif ( rHint.GetId() == SfxHintId::SwFormatField )
{ const SwFormatFieldHint * pFormatHint = static_cast<const SwFormatFieldHint*>(&rHint);
SwFormatField* pField = const_cast <SwFormatField*>( pFormatHint->GetField() ); switch ( pFormatHint->Which() )
{ case SwFormatFieldHintWhich::INSERTED :
{ if (!pField)
{
AddPostIts(); break;
} // get field to be inserted from hint if ( pField->IsFieldInDoc() )
{ bool bEmpty = !HasNotes();
SwAnnotationItem* pItem = InsertItem( pField, true, false );
if (bEmpty && !mvPostItFields.empty())
PrepareView(true);
// True until the layout of this post it finishes if (pItem)
pItem->mbPendingLayout = true;
} else
{
OSL_FAIL("Inserted field not in document!" );
} break;
} case SwFormatFieldHintWhich::REMOVED: case SwFormatFieldHintWhich::REDLINED_DELETION:
{ if (mbDeleteNote)
{ if (!pField)
{ constbool bWasRemoved = CheckForRemovedPostIts(); // tdf#143643 ensure relayout on undo of insert comment if (bWasRemoved)
mbLayout = true; break;
}
this->Broadcast(rHint);
RemoveItem(pField);
// If LOK has disabled tiled annotations, emit annotation callbacks if (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations())
{
SwPostItField* pPostItField = static_cast<SwPostItField*>(pField->GetField()); auto type = pFormatHint->Which() == SwFormatFieldHintWhich::REMOVED ? CommentNotificationType::Remove: CommentNotificationType::RedlinedDeletion;
lcl_CommentNotification(mpView, type, nullptr, pPostItField->GetPostItId());
}
} break;
} case SwFormatFieldHintWhich::FOCUS:
{ if (pFormatHint->GetView()== mpView)
Focus(rBC); break;
} case SwFormatFieldHintWhich::CHANGED: case SwFormatFieldHintWhich::RESOLVED:
{
SwFormatField* pFormatField = dynamic_cast<SwFormatField*>(&rBC); for (autoconst& postItField : mvPostItFields)
{ if ( pFormatField == postItField->GetBroadcaster() )
{ if (postItField->mpPostIt)
{
postItField->mpPostIt->SetPostItText();
mbLayout = true;
this->Forward(rBC, rHint);
}
// If LOK has disabled tiled annotations, emit annotation callbacks if (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations())
{ if(SwFormatFieldHintWhich::CHANGED == pFormatHint->Which())
lcl_CommentNotification(mpView, CommentNotificationType::Modify, postItField.get(), 0); else
lcl_CommentNotification(mpView, CommentNotificationType::Resolve, postItField.get(), 0);
} break;
}
} break;
}
}
} elseif ( rHint.GetId() == SfxHintId::StyleSheetModifiedExtended )
{ const SfxStyleSheetModifiedHint * pStyleHint = static_cast<const SfxStyleSheetModifiedHint*>(&rHint); for (constauto& postItField : mvPostItFields)
{ auto pField = static_cast<SwPostItField*>(postItField->GetFormatField().GetField());
pField->ChangeStyleSheetName(pStyleHint->GetOldName(), pStyleHint->GetStyleSheet());
}
} else
{
SfxHintId nId = rHint.GetId(); switch ( nId )
{ case SfxHintId::ModeChanged:
{ if ( mbReadOnly != mpView->GetDocShell()->IsReadOnly() )
{
mbReadOnly = !mbReadOnly;
SetReadOnlyState();
mbLayout = true;
} break;
} case SfxHintId::DocChanged:
{ if ( mpView->GetDocShell() == &rBC )
{ if ( !mbWaitingForCalcRects && !mvPostItFields.empty())
{
mbWaitingForCalcRects = true;
mnEventId = Application::PostUserEvent( LINK( this, SwPostItMgr, CalcHdl) );
}
} break;
} case SfxHintId::LanguageChanged:
{
SetSpellChecking(); break;
} case SfxHintId::SwSplitNodeOperation:
{ // if we are in a SplitNode/Cut operation, do not delete note and then add again, as this will flicker
mbDeleteNote = !mbDeleteNote; break;
} case SfxHintId::Dying:
{ if ( mpView->GetDocShell() != &rBC )
{ // field to be removed is the broadcaster
OSL_FAIL("Notification for removed SwFormatField was not sent!");
RemoveItem(&rBC);
} break;
} default: break;
}
}
}
for (autoconst& postItField : mvPostItFields)
{ // field to get the focus is the broadcaster if ( &rBC == postItField->GetBroadcaster() )
{ if (postItField->mpPostIt)
{ if (postItField->mpPostIt->IsResolved() &&
!mpWrtShell->GetViewOptions()->IsResolvedPostIts())
{
SfxRequest aRequest(mpView->GetViewFrame(), SID_TOGGLE_RESOLVED_NOTES);
mpView->ExecViewOptions(aRequest);
}
postItField->mpPostIt->GrabFocus();
MakeVisible(postItField->mpPostIt);
} else
{ // when the layout algorithm starts, this postit is created and receives focus
postItField->mbFocus = true;
}
}
}
}
bool SwPostItMgr::CalcRects()
{ if ( mnEventId )
{ // if CalcRects() was forced and an event is still pending: remove it // it is superfluous and also may cause reentrance problems if triggered while layouting
Application::RemoveUserEvent( mnEventId );
mnEventId = nullptr;
}
// show notes in right order in navigator //prevent Anchors during layout to overlap, e.g. when moving a frame if (mvPostItFields.size()>1 )
std::stable_sort(mvPostItFields.begin(), mvPostItFields.end(), comp_pos);
// sort the items into the right page vector, so layout can be done by page for (autoconst& pItem : mvPostItFields)
{ if( SwPostItHelper::INVISIBLE == pItem->mLayoutStatus )
{ if (pItem->mpPostIt)
pItem->mpPostIt->HideNote(); continue;
}
if( SwPostItHelper::HIDDEN == pItem->mLayoutStatus )
{ if (!mpWrtShell->GetViewOptions()->IsShowHiddenChar())
{ if (pItem->mpPostIt)
pItem->mpPostIt->HideNote(); continue;
}
}
bool SwPostItMgr::HasScrollbars() const
{ for (autoconst& postItField : mvPostItFields)
{ if (postItField->mbShow && postItField->mpPostIt && postItField->mpPostIt->HasScrollbar()) returntrue;
} returnfalse;
}
void SwPostItMgr::PreparePageContainer()
{ // we do not just delete the SwPostItPageItem, so offset/scrollbar is not lost
tools::Long lPageSize = mpWrtShell->GetNumPages();
tools::Long lContainerSize = mPages.size();
if (lContainerSize < lPageSize)
{
mPages.reserve(lPageSize); for (tools::Long i=0; i<lPageSize - lContainerSize;i++)
mPages.emplace_back( new SwPostItPageItem());
} elseif (lContainerSize > lPageSize)
{ for (int i=mPages.size()-1; i >= lPageSize;--i)
{
mPages.pop_back();
}
} // only clear the list, DO NOT delete the objects itself for (autoconst& page : mPages)
{
page->mvSidebarItems.clear(); if (mvPostItFields.empty())
page->bScrollbar = false;
}
}
VclPtr<SwAnnotationWin> SwPostItMgr::GetOrCreateAnnotationWindow(SwAnnotationItem& rItem, bool& rCreated)
{
VclPtr<SwAnnotationWin> pPostIt = rItem.mpPostIt; if (!pPostIt)
{
pPostIt = rItem.GetSidebarWindow( mpView->GetEditWin(),
*this );
pPostIt->InitControls();
pPostIt->SetReadonly(mbReadOnly);
rItem.mpPostIt = pPostIt; #if ENABLE_YRS
SAL_INFO("sw.yrs", "YRS GetOrCreateAnnotationWindow " << rItem.mpPostIt); #endif if (mpAnswer)
{ if (pPostIt->GetPostItField()->GetParentPostItId() != 0) //do we really have another note in front of this one
{
pPostIt->InitAnswer(*mpAnswer);
}
mpAnswer.reset();
}
//loop over all pages and do the layout // - create SwPostIt if necessary // - place SwPostIts on their initial position // - calculate necessary height for all PostIts together bool bUpdate = false; for (std::unique_ptr<SwPostItPageItem>& pPage : mPages)
{ // only layout if there are notes on this page if (!pPage->mvSidebarItems.empty())
{
std::vector<SwAnnotationWin*> aVisiblePostItList;
tools::ULong lNeededHeight = 0;
for (autoconst& pItem : pPage->mvSidebarItems)
{ if (pItem->mbShow)
{ bool bCreated = false;
VclPtr<SwAnnotationWin> pPostIt = GetOrCreateAnnotationWindow(*pItem, bCreated); if (bCreated)
{ // The annotation window was created for a previously existing, but not // laid out comment.
aCreatedPostIts.insert(pPostIt);
}
if (pItem->mbFocus)
{
mbLayout = true;
pPostIt->GrabFocus();
pItem->mbFocus = false;
} // only the visible postits are used for the final layout
aVisiblePostItList.push_back(pPostIt); if (bShowNotes)
lNeededHeight += pPostIt->IsFollow() ? aPostItHeight : aPostItHeight+GetSpaceBetween();
} else// we don't want to see it
{
VclPtr<SwAnnotationWin> pPostIt = pItem->mpPostIt; if (pPostIt)
pPostIt->HideNote();
}
SwFormatField* pFormatField = &(pItem->GetFormatField());
SwFormatFieldHintWhich nWhich = SwFormatFieldHintWhich::INSERTED;
this->Broadcast(SwFormatFieldHint(pFormatField, nWhich, mpView));
}
if (!aVisiblePostItList.empty() && ShowNotes())
{ bool bOldScrollbar = pPage->bScrollbar;
pPage->bScrollbar = LayoutByPage(aVisiblePostItList, pPage->mPageRect.SVRect(), lNeededHeight); if (!pPage->bScrollbar)
{
pPage->lOffset = 0;
} elseif (sal_Int32 nScrollSize = GetScrollSize())
{ //when we changed our zoom level, the offset value can be too big, so let's check for the largest possible zoom value
tools::Long aAvailableHeight = mpEditWin->LogicToPixel(Size(0,pPage->mPageRect.Height())).Height() - 2 * GetSidebarScrollerHeight();
tools::Long lOffset = -1 * nScrollSize * (aVisiblePostItList.size() - aAvailableHeight / nScrollSize); if (pPage->lOffset < lOffset)
pPage->lOffset = lOffset;
}
bUpdate = (bOldScrollbar != pPage->bScrollbar) || bUpdate; const tools::Long aSidebarheight = pPage->bScrollbar ? mpEditWin->PixelToLogic(Size(0,GetSidebarScrollerHeight())).Height() : 0; /* TODO - enlarge all notes till GetNextBorder(), as we resized to average value before
*/ //let's hide the ones which overlap the page for (autoconst& visiblePostIt : aVisiblePostItList)
{ if (pPage->lOffset != 0)
visiblePostIt->TranslateTopPosition(pPage->lOffset);
bool bBottom = mpEditWin->PixelToLogic(Point(0,visiblePostIt->VirtualPos().Y()+visiblePostIt->VirtualSize().Height())).Y() <= (pPage->mPageRect.Bottom()-aSidebarheight); bool bTop = mpEditWin->PixelToLogic(Point(0,visiblePostIt->VirtualPos().Y())).Y() >= (pPage->mPageRect.Top()+aSidebarheight); if ( bBottom && bTop )
{ // When tiled rendering, make sure that only the // view that has the comment focus emits callbacks, // so the editing view jumps to the comment, but // not the others. bool bTiledPainting = comphelper::LibreOfficeKit::isTiledPainting(); if (!bTiledPainting) // No focus -> disable callbacks.
comphelper::LibreOfficeKit::setTiledPainting(!visiblePostIt->HasChildPathFocus());
visiblePostIt->ShowNote(); if (!bTiledPainting)
comphelper::LibreOfficeKit::setTiledPainting(bTiledPainting);
} else
{ if (mpEditWin->PixelToLogic(Point(0,visiblePostIt->VirtualPos().Y())).Y() < (pPage->mPageRect.Top()+aSidebarheight))
{ if ( pPage->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT )
visiblePostIt->ShowAnchorOnly(Point( pPage->mPageRect.Left(),
pPage->mPageRect.Top())); elseif ( pPage->eSidebarPosition == sw::sidebarwindows::SidebarPosition::RIGHT )
visiblePostIt->ShowAnchorOnly(Point( pPage->mPageRect.Right(),
pPage->mPageRect.Top()));
} else
{ if ( pPage->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT )
visiblePostIt->ShowAnchorOnly(Point(pPage->mPageRect.Left(),
pPage->mPageRect.Bottom())); elseif ( pPage->eSidebarPosition == sw::sidebarwindows::SidebarPosition::RIGHT )
visiblePostIt->ShowAnchorOnly(Point(pPage->mPageRect.Right(),
pPage->mPageRect.Bottom()));
}
OSL_ENSURE(pPage->bScrollbar,"SwPostItMgr::LayoutByPage(): note overlaps, but bScrollbar is not true");
}
}
} else
{ for (autoconst& visiblePostIt : aVisiblePostItList)
{
visiblePostIt->SetPosAndSize();
}
for (autoconst& visiblePostIt : aVisiblePostItList)
{ if (bLoKitActive && !bTiledAnnotations)
{ if (visiblePostIt->GetSidebarItem().mbPendingLayout && visiblePostIt->GetSidebarItem().mLayoutStatus != SwPostItHelper::DELETED)
{ // Notify about a just inserted comment.
aCreatedPostIts.insert(visiblePostIt);
} elseif (visiblePostIt->IsAnchorRectChanged())
{
lcl_CommentNotification(mpView, CommentNotificationType::Modify, &visiblePostIt->GetSidebarItem(), 0);
visiblePostIt->ResetAnchorRectChanged();
}
}
// Layout for this post it finished now
visiblePostIt->GetSidebarItem().mbPendingLayout = false;
}
} else
{ if (pPage->bScrollbar)
bUpdate = true;
pPage->bScrollbar = false;
}
}
if (!bShowNotes)
{ // we do not want to see the notes anymore -> Options-Writer-View-Notes
IDocumentRedlineAccess const& rIDRA(mpWrtShell->getIDocumentRedlineAccess()); bool bRepair = false; for (autoconst& postItField : mvPostItFields)
{ if (!postItField->UseElement(*mpWrtShell->GetLayout(), rIDRA))
{
OSL_FAIL("PostIt is not in doc!");
bRepair = true; continue;
}
if (postItField->mpPostIt)
{
postItField->mpPostIt->HideNote(); if (postItField->mpPostIt->HasChildPathFocus())
{
SetActiveSidebarWin(nullptr);
postItField->mpPostIt->GrabFocusToDocument();
}
}
}
if ( bRepair )
CheckForRemovedPostIts();
}
// notes scrollbar is otherwise not drawn correctly for some cases // scrollbar area is enough if (bUpdate)
mpEditWin->Invalidate(); /*This is a super expensive relayout and render of the entire page*/
mbLayouting = false;
}
// Now that comments are laid out, notify about freshly laid out or just inserted comments. for (constauto& pPostIt : aCreatedPostIts)
{
lcl_CommentNotification(mpView, CommentNotificationType::Add, &pPostIt->GetSidebarItem(), 0);
}
if (bEnableMapMode)
mpEditWin->EnableMapMode(false);
}
bool SwPostItMgr::BorderOverPageBorder(tools::ULong aPage) const
{ if ( mPages[aPage-1]->mvSidebarItems.empty() )
{
OSL_FAIL("Notes SidePane painted but no rects and page lists calculated!"); returnfalse;
}
auto aItem = mPages[aPage-1]->mvSidebarItems.end();
--aItem;
OSL_ENSURE ((*aItem)->mpPostIt,"BorderOverPageBorder: NULL postIt, should never happen"); if ((*aItem)->mpPostIt)
{ const tools::Long aSidebarheight = mPages[aPage-1]->bScrollbar ? mpEditWin->PixelToLogic(Size(0,GetSidebarScrollerHeight())).Height() : 0; const tools::Long aEndValue = mpEditWin->PixelToLogic(Point(0,(*aItem)->mpPostIt->GetPosPixel().Y()+(*aItem)->mpPostIt->GetSizePixel().Height())).Y(); return aEndValue <= mPages[aPage-1]->mPageRect.Bottom()-aSidebarheight;
} else returnfalse;
}
void SwPostItMgr::DrawNotesForPage(OutputDevice *pOutDev, sal_uInt32 nPage)
{
assert(nPage < mPages.size()); if (nPage >= mPages.size()) return; for (autoconst& pItem : mPages[nPage]->mvSidebarItems)
{
SwAnnotationWin* pPostIt = pItem->mpPostIt; if (!pPostIt) continue;
Point aPoint(mpEditWin->PixelToLogic(pPostIt->GetPosPixel()));
pPostIt->DrawForPage(pOutDev, aPoint);
}
}
void SwPostItMgr::PaintTile(OutputDevice& rRenderContext)
{ for (const std::unique_ptr<SwAnnotationItem>& pItem : mvPostItFields)
{
SwAnnotationWin* pPostIt = pItem->mpPostIt; if (!pPostIt) continue;
rRenderContext.Pop(); if (bEnableMapMode)
mpEditWin->EnableMapMode(false);
}
}
void SwPostItMgr::Scroll(const tools::Long lScroll,const tools::ULong aPage)
{
OSL_ENSURE((lScroll % GetScrollSize() )==0,"SwPostItMgr::Scroll: scrolling by wrong value"); // do not scroll more than necessary up or down if ( ((mPages[aPage-1]->lOffset == 0) && (lScroll>0)) || ( BorderOverPageBorder(aPage) && (lScroll<0)) ) return;
constbool bOldUp = ArrowEnabled(KEY_PAGEUP,aPage); constbool bOldDown = ArrowEnabled(KEY_PAGEDOWN,aPage); const tools::Long aSidebarheight = mpEditWin->PixelToLogic(Size(0,GetSidebarScrollerHeight())).Height(); for (autoconst& item : mPages[aPage-1]->mvSidebarItems)
{
SwAnnotationWin* pPostIt = item->mpPostIt; // if this is an answer, we should take the normal position and not the real, slightly moved position
pPostIt->SetVirtualPosSize(pPostIt->GetPosPixel(),pPostIt->GetSizePixel());
pPostIt->TranslateTopPosition(lScroll);
Color SwPostItMgr::GetArrowColor(sal_uInt16 aDirection,tools::ULong aPage) const
{ if (ArrowEnabled(aDirection,aPage))
{ if (Application::GetSettings().GetStyleSettings().GetHighContrastMode()) return COL_WHITE; else return COL_NOTES_SIDEPANE_ARROW_ENABLED;
} else
{ return COL_NOTES_SIDEPANE_ARROW_DISABLED;
}
}
bool SwPostItMgr::LayoutByPage(std::vector<SwAnnotationWin*> &aVisiblePostItList, const tools::Rectangle& rBorder, tools::Long lNeededHeight)
{ /*** General layout idea:***/ // - if we have space left, we always move the current one up, // otherwise the next one down // - first all notes are resized // - then the real layout starts
// do all necessary resizings if (nPostItListSize > 0 && lVisibleHeight < lNeededHeight)
{ // ok, now we have to really resize and adding scrollbars const tools::Long lAverageHeight = (lVisibleHeight - nPostItListSize*GetSpaceBetween()) / nPostItListSize; if (lAverageHeight<GetMinimumSizeWithMeta())
{
bScrollbars = true;
lTopBorder += GetSidebarScrollerHeight() + 10;
lBottomBorder -= (GetSidebarScrollerHeight() + 10); for (autoconst& visiblePostIt : aVisiblePostItList)
visiblePostIt->SetSize(Size(visiblePostIt->VirtualSize().getWidth(),visiblePostIt->GetMinimumSizeWithMeta()));
} else
{ for (autoconst& visiblePostIt : aVisiblePostItList)
{ if ( visiblePostIt->VirtualSize().getHeight() > lAverageHeight)
visiblePostIt->SetSize(Size(visiblePostIt->VirtualSize().getWidth(),lAverageHeight));
}
}
}
//start the real layout so nothing overlaps anymore if (aVisiblePostItList.size()>1)
{ int loop = 0; bool bDone = false; // if no window is moved anymore we are finished while (!bDone)
{
loop++;
bDone = true;
tools::Long lSpaceUsed = lTopBorder + GetSpaceBetween(); for(auto i = aVisiblePostItList.begin(); i != aVisiblePostItList.end() ; ++i)
{ auto aNextPostIt = i;
++aNextPostIt;
if (aNextPostIt != aVisiblePostItList.end())
{
lTranslatePos = ( (*i)->VirtualPos().Y() + (*i)->VirtualSize().Height()) - (*aNextPostIt)->VirtualPos().Y(); if (lTranslatePos > 0) // note windows overlaps the next one
{ // we are not done yet, loop at least once more
bDone = false; // if there is space left, move the current note up // it could also happen that there is no space left for the first note due to a scrollbar // then we also jump into, so we move the current one up and the next one down if ( (lSpaceUsed <= (*i)->VirtualPos().Y()) || (i==aVisiblePostItList.begin()))
{ // we have space left, so let's move the current one up if ( ((*i)->VirtualPos().Y()- lTranslatePos - GetSpaceBetween()) > lTopBorder)
{ if ((*aNextPostIt)->IsFollow())
(*i)->TranslateTopPosition(-1*(lTranslatePos+ANCHORLINE_WIDTH)); else
(*i)->TranslateTopPosition(-1*(lTranslatePos+GetSpaceBetween()));
} else
{
tools::Long lMoveUp = (*i)->VirtualPos().Y() - lTopBorder;
(*i)->TranslateTopPosition(-1* lMoveUp); if ((*aNextPostIt)->IsFollow())
(*aNextPostIt)->TranslateTopPosition( (lTranslatePos+ANCHORLINE_WIDTH) - lMoveUp); else
(*aNextPostIt)->TranslateTopPosition( (lTranslatePos+GetSpaceBetween()) - lMoveUp);
}
} else
{ // no space left, left move the next one down if ((*aNextPostIt)->IsFollow())
(*aNextPostIt)->TranslateTopPosition(lTranslatePos+ANCHORLINE_WIDTH); else
(*aNextPostIt)->TranslateTopPosition(lTranslatePos+GetSpaceBetween());
}
} else
{ // the first one could overlap the topborder instead of a second note if (i==aVisiblePostItList.begin())
{
tools::Long lMoveDown = lTopBorder - (*i)->VirtualPos().Y(); if (lMoveDown>0)
{
bDone = false;
(*i)->TranslateTopPosition( lMoveDown);
}
}
} if ( (*aNextPostIt)->IsFollow() )
lSpaceUsed += (*i)->VirtualSize().Height() + ANCHORLINE_WIDTH; else
lSpaceUsed += (*i)->VirtualSize().Height() + GetSpaceBetween();
} else
{ //(*i) is the last visible item auto aPrevPostIt = i;
--aPrevPostIt;
lTranslatePos = ( (*aPrevPostIt)->VirtualPos().Y() + (*aPrevPostIt)->VirtualSize().Height() ) - (*i)->VirtualPos().Y(); if (lTranslatePos > 0)
{
bDone = false; if ( ((*i)->VirtualPos().Y()+ (*i)->VirtualSize().Height()+lTranslatePos) < lBottomBorder)
{ if ( (*i)->IsFollow() )
(*i)->TranslateTopPosition(lTranslatePos+ANCHORLINE_WIDTH); else
(*i)->TranslateTopPosition(lTranslatePos+GetSpaceBetween());
} else
{
(*i)->TranslateTopPosition(lBottomBorder - ((*i)->VirtualPos().Y()+ (*i)->VirtualSize().Height()) );
}
} else
{ // note does not overlap, but we might be over the lower border // only do this if there are no scrollbars, otherwise notes are supposed to overlap the border if (!bScrollbars && ((*i)->VirtualPos().Y()+ (*i)->VirtualSize().Height() > lBottomBorder) )
{
bDone = false;
(*i)->TranslateTopPosition(lBottomBorder - ((*i)->VirtualPos().Y()+ (*i)->VirtualSize().Height()));
}
}
}
} // security check so we don't loop forever if (loop>MAX_LOOP_COUNT)
{
OSL_FAIL("PostItMgr::Layout(): We are looping forever"); break;
}
}
} else
{ // only one left, make sure it is not hidden at the top or bottom auto i = aVisiblePostItList.begin();
lTranslatePos = lTopBorder - (*i)->VirtualPos().Y(); if (lTranslatePos>0)
{
(*i)->TranslateTopPosition(lTranslatePos+GetSpaceBetween());
}
lTranslatePos = lBottomBorder - ((*i)->VirtualPos().Y()+ (*i)->VirtualSize().Height()); if (lTranslatePos<0)
{
(*i)->TranslateTopPosition(lTranslatePos);
}
} return bScrollbars;
}
for (std::vector<SwFormatField*>::iterator i = vFormatFields.begin(); i != vFormatFields.end(); i++)
{
SwPostItField *pChildPostIt = static_cast<SwPostItField*>((*i)->GetField());
for(auto pFormatField : vFormatFields)
InsertItem(pFormatField, bCheckExistence, bFocus); // if we just added the first one we have to update the view for centering if (bEmpty && !mvPostItFields.empty())
PrepareView(true);
}
std::unique_ptr<SwPostItMgr::CommentDeleteFlagsRestore> SwPostItMgr::ConfigureForCommentDelete()
{ if (!mpWrtShell->IsRedlineOn()) return {}; // No track changes - no need to disable it if (isOwnFileFormat(mpView->GetDocShell()->GetMedium())) return {}; // Format is smart enough to handle deleted comments in redlines
return std::unique_ptr<CommentDeleteFlagsRestore>( new CommentDeleteFlagsRestoreImpl(mpWrtShell));
}
// copy to new vector, otherwise RemoveItem would operate and delete stuff on mvPostItFields as well // RemoveItem will clean up the core field and visible postit if necessary // we cannot just delete everything as before, as postits could move into change tracking void SwPostItMgr::Delete(const OUString& rAuthor)
{
OUString sQuestion = SwResId(STR_QUERY_DELALLCOMMENTSAUTHOR_QUESTION);
sQuestion = sQuestion.replaceAll("%AUTHOR", rAuthor); if (!ConfirmDeleteAll(mpWrtShell->GetView(), sQuestion)) return;
// tdf#136540 - prevent scrolling to cursor during deletion of annotations constbool bUnLockView = !mpWrtShell->IsViewLocked();
mpWrtShell->LockView(true);
SwAnnotationWin* SwPostItMgr::GetNextPostIt( sal_uInt16 aDirection,
SwAnnotationWin* aPostIt )
{ if (mvPostItFields.size()>1)
{ auto i = std::find_if(mvPostItFields.begin(), mvPostItFields.end(),
[&aPostIt](const std::unique_ptr<SwAnnotationItem>& pField) { return pField->mpPostIt == aPostIt; }); if (i == mvPostItFields.end()) return nullptr;
auto iNextPostIt = i; if (aDirection == KEY_PAGEUP)
{ if ( iNextPostIt == mvPostItFields.begin() )
{ return nullptr;
}
--iNextPostIt;
} else
{
++iNextPostIt; if ( iNextPostIt == mvPostItFields.end() )
{ return nullptr;
}
} // let's quit, we are back at the beginning if ( (*iNextPostIt)->mpPostIt == aPostIt) return nullptr; return (*iNextPostIt)->mpPostIt;
} else return nullptr;
}
tools::Long SwPostItMgr::GetNextBorder()
{ for (autoconst& pPage : mPages)
{ for(auto b = pPage->mvSidebarItems.begin(); b!= pPage->mvSidebarItems.end(); ++b)
{ if ((*b)->mpPostIt == mpActivePostIt)
{ auto aNext = b;
++aNext; bool bFollow = (aNext != pPage->mvSidebarItems.end()) && (*aNext)->mpPostIt->IsFollow(); if ( pPage->bScrollbar || bFollow )
{ return -1;
} else
{ //if this is the last item, return the bottom border otherwise the next item if (aNext == pPage->mvSidebarItems.end()) return mpEditWin->LogicToPixel(Point(0,pPage->mPageRect.Bottom())).Y() - GetSpaceBetween(); else return (*aNext)->mpPostIt->GetPosPixel().Y() - GetSpaceBetween();
}
}
}
}
OSL_FAIL("SwPostItMgr::GetNextBorder(): We have to find a next border here"); return -1;
}
void SwPostItMgr::SetShadowState(const SwPostItField* pField,bool bCursor)
{ if (pField)
{ if (pField !=mShadowState.mpShadowField)
{ if (mShadowState.mpShadowField)
{ // reset old one if still alive // TODO: does not work properly if mouse and cursor was set
sw::annotation::SwAnnotationWin* pOldPostIt =
GetAnnotationWin(mShadowState.mpShadowField); if (pOldPostIt && pOldPostIt->Shadow() && (pOldPostIt->Shadow()->GetShadowState() != SS_EDIT))
pOldPostIt->SetViewState(ViewState::NORMAL);
} //set new one, if it is not currently edited
sw::annotation::SwAnnotationWin* pNewPostIt = GetAnnotationWin(pField); if (pNewPostIt && pNewPostIt->Shadow() && (pNewPostIt->Shadow()->GetShadowState() != SS_EDIT))
{
pNewPostIt->SetViewState(ViewState::VIEW); //remember our new field
mShadowState.mpShadowField = pField;
mShadowState.bCursor = false;
mShadowState.bMouse = false;
}
} if (bCursor)
mShadowState.bCursor = true; else
mShadowState.bMouse = true;
} else
{ if (mShadowState.mpShadowField)
{ if (bCursor)
mShadowState.bCursor = false; else
mShadowState.bMouse = false; if (!mShadowState.bCursor && !mShadowState.bMouse)
{ // reset old one if still alive
sw::annotation::SwAnnotationWin* pOldPostIt = GetAnnotationWin(mShadowState.mpShadowField); if (pOldPostIt && pOldPostIt->Shadow() && (pOldPostIt->Shadow()->GetShadowState() != SS_EDIT))
{
pOldPostIt->SetViewState(ViewState::NORMAL);
mShadowState.mpShadowField = nullptr;
}
}
}
}
}
bool SwPostItMgr::IsHit(const Point& aPointPixel)
{ if (!HasNotes() || !ShowNotes()) returnfalse;
const Point aPoint = mpEditWin->PixelToLogic(aPointPixel);
tools::Rectangle aRect(GetSidebarRect(aPoint)); if (!aRect.Contains(aPoint)) returnfalse;
// we hit the note's sidebar // let's now test for the arrow area
SwRect aPageFrame; const tools::ULong nPageNum
= SwPostItHelper::getPageInfo(aPageFrame, mpWrtShell->GetLayout(), aPoint); if (!nPageNum) returnfalse; if (mPages[nPageNum - 1]->bScrollbar) return ScrollbarHit(nPageNum, aPoint); returnfalse;
}
vcl::Window* SwPostItMgr::IsHitSidebarWindow(const Point& rPointLogic)
{
vcl::Window* pRet = nullptr;
if (HasNotes() && ShowNotes())
{ bool bEnableMapMode = !mpEditWin->IsMapModeEnabled(); if (bEnableMapMode)
mpEditWin->EnableMapMode();
for (const std::unique_ptr<SwAnnotationItem>& pItem : mvPostItFields)
{
SwAnnotationWin* pPostIt = pItem->mpPostIt; if (!pPostIt) continue;
if (pPostIt->IsHitWindow(rPointLogic))
{
pRet = pPostIt; break;
}
}
if (bEnableMapMode)
mpEditWin->EnableMapMode(false);
}
// find first valid note
SwAnnotationWin *pFirstPostIt = nullptr; for (autoconst& postItField : mvPostItFields)
{
pFirstPostIt = postItField->mpPostIt; if (pFirstPostIt) break;
}
//if we have not found a valid note, forget about it and leave if (!pFirstPostIt) return;
// yeah, I know, if this is a left page it could be wrong, but finding the page and the note is probably not even faster than just doing it // check, if anchor overlay object exists. const tools::Long aAnchorX = pFirstPostIt->Anchor()
? mpEditWin->LogicToPixel( Point(static_cast<tools::Long>(pFirstPostIt->Anchor()->GetSixthPosition().getX()),0)).X()
: 0; const tools::Long aAnchorY = pFirstPostIt->Anchor()
? mpEditWin->LogicToPixel( Point(0,static_cast<tools::Long>(pFirstPostIt->Anchor()->GetSixthPosition().getY()))).Y() + 1
: 0; if (Point(aAnchorX,aAnchorY) == pFirstPostIt->GetPosPixel()) return;
bool SwPostItMgr::ShowNotes() const
{ // we only want to see notes if Options - Writer - View - Notes is ticked return mpWrtShell->GetViewOptions()->IsPostIts();
}
sw::sidebarwindows::SidebarPosition eSidebarPosition = GetSidebarPos(rPointLogic); if (eSidebarPosition == sw::sidebarwindows::SidebarPosition::NONE) return;
// Calculate the width to be applied in logic units
tools::Long nLogicWidth; if (eSidebarPosition == sw::sidebarwindows::SidebarPosition::RIGHT)
nLogicWidth = rPointLogic.X() - nSidebarRect.Left(); else
nLogicWidth = nSidebarRect.Right() - rPointLogic.X();
// The zoom level is conveniently used as reference to define the minimum width const sal_uInt16 nZoom = mpWrtShell->GetViewOptions()->GetZoom(); double nFactor = static_cast<double>(mpEditWin->LogicToPixel(Point(nLogicWidth, 0)).X())
/ static_cast<double>(nZoom); // The width may vary from 1x to 8x the zoom factor
nFactor = std::clamp(nFactor, 1.0, 8.0);
std::shared_ptr<comphelper::ConfigurationChanges> xChanges(
comphelper::ConfigurationChanges::create());
officecfg::Office::Writer::Notes::DisplayWidthFactor::set(nFactor, xChanges);
xChanges->commit();
// tdf#159146 After resizing the sidebar the layout and the ruler needs to be updated
mpWrtShell->InvalidateLayout(true);
mpView->GetHRuler().Invalidate();
mpView->InvalidateRulerPos();
LayoutPostIts();
}
tools::ULong SwPostItMgr::GetSidebarWidth(bool bPx) const
{ bool bEnableMapMode = !mpWrtShell->GetOut()->IsMapModeEnabled();
sal_uInt16 nZoom = mpWrtShell->GetViewOptions()->GetZoom(); if (comphelper::LibreOfficeKit::isActive() && !bEnableMapMode)
{ // The output device is the tile and contains the real wanted scale factor. double fScaleX = double(mpWrtShell->GetOut()->GetMapMode().GetScaleX());
nZoom = fScaleX * 100;
}
tools::ULong aWidth = static_cast<tools::ULong>(
nZoom * officecfg::Office::Writer::Notes::DisplayWidthFactor::get());
if (bPx) return aWidth; else
{ if (bEnableMapMode) // The output device is the window.
mpWrtShell->GetOut()->EnableMapMode();
tools::Long nRet = mpWrtShell->GetOut()->PixelToLogic(Size(aWidth, 0)).Width(); if (bEnableMapMode)
mpWrtShell->GetOut()->EnableMapMode(false); return nRet;
}
}
Color SwPostItMgr::GetColorDark(std::size_t aAuthorIndex)
{
Color aColor = GetColorAnchor(aAuthorIndex);
svtools::ColorConfig aColorConfig; const Color aBgColor(aColorConfig.GetColorValue(svtools::DOCCOLOR).nColor); if (aBgColor.IsDark())
aColor.DecreaseLuminance(80); else
aColor.IncreaseLuminance(150); return aColor;
}
Color SwPostItMgr::GetColorLight(std::size_t aAuthorIndex)
{
Color aColor = GetColorAnchor(aAuthorIndex);
svtools::ColorConfig aColorConfig; const Color aBgColor(aColorConfig.GetColorValue(svtools::DOCCOLOR).nColor); if (aBgColor.IsDark())
aColor.DecreaseLuminance(130); else
aColor.IncreaseLuminance(200); return aColor;
}
Color SwPostItMgr::GetColorAnchor(std::size_t aAuthorIndex)
{ if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
{
svtools::ColorConfig aColorConfig; switch (aAuthorIndex % 9)
{ case 0: return aColorConfig.GetColorValue(svtools::AUTHOR1).nColor; case 1: return aColorConfig.GetColorValue(svtools::AUTHOR2).nColor; case 2: return aColorConfig.GetColorValue(svtools::AUTHOR3).nColor; case 3: return aColorConfig.GetColorValue(svtools::AUTHOR4).nColor; case 4: return aColorConfig.GetColorValue(svtools::AUTHOR5).nColor; case 5: return aColorConfig.GetColorValue(svtools::AUTHOR6).nColor; case 6: return aColorConfig.GetColorValue(svtools::AUTHOR7).nColor; case 7: return aColorConfig.GetColorValue(svtools::AUTHOR8).nColor; case 8: return aColorConfig.GetColorValue(svtools::AUTHOR9).nColor;
}
}
return COL_WHITE;
}
void SwPostItMgr::SetActiveSidebarWin( SwAnnotationWin* p)
{ if ( p == mpActivePostIt ) return;
// we need the temp variable so we can set mpActivePostIt before we call DeactivatePostIt // therefore we get a new layout in DOCCHANGED when switching from postit to document, // otherwise, GetActivePostIt() would still hold our old postit
SwAnnotationWin* pActive = mpActivePostIt;
mpActivePostIt = p; if (pActive)
{
pActive->DeactivatePostIt();
mShadowState.mpShadowField = nullptr;
} if (mpActivePostIt)
{
mpActivePostIt->GotoPos();
mpView->AttrChangedNotify(nullptr);
mpActivePostIt->ActivatePostIt();
}
}
IMPL_LINK_NOARG( SwPostItMgr, CalcHdl, void*, void )
{
mnEventId = nullptr; if ( mbLayouting )
{
OSL_FAIL("Reentrance problem in Layout Manager!");
mbWaitingForCalcRects = false; return;
}
// do not change order, even if it would seem so in the first place, we need the calcrects always if (CalcRects() || mbLayout)
{
mbLayout = false;
LayoutPostIts();
}
}
void SwPostItMgr::Rescale()
{ for (autoconst& postItField : mvPostItFields) if ( postItField->mpPostIt )
postItField->mpPostIt->Rescale();
}
void SwPostItMgr::ShowHideResolvedNotes(bool visible) { for (autoconst& pPage : mPages)
{ for(auto b = pPage->mvSidebarItems.begin(); b!= pPage->mvSidebarItems.end(); ++b)
{ if ((*b)->mpPostIt->IsResolved())
{
(*b)->mpPostIt->SetResolved(true);
(*b)->mpPostIt->GetSidebarItem().mbShow = visible;
}
}
}
LayoutPostIts();
}
void SwPostItMgr::UpdateResolvedStatus(const sw::annotation::SwAnnotationWin* topNote) { // Given the topmost note as an argument, scans over all notes and sets the // 'resolved' state of each descendant of the top notes to the resolved state // of the top note. bool resolved = topNote->IsResolved(); for (autoconst& pPage : mPages)
{ for(auto b = pPage->mvSidebarItems.begin(); b!= pPage->mvSidebarItems.end(); ++b)
{ if((*b)->mpPostIt->GetTopReplyNote() == topNote) {
(*b)->mpPostIt->SetResolved(resolved);
}
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.