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 <viewopt.hxx>
16 #include <edtwin.hxx>
17 #include <PostItMgr.hxx>
18 #include <view.hxx>
19 #include <cmdid.h>
20 #include <sfx2/request.hxx>
21 #include <vcl/commandevent.hxx>
22 #include <vcl/event.hxx>
23 #include <vcl/ptrstyle.hxx>
24 #include <vcl/window.hxx>
25 #include <vcl/settings.hxx>
26 #include <strings.hrc>
27 #include <comphelper/lok.hxx>
28
29 #define CONTROL_BORDER_WIDTH 1
30
31 namespace
32 {
33 /**
34 * Draw a little arrow / triangle with different directions
35 *
36 * \param nX left coordinate of arrow square
37 * \param nY top coordinate of arrow square
38 * \param nSize size of the long triangle side / arrow square
39 * \param Color arrow color
40 * \param bCollapsed if the arrow should display the collapsed state
41 */
ImplDrawArrow(vcl::RenderContext & rRenderContext,tools::Long nX,tools::Long nY,tools::Long nSize,const Color & rColor,bool bCollapsed)42 void ImplDrawArrow(vcl::RenderContext& rRenderContext, tools::Long nX, tools::Long nY,
43 tools::Long nSize, const Color& rColor, bool bCollapsed)
44 {
45 tools::Polygon aTrianglePolygon(4);
46
47 if (bCollapsed)
48 {
49 if (AllSettings::GetLayoutRTL()) // <
50 {
51 aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 0);
52 aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 1);
53 aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 2);
54 aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 3);
55 }
56 else // >
57 {
58 aTrianglePolygon.SetPoint({ nX, nY }, 0);
59 aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize / 2 }, 1);
60 aTrianglePolygon.SetPoint({ nX, nY + nSize }, 2);
61 aTrianglePolygon.SetPoint({ nX, nY }, 3);
62 }
63 }
64 else // v
65 {
66 aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 0);
67 aTrianglePolygon.SetPoint({ nX + nSize, nY + nSize / 2 }, 1);
68 aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 2);
69 aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 3);
70 }
71
72 rRenderContext.SetLineColor();
73 rRenderContext.SetFillColor(rColor);
74 rRenderContext.DrawPolygon(aTrianglePolygon);
75 }
76 }
77
78 // Constructor
SwCommentRuler(SwViewShell * pViewSh,vcl::Window * pParent,SwEditWin * pWin,SvxRulerSupportFlags nRulerFlags,SfxBindings & rBindings,WinBits nWinStyle)79 SwCommentRuler::SwCommentRuler(SwViewShell* pViewSh, vcl::Window* pParent, SwEditWin* pWin,
80 SvxRulerSupportFlags nRulerFlags, SfxBindings& rBindings,
81 WinBits nWinStyle)
82 : SvxRuler(pParent, pWin, nRulerFlags, rBindings, nWinStyle | WB_HSCROLL)
83 , mpViewShell(pViewSh)
84 , mpSwWin(pWin)
85 , mbIsDrag(false)
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
92 maFadeTimer.SetTimeout(40);
93 maFadeTimer.SetInvokeHandler(LINK(this, SwCommentRuler, FadeHandler));
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
~SwCommentRuler()101 SwCommentRuler::~SwCommentRuler() { disposeOnce(); }
102
dispose()103 void SwCommentRuler::dispose()
104 {
105 mpSwWin.clear();
106 SvxRuler::dispose();
107 }
108
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle & rRect)109 void SwCommentRuler::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
110 {
111 if (comphelper::LibreOfficeKit::isActive())
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
117 if (mpViewShell->GetPostItMgr() && mpViewShell->GetPostItMgr()->HasNotes())
118 DrawCommentControl(rRenderContext);
119 }
120
DrawCommentControl(vcl::RenderContext & rRenderContext)121 void SwCommentRuler::DrawCommentControl(vcl::RenderContext& rRenderContext)
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() / 2;
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 / 2);
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
Command(const CommandEvent & rCEvt)191 void SwCommentRuler::Command(const CommandEvent& rCEvt)
192 {
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);
198 }
199
MouseMove(const MouseEvent & rMEvt)200 void SwCommentRuler::MouseMove(const MouseEvent& rMEvt)
201 {
202 if (mbIsDrag)
203 return;
204
205 SvxRuler::MouseMove(rMEvt);
206 if (!mpViewShell->GetPostItMgr() || !mpViewShell->GetPostItMgr()->HasNotes())
207 return;
208
209 UpdateCommentHelpText();
210
211 Point aMousePos = rMEvt.GetPosPixel();
212 if (GetDragArea().Contains(aMousePos))
213 SetPointer(PointerStyle::HSizeBar);
214
215 bool bWasHighlighted = mbIsHighlighted;
216 mbIsHighlighted = GetCommentControlRegion().Contains(aMousePos);
217 if (mbIsHighlighted != bWasHighlighted)
218 // Do start fading
219 maFadeTimer.Start();
220 }
221
MouseButtonDown(const MouseEvent & rMEvt)222 void SwCommentRuler::MouseButtonDown(const MouseEvent& rMEvt)
223 {
224 Point aMousePos = rMEvt.GetPosPixel();
225 if (!rMEvt.IsLeft() || IsTracking()
226 || (!GetCommentControlRegion().Contains(aMousePos) && !GetDragArea().Contains(aMousePos)))
227 {
228 SvxRuler::MouseButtonDown(rMEvt);
229 return;
230 }
231
232 if (GetDragArea().Contains(aMousePos))
233 {
234 mbIsDrag = true;
235 }
236 else
237 {
238 // Toggle notes visibility
239 SwView& rView = mpSwWin->GetView();
240 SfxRequest aRequest(rView.GetViewFrame(), SID_TOGGLE_NOTES);
241 rView.ExecViewOptions(aRequest);
242
243 // It is inside comment control, so update help text
244 UpdateCommentHelpText();
245 }
246
247 Invalidate();
248 }
249
MouseButtonUp(const MouseEvent & rMEvt)250 void SwCommentRuler::MouseButtonUp(const MouseEvent& rMEvt)
251 {
252 if (!mbIsDrag)
253 {
254 SvxRuler::MouseButtonUp(rMEvt);
255 return;
256 }
257 mpViewShell->GetPostItMgr()->SetSidebarWidth(rMEvt.GetPosPixel());
258 mbIsDrag = false;
259 Invalidate();
260 }
261
Update()262 void SwCommentRuler::Update()
263 {
264 tools::Rectangle aPreviousControlRect = GetCommentControlRegion();
265 SvxRuler::Update();
266 if (aPreviousControlRect != GetCommentControlRegion())
267 Invalidate();
268 }
269
UpdateCommentHelpText()270 void SwCommentRuler::UpdateCommentHelpText()
271 {
272 TranslateId pTooltipResId;
273 if (mpViewShell->GetPostItMgr()->ShowNotes())
274 pTooltipResId = STR_HIDE_COMMENTS;
275 else
276 pTooltipResId = STR_SHOW_COMMENTS;
277 SetQuickHelpText(SwResId(pTooltipResId));
278 }
279
GetDragArea()280 tools::Rectangle SwCommentRuler::GetDragArea()
281 {
282 tools::Rectangle base(GetCommentControlRegion());
283 base.Move(Size(base.GetWidth() - 5, 0));
284 base.SetWidth(10);
285 return base;
286 }
287
288 // TODO Make Ruler return its central rectangle instead of margins.
GetCommentControlRegion()289 tools::Rectangle SwCommentRuler::GetCommentControlRegion()
290 {
291 SwPostItMgr* pPostItMgr = mpViewShell->GetPostItMgr();
292
293 //rhbz#1006850 When the SwPostItMgr ctor is called from SwView::SwView it
294 //triggers an update of the uiview, but the result of the ctor hasn't been
295 //set into the mpViewShell yet, so GetPostItMgr is temporarily still NULL
296 if (!pPostItMgr)
297 return tools::Rectangle();
298
299 const tools::ULong nSidebarWidth = pPostItMgr->GetSidebarWidth(true);
300
301 //FIXME When the page width is larger then screen, the ruler is misplaced by one pixel
302 tools::Long nLeft = GetPageOffset();
303 if (GetTextRTL())
304 nLeft += GetBorderOffset() - nSidebarWidth;
305 else
306 nLeft += GetWinOffset() + mpSwWin->LogicToPixel(Size(GetPageWidth(), 0)).Width();
307
308 // Ruler::ImplDraw uses RULER_OFF (value: 3px) as offset, and Ruler::ImplFormat adds one extra pixel
309 tools::Long nTop = 4;
310 // Somehow pPostItMgr->GetSidebarBorderWidth() returns border width already doubled
311 tools::Long nRight = nLeft + nSidebarWidth + pPostItMgr->GetSidebarBorderWidth(true);
312 tools::Long nBottom = nTop + GetRulerVirHeight() - 3;
313
314 tools::Rectangle aRect(nLeft, nTop, nRight, nBottom);
315 return aRect;
316 }
317
GetFadedColor(const Color & rHighColor,const Color & rLowColor)318 Color SwCommentRuler::GetFadedColor(const Color& rHighColor, const Color& rLowColor)
319 {
320 if (!maFadeTimer.IsActive())
321 return mbIsHighlighted ? rHighColor : rLowColor;
322
323 Color aColor = rHighColor;
324 aColor.Merge(rLowColor, mnFadeRate * 255 / 100.0f);
325 return aColor;
326 }
327
IMPL_LINK_NOARG(SwCommentRuler,FadeHandler,Timer *,void)328 IMPL_LINK_NOARG(SwCommentRuler, FadeHandler, Timer*, void)
329 {
330 const int nStep = 25;
331 if (mbIsHighlighted && mnFadeRate < 100)
332 mnFadeRate += nStep;
333 else if (!mbIsHighlighted && mnFadeRate > 0)
334 mnFadeRate -= nStep;
335 else
336 return;
337
338 Invalidate();
339
340 if (mnFadeRate != 0 && mnFadeRate != 100)
341 maFadeTimer.Start();
342 }
343
344 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
345