summaryrefslogtreecommitdiffstats
path: root/sw/source/uibase/misc/swruler.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /sw/source/uibase/misc/swruler.cxx
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/uibase/misc/swruler.cxx')
-rw-r--r--sw/source/uibase/misc/swruler.cxx371
1 files changed, 371 insertions, 0 deletions
diff --git a/sw/source/uibase/misc/swruler.cxx b/sw/source/uibase/misc/swruler.cxx
new file mode 100644
index 000000000..680031a26
--- /dev/null
+++ b/sw/source/uibase/misc/swruler.cxx
@@ -0,0 +1,371 @@
+/* -*- 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/.
+ */
+
+// Design proposal: https://wiki.documentfoundation.org/Design/Whiteboards/Comments_Ruler_Control
+
+#include <swruler.hxx>
+
+#include <viewsh.hxx>
+#include <edtwin.hxx>
+#include <PostItMgr.hxx>
+#include <view.hxx>
+#include <cmdid.h>
+#include <sfx2/request.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+#include <strings.hrc>
+#include <comphelper/lok.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <boost/property_tree/json_parser.hpp>
+
+#define CONTROL_BORDER_WIDTH 1
+
+namespace
+{
+/**
+ * Draw a little arrow / triangle with different directions
+ *
+ * \param nX left coordinate of arrow square
+ * \param nY top coordinate of arrow square
+ * \param nSize size of the long triangle side / arrow square
+ * \param Color arrow color
+ * \param bCollapsed if the arrow should display the collapsed state
+ */
+void ImplDrawArrow(vcl::RenderContext& rRenderContext, long nX, long nY, long nSize,
+ const Color& rColor, bool bCollapsed)
+{
+ tools::Polygon aTriaglePolygon(4);
+
+ if (bCollapsed)
+ {
+ if (AllSettings::GetLayoutRTL()) // <
+ {
+ aTriaglePolygon.SetPoint({ nX + nSize / 2, nY }, 0);
+ aTriaglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 1);
+ aTriaglePolygon.SetPoint({ nX, nY + nSize / 2 }, 2);
+ aTriaglePolygon.SetPoint({ nX + nSize / 2, nY }, 3);
+ }
+ else // >
+ {
+ aTriaglePolygon.SetPoint({ nX, nY }, 0);
+ aTriaglePolygon.SetPoint({ nX + nSize / 2, nY + nSize / 2 }, 1);
+ aTriaglePolygon.SetPoint({ nX, nY + nSize }, 2);
+ aTriaglePolygon.SetPoint({ nX, nY }, 3);
+ }
+ }
+ else // v
+ {
+ aTriaglePolygon.SetPoint({ nX, nY + nSize / 2 }, 0);
+ aTriaglePolygon.SetPoint({ nX + nSize, nY + nSize / 2 }, 1);
+ aTriaglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 2);
+ aTriaglePolygon.SetPoint({ nX, nY + nSize / 2 }, 3);
+ }
+
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(rColor);
+ rRenderContext.DrawPolygon(aTriaglePolygon);
+}
+}
+
+// Constructor
+SwCommentRuler::SwCommentRuler(SwViewShell* pViewSh, vcl::Window* pParent, SwEditWin* pWin,
+ SvxRulerSupportFlags nRulerFlags, SfxBindings& rBindings,
+ WinBits nWinStyle)
+ : SvxRuler(pParent, pWin, nRulerFlags, rBindings, nWinStyle | WB_HSCROLL)
+ , mpViewShell(pViewSh)
+ , mpSwWin(pWin)
+ , mbIsHighlighted(false)
+ , mnFadeRate(0)
+ , maVirDev(VclPtr<VirtualDevice>::Create(*this))
+{
+ // Set fading timeout: 5 x 40ms = 200ms
+ maFadeTimer.SetTimeout(40);
+ maFadeTimer.SetInvokeHandler(LINK(this, SwCommentRuler, FadeHandler));
+ maFadeTimer.SetDebugName("sw::SwCommentRuler maFadeTimer");
+
+ // we have a little bit more space, as we don't draw ruler ticks
+ vcl::Font aFont(maVirDev->GetFont());
+ aFont.SetFontHeight(aFont.GetFontHeight() + 1);
+ maVirDev->SetFont(aFont);
+}
+
+SwCommentRuler::~SwCommentRuler() { disposeOnce(); }
+
+void SwCommentRuler::dispose()
+{
+ mpSwWin.clear();
+ SvxRuler::dispose();
+}
+
+void SwCommentRuler::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return; // no need to waste time on startup
+
+ SvxRuler::Paint(rRenderContext, rRect);
+
+ // Don't draw if there is not any note
+ if (mpViewShell->GetPostItMgr() && mpViewShell->GetPostItMgr()->HasNotes())
+ DrawCommentControl(rRenderContext);
+}
+
+void SwCommentRuler::DrawCommentControl(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ const bool bIsCollapsed = !mpViewShell->GetPostItMgr()->ShowNotes();
+ const tools::Rectangle aControlRect = GetCommentControlRegion();
+
+ maVirDev->SetOutputSizePixel(aControlRect.GetSize());
+
+ // set colors
+ if (!bIsCollapsed)
+ {
+ if (mbIsHighlighted)
+ maVirDev->SetFillColor(
+ GetFadedColor(rStyleSettings.GetHighlightColor(), rStyleSettings.GetDialogColor()));
+ else
+ maVirDev->SetFillColor(rStyleSettings.GetDialogColor());
+ maVirDev->SetLineColor(rStyleSettings.GetShadowColor());
+ }
+ else
+ {
+ if (mbIsHighlighted)
+ maVirDev->SetFillColor(GetFadedColor(rStyleSettings.GetHighlightColor(),
+ rStyleSettings.GetWorkspaceColor()));
+ else
+ maVirDev->SetFillColor(rStyleSettings.GetWorkspaceColor());
+ maVirDev->SetLineColor();
+ }
+ Color aTextColor = GetFadedColor(rStyleSettings.GetHighlightTextColor(),
+ rStyleSettings.GetButtonTextColor());
+ maVirDev->SetTextColor(aTextColor);
+
+ // calculate label and arrow positions
+ const OUString aLabel = SwResId(STR_COMMENTS_LABEL);
+ const long nTriangleSize = maVirDev->GetTextHeight() / 2 + 1;
+ const long nTrianglePad = maVirDev->GetTextHeight() / 4;
+
+ Point aLabelPos(0, (aControlRect.GetHeight() - maVirDev->GetTextHeight()) / 2);
+ Point aArrowPos(0, (aControlRect.GetHeight() - nTriangleSize) / 2);
+
+ if (!AllSettings::GetLayoutRTL()) // | > Comments |
+ {
+ aArrowPos.setX(nTrianglePad);
+ aLabelPos.setX(aArrowPos.X() + nTriangleSize + nTrianglePad);
+ }
+ else // RTL => | Comments < |
+ {
+ const long nLabelWidth = maVirDev->GetTextWidth(aLabel);
+ if (!bIsCollapsed)
+ {
+ aArrowPos.setX(aControlRect.GetWidth() - 1 - nTrianglePad - CONTROL_BORDER_WIDTH
+ - nTriangleSize);
+ aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
+ }
+ else
+ {
+ // if comments are collapsed, left align the text, because otherwise it's very likely to be invisible
+ aArrowPos.setX(nLabelWidth + nTrianglePad + nTriangleSize);
+ aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
+ }
+ }
+
+ // draw control
+ maVirDev->DrawRect(tools::Rectangle(Point(), aControlRect.GetSize()));
+ maVirDev->DrawText(aLabelPos, aLabel);
+ ImplDrawArrow(*maVirDev, aArrowPos.X(), aArrowPos.Y(), nTriangleSize, aTextColor, bIsCollapsed);
+ rRenderContext.DrawOutDev(aControlRect.TopLeft(), aControlRect.GetSize(), Point(),
+ aControlRect.GetSize(), *maVirDev);
+}
+
+// Just accept double-click outside comment control
+void SwCommentRuler::Command(const CommandEvent& rCEvt)
+{
+ Point aMousePos = rCEvt.GetMousePosPixel();
+ // Ignore command request if it is inside Comment Control
+ if (!mpViewShell->GetPostItMgr() || !mpViewShell->GetPostItMgr()->HasNotes()
+ || !GetCommentControlRegion().IsInside(aMousePos))
+ SvxRuler::Command(rCEvt);
+}
+
+void SwCommentRuler::MouseMove(const MouseEvent& rMEvt)
+{
+ SvxRuler::MouseMove(rMEvt);
+ if (!mpViewShell->GetPostItMgr() || !mpViewShell->GetPostItMgr()->HasNotes())
+ return;
+
+ UpdateCommentHelpText();
+
+ Point aMousePos = rMEvt.GetPosPixel();
+ bool bWasHighlighted = mbIsHighlighted;
+ mbIsHighlighted = GetCommentControlRegion().IsInside(aMousePos);
+ if (mbIsHighlighted != bWasHighlighted)
+ // Do start fading
+ maFadeTimer.Start();
+}
+
+void SwCommentRuler::MouseButtonDown(const MouseEvent& rMEvt)
+{
+ Point aMousePos = rMEvt.GetPosPixel();
+ if (!rMEvt.IsLeft() || IsTracking() || !GetCommentControlRegion().IsInside(aMousePos))
+ {
+ SvxRuler::MouseButtonDown(rMEvt);
+ return;
+ }
+
+ // Toggle notes visibility
+ SwView& rView = mpSwWin->GetView();
+ SfxRequest aRequest(rView.GetViewFrame(), SID_TOGGLE_NOTES);
+ rView.ExecViewOptions(aRequest);
+
+ // It is inside comment control, so update help text
+ UpdateCommentHelpText();
+
+ Invalidate();
+}
+
+std::string SwCommentRuler::CreateJsonNotification()
+{
+ boost::property_tree::ptree jsonNotif;
+
+ // Note that GetMargin1(), GetMargin2(), GetNullOffset(), and GetPageOffset() return values in
+ // pixels. Not twips. So "converting" the returned values with convertTwipToMm100() is quite
+ // wrong. (Also, even if the return values actually were in twips, it is questionable why we
+ // would want to pass them in mm100, as all other length values in the LOKit protocol apparently
+ // are in twips.)
+
+ // Anyway, as the consuming code in Online mostly seems to work anyway, it is likely that it
+ // would work as well even if the values in pixels were passed without a bogus "conversion" to
+ // mm100. But let's keep this as is for now.
+
+ // Also note that in desktop LibreOffice, these pixel values for the ruler of course change as
+ // one changes the zoom level. (Can be seen if one temporarily modifies the NotifyKit() function
+ // below to call this CreateJsonNotification() function and print its result in all cases even
+ // without LibreOfficeKit::isActive().) But in both web-based Online and in the iOS app, the
+ // zoom level from the point of view of this code here apparently does not change even if one
+ // zooms from the Online code's point of view.
+ jsonNotif.put("margin1", convertTwipToMm100(GetMargin1()));
+ jsonNotif.put("margin2", convertTwipToMm100(GetMargin2()));
+ jsonNotif.put("leftOffset", convertTwipToMm100(GetNullOffset()));
+ jsonNotif.put("pageOffset", convertTwipToMm100(GetPageOffset()));
+
+ // GetPageWidth() on the other hand does return a value in twips.
+ // So here convertTwipToMm100() really does produce actual mm100. Fun.
+ jsonNotif.put("pageWidth", convertTwipToMm100(GetPageWidth()));
+
+ boost::property_tree::ptree tabs;
+
+ // The RulerTab array elements that GetTabs() returns have their nPos field in twips. So these
+ // too are actual mm100.
+ for (auto const& tab : GetTabs())
+ {
+ boost::property_tree::ptree element;
+ element.put("position", convertTwipToMm100(tab.nPos));
+ element.put("style", tab.nStyle);
+ tabs.push_back(std::make_pair("", element));
+ }
+
+ jsonNotif.add_child("tabs", tabs);
+
+ RulerUnitData aUnitData = GetCurrentRulerUnit();
+ jsonNotif.put("unit", aUnitData.aUnitStr);
+
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, jsonNotif);
+ return aStream.str();
+}
+
+void SwCommentRuler::NotifyKit()
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ const std::string test = CreateJsonNotification();
+ mpViewShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_RULER_UPDATE,
+ test.c_str());
+}
+
+void SwCommentRuler::Update()
+{
+ tools::Rectangle aPreviousControlRect = GetCommentControlRegion();
+ SvxRuler::Update();
+ if (aPreviousControlRect != GetCommentControlRegion())
+ Invalidate();
+ NotifyKit();
+}
+
+void SwCommentRuler::UpdateCommentHelpText()
+{
+ const char* pTooltipResId;
+ if (mpViewShell->GetPostItMgr()->ShowNotes())
+ pTooltipResId = STR_HIDE_COMMENTS;
+ else
+ pTooltipResId = STR_SHOW_COMMENTS;
+ SetQuickHelpText(SwResId(pTooltipResId));
+}
+
+// TODO Make Ruler return its central rectangle instead of margins.
+tools::Rectangle SwCommentRuler::GetCommentControlRegion()
+{
+ SwPostItMgr* pPostItMgr = mpViewShell->GetPostItMgr();
+
+ //rhbz#1006850 When the SwPostItMgr ctor is called from SwView::SwView it
+ //triggers an update of the uiview, but the result of the ctor hasn't been
+ //set into the mpViewShell yet, so GetPostItMgr is temporarily still NULL
+ if (!pPostItMgr)
+ return tools::Rectangle();
+
+ const unsigned long nSidebarWidth = pPostItMgr->GetSidebarWidth(true);
+
+ //FIXME When the page width is larger then screen, the ruler is misplaced by one pixel
+ long nLeft = GetPageOffset();
+ if (GetTextRTL())
+ nLeft += GetBorderOffset() - nSidebarWidth;
+ else
+ nLeft += GetWinOffset() + mpSwWin->LogicToPixel(Size(GetPageWidth(), 0)).Width();
+
+ // Ruler::ImplDraw uses RULER_OFF (value: 3px) as offset, and Ruler::ImplFormat adds one extra pixel
+ long nTop = 4;
+ // Somehow pPostItMgr->GetSidebarBorderWidth() returns border width already doubled
+ long nRight = nLeft + nSidebarWidth + pPostItMgr->GetSidebarBorderWidth(true);
+ long nBottom = nTop + GetRulerVirHeight() - 3;
+
+ tools::Rectangle aRect(nLeft, nTop, nRight, nBottom);
+ return aRect;
+}
+
+Color SwCommentRuler::GetFadedColor(const Color& rHighColor, const Color& rLowColor)
+{
+ if (!maFadeTimer.IsActive())
+ return mbIsHighlighted ? rHighColor : rLowColor;
+
+ Color aColor = rHighColor;
+ aColor.Merge(rLowColor, mnFadeRate * 255 / 100.0f);
+ return aColor;
+}
+
+IMPL_LINK_NOARG(SwCommentRuler, FadeHandler, Timer*, void)
+{
+ const int nStep = 25;
+ if (mbIsHighlighted && mnFadeRate < 100)
+ mnFadeRate += nStep;
+ else if (!mbIsHighlighted && mnFadeRate > 0)
+ mnFadeRate -= nStep;
+ else
+ return;
+
+ Invalidate();
+
+ if (mnFadeRate != 0 && mnFadeRate != 100)
+ maFadeTimer.Start();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */