Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sw/source/uibase/misc/swruler.cxx
Line
Count
Source
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 <wrtsh.hxx>
20
#include <cmdid.h>
21
#include <sfx2/request.hxx>
22
#include <vcl/commandevent.hxx>
23
#include <vcl/event.hxx>
24
#include <vcl/ptrstyle.hxx>
25
#include <vcl/window.hxx>
26
#include <vcl/settings.hxx>
27
#include <strings.hrc>
28
#include <comphelper/lok.hxx>
29
30
0
#define CONTROL_BORDER_WIDTH 1
31
32
namespace
33
{
34
/**
35
 * Draw a little arrow / triangle with different directions
36
 *
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
42
 */
43
void ImplDrawArrow(vcl::RenderContext& rRenderContext, tools::Long nX, tools::Long nY,
44
                   tools::Long nSize, const Color& rColor, bool bCollapsed)
45
0
{
46
0
    tools::Polygon aTrianglePolygon(4);
47
48
0
    if (bCollapsed)
49
0
    {
50
0
        if (AllSettings::GetLayoutRTL()) // <
51
0
        {
52
0
            aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 0);
53
0
            aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 1);
54
0
            aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 2);
55
0
            aTrianglePolygon.SetPoint({ nX + nSize / 2, nY }, 3);
56
0
        }
57
0
        else // >
58
0
        {
59
0
            aTrianglePolygon.SetPoint({ nX, nY }, 0);
60
0
            aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize / 2 }, 1);
61
0
            aTrianglePolygon.SetPoint({ nX, nY + nSize }, 2);
62
0
            aTrianglePolygon.SetPoint({ nX, nY }, 3);
63
0
        }
64
0
    }
65
0
    else // v
66
0
    {
67
0
        aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 0);
68
0
        aTrianglePolygon.SetPoint({ nX + nSize, nY + nSize / 2 }, 1);
69
0
        aTrianglePolygon.SetPoint({ nX + nSize / 2, nY + nSize }, 2);
70
0
        aTrianglePolygon.SetPoint({ nX, nY + nSize / 2 }, 3);
71
0
    }
72
73
0
    rRenderContext.SetLineColor();
74
0
    rRenderContext.SetFillColor(rColor);
75
0
    rRenderContext.DrawPolygon(aTrianglePolygon);
76
0
}
77
}
78
79
// Constructor
80
SwCommentRuler::SwCommentRuler(SwViewShell* pViewSh, vcl::Window* pParent, SwEditWin* pWin,
81
                               SvxRulerSupportFlags nRulerFlags, SfxBindings& rBindings,
82
                               WinBits nWinStyle)
83
3.28k
    : SvxRuler(pParent, pWin, nRulerFlags, rBindings, nWinStyle | WB_HSCROLL)
84
3.28k
    , mpViewShell(pViewSh)
85
3.28k
    , mpSwWin(pWin)
86
3.28k
    , mbIsDrag(false)
87
3.28k
    , mbIsHighlighted(false)
88
3.28k
    , maFadeTimer("sw::SwCommentRuler maFadeTimer")
89
3.28k
    , mnFadeRate(0)
90
3.28k
    , maVirDev(VclPtr<VirtualDevice>::Create(*GetOutDev()))
91
3.28k
{
92
    // Set fading timeout: 5 x 40ms = 200ms
93
3.28k
    maFadeTimer.SetTimeout(40);
94
3.28k
    maFadeTimer.SetInvokeHandler(LINK(this, SwCommentRuler, FadeHandler));
95
96
    // we have a little bit more space, as we don't draw ruler ticks
97
3.28k
    vcl::Font aFont(maVirDev->GetFont());
98
3.28k
    aFont.SetFontHeight(aFont.GetFontHeight() + 1);
99
3.28k
    maVirDev->SetFont(aFont);
100
3.28k
}
Unexecuted instantiation: SwCommentRuler::SwCommentRuler(SwViewShell*, vcl::Window*, SwEditWin*, SvxRulerSupportFlags, SfxBindings&, long)
SwCommentRuler::SwCommentRuler(SwViewShell*, vcl::Window*, SwEditWin*, SvxRulerSupportFlags, SfxBindings&, long)
Line
Count
Source
83
3.28k
    : SvxRuler(pParent, pWin, nRulerFlags, rBindings, nWinStyle | WB_HSCROLL)
84
3.28k
    , mpViewShell(pViewSh)
85
3.28k
    , mpSwWin(pWin)
86
3.28k
    , mbIsDrag(false)
87
3.28k
    , mbIsHighlighted(false)
