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 <tools/UnitConversion.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 #include <boost/property_tree/json_parser.hpp>
30 
31 #define CONTROL_BORDER_WIDTH 1
32 
33 namespace
34 {
44 void ImplDrawArrow(vcl::RenderContext& rRenderContext, long nX, long nY, long nSize,
45  const Color& rColor, bool bCollapsed)
46 {
47  tools::Polygon aTrianglePolygon(4);
48 
49  if (bCollapsed)
50  {
51  if (AllSettings::GetLayoutRTL()) // <
52  {
53  aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 0);
54  aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 1);
55  aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 2);
56  aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 3);
57  }
58  else // >
59  {
60  aTrianglePolygon.SetPoint({ nX, nY }, 0);
61  aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize / 2 }, 1);
62  aTrianglePolygon.SetPoint({ nX, nY + nSize }, 2);
63  aTrianglePolygon.SetPoint({ nX, nY }, 3);
64  }
65  }
66  else // v
67  {
68  aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 0);
69  aTrianglePolygon.SetPoint({ nX + nSize, nY + nSize / 2 }, 1);
70  aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 2);
71  aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 3);
72  }
73 
74  rRenderContext.SetLineColor();
75  rRenderContext.SetFillColor(rColor);
76  rRenderContext.DrawPolygon(aTrianglePolygon);
77 }
78 }
79 
80 // Constructor
82  SvxRulerSupportFlags nRulerFlags, SfxBindings& rBindings,
83  WinBits nWinStyle)
84  : SvxRuler(pParent, pWin, nRulerFlags, rBindings, nWinStyle | WB_HSCROLL)
85  , mpViewShell(pViewSh)
86  , mpSwWin(pWin)
87  , mbIsHighlighted(false)
88  , mnFadeRate(0)
89  , maVirDev(VclPtr<VirtualDevice>::Create(*this))
90 {
91  // Set fading timeout: 5 x 40ms = 200ms
93  maFadeTimer.SetInvokeHandler(LINK(this, SwCommentRuler, FadeHandler));
94  maFadeTimer.SetDebugName("sw::SwCommentRuler maFadeTimer");
95 
96  // we have a little bit more space, as we don't draw ruler ticks
97  vcl::Font aFont(maVirDev->GetFont());
98  aFont.SetFontHeight(aFont.GetFontHeight() + 1);
99  maVirDev->SetFont(aFont);
100 }
101 
103 
105 {
106  mpSwWin.clear();
108 }
109 
110 void SwCommentRuler::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
111 {
113  return; // no need to waste time on startup
114 
115  SvxRuler::Paint(rRenderContext, rRect);
116 
117  // Don't draw if there is not any note
119  DrawCommentControl(rRenderContext);
120 }
121 
123 {
124  const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
125  const bool bIsCollapsed = !mpViewShell->GetPostItMgr()->ShowNotes();
126  const tools::Rectangle aControlRect = GetCommentControlRegion();
127 
128  maVirDev->SetOutputSizePixel(aControlRect.GetSize());
129 
130  // set colors
131  if (!bIsCollapsed)
132  {
133  if (mbIsHighlighted)
134  maVirDev->SetFillColor(
135  GetFadedColor(rStyleSettings.GetHighlightColor(), rStyleSettings.GetDialogColor()));
136  else
137  maVirDev->SetFillColor(rStyleSettings.GetDialogColor());
138  maVirDev->SetLineColor(rStyleSettings.GetShadowColor());
139  }
140  else
141  {
142  if (mbIsHighlighted)
143  maVirDev->SetFillColor(GetFadedColor(rStyleSettings.GetHighlightColor(),
144  rStyleSettings.GetWorkspaceColor()));
145  else
146  maVirDev->SetFillColor(rStyleSettings.GetWorkspaceColor());
147  maVirDev->SetLineColor();
148  }
149  Color aTextColor = GetFadedColor(rStyleSettings.GetHighlightTextColor(),
150  rStyleSettings.GetButtonTextColor());
151  maVirDev->SetTextColor(aTextColor);
152 
153  // calculate label and arrow positions
154  const OUString aLabel = SwResId(STR_COMMENTS_LABEL);
155  const long nTriangleSize = maVirDev->GetTextHeight() / 2 + 1;
156  const long nTrianglePad = maVirDev->GetTextHeight() / 4;
157 
158  Point aLabelPos(0, (aControlRect.GetHeight() - maVirDev->GetTextHeight()) / 2);
159  Point aArrowPos(0, (aControlRect.GetHeight() - nTriangleSize) / 2);
160 
161  if (!AllSettings::GetLayoutRTL()) // | > Comments |
162  {
163  aArrowPos.setX(nTrianglePad);
164  aLabelPos.setX(aArrowPos.X() + nTriangleSize + nTrianglePad);
165  }
166  else // RTL => | Comments < |
167  {
168  const long nLabelWidth = maVirDev->GetTextWidth(aLabel);
169  if (!bIsCollapsed)
170  {
171  aArrowPos.setX(aControlRect.GetWidth() - 1 - nTrianglePad - CONTROL_BORDER_WIDTH
172  - nTriangleSize);
173  aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
174  }
175  else
176  {
177  // if comments are collapsed, left align the text, because otherwise it's very likely to be invisible
178  aArrowPos.setX(nLabelWidth + nTrianglePad + nTriangleSize);
179  aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
180  }
181  }
182 
183  // draw control
184  maVirDev->DrawRect(tools::Rectangle(Point(), aControlRect.GetSize()));
185  maVirDev->DrawText(aLabelPos, aLabel);
186  ImplDrawArrow(*maVirDev, aArrowPos.X(), aArrowPos.Y(), nTriangleSize, aTextColor, bIsCollapsed);
187  rRenderContext.DrawOutDev(aControlRect.TopLeft(), aControlRect.GetSize(), Point(),
188  aControlRect.GetSize(), *maVirDev);
189 }
190 
191 // Just accept double-click outside comment control
193 {
194  Point aMousePos = rCEvt.GetMousePosPixel();
195  // Ignore command request if it is inside Comment Control
197  || !GetCommentControlRegion().IsInside(aMousePos))
198  SvxRuler::Command(rCEvt);
199 }
200 
202 {
203  SvxRuler::MouseMove(rMEvt);
205  return;
206 
208 
209  Point aMousePos = rMEvt.GetPosPixel();
210  bool bWasHighlighted = mbIsHighlighted;
212  if (mbIsHighlighted != bWasHighlighted)
213  // Do start fading
214  maFadeTimer.Start();
215 }
216 
218 {
219  Point aMousePos = rMEvt.GetPosPixel();
220  if (!rMEvt.IsLeft() || IsTracking() || !GetCommentControlRegion().IsInside(aMousePos))
221  {
222  SvxRuler::MouseButtonDown(rMEvt);
223  return;
224  }
225 
226  // Toggle notes visibility
227  SwView& rView = mpSwWin->GetView();
228  SfxRequest aRequest(rView.GetViewFrame(), SID_TOGGLE_NOTES);
229  rView.ExecViewOptions(aRequest);
230 
231  // It is inside comment control, so update help text
233 
234  Invalidate();
235 }
236 
238 {
239  // Note that GetMargin1(), GetMargin2(), GetNullOffset(), and GetPageOffset() return values in
240  // pixels. Not twips. So "converting" the returned values with convertTwipToMm100() is quite
241  // wrong. (Also, even if the return values actually were in twips, it is questionable why we
242  // would want to pass them in mm100, as all other length values in the LOKit protocol apparently
243  // are in twips.)
244 
245  // Anyway, as the consuming code in Online mostly seems to work anyway, it is likely that it
246  // would work as well even if the values in pixels were passed without a bogus "conversion" to
247  // mm100. But let's keep this as is for now.
248 
249  // Also note that in desktop LibreOffice, these pixel values for the ruler of course change as
250  // one changes the zoom level. (Can be seen if one temporarily modifies the NotifyKit() function
251  // below to call this CreateJsonNotification() function and print its result in all cases even
252  // without LibreOfficeKit::isActive().) But in both web-based Online and in the iOS app, the
253  // zoom level from the point of view of this code here apparently does not change even if one
254  // zooms from the Online code's point of view.
255  rJsonWriter.put("margin1", convertTwipToMm100(GetMargin1()));
256  rJsonWriter.put("margin2", convertTwipToMm100(GetMargin2()));
257  rJsonWriter.put("leftOffset", convertTwipToMm100(GetNullOffset()));
258  rJsonWriter.put("pageOffset", convertTwipToMm100(GetPageOffset()));
259 
260  // GetPageWidth() on the other hand does return a value in twips.
261  // So here convertTwipToMm100() really does produce actual mm100. Fun.
262  rJsonWriter.put("pageWidth", convertTwipToMm100(GetPageWidth()));
263 
264  {
265  auto tabsNode = rJsonWriter.startNode("tabs");
266 
267  // The RulerTab array elements that GetTabs() returns have their nPos field in twips. So these
268  // too are actual mm100.
269  for (auto const& tab : GetTabs())
270  {
271  auto tabNode = rJsonWriter.startNode("");
272  rJsonWriter.put("position", convertTwipToMm100(tab.nPos));
273  rJsonWriter.put("style", tab.nStyle);
274  }
275  }
276 
277  RulerUnitData aUnitData = GetCurrentRulerUnit();
278  rJsonWriter.put("unit", aUnitData.aUnitStr);
279 }
280 
282 {
284  return;
285 
286  tools::JsonWriter aJsonWriter;
287  CreateJsonNotification(aJsonWriter);
288  mpViewShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_RULER_UPDATE,
289  aJsonWriter.extractData());
290 }
291 
293 {
294  tools::Rectangle aPreviousControlRect = GetCommentControlRegion();
296  if (aPreviousControlRect != GetCommentControlRegion())
297  Invalidate();
298  NotifyKit();
299 }
300 
302 {
303  const char* 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 unsigned long nSidebarWidth = pPostItMgr->GetSidebarWidth(true);
323 
324  //FIXME When the page width is larger then screen, the ruler is misplaced by one pixel
325  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  long nTop = 4;
333  // Somehow pPostItMgr->GetSidebarBorderWidth() returns border width already doubled
334  long nRight = nLeft + nSidebarWidth + pPostItMgr->GetSidebarBorderWidth(true);
335  long nBottom = nTop + GetRulerVirHeight() - 3;
336 
337  tools::Rectangle aRect(nLeft, nTop, nRight, nBottom);
338  return aRect;
339 }
340 
341 Color 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 
351 IMPL_LINK_NOARG(SwCommentRuler, FadeHandler, Timer*, void)
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: */
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:301
void Merge(const Color &rMergeColor, sal_uInt8 cTransparency)
const Color & GetHighlightTextColor() const
const SwView & GetView() const
Definition: edtwin.hxx:243
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:441
unsigned long GetSidebarBorderWidth(bool bPx=false) const
Definition: PostItMgr.cxx:2130
void SetPoint(const Point &rPt, sal_uInt16 nPos)
virtual ~SwCommentRuler() override
Definition: swruler.cxx:102
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:351
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:358
virtual void MouseButtonDown(const MouseEvent &rMEvt) override
Callback function to handle a mouse button down event.
Definition: swruler.cxx:217
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:281
virtual void Start() override
void CreateJsonNotification(tools::JsonWriter &rJsonWriter)
Definition: swruler.cxx:237
bool IsInside(const Point &rPOINT) const
exports com.sun.star.awt. tab
const AllSettings & GetSettings() const
OUString SwResId(const char *pId)
Definition: swmodule.cxx:165
virtual void Paint(vcl::RenderContext &rRenderContext, const tools::Rectangle &rRect) override
Paint the ruler.
Definition: swruler.cxx:110
void SetTimeout(sal_uInt64 nTimeoutMs)
virtual void dispose() override
Definition: swruler.cxx:104
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:341
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:201
bool IsLeft() const
void SetFontHeight(long nHeight)
OUString aLabel
virtual void Update() override
Update the view.
Definition: swruler.cxx:292
void SetInvokeHandler(const Link< Timer *, void > &rLink)
bool HasNotes() const
Definition: PostItMgr.cxx:2099
const Point & GetPosPixel() const
const SwPostItMgr * GetPostItMgr() const
Definition: viewsh.hxx:556
virtual void Command(const CommandEvent &rCEvt) override
Callback function to handle a context menu call (mouse right button click).
Definition: swruler.cxx:192
SwCommentRuler(SwViewShell *pViewSh, vcl::Window *pParent, SwEditWin *pWin, SvxRulerSupportFlags nRulerFlags, SfxBindings &rBindings, WinBits nWinStyle)
Definition: swruler.cxx:81
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:2104
#define CONTROL_BORDER_WIDTH
Definition: swruler.cxx:31
void(* f)(TrueTypeTable *)
tools::Rectangle GetCommentControlRegion()
Get the rectangle area that should be used to draw the comment control.
Definition: swruler.cxx:312
bool ShowNotes() const
Definition: PostItMgr.cxx:2093
void DrawCommentControl(vcl::RenderContext &rRenderContext)
Paint the comment control on VirtualDevice.
Definition: swruler.cxx:122
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