LibreOffice Module sw (master) 1
swruler.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
9
10// Design proposal: https://wiki.documentfoundation.org/Design/Whiteboards/Comments_Ruler_Control
11
12#include <swruler.hxx>
13
14#include <viewsh.hxx>
15#include <edtwin.hxx>
16#include <PostItMgr.hxx>
17#include <view.hxx>
18#include <cmdid.h>
19#include <sfx2/request.hxx>
21#include <vcl/commandevent.hxx>
22#include <vcl/event.hxx>
23#include <vcl/window.hxx>
24#include <vcl/settings.hxx>
25#include <tools/json_writer.hxx>
26#include <strings.hrc>
27#include <comphelper/lok.hxx>
28#include <LibreOfficeKit/LibreOfficeKitEnums.h>
29
30#define CONTROL_BORDER_WIDTH 1
31
32namespace
33{
43void ImplDrawArrow(vcl::RenderContext& rRenderContext, tools::Long nX, tools::Long nY,
44 tools::Long nSize, const Color& rColor, bool bCollapsed)
45{
46 tools::Polygon aTrianglePolygon(4);
47
48 if (bCollapsed)
49 {
51 {
52 aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 0);
53 aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 1);
54 aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 2);
55 aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 3);
56 }
57 else // >
58 {
59 aTrianglePolygon.SetPoint({ nX, nY }, 0);
60 aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize / 2 }, 1);
61 aTrianglePolygon.SetPoint({ nX, nY + nSize }, 2);
62 aTrianglePolygon.SetPoint({ nX, nY }, 3);
63 }
64 }
65 else // v
66 {
67 aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 0);
68 aTrianglePolygon.SetPoint({ nX + nSize, nY + nSize / 2 }, 1);
69 aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 2);
70 aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 3);
71 }
72
73 rRenderContext.SetLineColor();
74 rRenderContext.SetFillColor(rColor);
75 rRenderContext.DrawPolygon(aTrianglePolygon);
76}
77}
78
79// Constructor
81 SvxRulerSupportFlags nRulerFlags, SfxBindings& rBindings,
82 WinBits nWinStyle)
83 : SvxRuler(pParent, pWin, nRulerFlags, rBindings, nWinStyle | WB_HSCROLL)
84 , mpViewShell(pViewSh)
85 , mpSwWin(pWin)
86 , mbIsHighlighted(false)
87 , maFadeTimer("sw::SwCommentRuler maFadeTimer")
88 , mnFadeRate(0)
89 , maVirDev(VclPtr<VirtualDevice>::Create(*GetOutDev()))
90{
91 // Set fading timeout: 5 x 40ms = 200ms
94
95 // we have a little bit more space, as we don't draw ruler ticks
96 vcl::Font aFont(maVirDev->GetFont());
97 aFont.SetFontHeight(aFont.GetFontHeight() + 1);
98 maVirDev->SetFont(aFont);
99}
100
102
104{
105 mpSwWin.clear();
107}
108
110{
112 return; // no need to waste time on startup
113
114 SvxRuler::Paint(rRenderContext, rRect);
115
116 // Don't draw if there is not any note
118 DrawCommentControl(rRenderContext);
119}
120
122{
123 const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
124 const bool bIsCollapsed = !mpViewShell->GetPostItMgr()->ShowNotes();
125 const tools::Rectangle aControlRect = GetCommentControlRegion();
126
127 maVirDev->SetOutputSizePixel(aControlRect.GetSize());
128
129 // set colors
130 if (!bIsCollapsed)
131 {
132 if (mbIsHighlighted)
133 maVirDev->SetFillColor(
134 GetFadedColor(rStyleSettings.GetHighlightColor(), rStyleSettings.GetDialogColor()));
135 else
136 maVirDev->SetFillColor(rStyleSettings.GetDialogColor());
137 maVirDev->SetLineColor(rStyleSettings.GetShadowColor());
138 }
139 else
140 {
141 if (mbIsHighlighted)
142 maVirDev->SetFillColor(GetFadedColor(rStyleSettings.GetHighlightColor(),
143 rStyleSettings.GetWorkspaceColor()));
144 else
145 maVirDev->SetFillColor(rStyleSettings.GetWorkspaceColor());
146 maVirDev->SetLineColor();
147 }
148 Color aTextColor = GetFadedColor(rStyleSettings.GetHighlightTextColor(),
149 rStyleSettings.GetButtonTextColor());
150 maVirDev->SetTextColor(aTextColor);
151
152 // calculate label and arrow positions
153 const OUString aLabel = SwResId(STR_COMMENTS_LABEL);
154 const tools::Long nTriangleSize = maVirDev->GetTextHeight() / 2 + 1;
155 const tools::Long nTrianglePad = maVirDev->GetTextHeight() / 4;
156
157 Point aLabelPos(0, (aControlRect.GetHeight() - maVirDev->GetTextHeight()) / 2);
158 Point aArrowPos(0, (aControlRect.GetHeight() - nTriangleSize) / 2);
159
160 if (!AllSettings::GetLayoutRTL()) // | > Comments |
161 {
162 aArrowPos.setX(nTrianglePad);
163 aLabelPos.setX(aArrowPos.X() + nTriangleSize + nTrianglePad);
164 }
165 else // RTL => | Comments < |
166 {
167 const tools::Long nLabelWidth = maVirDev->GetTextWidth(aLabel);
168 if (!bIsCollapsed)
169 {
170 aArrowPos.setX(aControlRect.GetWidth() - 1 - nTrianglePad - CONTROL_BORDER_WIDTH
171 - nTriangleSize);
172 aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
173 }
174 else
175 {
176 // if comments are collapsed, left align the text, because otherwise it's very likely to be invisible
177 aArrowPos.setX(nLabelWidth + nTrianglePad + nTriangleSize);
178 aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
179 }
180 }
181
182 // draw control
183 maVirDev->DrawRect(tools::Rectangle(Point(), aControlRect.GetSize()));
184 maVirDev->DrawText(aLabelPos, aLabel);
185 ImplDrawArrow(*maVirDev, aArrowPos.X(), aArrowPos.Y(), nTriangleSize, aTextColor, bIsCollapsed);
186 rRenderContext.DrawOutDev(aControlRect.TopLeft(), aControlRect.GetSize(), Point(),
187 aControlRect.GetSize(), *maVirDev);
188}
189
190// Just accept double-click outside comment control
192{
193 Point aMousePos = rCEvt.GetMousePosPixel();
194 // Ignore command request if it is inside Comment Control
196 || !GetCommentControlRegion().Contains(aMousePos))
197 SvxRuler::Command(rCEvt);
198}
199
201{
202 SvxRuler::MouseMove(rMEvt);
204 return;
205
207
208 Point aMousePos = rMEvt.GetPosPixel();
209 bool bWasHighlighted = mbIsHighlighted;
211 if (mbIsHighlighted != bWasHighlighted)
212 // Do start fading
214}
215
217{
218 Point aMousePos = rMEvt.GetPosPixel();
219 if (!rMEvt.IsLeft() || IsTracking() || !GetCommentControlRegion().Contains(aMousePos))
220 {
221 SvxRuler::MouseButtonDown(rMEvt);
222 return;
223 }
224
225 // Toggle notes visibility
226 SwView& rView = mpSwWin->GetView();
227 SfxRequest aRequest(rView.GetViewFrame(), SID_TOGGLE_NOTES);
228 rView.ExecViewOptions(aRequest);
229
230 // It is inside comment control, so update help text
232
233 Invalidate();
234}
235
237{
238 // Note that GetMargin1(), GetMargin2(), GetNullOffset(), and GetPageOffset() return values in
239 // pixels. Not twips. So "converting" the returned values with convertTwipToMm100() is quite
240 // wrong. (Also, even if the return values actually were in twips, it is questionable why we
241 // would want to pass them in mm100, as all other length values in the LOKit protocol apparently
242 // are in twips.)
243
244 // Anyway, as the consuming code in Online mostly seems to work anyway, it is likely that it
245 // would work as well even if the values in pixels were passed without a bogus "conversion" to
246 // mm100. But let's keep this as is for now.
247
248 // Also note that in desktop LibreOffice, these pixel values for the ruler of course change as
249 // one changes the zoom level. (Can be seen if one temporarily modifies the NotifyKit() function
250 // below to call this CreateJsonNotification() function and print its result in all cases even
251 // without LibreOfficeKit::isActive().) But in both web-based Online and in the iOS app, the
252 // zoom level from the point of view of this code here apparently does not change even if one
253 // zooms from the Online code's point of view.
254 rJsonWriter.put("margin1", convertTwipToMm100(GetMargin1()));
255 rJsonWriter.put("margin2", convertTwipToMm100(GetMargin2()));
256 rJsonWriter.put("leftOffset", convertTwipToMm100(GetNullOffset()));
257 rJsonWriter.put("pageOffset", convertTwipToMm100(GetPageOffset()));
258
259 // GetPageWidth() on the other hand does return a value in twips.
260 // So here convertTwipToMm100() really does produce actual mm100. Fun.
261 rJsonWriter.put("pageWidth", convertTwipToMm100(GetPageWidth()));
262
263 {
264 auto tabsNode = rJsonWriter.startNode("tabs");
265
266 // The RulerTab array elements that GetTabs() returns have their nPos field in twips. So these
267 // too are actual mm100.
268 for (auto const& tab : GetTabs())
269 {
270 auto tabNode = rJsonWriter.startNode("");
271 rJsonWriter.put("position", convertTwipToMm100(tab.nPos));
272 rJsonWriter.put("style", tab.nStyle);
273 }
274 }
275
276 RulerUnitData aUnitData = GetCurrentRulerUnit();
277 rJsonWriter.put("unit", aUnitData.aUnitStr);
278}
279
281{
283 return;
284
285 tools::JsonWriter aJsonWriter;
286 CreateJsonNotification(aJsonWriter);
287 OString pJsonData = aJsonWriter.finishAndGetAsOString();
288 mpViewShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_RULER_UPDATE,
289 pJsonData);
290}
291
293{
294 tools::Rectangle aPreviousControlRect = GetCommentControlRegion();
296 if (aPreviousControlRect != GetCommentControlRegion())
297 Invalidate();
298 NotifyKit();
299}
300
302{
303 TranslateId pTooltipResId;
305 pTooltipResId = STR_HIDE_COMMENTS;
306 else
307 pTooltipResId = STR_SHOW_COMMENTS;
308 SetQuickHelpText(SwResId(pTooltipResId));
309}
310
311// TODO Make Ruler return its central rectangle instead of margins.
313{
314 SwPostItMgr* pPostItMgr = mpViewShell->GetPostItMgr();
315
316 //rhbz#1006850 When the SwPostItMgr ctor is called from SwView::SwView it
317 //triggers an update of the uiview, but the result of the ctor hasn't been
318 //set into the mpViewShell yet, so GetPostItMgr is temporarily still NULL
319 if (!pPostItMgr)
320 return tools::Rectangle();
321
322 const tools::ULong nSidebarWidth = pPostItMgr->GetSidebarWidth(true);
323
324 //FIXME When the page width is larger then screen, the ruler is misplaced by one pixel
325 tools::Long nLeft = GetPageOffset();
326 if (GetTextRTL())
327 nLeft += GetBorderOffset() - nSidebarWidth;
328 else
329 nLeft += GetWinOffset() + mpSwWin->LogicToPixel(Size(GetPageWidth(), 0)).Width();
330
331 // Ruler::ImplDraw uses RULER_OFF (value: 3px) as offset, and Ruler::ImplFormat adds one extra pixel
332 tools::Long nTop = 4;
333 // Somehow pPostItMgr->GetSidebarBorderWidth() returns border width already doubled
334 tools::Long nRight = nLeft + nSidebarWidth + pPostItMgr->GetSidebarBorderWidth(true);
335 tools::Long nBottom = nTop + GetRulerVirHeight() - 3;
336
337 tools::Rectangle aRect(nLeft, nTop, nRight, nBottom);
338 return aRect;
339}
340
341Color SwCommentRuler::GetFadedColor(const Color& rHighColor, const Color& rLowColor)
342{
343 if (!maFadeTimer.IsActive())
344 return mbIsHighlighted ? rHighColor : rLowColor;
345
346 Color aColor = rHighColor;
347 aColor.Merge(rLowColor, mnFadeRate * 255 / 100.0f);
348 return aColor;
349}
350
352{
353 const int nStep = 25;
354 if (mbIsHighlighted && mnFadeRate < 100)
355 mnFadeRate += nStep;
356 else if (!mbIsHighlighted && mnFadeRate > 0)
357 mnFadeRate -= nStep;
358 else
359 return;
360
361 Invalidate();
362
363 if (mnFadeRate != 0 && mnFadeRate != 100)
364 maFadeTimer.Start();
365}
366
367/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
constexpr auto convertTwipToMm100(N n)
const StyleSettings & GetStyleSettings() const
static bool GetLayoutRTL()
void Merge(const Color &rMergeColor, sal_uInt8 cTransparency)
const Point & GetMousePosPixel() const
const Point & GetPosPixel() const
bool IsLeft() const
void SetLineColor()
void DrawPolygon(const tools::Polygon &rPoly)
void SetFillColor()
SAL_DLLPRIVATE void DrawOutDev(const Point &, const Size &, const Point &, const Size &, const Printer &)=delete
const AllSettings & GetSettings() const
constexpr tools::Long Y() const
void setX(tools::Long nX)
constexpr tools::Long X() const
virtual void libreOfficeKitViewCallback(int nType, const OString &pPayload) const override
SfxViewFrame & GetViewFrame() const
const Color & GetShadowColor() const
const Color & GetWorkspaceColor() const
const Color & GetDialogColor() const
const Color & GetHighlightColor() const
const Color & GetHighlightTextColor() const
const Color & GetButtonTextColor() const
virtual void MouseMove(const MouseEvent &rMEvt) override
virtual void Command(const CommandEvent &rCEvt) override
virtual void Update()
tools::Long GetPageWidth() const
virtual void dispose() override
An horizontal ruler with a control for comment panel visibility for Writer.
Definition: swruler.hxx:29
void UpdateCommentHelpText()
Update the tooltip text.
Definition: swruler.cxx:301
virtual void MouseButtonDown(const MouseEvent &rMEvt) override
Callback function to handle a mouse button down event.
Definition: swruler.cxx:216
virtual void Update() override
Update the view.
Definition: swruler.cxx:292
virtual void Paint(vcl::RenderContext &rRenderContext, const tools::Rectangle &rRect) override
Paint the ruler.
Definition: swruler.cxx:109
SwViewShell * mpViewShell
Definition: swruler.hxx:49
virtual void MouseMove(const MouseEvent &rMEvt) override
Callback function to handle a mouse move event.
Definition: swruler.cxx:200
virtual void Command(const CommandEvent &rCEvt) override
Callback function to handle a context menu call (mouse right button click).
Definition: swruler.cxx:191
bool mbIsHighlighted
Definition: swruler.hxx:51
VclPtr< SwEditWin > mpSwWin
Definition: swruler.hxx:50
void CreateJsonNotification(tools::JsonWriter &rJsonWriter)
Definition: swruler.cxx:236
void DrawCommentControl(vcl::RenderContext &rRenderContext)
Paint the comment control on VirtualDevice.
Definition: swruler.cxx:121
Timer maFadeTimer
Definition: swruler.hxx:52
ScopedVclPtr< VirtualDevice > maVirDev
Definition: swruler.hxx:54
void NotifyKit()
Definition: swruler.cxx:280
virtual ~SwCommentRuler() override
Definition: swruler.cxx:101
virtual void dispose() override
Definition: swruler.cxx:103
tools::Rectangle GetCommentControlRegion()
Get the rectangle area that should be used to draw the comment control.
Definition: swruler.cxx:312
Color GetFadedColor(const Color &rHighColor, const Color &rLowColor)
Get the proper color between two options, according to current status.
Definition: swruler.cxx:341
SwCommentRuler(SwViewShell *pViewSh, vcl::Window *pParent, SwEditWin *pWin, SvxRulerSupportFlags nRulerFlags, SfxBindings &rBindings, WinBits nWinStyle)
Definition: swruler.cxx:80
Window class for the Writer edit area, this is the one handling mouse and keyboard events and doing t...
Definition: edtwin.hxx:61
bool HasNotes() const
Definition: PostItMgr.cxx:2116
bool ShowNotes() const
Definition: PostItMgr.cxx:2110
tools::ULong GetSidebarWidth(bool bPx=false) const
Definition: PostItMgr.cxx:2121
tools::ULong GetSidebarBorderWidth(bool bPx=false) const
Definition: PostItMgr.cxx:2147
SfxViewShell * GetSfxViewShell() const
Definition: viewsh.hxx:470
const SwPostItMgr * GetPostItMgr() const
Definition: viewsh.hxx:583
Definition: view.hxx:146
void ExecViewOptions(SfxRequest &)
Definition: view0.cxx:377
bool IsActive() const
void SetTimeout(sal_uInt64 nTimeoutMs)
void SetInvokeHandler(const Link< Timer *, void > &rLink)
virtual void Start(bool bStartTimer=true) override
void clear()
void put(std::u16string_view pPropName, const OUString &rPropValue)
OString finishAndGetAsOString()
ScopedJsonWriterNode startNode(std::string_view)
void SetPoint(const Point &rPt, sal_uInt16 nPos)
constexpr tools::Long GetWidth() const
bool Contains(const Point &rPOINT) const
constexpr Point TopLeft() const
constexpr Size GetSize() const
constexpr tools::Long GetHeight() const
tools::Long GetFontHeight() const
void SetFontHeight(tools::Long nHeight)
void Create(SwFormatVertOrient &rItem, SvStream &rStrm, sal_uInt16 nVersionAbusedAsSize)
Definition: legacyitem.cxx:32
unsigned long ULong
long Long
SvxRulerSupportFlags
char aUnitStr[8]
OUString SwResId(TranslateId aId)
Definition: swmodule.cxx:168
#define CONTROL_BORDER_WIDTH
Definition: swruler.cxx:30
IMPL_LINK_NOARG(SwCommentRuler, FadeHandler, Timer *, void)
Definition: swruler.cxx:351
OUString aLabel
sal_Int64 WinBits
WinBits const WB_HSCROLL