1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
3 * This file is part of the LibreOffice project.
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/.
10 // Design proposal: https://wiki.documentfoundation.org/Design/Whiteboards/Comments_Ruler_Control
12 #include <swruler.hxx>
16 #include <PostItMgr.hxx>
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>
30 #define CONTROL_BORDER_WIDTH 1
35 * Draw a little arrow / triangle with different directions
37 * \param nX left coordinate of arrow square
38 * \param nY top coordinate of arrow square
39 * \param nSize size of the long triangle side / arrow square
40 * \param Color arrow color
41 * \param bCollapsed if the arrow should display the collapsed state
43 void ImplDrawArrow(vcl::RenderContext
& rRenderContext
, tools::Long nX
, tools::Long nY
,
44 tools::Long nSize
, const Color
& rColor
, bool bCollapsed
)
46 tools::Polygon
aTrianglePolygon(4);
50 if (AllSettings::GetLayoutRTL()) // <
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);
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);
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);
73 rRenderContext
.SetLineColor();
74 rRenderContext
.SetFillColor(rColor
);
75 rRenderContext
.DrawPolygon(aTrianglePolygon
);
80 SwCommentRuler::SwCommentRuler(SwViewShell
* pViewSh
, vcl::Window
* pParent
, SwEditWin
* pWin
,
81 SvxRulerSupportFlags nRulerFlags
, SfxBindings
& rBindings
,
83 : SvxRuler(pParent
, pWin
, nRulerFlags
, rBindings
, nWinStyle
| WB_HSCROLL
)
84 , mpViewShell(pViewSh
)
86 , mbIsHighlighted(false)
87 , maFadeTimer("sw::SwCommentRuler maFadeTimer")
89 , maVirDev(VclPtr
<VirtualDevice
>::Create(*GetOutDev()))
91 // Set fading timeout: 5 x 40ms = 200ms
92 maFadeTimer
.SetTimeout(40);
93 maFadeTimer
.SetInvokeHandler(LINK(this, SwCommentRuler
, FadeHandler
));
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
);
101 SwCommentRuler::~SwCommentRuler() { disposeOnce(); }
103 void SwCommentRuler::dispose()
109 void SwCommentRuler::Paint(vcl::RenderContext
& rRenderContext
, const tools::Rectangle
& rRect
)
111 if (comphelper::LibreOfficeKit::isActive())
112 return; // no need to waste time on startup
114 SvxRuler::Paint(rRenderContext
, rRect
);
116 // Don't draw if there is not any note
117 if (mpViewShell
->GetPostItMgr() && mpViewShell
->GetPostItMgr()->HasNotes())
118 DrawCommentControl(rRenderContext
);
121 void SwCommentRuler::DrawCommentControl(vcl::RenderContext
& rRenderContext
)
123 const StyleSettings
& rStyleSettings
= rRenderContext
.GetSettings().GetStyleSettings();
124 const bool bIsCollapsed
= !mpViewShell
->GetPostItMgr()->ShowNotes();
125 const tools::Rectangle aControlRect
= GetCommentControlRegion();
127 maVirDev
->SetOutputSizePixel(aControlRect
.GetSize());
133 maVirDev
->SetFillColor(
134 GetFadedColor(rStyleSettings
.GetHighlightColor(), rStyleSettings
.GetDialogColor()));
136 maVirDev
->SetFillColor(rStyleSettings
.GetDialogColor());
137 maVirDev
->SetLineColor(rStyleSettings
.GetShadowColor());
142 maVirDev
->SetFillColor(GetFadedColor(rStyleSettings
.GetHighlightColor(),
143 rStyleSettings
.GetWorkspaceColor()));
145 maVirDev
->SetFillColor(rStyleSettings
.GetWorkspaceColor());
146 maVirDev
->SetLineColor();
148 Color aTextColor
= GetFadedColor(rStyleSettings
.GetHighlightTextColor(),
149 rStyleSettings
.GetButtonTextColor());
150 maVirDev
->SetTextColor(aTextColor
);
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;
157 Point
aLabelPos(0, (aControlRect
.GetHeight() - maVirDev
->GetTextHeight()) / 2);
158 Point
aArrowPos(0, (aControlRect
.GetHeight() - nTriangleSize
) / 2);
160 if (!AllSettings::GetLayoutRTL()) // | > Comments |
162 aArrowPos
.setX(nTrianglePad
);
163 aLabelPos
.setX(aArrowPos
.X() + nTriangleSize
+ nTrianglePad
);
165 else // RTL => | Comments < |
167 const tools::Long nLabelWidth
= maVirDev
->GetTextWidth(aLabel
);
170 aArrowPos
.setX(aControlRect
.GetWidth() - 1 - nTrianglePad
- CONTROL_BORDER_WIDTH
172 aLabelPos
.setX(aArrowPos
.X() - nTrianglePad
- nLabelWidth
);
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
);
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
);
190 // Just accept double-click outside comment control
191 void SwCommentRuler::Command(const CommandEvent
& rCEvt
)
193 Point aMousePos
= rCEvt
.GetMousePosPixel();
194 // Ignore command request if it is inside Comment Control
195 if (!mpViewShell
->GetPostItMgr() || !mpViewShell
->GetPostItMgr()->HasNotes()
196 || !GetCommentControlRegion().Contains(aMousePos
))
197 SvxRuler::Command(rCEvt
);
200 void SwCommentRuler::MouseMove(const MouseEvent
& rMEvt
)
202 SvxRuler::MouseMove(rMEvt
);
203 if (!mpViewShell
->GetPostItMgr() || !mpViewShell
->GetPostItMgr()->HasNotes())
206 UpdateCommentHelpText();
208 Point aMousePos
= rMEvt
.GetPosPixel();
209 bool bWasHighlighted
= mbIsHighlighted
;
210 mbIsHighlighted
= GetCommentControlRegion().Contains(aMousePos
);
211 if (mbIsHighlighted
!= bWasHighlighted
)
216 void SwCommentRuler::MouseButtonDown(const MouseEvent
& rMEvt
)
218 Point aMousePos
= rMEvt
.GetPosPixel();
219 if (!rMEvt
.IsLeft() || IsTracking() || !GetCommentControlRegion().Contains(aMousePos
))
221 SvxRuler::MouseButtonDown(rMEvt
);
225 // Toggle notes visibility
226 SwView
& rView
= mpSwWin
->GetView();
227 SfxRequest
aRequest(rView
.GetViewFrame(), SID_TOGGLE_NOTES
);
228 rView
.ExecViewOptions(aRequest
);
230 // It is inside comment control, so update help text
231 UpdateCommentHelpText();
236 void SwCommentRuler::CreateJsonNotification(tools::JsonWriter
& rJsonWriter
)
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
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.
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()));
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()));
264 auto tabsNode
= rJsonWriter
.startNode("tabs");
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())
270 auto tabNode
= rJsonWriter
.startNode("");
271 rJsonWriter
.put("position", convertTwipToMm100(tab
.nPos
));
272 rJsonWriter
.put("style", tab
.nStyle
);
276 RulerUnitData aUnitData
= GetCurrentRulerUnit();
277 rJsonWriter
.put("unit", aUnitData
.aUnitStr
);
280 void SwCommentRuler::NotifyKit()
282 if (!comphelper::LibreOfficeKit::isActive())
285 tools::JsonWriter aJsonWriter
;
286 CreateJsonNotification(aJsonWriter
);
287 OString pJsonData
= aJsonWriter
.finishAndGetAsOString();
288 mpViewShell
->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_RULER_UPDATE
,
292 void SwCommentRuler::Update()
294 tools::Rectangle aPreviousControlRect
= GetCommentControlRegion();
296 if (aPreviousControlRect
!= GetCommentControlRegion())
301 void SwCommentRuler::UpdateCommentHelpText()
303 TranslateId pTooltipResId
;
304 if (mpViewShell
->GetPostItMgr()->ShowNotes())
305 pTooltipResId
= STR_HIDE_COMMENTS
;
307 pTooltipResId
= STR_SHOW_COMMENTS
;
308 SetQuickHelpText(SwResId(pTooltipResId
));
311 // TODO Make Ruler return its central rectangle instead of margins.
312 tools::Rectangle
SwCommentRuler::GetCommentControlRegion()
314 SwPostItMgr
* pPostItMgr
= mpViewShell
->GetPostItMgr();
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
320 return tools::Rectangle();
322 const tools::ULong nSidebarWidth
= pPostItMgr
->GetSidebarWidth(true);
324 //FIXME When the page width is larger then screen, the ruler is misplaced by one pixel
325 tools::Long nLeft
= GetPageOffset();
327 nLeft
+= GetBorderOffset() - nSidebarWidth
;
329 nLeft
+= GetWinOffset() + mpSwWin
->LogicToPixel(Size(GetPageWidth(), 0)).Width();
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;
337 tools::Rectangle
aRect(nLeft
, nTop
, nRight
, nBottom
);
341 Color
SwCommentRuler::GetFadedColor(const Color
& rHighColor
, const Color
& rLowColor
)
343 if (!maFadeTimer
.IsActive())
344 return mbIsHighlighted
? rHighColor
: rLowColor
;
346 Color aColor
= rHighColor
;
347 aColor
.Merge(rLowColor
, mnFadeRate
* 255 / 100.0f
);
351 IMPL_LINK_NOARG(SwCommentRuler
, FadeHandler
, Timer
*, void)
353 const int nStep
= 25;
354 if (mbIsHighlighted
&& mnFadeRate
< 100)
356 else if (!mbIsHighlighted
&& mnFadeRate
> 0)
363 if (mnFadeRate
!= 0 && mnFadeRate
!= 100)
367 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */