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>
20 #include <vcl/commandevent.hxx>
21 #include <vcl/event.hxx>
22 #include <vcl/window.hxx>
23 #include <vcl/settings.hxx>
24 #include <tools/json_writer.hxx>
25 #include <strings.hrc>
26 #include <comphelper/lok.hxx>
27 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
28 #include <boost/property_tree/json_parser.hpp>
29 
30 #define CONTROL_BORDER_WIDTH 1
31 
32 namespace
33 {
43 void ImplDrawArrow(vcl::RenderContext& rRenderContext, long nX, long nY, long nSize,
44  const Color& rColor, bool bCollapsed)
45 {
46  tools::Polygon aTriaglePolygon(4);
47 
48  if (bCollapsed)
49  {
50  if (AllSettings::GetLayoutRTL()) // <
51  {
52  aTriaglePolygon.SetPoint({ nX + nSize / 2, nY }, 0);
53  aTriaglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 1);
54  aTriaglePolygon.SetPoint({ nX, nY + nSize / 2 }, 2);
55  aTriaglePolygon.SetPoint({ nX + nSize / 2, nY }, 3);
56  }
57  else // >
58  {
59  aTriaglePolygon.SetPoint({ nX, nY }, 0);
60  aTriaglePolygon.SetPoint({ nX + nSize / 2, nY + nSize / 2 }, 1);
61  aTriaglePolygon.SetPoint({ nX, nY + nSize }, 2);
62  aTriaglePolygon.SetPoint({ nX, nY }, 3);
63  }
64  }
65  else // v
66  {
67  aTriaglePolygon.SetPoint({ nX, nY + nSize / 2 }, 0);
68  aTriaglePolygon.SetPoint({ nX + nSize, nY + nSize / 2 }, 1);
69  aTriaglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 2);
70  aTriaglePolygon.SetPoint({ nX, nY + nSize / 2 }, 3);
71  }
72 
73  rRenderContext.SetLineColor();
74  rRenderContext.SetFillColor(rColor);
75  rRenderContext.DrawPolygon(aTriaglePolygon);
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  , mnFadeRate(0)
88  , maVirDev(VclPtr<VirtualDevice>::Create(*this))
89 {
90  // Set fading timeout: 5 x 40ms = 200ms
92  maFadeTimer.SetInvokeHandler(LINK(this, SwCommentRuler, FadeHandler));
93  maFadeTimer.SetDebugName("sw::SwCommentRuler maFadeTimer");
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 
109 void SwCommentRuler::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
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 long nTriangleSize = maVirDev->GetTextHeight() / 2 + 1;
155  const 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 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().IsInside(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
213  maFadeTimer.Start();
214 }
215 
217 {
218  Point aMousePos = rMEvt.GetPosPixel();
219  if (!rMEvt.IsLeft() || IsTracking() || !GetCommentControlRegion().IsInside(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  mpViewShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_RULER_UPDATE,
288  aJsonWriter.extractData());
289 }
290 
292 {
293  tools::Rectangle aPreviousControlRect = GetCommentControlRegion();
295  if (aPreviousControlRect != GetCommentControlRegion())
296  Invalidate();
297  NotifyKit();
298 }
299 
301 {
302  const char* pTooltipResId;
304  pTooltipResId = STR_HIDE_COMMENTS;
305  else
306  pTooltipResId = STR_SHOW_COMMENTS;
307  SetQuickHelpText(SwResId(pTooltipResId));
308 }
309 
310 // TODO Make Ruler return its central rectangle instead of margins.
312 {
313  SwPostItMgr* pPostItMgr = mpViewShell->GetPostItMgr();
314 
315  //rhbz#1006850 When the SwPostItMgr ctor is called from SwView::SwView it
316  //triggers an update of the uiview, but the result of the ctor hasn't been
317  //set into the mpViewShell yet, so GetPostItMgr is temporarily still NULL
318  if (!pPostItMgr)
319  return tools::Rectangle();
320 
321  const unsigned long nSidebarWidth = pPostItMgr->GetSidebarWidth(true);
322 
323  //FIXME When the page width is larger then screen, the ruler is misplaced by one pixel
324  long nLeft = GetPageOffset();
325  if (GetTextRTL())
326  nLeft += GetBorderOffset() - nSidebarWidth;
327  else
328  nLeft += GetWinOffset() + mpSwWin->LogicToPixel(Size(GetPageWidth(), 0)).Width();
329 
330  // Ruler::ImplDraw uses RULER_OFF (value: 3px) as offset, and Ruler::ImplFormat adds one extra pixel
331  long nTop = 4;
332  // Somehow pPostItMgr->GetSidebarBorderWidth() returns border width already doubled
333  long nRight = nLeft + nSidebarWidth + pPostItMgr->GetSidebarBorderWidth(true);
334  long nBottom = nTop + GetRulerVirHeight() - 3;
335 
336  tools::Rectangle aRect(nLeft, nTop, nRight, nBottom);
337  return aRect;
338 }
339 
340 Color SwCommentRuler::GetFadedColor(const Color& rHighColor, const Color& rLowColor)
341 {
342  if (!maFadeTimer.IsActive())
343  return mbIsHighlighted ? rHighColor : rLowColor;
344 
345  Color aColor = rHighColor;
346  aColor.Merge(rLowColor, mnFadeRate * 255 / 100.0f);
347  return aColor;
348 }
349 
350 IMPL_LINK_NOARG(SwCommentRuler, FadeHandler, Timer*, void)
351 {
352  const int nStep = 25;
353  if (mbIsHighlighted && mnFadeRate < 100)
354  mnFadeRate += nStep;
355  else if (!mbIsHighlighted && mnFadeRate > 0)
356  mnFadeRate -= nStep;
357  else
358  return;
359 
360  Invalidate();
361 
362  if (mnFadeRate != 0 && mnFadeRate != 100)
363  maFadeTimer.Start();
364 }
365 
366 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Point TopLeft() const
SfxViewFrame * GetViewFrame() const
const Color & GetShadowColor() const
long GetWidth() const
SvxRulerSupportFlags
const Color & GetDialogColor() const
long GetHeight() const
void UpdateCommentHelpText()
Update the tooltip text.
Definition: swruler.cxx:300
void Merge(const Color &rMergeColor, sal_uInt8 cTransparency)
const Color & GetHighlightTextColor() const
const SwView & GetView() const
Definition: edtwin.hxx:241
void Create(SwFormatVertOrient &rItem, SvStream &rStrm, sal_uInt16 nVersionAbusedAsSize)
Definition: legacyitem.cxx:32
const StyleSettings & GetStyleSettings() const
virtual void Update()
char * extractData()
void DrawPolygon(const tools::Polygon &rPoly)
ScopedJsonWriterNode startNode(const char *)
const Color & GetWorkspaceColor() const
Timer maFadeTimer
Definition: swruler.hxx:52
SfxViewShell * GetSfxViewShell() const
Definition: viewsh.hxx:442
unsigned long GetSidebarBorderWidth(bool bPx=false) const
Definition: PostItMgr.cxx:2081
void SetPoint(const Point &rPt, sal_uInt16 nPos)
virtual ~SwCommentRuler() override
Definition: swruler.cxx:101
virtual void Command(const CommandEvent &rCEvt) override
SwViewShell * mpViewShell
Definition: swruler.hxx:49
const Color & GetHighlightColor() const
ScopedVclPtr< VirtualDevice > maVirDev
Definition: swruler.hxx:54
bool IsActive() const
virtual void dispose() override
sal_Int64 WinBits
IMPL_LINK_NOARG(SwCommentRuler, FadeHandler, Timer *, void)
Definition: swruler.cxx:350
virtual void MouseMove(const MouseEvent &rMEvt) override
WinBits const WB_HSCROLL
void libreOfficeKitViewCallback(int nType, const char *pPayload) const override
void ExecViewOptions(SfxRequest &)
Definition: view0.cxx:352
virtual void MouseButtonDown(const MouseEvent &rMEvt) override
Callback function to handle a mouse button down event.
Definition: swruler.cxx:216
void SetDebugName(const char *pDebugName)
void SetLineColor()
void clear()
char aUnitStr[8]
Window class for the Writer edit area, this is the one handling mouse and keyboard events and doing t...
Definition: edtwin.hxx:58
void SetFillColor()
static bool GetLayoutRTL()
void NotifyKit()
Definition: swruler.cxx:280
virtual void Start() override
void CreateJsonNotification(tools::JsonWriter &rJsonWriter)
Definition: swruler.cxx:236
bool IsInside(const Point &rPOINT) const
exports com.sun.star.awt. tab
const AllSettings & GetSettings() const
OUString SwResId(const char *pId)
Definition: swmodule.cxx:178
virtual void Paint(vcl::RenderContext &rRenderContext, const tools::Rectangle &rRect) override
Paint the ruler.
Definition: swruler.cxx:109
void SetTimeout(sal_uInt64 nTimeoutMs)
virtual void dispose() override
Definition: swruler.cxx:103
Size GetSize() const
void put(const char *pPropName, const OUString &rPropValue)
const Point & GetMousePosPixel() const
Point LogicToPixel(const Point &rLogicPt) const
Color GetFadedColor(const Color &rHighColor, const Color &rLowColor)
Get the proper color between two options, according to current status.
Definition: swruler.cxx:340
long GetPageWidth() const
constexpr sal_Int64 convertTwipToMm100(sal_Int64 n)
const Color & GetButtonTextColor() const
virtual void MouseMove(const MouseEvent &rMEvt) override
Callback function to handle a mouse move event.
Definition: swruler.cxx:200
bool IsLeft() const
void SetFontHeight(long nHeight)
OUString aLabel
virtual void Update() override
Update the view.
Definition: swruler.cxx:291
void SetInvokeHandler(const Link< Timer *, void > &rLink)
bool HasNotes() const
Definition: PostItMgr.cxx:2050
const Point & GetPosPixel() const
const SwPostItMgr * GetPostItMgr() const
Definition: viewsh.hxx:557
virtual void Command(const CommandEvent &rCEvt) override
Callback function to handle a context menu call (mouse right button click).
Definition: swruler.cxx:191
SwCommentRuler(SwViewShell *pViewSh, vcl::Window *pParent, SwEditWin *pWin, SvxRulerSupportFlags nRulerFlags, SfxBindings &rBindings, WinBits nWinStyle)
Definition: swruler.cxx:80
An horizontal ruler with a control for comment panel visibility for Writer.
Definition: swruler.hxx:28
unsigned long GetSidebarWidth(bool bPx=false) const
Definition: PostItMgr.cxx:2055
#define CONTROL_BORDER_WIDTH
Definition: swruler.cxx:30
void(* f)(TrueTypeTable *)
tools::Rectangle GetCommentControlRegion()
Get the rectangle area that should be used to draw the comment control.
Definition: swruler.cxx:311
bool ShowNotes() const
Definition: PostItMgr.cxx:2044
void DrawCommentControl(vcl::RenderContext &rRenderContext)
Paint the comment control on VirtualDevice.
Definition: swruler.cxx:121
VclPtr< SwEditWin > mpSwWin
Definition: swruler.hxx:50
SAL_DLLPRIVATE void DrawOutDev(const Point &, const Size &, const Point &, const Size &, const Printer &)=delete
Definition: view.hxx:144
bool mbIsHighlighted
Definition: swruler.hxx:51