sw: add Comments Panel in sidebar

This commit adds a sidebar deck for comments.
It has all the functionalities of the old comments
shown via annotation window.

Change-Id: Ibab0e21a54c097d0f42bddb1d07da74319970135
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/167840
Tested-by: Jenkins
Reviewed-by: Sarper Akdemir <sarper.akdemir@allotropia.de>
This commit is contained in:
Mohit Marathe
2024-09-13 12:35:45 +02:00
committed by Sarper Akdemir
parent 59c123d62a
commit be16c6cf5c
14 changed files with 1228 additions and 3 deletions
+2
View File
@@ -20,6 +20,8 @@ TOOLS_DLLPUBLIC OUString DateTimeToOUString(const DateTime& rDateTime);
// This function converts a 'Date' object to an 'OString' object in ISO-8601 representation
TOOLS_DLLPUBLIC OString DateToOString(const Date& rDate);
TOOLS_DLLPUBLIC OString TimeToOString(const tools::Time& rTime);
// This function converts a 'Date' object to an 'OUString' object in DD/MM/YYYY format
TOOLS_DLLPUBLIC OUString DateToDDMMYYYYOUString(const Date& rDate);
@@ -81,6 +81,29 @@
</prop>
</node>
<node oor:name="CommentsDeck" oor:op="replace">
<prop oor:name="Title" oor:type="xs:string">
<value xml:lang="en-US">Comments</value>
</prop>
<prop oor:name="IsExperimental" oor:type="xs:boolean">
<value>true</value>
</prop>
<prop oor:name="Id" oor:type="xs:string">
<value>CommentsDeck</value>
</prop>
<prop oor:name="IconURL" oor:type="xs:string">
<value>private:graphicrepository/cmd/lc_showannotations.png</value>
</prop>
<prop oor:name="ContextList">
<value oor:separator=";">
WriterVariants, any, visible ;
</value>
</prop>
<prop oor:name="OrderIndex" oor:type="xs:int">
<value>900</value>
</prop>
</node>
<node oor:name="A11yCheckDeck" oor:op="replace">
<prop oor:name="Title" oor:type="xs:string">
<value xml:lang="en-US">Accessibility Check</value>
@@ -618,6 +641,41 @@
</prop>
</node>
<node oor:name="CommentsPanel" oor:op="replace">
<prop oor:name="Title" oor:type="xs:string">
<value xml:lang="en-US">Comments</value>
</prop>
<prop oor:name="IsExperimental" oor:type="xs:boolean">
<value>true</value>
</prop>
<prop oor:name="TitleBarIsOptional" oor:type="xs:boolean">
<value>true</value>
</prop>
<prop oor:name="Id" oor:type="xs:string">
<value>CommentsPanel</value>
</prop>
<prop oor:name="DeckId" oor:type="xs:string">
<value>CommentsDeck</value>
</prop>
<prop oor:name="DefaultMenuCommand">
<value></value>
</prop>
<prop oor:name="ContextList">
<value oor:separator=";">
WriterVariants, any, visible ;
</value>
</prop>
<prop oor:name="ImplementationURL" oor:type="xs:string">
<value>private:resource/toolpanel/SwPanelFactory/CommentsPanel</value>
</prop>
<prop oor:name="OrderIndex" oor:type="xs:int">
<value>100</value>
</prop>
<prop oor:name="WantsAWT" oor:type="xs:boolean">
<value>false</value>
</prop>
</node>
<node oor:name="A11yCheckIssuesPanel" oor:op="replace">
<prop oor:name="Title" oor:type="xs:string">
<value xml:lang="en-US">Accessibility Issues</value>
+1
View File
@@ -735,6 +735,7 @@ $(eval $(call gb_Library_add_exception_objects,sw,\
sw/source/uibase/sidebar/ThemePanel \
sw/source/uibase/sidebar/SwPanelFactory \
sw/source/uibase/sidebar/WriterInspectorTextPanel \
sw/source/uibase/sidebar/CommentsPanel \
sw/source/uibase/sidebar/A11yCheckIssuesPanel \
sw/source/uibase/sidebar/QuickFindPanel \
sw/source/uibase/table/chartins \
+3
View File
@@ -285,6 +285,9 @@ $(eval $(call gb_UIConfig_add_uifiles,modules/swriter,\
sw/uiconfig/swriter/ui/pagestylespanel \
sw/uiconfig/swriter/ui/pageheaderpanel \
sw/uiconfig/swriter/ui/pagefooterpanel \
sw/uiconfig/swriter/ui/commentspanel \
sw/uiconfig/swriter/ui/commentsthread \
sw/uiconfig/swriter/ui/commentwidget \
sw/uiconfig/swriter/ui/a11ycheckissuespanel \
sw/uiconfig/swriter/ui/poseditbox \
sw/uiconfig/swriter/ui/sidebarwrap \
+1
View File
@@ -71,6 +71,7 @@ class SAL_DLLPUBLIC_RTTI SwAnnotationWin final : public InterimItemWindow
void Delete();
void GotoPos();
const SwPostItField* GetPostItField() const { return mpField; }
SwFormatField* GetFormatField() const { return mpFormatField; }
void UpdateText(const OUString& aText);
OUString GetAuthor() const;
+6 -1
View File
@@ -82,7 +82,8 @@ struct FieldShadowState
}
};
class SAL_DLLPUBLIC_RTTI SwPostItMgr final : public SfxListener
class SAL_DLLPUBLIC_RTTI SwPostItMgr final : public SfxListener,
public SfxBroadcaster
{
private:
SwView* mpView;
@@ -141,9 +142,11 @@ class SAL_DLLPUBLIC_RTTI SwPostItMgr final : public SfxListener
typedef std::vector< std::unique_ptr<SwSidebarItem> >::const_iterator const_iterator;
const_iterator begin() const { return mvPostItFields.begin(); }
const_iterator end() const { return mvPostItFields.end(); }
const std::vector<std::unique_ptr<SwSidebarItem>>& GetPostItFields() { return mvPostItFields; }
void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
std::vector<SwFormatField*> UpdatePostItsParentInfo();
void LayoutPostIts();
bool CalcRects();
@@ -169,6 +172,8 @@ class SAL_DLLPUBLIC_RTTI SwPostItMgr final : public SfxListener
void ToggleResolved(sal_uInt32 nPostItId);
void ToggleResolvedForThread(sal_uInt32 nPostItId);
sw::annotation::SwAnnotationWin* GetRemovedAnnotationWin(const SfxBroadcaster* pBroadcast);
void ExecuteFormatAllDialog(SwView& rView);
void FormatAll(const SfxItemSet &rNewAttr);
+25 -2
View File
@@ -319,6 +319,17 @@ SwSidebarItem* SwPostItMgr::InsertItem(SfxBroadcaster* pItem, bool bCheckExisten
return pAnnotationItem;
}
sw::annotation::SwAnnotationWin* SwPostItMgr::GetRemovedAnnotationWin( const SfxBroadcaster* pBroadcast )
{
auto i = std::find_if(mvPostItFields.begin(), mvPostItFields.end(),
[&pBroadcast](const std::unique_ptr<SwSidebarItem>& pField) { return pField->GetBroadcaster() == pBroadcast; });
if (i != mvPostItFields.end())
{
return (*i)->mpPostIt;
}
return nullptr;
}
void SwPostItMgr::RemoveItem( SfxBroadcaster* pBroadcast )
{
EndListening(*pBroadcast);
@@ -400,6 +411,7 @@ void SwPostItMgr::Notify( SfxBroadcaster& rBC, const SfxHint& rHint )
mbLayout = true;
break;
}
this->Broadcast(rHint);
RemoveItem(pField);
// If LOK has disabled tiled annotations, emit annotation callbacks
@@ -430,6 +442,7 @@ void SwPostItMgr::Notify( SfxBroadcaster& rBC, const SfxHint& rHint )
{
postItField->mpPostIt->SetPostItText();
mbLayout = true;
this->Forward(rBC, rHint);
}
// If LOK has disabled tiled annotations, emit annotation callbacks
@@ -821,6 +834,9 @@ void SwPostItMgr::LayoutPostIts()
if (pPostIt)
pPostIt->HideNote();
}
SwFormatField* pFormatField = &(pItem->GetFormatField());
SwFormatFieldHintWhich nWhich = SwFormatFieldHintWhich::INSERTED;
this->Broadcast(SwFormatFieldHint(pFormatField, nWhich, mpView));
}
if (!aVisiblePostItList.empty() && ShowNotes())
@@ -1331,9 +1347,8 @@ bool SwPostItMgr::LayoutByPage(std::vector<SwAnnotationWin*> &aVisiblePostItList
return bScrollbars;
}
void SwPostItMgr::AddPostIts(const bool bCheckExistence, const bool bFocus)
std::vector<SwFormatField*> SwPostItMgr::UpdatePostItsParentInfo()
{
const bool bEmpty = mvPostItFields.empty();
IDocumentRedlineAccess const& rIDRA(mpWrtShell->getIDocumentRedlineAccess());
SwFieldType* pType = mpView->GetDocShell()->GetDoc()->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::Postit, OUString(),false);
std::vector<SwFormatField*> vFormatFields;
@@ -1361,6 +1376,14 @@ void SwPostItMgr::AddPostIts(const bool bCheckExistence, const bool bFocus)
}
}
}
return vFormatFields;
}
void SwPostItMgr::AddPostIts(const bool bCheckExistence, const bool bFocus)
{
const bool bEmpty = mvPostItFields.empty();
std::vector<SwFormatField*> vFormatFields = UpdatePostItsParentInfo();
for(auto pFormatField : vFormatFields)
InsertItem(pFormatField, bCheckExistence, bFocus);
+443
View File
@@ -0,0 +1,443 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* 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 <sal/config.h>
#include <sfx2/bindings.hxx>
#include <PostItMgr.hxx>
#include <postithelper.hxx>
#include <AnnotationWin.hxx>
#include <fmtfld.hxx>
#include <docufld.hxx>
#include <swmodule.hxx>
#include <vcl/svapp.hxx>
#include <rtl/ustring.hxx>
#include <xmloff/xmlmetae.hxx>
#include <tools/datetimeutils.hxx>
#include <swtypes.hxx>
#include <view.hxx>
#include <wrtsh.hxx>
#include <tools/date.hxx>
#include <tools/datetime.hxx>
#include <tools/time.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/syslocale.hxx>
#include <tools/link.hxx>
#include <editeng/outliner.hxx>
#include <editeng/editeng.hxx>
#include <strings.hrc>
#include <cmdid.h>
#include "CommentsPanel.hxx"
#include <com/sun/star/lang/IllegalArgumentException.hpp>
namespace sw::sidebar
{
Comment::Comment(weld::Container* pParent, CommentsPanel& rCommentsPanel)
: mxBuilder(Application::CreateBuilder(pParent, "modules/swriter/ui/commentwidget.ui"))
, mxContainer(mxBuilder->weld_container("Comment"))
, mxAuthor(mxBuilder->weld_label("authorlabel"))
, mxDate(mxBuilder->weld_label("datelabel"))
, mxTime(mxBuilder->weld_label("timelabel"))
, mxReply(mxBuilder->weld_button("replybutton"))
, mxResolve(mxBuilder->weld_check_button("resolvebutton"))
, mxTextView(mxBuilder->weld_text_view("textview"))
, mrCommentsPanel(rCommentsPanel)
, maDate(Date::EMPTY)
, maTime(tools::Time::EMPTY)
, mbResolved(false)
{
mxTextView->connect_focus_out(LINK(this, Comment, OnFocusOut));
mxResolve->connect_toggled(LINK(this, Comment, ResolveClicked));
mxReply->connect_clicked(LINK(this, Comment, ReplyClicked));
}
Comment::~Comment() {}
void Comment::InitControls(const SwPostItField* pPostItField)
{
if (!pPostItField)
return;
msText = pPostItField->GetText();
msAuthor = pPostItField->GetPar1();
msInitials = pPostItField->GetInitials();
msName = pPostItField->GetName();
maDate = Date(pPostItField->GetDateTime().GetDate());
maTime = tools::Time(pPostItField->GetDateTime().GetTime());
mbResolved = pPostItField->GetResolved();
// Format date and time according to the system locale
const SvtSysLocale aSysLocale;
const LocaleDataWrapper& rLocalData = aSysLocale.GetLocaleData();
OUString sMeta;
if (maDate.IsValidAndGregorian())
{
sMeta = rLocalData.getDate(maDate);
}
else
{
sMeta = SwResId(STR_NODATE);
}
if (mxDate->get_label() != sMeta)
{
mxDate->set_label(sMeta);
}
if (pPostItField->GetTime().GetTime() != 0)
{
sMeta = " " + rLocalData.getTime(pPostItField->GetTime(), false);
}
if (mxTime->get_label() != sMeta)
{
mxTime->set_label(sMeta);
}
mxAuthor->set_label(msAuthor);
mxAuthor->set_tooltip_text(msAuthor);
mxResolve->set_active(mbResolved);
mxTextView->set_text(msText);
}
IMPL_LINK_NOARG(Comment, OnFocusOut, weld::Widget&, void) { mrCommentsPanel.EditComment(this); }
IMPL_LINK_NOARG(Comment, ResolveClicked, weld::Toggleable&, void)
{
mrCommentsPanel.ToggleResolved(this);
}
IMPL_LINK_NOARG(Comment, ReplyClicked, weld::Button&, void) { mrCommentsPanel.ReplyComment(this); }
Thread::Thread(weld::Container* pParent)
: mxBuilder(Application::CreateBuilder(pParent, "modules/swriter/ui/commentsthread.ui"))
, mxContainer(mxBuilder->weld_container("Thread"))
, mxCommentBox(mxBuilder->weld_box("comments_box"))
, mxText(mxBuilder->weld_label("commentedtext"))
{
// mxContainer->set_size_request(-1, mxContainer->get_preferred_size().Height());
}
Thread::~Thread() {}
std::unique_ptr<PanelLayout> CommentsPanel::Create(weld::Widget* pParent)
{
if (pParent == nullptr)
throw ::com::sun::star::lang::IllegalArgumentException(
"no parent window given to CommentsPanel::Create", nullptr, 0);
return std::make_unique<CommentsPanel>(pParent);
}
CommentsPanel::CommentsPanel(weld::Widget* pParent)
: PanelLayout(pParent, "CommentsPanel", "modules/swriter/ui/commentspanel.ui")
, mpPostItMgr(nullptr)
, mxFilterAuthor(m_xBuilder->weld_combo_box("filter_author"))
, mxFilterTime(m_xBuilder->weld_combo_box("filter_time"))
, mxShowTime(m_xBuilder->weld_check_button("show_time"))
, mxShowResolved(m_xBuilder->weld_check_button("show_resolved"))
, mxShowReference(m_xBuilder->weld_check_button("show_reference"))
, mxSortbyPosition(m_xBuilder->weld_radio_button("sortby_position"))
, mxSortbyTime(m_xBuilder->weld_radio_button("sortby_time"))
, mxThreadsContainer(m_xBuilder->weld_box("comment_threads"))
{
SwView* pView = GetActiveView();
if (!pView)
return;
SwWrtShell& rSh = pView->GetWrtShell();
mpPostItMgr = rSh.GetPostItMgr();
populateComments();
StartListening(*mpPostItMgr);
}
void CommentsPanel::Notify(SfxBroadcaster& rBC, const SfxHint& rHint)
{
if (rHint.GetId() == SfxHintId::SwFormatField)
{
const SwFormatFieldHint* pFormatHint = static_cast<const SwFormatFieldHint*>(&rHint);
const SwFormatField* pField = pFormatHint->GetField();
switch (pFormatHint->Which())
{
case SwFormatFieldHintWhich::INSERTED:
{
if (!pField)
{
break;
}
// get field to be inserted from hint
if (pField->IsFieldInDoc())
{
addComment(pField);
}
else
{
OSL_FAIL("Inserted field not in document!");
}
break;
}
case SwFormatFieldHintWhich::REMOVED:
case SwFormatFieldHintWhich::REDLINED_DELETION:
{
sw::annotation::SwAnnotationWin* pAnnotationWin
= mpPostItMgr->GetRemovedAnnotationWin(pField);
deleteComment(pAnnotationWin);
break;
}
case SwFormatFieldHintWhich::FOCUS:
{
break;
}
case SwFormatFieldHintWhich::CHANGED:
case SwFormatFieldHintWhich::RESOLVED:
{
SwFormatField* pFormatField = dynamic_cast<SwFormatField*>(&rBC);
SwPostItField* pPostItField = static_cast<SwPostItField*>(pFormatField->GetField());
sw::annotation::SwAnnotationWin* pAnnotationWin
= mpPostItMgr->GetAnnotationWin(pPostItField);
setResolvedStatus(pAnnotationWin);
break;
}
}
}
}
sw::annotation::SwAnnotationWin* CommentsPanel::getRootCommentWin(const SwFormatField* pFormatField)
{
if (!pFormatField)
return nullptr;
const SwPostItField* pPostItField = static_cast<const SwPostItField*>(pFormatField->GetField());
sw::annotation::SwAnnotationWin* pAnnotationWin = mpPostItMgr->GetAnnotationWin(pPostItField);
if (!pAnnotationWin)
return nullptr;
sw::annotation::SwAnnotationWin* pRootNote = pAnnotationWin->GetTopReplyNote();
return pRootNote;
}
sal_uInt32 CommentsPanel::getPostItId(sw::annotation::SwAnnotationWin* pAnnotationWin)
{
const SwPostItField* pField = pAnnotationWin->GetPostItField();
return pField->GetPostItId();
}
sw::annotation::SwAnnotationWin* CommentsPanel::getAnnotationWin(Comment* pComment)
{
sal_uInt32 nPostItId = 0;
for (auto & [ rId, rComment ] : mpCommentsMap)
{
if (rComment.get() != pComment)
{
continue;
}
nPostItId = rId;
break;
}
auto& vPostItFields = mpPostItMgr->GetPostItFields();
SwPostItField* pPostItField = nullptr;
for (auto& pItem : vPostItFields)
{
SwFormatField* pFormatField = &pItem->GetFormatField();
SwPostItField* pField = static_cast<SwPostItField*>(pFormatField->GetField());
if (pField->GetPostItId() == nPostItId)
{
pPostItField = pField;
break;
}
}
return mpPostItMgr->GetAnnotationWin(pPostItField);
}
void CommentsPanel::populateComments()
{
if (!mpPostItMgr)
return;
std::vector<SwFormatField*> vFormatFields = mpPostItMgr->UpdatePostItsParentInfo();
for (auto pFormatField : vFormatFields)
{
sw::annotation::SwAnnotationWin* pRootNote = getRootCommentWin(pFormatField);
if (!pRootNote)
continue;
sal_uInt32 nPostItId = getPostItId(pRootNote);
if (mpThreadsMap.find(nPostItId) != mpThreadsMap.end())
continue; // Skip if root comment is already present
auto pThread = std::make_unique<Thread>(mxThreadsContainer.get());
mxThreadsContainer->reorder_child(pThread->get_widget(), mnThreads++);
for (sw::annotation::SwAnnotationWin* pCurrent = pRootNote;;)
{
sal_uInt32 nId = getPostItId(pCurrent);
auto pComment = std::make_unique<Comment>(pThread->getCommentBoxWidget(), *this);
pThread->getCommentBoxWidget()->reorder_child(pComment->get_widget(),
pThread->mnComments++);
pComment->InitControls(pCurrent->GetPostItField());
mpCommentsMap[nId] = std::move(pComment);
sw::annotation::SwAnnotationWin* next
= mpPostItMgr->GetNextPostIt(KEY_PAGEDOWN, pCurrent);
if (!next || next->GetTopReplyNote() != pRootNote)
break;
pCurrent = next;
}
mpThreadsMap[nPostItId] = std::move(pThread);
}
}
void CommentsPanel::addComment(const SwFormatField* pField)
{
// Get id of the note
const SwPostItField* pPostItField = static_cast<const SwPostItField*>(pField->GetField());
sal_uInt32 nNoteId = pPostItField->GetPostItId();
if (mpCommentsMap.contains(nNoteId))
return;
// Get id of the root note
sw::annotation::SwAnnotationWin* pRootNote = getRootCommentWin(pField);
if (!pRootNote)
return;
sal_uInt32 nRootId = getPostItId(pRootNote);
sw::annotation::SwAnnotationWin* pNote
= mpPostItMgr->GetAnnotationWin(static_cast<const SwPostItField*>(pField->GetField()));
// If comment is added to an existing thread
if (mpThreadsMap.find(nRootId) != mpThreadsMap.end())
{
auto& pThread = mpThreadsMap[nRootId];
auto pComment = std::make_unique<Comment>(pThread->getCommentBoxWidget(), *this);
pThread->getCommentBoxWidget()->reorder_child(pComment->get_widget(),
pThread->mnComments++);
pComment->InitControls(pNote->GetPostItField());
mpCommentsMap[nNoteId] = std::move(pComment);
}
// If a new thread is created
else
{
auto pThread = std::make_unique<Thread>(mxThreadsContainer.get());
mxThreadsContainer->reorder_child(pThread->get_widget(), mnThreads++);
auto pComment = std::make_unique<Comment>(pThread->getCommentBoxWidget(), *this);
pThread->getCommentBoxWidget()->reorder_child(pComment->get_widget(),
pThread->mnComments++);
mpThreadsMap[nRootId] = std::move(pThread);
pComment->InitControls(pNote->GetPostItField());
mpCommentsMap[nNoteId] = std::move(pComment);
}
}
void CommentsPanel::deleteComment(sw::annotation::SwAnnotationWin* pAnnotationWin)
{
if (!pAnnotationWin)
return;
sal_uInt32 nId = getPostItId(pAnnotationWin);
SwFormatField* pFormatField = pAnnotationWin->GetFormatField();
sw::annotation::SwAnnotationWin* pRootNote = nullptr;
// If the root comment is deleted, the new root comment of the thread should be the next comment in the thread
// but due to a bug `getRootCommentWin` returns root comment of some other/random thread so we completely lose
// access to the current thread.
if (mpThreadsMap.find(nId) != mpThreadsMap.end())
{
pRootNote = pAnnotationWin;
}
else
{
pRootNote = getRootCommentWin(pFormatField);
}
sal_uInt32 nRootId = getPostItId(pRootNote);
if (mpThreadsMap.find(nRootId) == mpThreadsMap.end())
return;
auto& pComment = mpCommentsMap[nId];
auto& pThread = mpThreadsMap[nRootId];
if (!pComment)
return;
pThread->getCommentBoxWidget()->move(pComment->get_widget(), nullptr);
mpCommentsMap.erase(nId);
// If the last comment in the thread is deleted, delete the thread
if (--pThread->mnComments == 0)
{
mxThreadsContainer->move(pThread->get_widget(), nullptr);
if (mpThreadsMap.find(nRootId) != mpThreadsMap.end())
mpThreadsMap.erase(nRootId);
}
}
void CommentsPanel::setResolvedStatus(sw::annotation::SwAnnotationWin* pAnnotationWin)
{
sal_uInt32 nId = getPostItId(pAnnotationWin);
if (mpCommentsMap.find(nId) == mpCommentsMap.end())
return;
auto& pComment = mpCommentsMap[nId];
if (!pComment)
return;
SwPostItField* pPostItField = const_cast<SwPostItField*>(pAnnotationWin->GetPostItField());
if (pPostItField->GetResolved() == pComment->mbResolved)
{
editComment(pPostItField, pComment.get());
return;
}
pComment->mbResolved = pPostItField->GetResolved();
pComment->mxResolve->set_active(pComment->mbResolved);
}
void CommentsPanel::editComment(SwPostItField* pPostItField, Comment* pComment)
{
OUString sText = pPostItField->GetText();
pComment->SetCommentText(sText);
pComment->mxTextView->set_text(sText);
}
void CommentsPanel::EditComment(Comment* pComment)
{
if (!pComment)
return;
const OUString sText = pComment->mxTextView->get_text();
sw::annotation::SwAnnotationWin* pWin = getAnnotationWin(pComment);
Outliner* pOutliner = pWin->GetOutliner();
pOutliner->Clear();
pOutliner->SetText(sText, pOutliner->GetParagraph(0));
}
void CommentsPanel::ToggleResolved(Comment* pComment)
{
if (!pComment)
return;
sw::annotation::SwAnnotationWin* pWin = getAnnotationWin(pComment);
pWin->ToggleResolved();
}
void CommentsPanel::ReplyComment(Comment* pComment)
{
if (!pComment)
return;
sw::annotation::SwAnnotationWin* pWin = getAnnotationWin(pComment);
pWin->ExecuteCommand(FN_REPLY);
}
CommentsPanel::~CommentsPanel() {}
void CommentsPanel::NotifyItemUpdate(const sal_uInt16 /*nSid*/, const SfxItemState /* eState */,
const SfxPoolItem* pState)
{
if (!pState) //disposed
return;
}
}
+156
View File
@@ -0,0 +1,156 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* 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 .
*/
#pragma once
#include <memory>
#include <sfx2/sidebar/PanelLayout.hxx>
#include <sfx2/sidebar/ControllerItem.hxx>
#include <AnnotationWin.hxx>
#include <svl/poolitem.hxx>
#include <tools/link.hxx>
#include <vcl/weld.hxx>
#include <PostItMgr.hxx>
#include <postithelper.hxx>
#include <view.hxx>
#include <tools/date.hxx>
#include <tools/datetime.hxx>
#include <tools/time.hxx>
class SwWrtShell;
class SwView;
class SwPostItField;
class SwFormatField;
class SwAnnotationWin;
class SfxBroadcaster;
class SwPostItMgr;
class SwSidebarItem;
namespace sw::sidebar
{
class CommentsPanel;
class Comment final
{
friend class CommentsPanel;
private:
std::unique_ptr<weld::Builder> mxBuilder;
std::unique_ptr<weld::Container> mxContainer;
std::unique_ptr<weld::Label> mxAuthor;
std::unique_ptr<weld::Label> mxDate;
std::unique_ptr<weld::Label> mxTime;
std::unique_ptr<weld::Button> mxReply;
std::unique_ptr<weld::CheckButton> mxResolve;
std::unique_ptr<weld::TextView> mxTextView;
sw::sidebar::CommentsPanel& mrCommentsPanel;
OUString msText;
OUString msAuthor;
OUString msInitials; //Initials of Author.
OUString msName; //Name of the comment
Date maDate;
tools::Time maTime;
public:
Comment(weld::Container* pParent, CommentsPanel& rCommentsPanel);
~Comment();
weld::Widget* get_widget() const { return mxContainer.get(); }
DECL_LINK(ReplyClicked, weld::Button&, void);
DECL_LINK(ResolveClicked, weld::Toggleable&, void);
DECL_LINK(OnFocusOut, weld::Widget&, void);
bool mbResolved;
void InitControls(const SwPostItField* pPostItField);
void SetCommentText(OUString sText) { msText = sText; }
};
class Thread final
{
friend class CommentsPanel;
private:
std::unique_ptr<weld::Builder> mxBuilder;
std::unique_ptr<weld::Container> mxContainer;
std::unique_ptr<weld::Box> mxCommentBox;
std::unique_ptr<weld::Label> mxText;
public:
Thread(weld::Container* pParent);
~Thread();
weld::Widget* get_widget() const { return mxContainer.get(); }
weld::Box* getCommentBoxWidget() const { return mxCommentBox.get(); }
sal_uInt16 mnComments = 0;
};
class CommentsPanel : public PanelLayout,
public ::sfx2::sidebar::ControllerItem::ItemUpdateReceiverInterface,
public SfxListener
{
public:
static std::unique_ptr<PanelLayout> Create(weld::Widget* pParent);
virtual void NotifyItemUpdate(const sal_uInt16 nSId, const SfxItemState eState,
const SfxPoolItem* pState) override;
virtual void GetControlState(const sal_uInt16 /*nSId*/,
boost::property_tree::ptree& /*rState*/) override{};
CommentsPanel(weld::Widget* pParent);
virtual ~CommentsPanel() override;
void Notify(SfxBroadcaster& rBC, const SfxHint& rHint) override;
void EditComment(Comment* pComment);
void ToggleResolved(Comment* pComment);
void ReplyComment(Comment* pComment);
private:
SwPostItMgr* mpPostItMgr;
std::unordered_map<sal_uInt32, std::unique_ptr<Thread>> mpThreadsMap;
std::unordered_map<sal_uInt32, std::unique_ptr<Comment>> mpCommentsMap;
std::unique_ptr<weld::ComboBox> mxFilterAuthor;
std::unique_ptr<weld::ComboBox> mxFilterTime;
std::unique_ptr<weld::CheckButton> mxShowTime;
std::unique_ptr<weld::CheckButton> mxShowResolved;
std::unique_ptr<weld::CheckButton> mxShowReference;
std::unique_ptr<weld::RadioButton> mxSortbyPosition;
std::unique_ptr<weld::RadioButton> mxSortbyTime;
std::unique_ptr<weld::Box> mxThreadsContainer;
sal_uInt16 mnThreads = 0;
sw::annotation::SwAnnotationWin* getRootCommentWin(const SwFormatField* pField);
static sal_uInt32 getPostItId(sw::annotation::SwAnnotationWin* pAnnotationWin);
sw::annotation::SwAnnotationWin* getAnnotationWin(Comment* pComment);
void populateComments();
void addComment(const SwFormatField* pField);
void deleteComment(sw::annotation::SwAnnotationWin* pAnnotationWin);
void setResolvedStatus(sw::annotation::SwAnnotationWin* pAnnotationWin);
static void editComment(SwPostItField* pPostItField, Comment* pComment);
};
} //end of namespace sw::sidebar
@@ -20,6 +20,7 @@
#include <com/sun/star/ui/XUIElementFactory.hpp>
#include "A11yCheckIssuesPanel.hxx"
#include "CommentsPanel.hxx"
#include "ThemePanel.hxx"
#include "StylePresetsPanel.hxx"
#include "PageStylesPanel.hxx"
@@ -40,6 +41,7 @@
#include <comphelper/namedvaluecollection.hxx>
#include <comphelper/compbase.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <officecfg/Office/Common.hxx>
using namespace css;
@@ -198,6 +200,15 @@ Reference<ui::XUIElement> SAL_CALL SwPanelFactory::createUIElement (
xElement = sfx2::sidebar::SidebarPanelBase::Create(
rsResourceURL, xFrame, std::move(xPanel), ui::LayoutSize(-1,-1,-1));
}
else if (rsResourceURL.endsWith("/CommentsPanel"))
{
if (officecfg::Office::Common::Misc::ExperimentalMode::get())
{
std::unique_ptr<PanelLayout> xPanel = sw::sidebar::CommentsPanel::Create(pParent);
xElement = sfx2::sidebar::SidebarPanelBase::Create(
rsResourceURL, xFrame, std::move(xPanel), ui::LayoutSize(-1,-1,-1));
}
}
else if (rsResourceURL.endsWith("/A11yCheckIssuesPanel"))
{
std::unique_ptr<PanelLayout> xPanel = sw::sidebar::A11yCheckIssuesPanel::Create(pParent, pBindings);
+306
View File
@@ -0,0 +1,306 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface domain="sw">
<requires lib="gtk+" version="3.20"/>
<!-- n-columns=1 n-rows=1 -->
<object class="GtkGrid" id="CommentsPanel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<!-- n-columns=1 n-rows=2 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="border-width">6</property>
<child>
<object class="GtkExpander" id="options">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="expanded">True</property>
<child>
<!-- n-columns=2 n-rows=3 -->
<object class="GtkGrid" id="grid1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="row-spacing">6</property>
<property name="column-spacing">6</property>
<child>
<object class="GtkLabel" id="label_sortby">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes" context="commentspanel|label_sortby">Sort by:</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">sortby_options</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<!-- n-columns=2 n-rows=1 -->
<object class="GtkGrid" id="sortby_options">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<child>
<object class="GtkRadioButton" id="sortby_position">
<property name="label" translatable="yes" context="commentspanel|radiobutton_position">Position</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="sortby_time">
<property name="label" translatable="yes" context="commentspanel|radiobutton_time">Time</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_show">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes" context="commentspanel|label_show">Show:</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">show_options</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<!-- n-columns=3 n-rows=1 -->
<object class="GtkGrid" id="show_options">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkCheckButton" id="show_time">
<property name="label" translatable="yes" context="commentspanel|checkbutton_time">Time</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="show_resolved">
<property name="label" translatable="yes" context="commentspanel|checkbutton_resolved">Resolved</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="show_reference">
<property name="label" translatable="yes" context="commentspanel|checkbutton_reference">Reference</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">2</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_filter">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="valign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes" context="commentspanel|label_filter">Filter:</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">filter_options</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<!-- n-columns=2 n-rows=2 -->
<object class="GtkGrid" id="filter_options">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="row-spacing">3</property>
<property name="column-spacing">6</property>
<child>
<object class="GtkLabel" id="label_author">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes" context="commentspanel|label_author">Author</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">filter_author</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_time">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes" context="commentspanel|label_time">Time</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">filter_time</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="filter_author">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="filter_time">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="options_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes" context="commentspanel|options_label">Options</property>
<attributes>
<attribute name="weight" value="ultrabold"/>
</attributes>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="hscrollbar-policy">never</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkBox" id="comment_threads">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<property name="baseline-position">top</property>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
</interface>
+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface domain="sw">
<requires lib="gtk+" version="3.20"/>
<!-- n-columns=1 n-rows=1 -->
<object class="GtkGrid" id="Thread">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkExpander">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="expanded">True</property>
<child>
<object class="GtkBox" id="comments_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="commentedtext">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes" context="commentspanel|referencetext">commentedtext</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
</interface>
+170
View File
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface domain="sw">
<requires lib="gtk+" version="3.20"/>
<!-- n-columns=1 n-rows=1 -->
<object class="GtkGrid" id="Comment">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkExpander">
<property name="height-request">-1</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="valign">start</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="expanded">True</property>
<property name="label-fill">True</property>
<property name="resize-toplevel">True</property>
<child>
<!-- n-columns=1 n-rows=2 -->
<object class="GtkGrid">
<property name="height-request">-1</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<!-- n-columns=5 n-rows=1 -->
<object class="GtkGrid" id="grid1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="column-homogeneous">True</property>
<child>
<object class="GtkLabel" id="authorlabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes" context="commentspanel|authorlabel">author</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="datelabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes" context="commentspanel|datelabel">date</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="replybutton">
<property name="label" translatable="yes" context="commentspanel|replybutton">Reply</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="left-attach">3</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="timelabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes" context="commentspanel|timelabel">time</property>
</object>
<packing>
<property name="left-attach">2</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="resolvebutton">
<property name="label" translatable="yes" context="commentspanel|resolvebutton"> </property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">center</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="left-attach">4</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="height-request">100</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="valign">start</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkViewport">
<property name="height-request">100</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<child>
<object class="GtkBox">
<property name="height-request">100</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkTextView" id="textview">
<property name="height-request">100</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="valign">start</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="collapsecommentbutton">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes" context="commentspanel|collapsecommentbutton">collapse</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">textview</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
</interface>
+6
View File
@@ -74,6 +74,12 @@ OString DateToOString( const Date& rDate )
return DateTimeToOString( DateTime( rDate, aTime ) );
}
OString TimeToOString( const tools::Time& rTime )
{
Date aDate( Date::EMPTY );
return DateTimeToOString( DateTime( aDate, rTime ) );
}
OUString DateToDDMMYYYYOUString( const Date& rDate )
{
OUStringBuffer aBuffer( 25 );