88
3.28k
    , maFadeTimer("sw::SwCommentRuler maFadeTimer")
89
3.28k
    , mnFadeRate(0)
90
3.28k
    , maVirDev(VclPtr<VirtualDevice>::Create(*GetOutDev()))
91
3.28k
{
92
    // Set fading timeout: 5 x 40ms = 200ms
93
3.28k
    maFadeTimer.SetTimeout(40);
94
3.28k
    maFadeTimer.SetInvokeHandler(LINK(this, SwCommentRuler, FadeHandler));
95
96
    // we have a little bit more space, as we don't draw ruler ticks
97
3.28k
    vcl::Font aFont(maVirDev->GetFont());
98
3.28k
    aFont.SetFontHeight(aFont.GetFontHeight() + 1);
99
3.28k
    maVirDev->SetFont(aFont);
100
3.28k
}
101
102
3.28k
SwCommentRuler::~SwCommentRuler() { disposeOnce(); }
103
104
void SwCommentRuler::dispose()
105
3.28k
{
106
3.28k
    mpSwWin.reset();
107
3.28k
    SvxRuler::dispose();
108
3.28k
}
109
110
sw::sidebarwindows::SidebarPosition SwCommentRuler::GetSidebarPosition()
111
692
{
112
692
    if (SwPostItMgr* pPostItMgr = mpViewShell->GetPostItMgr())
113
692
        return pPostItMgr->GetSidebarPos(mpSwWin->GetView().GetWrtShell().GetCursorDocPos());
114
0
    return sw::sidebarwindows::SidebarPosition::NONE;
115
692
}
116
117
void SwCommentRuler::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
118
0
{
119
0
    if (comphelper::LibreOfficeKit::isActive())
120
0
        return; // no need to waste time on startup
121
122
0
    SvxRuler::Paint(rRenderContext, rRect);
123
124
    // Don't draw if there is not any note
125
0
    if (mpViewShell->GetPostItMgr() && mpViewShell->GetPostItMgr()->HasNotes())
126
0
        DrawCommentControl(rRenderContext);
127
0
}
128
129
void SwCommentRuler::DrawCommentControl(vcl::RenderContext& rRenderContext)
130
0
{
131
0
    const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
132
0
    const bool bIsCollapsed = !mpViewShell->GetPostItMgr()->ShowNotes();
133
0
    const tools::Rectangle aControlRect = GetCommentControlRegion();
134
135
0
    maVirDev->SetOutputSizePixel(aControlRect.GetSize());
136
137
    // set colors
138
0
    if (!bIsCollapsed)
139
0
    {
140
0
        if (mbIsHighlighted)
141
0
            maVirDev->SetFillColor(
142
0
                GetFadedColor(rStyleSettings.GetHighlightColor(), rStyleSettings.GetDialogColor()));
143
0
        else
144
0
            maVirDev->SetFillColor(rStyleSettings.GetDialogColor());
145
0
        maVirDev->SetLineColor(rStyleSettings.GetShadowColor());
146
0
    }
147
0
    else
148
0
    {
149
0
        if (mbIsHighlighted)
150
0
            maVirDev->SetFillColor(GetFadedColor(rStyleSettings.GetHighlightColor(),
151
0
                                                 rStyleSettings.GetWorkspaceColor()));
152
0
        else
153
0
            maVirDev->SetFillColor(rStyleSettings.GetWorkspaceColor());
154
0
        maVirDev->SetLineColor();
155
0
    }
156
0
    Color aTextColor = GetFadedColor(rStyleSettings.GetHighlightTextColor(),
157
0
                                     rStyleSettings.GetButtonTextColor());
158
0
    maVirDev->SetTextColor(aTextColor);
159
160
    // calculate label and arrow positions
161
0
    const OUString aLabel = SwResId(STR_COMMENTS_LABEL);
162
0
    const tools::Long nTriangleSize = maVirDev->GetTextHeight() / 2 + 1;
163
0
    const tools::Long nTrianglePad = maVirDev->GetTextHeight() / 2;
164
165
0
    Point aLabelPos(0, (aControlRect.GetHeight() - maVirDev->GetTextHeight()) / 2);
166
0
    Point aArrowPos(0, (aControlRect.GetHeight() - nTriangleSize) / 2);
167
168
0
    if (!AllSettings::GetLayoutRTL()) // | > Comments |
169
0
    {
170
0
        aArrowPos.setX(nTrianglePad);
171
0
        aLabelPos.setX(aArrowPos.X() + nTriangleSize + nTrianglePad / 2);
172
0
    }
173
0
    else // RTL => | Comments < |
174
0
    {
175
0
        const tools::Long nLabelWidth = maVirDev->GetTextWidth(aLabel);
176
0
        if (!bIsCollapsed)
177
0
        {
178
0
            aArrowPos.setX(aControlRect.GetWidth() - 1 - nTrianglePad - CONTROL_BORDER_WIDTH
179
0
                           - nTriangleSize);
180
0
            aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
181
0
        }
182
0
        else
183
0
        {
184
            // if comments are collapsed, left align the text, because otherwise it's very likely to be invisible
185
0
            aArrowPos.setX(nLabelWidth + nTrianglePad + nTriangleSize);
186
0
            aLabelPos.setX(aArrowPos.X() - nTrianglePad - nLabelWidth);
187
0
        }
188
0
    }
189
190
    // draw control
191
0
    maVirDev->DrawRect(tools::Rectangle(Point(), aControlRect.GetSize()));
192
0
    maVirDev->DrawText(aLabelPos, aLabel);
193
0
    ImplDrawArrow(*maVirDev, aArrowPos.X(), aArrowPos.Y(), nTriangleSize, aTextColor, bIsCollapsed);
194
0
    rRenderContext.DrawOutDev(aControlRect.TopLeft(), aControlRect.GetSize(), Point(),
195
0
                              aControlRect.GetSize(), *maVirDev);
196
0
}
197
198
// Just accept double-click outside comment control
199
void SwCommentRuler::Command(const CommandEvent& rCEvt)
200
0
{
201
0
    Point aMousePos = rCEvt.GetMousePosPixel();
202
    // Ignore command request if it is inside Comment Control
203
0
    if (!mpViewShell->GetPostItMgr() || !mpViewShell->GetPostItMgr()->HasNotes()
204
0
        || !GetCommentControlRegion().Contains(aMousePos))
205
0
        SvxRuler::Command(rCEvt);
206
0
}
207
208
void SwCommentRuler::MouseMove(const MouseEvent& rMEvt)
209
0
{
210
0
    if (mbIsDrag)
211
0
    {
212
0
        mpSwWin->DrawCommentGuideLine(rMEvt.GetPosPixel());
213
0
        return;
214
0
    }
215
216
0
    SvxRuler::MouseMove(rMEvt);
217
0
    if (!mpViewShell->GetPostItMgr() || !mpViewShell->GetPostItMgr()->HasNotes())
218
0
        return;
219
220
0
    UpdateCommentHelpText();
221
222
0
    Point aMousePos = rMEvt.GetPosPixel();
223
0
    if (GetDragArea().Contains(aMousePos))
224
0
        SetPointer(PointerStyle::HSizeBar);
225
226
0
    bool bWasHighlighted = mbIsHighlighted;
227
0
    mbIsHighlighted = GetCommentControlRegion().Contains(aMousePos);
228
0
    if (mbIsHighlighted != bWasHighlighted)
229
        // Do start fading
230
0
        maFadeTimer.Start();
231
0
}
232
233
void SwCommentRuler::MouseButtonDown(const MouseEvent& rMEvt)
234
0
{
235
    // If right-click while dragging to resize the comment width, stop resizing
236
0
    if (mbIsDrag && rMEvt.GetButtons() == MOUSE_RIGHT)
237
0
    {
238
0
        ReleaseMouse();
239
0
        mpSwWin->ReleaseCommentGuideLine();
240
0
        mbIsDrag = false;
241
0
        return;
242
0
    }
243
244
0
    Point aMousePos = rMEvt.GetPosPixel();
245
0
    if (!rMEvt.IsLeft() || IsTracking()
246
0
        || (!GetCommentControlRegion().Contains(aMousePos) && !GetDragArea().Contains(aMousePos)))
247
0
    {
248
0
        SvxRuler::MouseButtonDown(rMEvt);
249
0
        return;
250
0
    }
251
252
0
    if (GetDragArea().Contains(aMousePos))
253
0
    {
254
0
        mbIsDrag = true;
255
0
        CaptureMouse();
256
0
    }
257
0
    else
258
0
    {
259
        // Toggle notes visibility
260
0
        SwView& rView = mpSwWin->GetView();
261
0
        SfxRequest aRequest(rView.GetViewFrame(), SID_TOGGLE_NOTES);
262
0
        rView.ExecViewOptions(aRequest);
263
264
        // It is inside comment control, so update help text
265
0
        UpdateCommentHelpText();
266
0
    }
267
268
0
    Invalidate();
269
0
}
270
271
void SwCommentRuler::MouseButtonUp(const MouseEvent& rMEvt)
272
0
{
273
0
    if (!mbIsDrag)
274
0
    {
275
0
        SvxRuler::MouseButtonUp(rMEvt);
276
0
        return;
277
0
    }
278
279
0
    mpSwWin->SetSidebarWidth(rMEvt.GetPosPixel());
280
0
    ReleaseMouse();
281
0
    mpSwWin->ReleaseCommentGuideLine();
282
0
    mbIsDrag = false;
283
0
    Invalidate();
284
0
}
285
286
void SwCommentRuler::Update()
287
346
{
288
346
    tools::Rectangle aPreviousControlRect = GetCommentControlRegion();
289
346
    SvxRuler::Update();
290
346
    if (aPreviousControlRect != GetCommentControlRegion())
291
0
        Invalidate();
292
346
}
293
294
void SwCommentRuler::UpdateCommentHelpText()
295
0
{
296
0
    TranslateId pTooltipResId;
297
0
    if (mpViewShell->GetPostItMgr()->ShowNotes())
298
0
        pTooltipResId = STR_HIDE_COMMENTS;
299
0
    else
300
0
        pTooltipResId = STR_SHOW_COMMENTS;
301
0
    SetQuickHelpText(SwResId(pTooltipResId));
302
0
}
303
304
tools::Rectangle SwCommentRuler::GetDragArea()
305
0
{
306
0
    tools::Rectangle base(GetCommentControlRegion());
307
0
    if (GetSidebarPosition() == sw::sidebarwindows::SidebarPosition::LEFT)
308
0
        base.Move(-5, 0);
309
0
    else
310
0
        base.Move(Size(base.GetWidth() - 5, 0));
311
0
    base.SetWidth(10);
312
0
    return base;
313
0
}
314
315
// TODO Make Ruler return its central rectangle instead of margins.
316
tools::Rectangle SwCommentRuler::GetCommentControlRegion()
317
692
{
318
692
    SwPostItMgr* pPostItMgr = mpViewShell->GetPostItMgr();
319
320
    //rhbz#1006850 When the SwPostItMgr ctor is called from SwView::SwView it
321
    //triggers an update of the uiview, but the result of the ctor hasn't been
322
    //set into the mpViewShell yet, so GetPostItMgr is temporarily still NULL
323
692
    if (!pPostItMgr)
324
0
        return tools::Rectangle();
325
326
692
    const tools::ULong nSidebarWidth = pPostItMgr->GetSidebarWidth(true);
327
328
    //FIXME When the page width is larger then screen, the ruler is misplaced by one pixel
329
692
    tools::Long nLeft = GetPageOffset();
330
692
    if (GetSidebarPosition() == sw::sidebarwindows::SidebarPosition::LEFT)
331
0
        nLeft += GetBorderOffset() - nSidebarWidth;
332
692
    else
333
692
        nLeft += GetWinOffset() + mpSwWin->LogicToPixel(Size(GetPageWidth(), 0)).Width();
334
335
    // Ruler::ImplDraw uses RULER_OFF (value: 3px) as offset, and Ruler::ImplFormat adds one extra pixel
336
692
    tools::Long nTop = 4;
337
    // Somehow pPostItMgr->GetSidebarBorderWidth() returns border width already doubled
338
692
    tools::Long nRight = nLeft + nSidebarWidth + pPostItMgr->GetSidebarBorderWidth(true);
339
692
    tools::Long nBottom = nTop + GetRulerVirHeight() - 3;
340
341
692
    tools::Rectangle aRect(nLeft, nTop, nRight, nBottom);
342
692
    return aRect;
343
692
}
344
345
Color SwCommentRuler::GetFadedColor(const Color& rHighColor, const Color& rLowColor)
346
0
{
347
0
    if (!maFadeTimer.IsActive())
348
0
        return mbIsHighlighted ? rHighColor : rLowColor;
349
350
0
    Color aColor = rHighColor;
351
0
    aColor.Merge(rLowColor, mnFadeRate * 255 / 100.0f);
352
0
    return aColor;
353
0
}
354
355
IMPL_LINK_NOARG(SwCommentRuler, FadeHandler, Timer*, void)
356
0
{
357
0
    const int nStep = 25;
358
0
    if (mbIsHighlighted && mnFadeRate < 100)
359
0
        mnFadeRate += nStep;
360
0
    else if (!mbIsHighlighted && mnFadeRate > 0)
361
0
        mnFadeRate -= nStep;
362
0
    else
363
0
        return;
364
365
0
    Invalidate();
366
367
0
    if (mnFadeRate != 0 && mnFadeRate != 100)
368
0
        maFadeTimer.Start();
369
0
}
370
371
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */