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 // FIX fdo#38246 https://bugs.libreoffice.org/show_bug.cgi?id=38246
11 // Design proposal: https://wiki.documentfoundation.org/Design/Whiteboards/Comments_Ruler_Control
12 // TODO Alpha blend border when it doesn't fit in window
13 
14 #include <swruler.hxx>
15 
16 #include <viewsh.hxx>
17 #include <edtwin.hxx>
18 #include <PostItMgr.hxx>
19 #include <view.hxx>
20 #include <cmdid.h>
21 #include <sfx2/request.hxx>
22 #include <vcl/commandevent.hxx>
23 #include <vcl/event.hxx>
24 #include <vcl/window.hxx>
25 #include <vcl/settings.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 #define CONTROL_LEFT_OFFSET 6
33 #define CONTROL_RIGHT_OFFSET 3
34 #define CONTROL_TOP_OFFSET 4
35 
36 #define CONTROL_TRIANGLE_WIDTH 4
37 #define CONTROL_TRIANGLE_PAD 3
38 
39 namespace {
40 
48 void ImplDrawArrow(vcl::RenderContext& rRenderContext, long nX, long nY, const Color& rColor, bool bPointRight)
49 {
50  rRenderContext.SetLineColor();
51  rRenderContext.SetFillColor(rColor);
52  if (bPointRight)
53  {
54  rRenderContext.DrawRect(tools::Rectangle(nX + 0, nY + 0, nX + 0, nY + 6) );
55  rRenderContext.DrawRect(tools::Rectangle(nX + 1, nY + 1, nX + 1, nY + 5) );
56  rRenderContext.DrawRect(tools::Rectangle(nX + 2, nY + 2, nX + 2, nY + 4) );
57  rRenderContext.DrawRect(tools::Rectangle(nX + 3, nY + 3, nX + 3, nY + 3) );
58  }
59  else
60  {
61  rRenderContext.DrawRect(tools::Rectangle(nX + 0, nY + 3, nX + 0, nY + 3));
62  rRenderContext.DrawRect(tools::Rectangle(nX + 1, nY + 2, nX + 1, nY + 4));
63  rRenderContext.DrawRect(tools::Rectangle(nX + 2, nY + 1, nX + 2, nY + 5));
64  rRenderContext.DrawRect(tools::Rectangle(nX + 3, nY + 0, nX + 3, nY + 6));
65  }
66 }
67 
68 }
69 
70 // Constructor
71 SwCommentRuler::SwCommentRuler( SwViewShell* pViewSh, vcl::Window* pParent, SwEditWin* pWin, SvxRulerSupportFlags nRulerFlags, SfxBindings& rBindings, WinBits nWinStyle)
72 : SvxRuler(pParent, pWin, nRulerFlags, rBindings, nWinStyle | WB_HSCROLL)
73 , mpViewShell(pViewSh)
74 , mpSwWin(pWin)
75 , mbIsHighlighted(false)
76 , mnFadeRate(0)
77 , maVirDev( VclPtr<VirtualDevice>::Create(*this) )
78 {
79  // Set fading timeout: 5 x 40ms = 200ms
81  maFadeTimer.SetInvokeHandler( LINK( this, SwCommentRuler, FadeHandler ) );
82  maFadeTimer.SetDebugName( "sw::SwCommentRuler maFadeTimer" );
83 }
84 
85 // Destructor
87 {
88  disposeOnce();
89 }
90 
92 {
93  mpSwWin.clear();
95 }
96 
97 void SwCommentRuler::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
98 {
100  return; // no need to waste time on startup
101 
102  SvxRuler::Paint(rRenderContext, rRect);
103 
104  // Don't draw if there is not any note
106  DrawCommentControl(rRenderContext);
107 }
108 
110 {
111  const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
112  bool bIsCollapsed = ! mpViewShell->GetPostItMgr()->ShowNotes();
113 
114  tools::Rectangle aControlRect = GetCommentControlRegion();
115  maVirDev->SetOutputSizePixel(aControlRect.GetSize());
116 
117  // Paint comment control background
118  // TODO Check if these are best colors to be used
119  Color aBgColor = GetFadedColor( rStyleSettings.GetDialogColor(), rStyleSettings.GetWorkspaceColor() );
120  maVirDev->SetFillColor( aBgColor );
121 
122  if ( mbIsHighlighted || !bIsCollapsed )
123  {
124  // Draw borders
125  maVirDev->SetLineColor( rStyleSettings.GetShadowColor() );
126  }
127  else
128  {
129  // No borders
130  maVirDev->SetLineColor();
131  }
132 
133  maVirDev->DrawRect( tools::Rectangle( Point(), aControlRect.GetSize() ) );
134 
135  // Label and arrow tip
136  OUString aLabel( SwResId ( STR_COMMENTS_LABEL ) );
137  // Get label and arrow coordinates
138  Point aLabelPos;
139  Point aArrowPos;
140  bool bArrowToRight;
141  // TODO Discover why it should be 0 instead of CONTROL_BORDER_WIDTH + CONTROL_TOP_OFFSET
142  aLabelPos.setY( 0 );
144  if ( !AllSettings::GetLayoutRTL() )
145  {
146  // LTR
147  if ( bIsCollapsed )
148  {
149  // It should draw something like | > Comments |
151  aArrowPos.setX( CONTROL_LEFT_OFFSET );
152  }
153  else
154  {
155  // It should draw something like | Comments < |
156  aLabelPos.setX( CONTROL_LEFT_OFFSET );
157  aArrowPos.setX( aControlRect.GetSize().Width() - 1 - CONTROL_RIGHT_OFFSET - CONTROL_BORDER_WIDTH - CONTROL_TRIANGLE_WIDTH );
158  }
159  bArrowToRight = bIsCollapsed;
160  }
161  else
162  {
163  // RTL
164  long nLabelWidth = GetTextWidth( aLabel );
165  if ( bIsCollapsed )
166  {
167  // It should draw something like | Comments < |
168  aArrowPos.setX( aControlRect.GetSize().Width() - 1 - CONTROL_RIGHT_OFFSET - CONTROL_BORDER_WIDTH - CONTROL_TRIANGLE_WIDTH );
169  aLabelPos.setX( aArrowPos.X() - CONTROL_TRIANGLE_PAD - nLabelWidth );
170  }
171  else
172  {
173  // It should draw something like | > Comments |
174  aLabelPos.setX( aControlRect.GetSize().Width() - 1 - CONTROL_RIGHT_OFFSET - CONTROL_BORDER_WIDTH - nLabelWidth );
175  aArrowPos.setX( CONTROL_LEFT_OFFSET );
176  }
177  bArrowToRight = !bIsCollapsed;
178  }
179 
180  // Draw label
181  Color aTextColor = GetFadedColor( rStyleSettings.GetButtonTextColor(), rStyleSettings.GetDarkShadowColor() );
182  maVirDev->SetTextColor( aTextColor );
183  // FIXME Expected font size?
184  maVirDev->DrawText( aLabelPos, aLabel );
185 
186  // Draw arrow
187  // FIXME consistence of button colors. https://opengrok.libreoffice.org/xref/core/vcl/source/control/button.cxx#785
188  ImplDrawArrow(*maVirDev, aArrowPos.X(), aArrowPos.Y(), aTextColor, bArrowToRight);
189 
190  // Blit comment control
191  rRenderContext.DrawOutDev(aControlRect.TopLeft(), aControlRect.GetSize(), Point(), aControlRect.GetSize(), *maVirDev);
192 }
193 
194 // Just accept double-click outside comment control
196 {
197  Point aMousePos = rCEvt.GetMousePosPixel();
198  // Ignore command request if it is inside Comment Control
199  if ( !mpViewShell->GetPostItMgr()
201  || !GetCommentControlRegion().IsInside( aMousePos ) )
202  SvxRuler::Command( rCEvt );
203 }
204 
206 {
207  SvxRuler::MouseMove(rMEvt);
209  return;
210 
211  Point aMousePos = rMEvt.GetPosPixel();
212  bool bWasHighlighted = mbIsHighlighted;
214  if ( mbIsHighlighted != bWasHighlighted )
215  {
216  // Set proper help text
217  if ( mbIsHighlighted )
218  {
219  // Mouse over comment control
221  }
222  else
223  {
224  // Mouse out of comment control
225  // FIXME Should remember previous tooltip text?
226  SetQuickHelpText( OUString() );
227  }
228  // Do start fading
229  maFadeTimer.Start();
230  }
231 }
232 
234 {
235  Point aMousePos = rMEvt.GetPosPixel();
236  if ( !rMEvt.IsLeft() || IsTracking() || !GetCommentControlRegion().IsInside( aMousePos ) )
237  {
238  SvxRuler::MouseButtonDown(rMEvt);
239  return;
240  }
241 
242  // Toggle notes visibility
243  SwView &rView = mpSwWin->GetView();
244  SfxRequest aRequest( rView.GetViewFrame(), SID_TOGGLE_NOTES );
245  rView.ExecViewOptions( aRequest );
246 
247  // It is inside comment control, so update help text
249 
250  Invalidate();
251 }
252 
254 {
255  boost::property_tree::ptree jsonNotif;
256 
257  // Note that GetMargin1(), GetMargin2(), GetNullOffset(), and GetPageOffset() return values in
258  // pixels. Not twips. So "converting" the returned values with convertTwipToMm100() is quite
259  // wrong. (Also, even if the return values actually were in twips, it is questionable why we
260  // would want to pass them in mm100, as all other length values in the LOKit protocol apparently
261  // are in twips.)
262 
263  // Anyway, as the consuming code in Online mostly seems to work anyway, it is likely that it
264  // would work as well even if the values in pixels were passed without a bogus "conversion" to
265  // mm100. But let's keep this as is for now.
266 
267  // Also note that in desktop LibreOffice, these pixel values for the ruler of course change as
268  // one changes the zoom level. (Can be seen if one temporarily modifies the NotifyKit() function
269  // below to call this CreateJsonNotification() function and print its result in all cases even
270  // without LibreOfficeKit::isActive().) But in both web-based Online and in the iOS app, the
271  // zoom level from the point of view of this code here apparently does not change even if one
272  // zooms from the Online code's point of view.
273  jsonNotif.put("margin1", convertTwipToMm100(GetMargin1()));
274  jsonNotif.put("margin2", convertTwipToMm100(GetMargin2()));
275  jsonNotif.put("leftOffset", convertTwipToMm100(GetNullOffset()));
276  jsonNotif.put("pageOffset", convertTwipToMm100(GetPageOffset()));
277 
278  // GetPageWidth() on the other hand does return a value in twips.
279  // So here convertTwipToMm100() really does produce actual mm100. Fun.
280  jsonNotif.put("pageWidth", convertTwipToMm100(GetPageWidth()));
281 
282  boost::property_tree::ptree tabs;
283 
284  // The RulerTab array elements that GetTabs() returns have their nPos field in twips. So these
285  // too are actual mm100.
286  for (auto const& tab : GetTabs())
287  {
288  boost::property_tree::ptree element;
289  element.put("position", convertTwipToMm100(tab.nPos));
290  element.put("style", tab.nStyle);
291  tabs.push_back(std::make_pair("", element));
292  }
293 
294  jsonNotif.add_child("tabs", tabs);
295 
296  RulerUnitData aUnitData = GetCurrentRulerUnit();
297  jsonNotif.put("unit", aUnitData.aUnitStr);
298 
299  std::stringstream aStream;
300  boost::property_tree::write_json(aStream, jsonNotif);
301  return aStream.str();
302 }
303 
305 {
307  return;
308 
309  const std::string test = CreateJsonNotification();
310  mpViewShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_RULER_UPDATE, test.c_str());
311 }
312 
314 {
315  tools::Rectangle aPreviousControlRect = GetCommentControlRegion();
317  if (aPreviousControlRect != GetCommentControlRegion())
318  Invalidate();
319  NotifyKit();
320 }
321 
323 {
324  const char* pTooltipResId;
325  if ( mpViewShell->GetPostItMgr()->ShowNotes() )
326  pTooltipResId = STR_HIDE_COMMENTS;
327  else
328  pTooltipResId = STR_SHOW_COMMENTS;
329  SetQuickHelpText(SwResId(pTooltipResId));
330 }
331 
332 // TODO Make Ruler return its central rectangle instead of margins.
334 {
335  long nLeft = 0;
336  SwPostItMgr *pPostItMgr = mpViewShell->GetPostItMgr();
337 
338  //rhbz#1006850 When the SwPostItMgr ctor is called from SwView::SwView it
339  //triggers an update of the uiview, but the result of the ctor hasn't been
340  //set into the mpViewShell yet, so GetPostItMgr is temporarily still NULL
341  if (!pPostItMgr)
342  return tools::Rectangle();
343 
344  unsigned long nSidebarWidth = pPostItMgr->GetSidebarWidth(true);
345  //FIXME When the page width is larger then screen, the ruler is misplaced by one pixel
346  if (GetTextRTL())
347  nLeft = GetPageOffset() - nSidebarWidth + GetBorderOffset();
348  else
349  nLeft = GetWinOffset() + GetPageOffset() + mpSwWin->LogicToPixel(Size(GetPageWidth(), 0)).Width();
350  long nTop = 0 + 4; // Ruler::ImplDraw uses RULER_OFF (value: 3px) as offset, and Ruler::ImplFormat adds one extra pixel
351  // Somehow pPostItMgr->GetSidebarBorderWidth() returns border width already doubled
352  long nRight = nLeft + nSidebarWidth + pPostItMgr->GetSidebarBorderWidth(true);
353  long nBottom = nTop + GetRulerVirHeight() - 3;
354 
355  tools::Rectangle aRect(nLeft, nTop, nRight, nBottom);
356  return aRect;
357 }
358 
359 Color SwCommentRuler::GetFadedColor(const Color &rHighColor, const Color &rLowColor)
360 {
361  if (!maFadeTimer.IsActive())
362  return mbIsHighlighted ? rHighColor : rLowColor;
363 
364  Color aColor = rHighColor;
365  aColor.Merge(rLowColor, mnFadeRate * 255 / 100.0f);
366  return aColor;
367 }
368 
369 IMPL_LINK_NOARG(SwCommentRuler, FadeHandler, Timer *, void)
370 {
371  const int nStep = 25;
372  if ( mbIsHighlighted && mnFadeRate < 100 )
373  mnFadeRate += nStep;
374  else if ( !mbIsHighlighted && mnFadeRate > 0 )
375  mnFadeRate -= nStep;
376  else
377  return;
378 
379  Invalidate();
380 
381  if ( mnFadeRate != 0 && mnFadeRate != 100)
382  maFadeTimer.Start();
383 }
384 
385 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Point TopLeft() const
SfxViewFrame * GetViewFrame() const
long Width() const
const Color & GetShadowColor() const
SvxRulerSupportFlags
const Color & GetDialogColor() const
std::string CreateJsonNotification()
Definition: swruler.cxx:253
void UpdateCommentHelpText()
Update the tooltip text.
Definition: swruler.cxx:322
void Merge(const Color &rMergeColor, sal_uInt8 cTransparency)
const SwView & GetView() const
Definition: edtwin.hxx:241
void Create(SwFormatVertOrient &rItem, SvStream &rStrm, sal_uInt16 nVersionAbusedAsSize)
Definition: legacyitem.cxx:32
char const aUnitStr[8]
#define CONTROL_LEFT_OFFSET
Definition: swruler.cxx:32
const StyleSettings & GetStyleSettings() const
virtual void Update()
const Color & GetWorkspaceColor() const
Timer maFadeTimer
Definition: swruler.hxx:51
SfxViewShell * GetSfxViewShell() const
Definition: viewsh.hxx:444
#define CONTROL_RIGHT_OFFSET
Definition: swruler.cxx:33
unsigned long GetSidebarBorderWidth(bool bPx=false) const
Definition: PostItMgr.cxx:2092
virtual ~SwCommentRuler() override
Definition: swruler.cxx:86
virtual void Command(const CommandEvent &rCEvt) override
SwViewShell * mpViewShell
Definition: swruler.hxx:48
ScopedVclPtr< VirtualDevice > maVirDev
Definition: swruler.hxx:53
bool IsActive() const
virtual void dispose() override
sal_Int64 WinBits
IMPL_LINK_NOARG(SwCommentRuler, FadeHandler, Timer *, void)
Definition: swruler.cxx:369
void setX(long nX)
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:345
void setY(long nY)
void DrawRect(const tools::Rectangle &rRect)
virtual void MouseButtonDown(const MouseEvent &rMEvt) override
Callback function to handle a mouse button down event.
Definition: swruler.cxx:233
void SetDebugName(const char *pDebugName)
const Color & GetDarkShadowColor() const
void SetLineColor()
void clear()
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()
#define CONTROL_TRIANGLE_PAD
Definition: swruler.cxx:37
static bool GetLayoutRTL()
void NotifyKit()
Definition: swruler.cxx:304
virtual void Start() override
bool IsInside(const Point &rPOINT) const
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:97
#define CONTROL_TOP_OFFSET
Definition: swruler.cxx:34
void SetTimeout(sal_uInt64 nTimeoutMs)
virtual void dispose() override
Definition: swruler.cxx:91
long X() const
Size GetSize() const
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:359
long GetPageWidth() const
#define CONTROL_TRIANGLE_WIDTH
Definition: swruler.cxx:36
const Color & GetButtonTextColor() const
virtual void MouseMove(const MouseEvent &rMEvt) override
Callback function to handle a mouse move event.
Definition: swruler.cxx:205
bool IsLeft() const
OUString aLabel
virtual void Update() override
Update the view.
Definition: swruler.cxx:313
void SetInvokeHandler(const Link< Timer *, void > &rLink)
bool HasNotes() const
Definition: PostItMgr.cxx:2061
const Point & GetPosPixel() const
const SwPostItMgr * GetPostItMgr() const
Definition: viewsh.hxx:559
virtual void Command(const CommandEvent &rCEvt) override
Callback function to handle a context menu call (mouse right button click).
Definition: swruler.cxx:195
SwCommentRuler(SwViewShell *pViewSh, vcl::Window *pParent, SwEditWin *pWin, SvxRulerSupportFlags nRulerFlags, SfxBindings &rBindings, WinBits nWinStyle)
Definition: swruler.cxx:71
An horizontal ruler with a control for comment panel visibility for Writer.
Definition: swruler.hxx:27
unsigned long GetSidebarWidth(bool bPx=false) const
Definition: PostItMgr.cxx:2066
constexpr sal_Int64 convertTwipToMm100(sal_Int64 n)
#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:333
bool ShowNotes() const
Definition: PostItMgr.cxx:2055
void DrawCommentControl(vcl::RenderContext &rRenderContext)
Paint the comment control on VirtualDevice.
Definition: swruler.cxx:109
VclPtr< SwEditWin > mpSwWin
Definition: swruler.hxx:49
SAL_DLLPRIVATE void DrawOutDev(const Point &, const Size &, const Point &, const Size &, const Printer &)=delete
Definition: view.hxx:146
long Y() const
bool mbIsHighlighted
Definition: swruler.hxx:50