include/tools/datetimeutils.hxx | 2 officecfg/registry/data/org/openoffice/Office/UI/Sidebar.xcu | 58 + sw/Library_sw.mk | 1 sw/UIConfig_swriter.mk | 3 sw/inc/AnnotationWin.hxx | 1 sw/inc/PostItMgr.hxx | 7 sw/source/uibase/docvw/PostItMgr.cxx | 27 sw/source/uibase/sidebar/CommentsPanel.cxx | 443 +++++++++++ sw/source/uibase/sidebar/CommentsPanel.hxx | 156 +++ sw/source/uibase/sidebar/SwPanelFactory.cxx | 11 sw/uiconfig/swriter/ui/commentspanel.ui | 306 +++++++ sw/uiconfig/swriter/ui/commentsthread.ui | 40 sw/uiconfig/swriter/ui/commentwidget.ui | 170 ++++ tools/source/datetime/datetimeutils.cxx | 6 14 files changed, 1228 insertions(+), 3 deletions(-)
New commits: commit be16c6cf5c14c17ec3e7d860636e5c7b2cd7f654 Author: Mohit Marathe <[email protected]> AuthorDate: Fri May 17 14:00:18 2024 +0530 Commit: Sarper Akdemir <[email protected]> CommitDate: Fri Sep 13 12:35:45 2024 +0200 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 <[email protected]> diff --git a/include/tools/datetimeutils.hxx b/include/tools/datetimeutils.hxx index deb7d7ee4f4c..96abd8fd71df 100644 --- a/include/tools/datetimeutils.hxx +++ b/include/tools/datetimeutils.hxx @@ -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); diff --git a/officecfg/registry/data/org/openoffice/Office/UI/Sidebar.xcu b/officecfg/registry/data/org/openoffice/Office/UI/Sidebar.xcu index 1e9475993207..543d13edf274 100644 --- a/officecfg/registry/data/org/openoffice/Office/UI/Sidebar.xcu +++ b/officecfg/registry/data/org/openoffice/Office/UI/Sidebar.xcu @@ -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> diff --git a/sw/Library_sw.mk b/sw/Library_sw.mk index 872ae8812286..fd0c261c1878 100644 --- a/sw/Library_sw.mk +++ b/sw/Library_sw.mk @@ -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 \ diff --git a/sw/UIConfig_swriter.mk b/sw/UIConfig_swriter.mk index 262123969dae..a96ee9dd9749 100644 --- a/sw/UIConfig_swriter.mk +++ b/sw/UIConfig_swriter.mk @@ -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 \ diff --git a/sw/inc/AnnotationWin.hxx b/sw/inc/AnnotationWin.hxx index 32f7b77f4829..d5f31937409f 100644 --- a/sw/inc/AnnotationWin.hxx +++ b/sw/inc/AnnotationWin.hxx @@ -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; diff --git a/sw/inc/PostItMgr.hxx b/sw/inc/PostItMgr.hxx index f11c821240cd..610c3c79a07b 100644 --- a/sw/inc/PostItMgr.hxx +++ b/sw/inc/PostItMgr.hxx @@ -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); diff --git a/sw/source/uibase/docvw/PostItMgr.cxx b/sw/source/uibase/docvw/PostItMgr.cxx index 589f6c02a9cd..56abcb248cfd 100644 --- a/sw/source/uibase/docvw/PostItMgr.cxx +++ b/sw/source/uibase/docvw/PostItMgr.cxx @@ -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); diff --git a/sw/source/uibase/sidebar/CommentsPanel.cxx b/sw/source/uibase/sidebar/CommentsPanel.cxx new file mode 100644 index 000000000000..1e5e3e4ea1eb --- /dev/null +++ b/sw/source/uibase/sidebar/CommentsPanel.cxx @@ -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; +} +} diff --git a/sw/source/uibase/sidebar/CommentsPanel.hxx b/sw/source/uibase/sidebar/CommentsPanel.hxx new file mode 100644 index 000000000000..3e20ac47798e --- /dev/null +++ b/sw/source/uibase/sidebar/CommentsPanel.hxx @@ -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 diff --git a/sw/source/uibase/sidebar/SwPanelFactory.cxx b/sw/source/uibase/sidebar/SwPanelFactory.cxx index 4e5cd83d4116..31f2a3f60ee2 100644 --- a/sw/source/uibase/sidebar/SwPanelFactory.cxx +++ b/sw/source/uibase/sidebar/SwPanelFactory.cxx @@ -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); diff --git a/sw/uiconfig/swriter/ui/commentspanel.ui b/sw/uiconfig/swriter/ui/commentspanel.ui new file mode 100644 index 000000000000..da3f6d4f70cd --- /dev/null +++ b/sw/uiconfig/swriter/ui/commentspanel.ui @@ -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> diff --git a/sw/uiconfig/swriter/ui/commentsthread.ui b/sw/uiconfig/swriter/ui/commentsthread.ui new file mode 100644 index 000000000000..9960a3fe63cb --- /dev/null +++ b/sw/uiconfig/swriter/ui/commentsthread.ui @@ -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> diff --git a/sw/uiconfig/swriter/ui/commentwidget.ui b/sw/uiconfig/swriter/ui/commentwidget.ui new file mode 100644 index 000000000000..7d0e66480eb4 --- /dev/null +++ b/sw/uiconfig/swriter/ui/commentwidget.ui @@ -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> diff --git a/tools/source/datetime/datetimeutils.cxx b/tools/source/datetime/datetimeutils.cxx index 345e18f4a721..b102b1f410e5 100644 --- a/tools/source/datetime/datetimeutils.cxx +++ b/tools/source/datetime/datetimeutils.cxx @@ -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 );
