Coverage Report

Created: 2025-12-08 09:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/diagram/IDiagramHelper.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <svx/diagram/IDiagramHelper.hxx>
21
#include <svx/svdogrp.hxx>
22
#include <svx/svdhdl.hxx>
23
#include <svx/svdmrkv.hxx>
24
#include <svx/svdpagv.hxx>
25
#include <svx/sdrpagewindow.hxx>
26
#include <svx/sdrpaintwindow.hxx>
27
#include <basegfx/polygon/b2dpolygontools.hxx>
28
#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
29
#include <drawinglayer/primitive2d/primitivetools2d.hxx>
30
#include <svx/sdr/overlay/overlaymanager.hxx>
31
#include <drawinglayer/attribute/lineattribute.hxx>
32
#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
33
#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
34
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
35
#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
36
#include <comphelper/dispatchcommand.hxx>
37
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
38
#include <officecfg/Office/Common.hxx>
39
#include <svx/strings.hrc>
40
#include <svx/dialmgr.hxx>
41
42
namespace {
43
44
// helper to create the geometry for a rounded polygon, maybe
45
// containing a Lap positioned inside top-left for some text
46
basegfx::B2DPolygon createRoundedPolygon(
47
    const basegfx::B2DRange& rRange,
48
    double fDistance,
49
    bool bCreateLap,
50
    double fTextWidth)
51
0
{
52
0
    basegfx::B2DPolygon aRetval;
53
54
    // TopLeft rounded edge
55
0
    aRetval.append(
56
0
        basegfx::utils::createPolygonFromEllipseSegment(
57
0
            basegfx::B2DPoint(rRange.getMinX(), rRange.getMinY()),
58
0
            fDistance,
59
0
            fDistance,
60
0
            M_PI * 1.0,
61
0
            M_PI * 1.5));
62
63
    // create Lap topLeft inside
64
0
    if(bCreateLap)
65
0
    {
66
0
        const double fLapLeft(rRange.getMinX() + fDistance);
67
0
        double fLapRight(rRange.getMinX() + (rRange.getWidth() * 0.5)  - fDistance);
68
0
        const double fLapTop(rRange.getMinY() - fDistance);
69
0
        const double fLapBottom(fLapTop + (fDistance * 2.0));
70
0
        const double fExtendedTextWidth(fTextWidth + (fDistance * 3.0));
71
72
0
        if(0.0 != fExtendedTextWidth && fLapLeft + fExtendedTextWidth < fLapRight)
73
0
        {
74
0
            fLapRight = fLapLeft + fExtendedTextWidth;
75
0
        }
76
77
0
        aRetval.append(basegfx::B2DPoint(fLapLeft, fLapTop));
78
0
        aRetval.append(basegfx::B2DPoint(fLapLeft + (fDistance * 0.5), fLapBottom));
79
0
        aRetval.append(basegfx::B2DPoint(fLapRight - (fDistance * 0.5), fLapBottom));
80
0
        aRetval.append(basegfx::B2DPoint(fLapRight, fLapTop));
81
0
    }
82
83
    // TopRight rounded edge
84
0
    aRetval.append(
85
0
        basegfx::utils::createPolygonFromEllipseSegment(
86
0
            basegfx::B2DPoint(rRange.getMaxX(), rRange.getMinY()),
87
0
            fDistance,
88
0
            fDistance,
89
0
            M_PI * 1.5,
90
0
            M_PI * 0.0));
91
92
    // BottomRight rounded edge
93
0
    aRetval.append(
94
0
        basegfx::utils::createPolygonFromEllipseSegment(
95
0
            basegfx::B2DPoint(rRange.getMaxX(), rRange.getMaxY()),
96
0
            fDistance,
97
0
            fDistance,
98
0
            M_PI * 0.0,
99
0
            M_PI * 0.5));
100
101
    // BottomLeft rounded edge
102
0
    aRetval.append(
103
0
        basegfx::utils::createPolygonFromEllipseSegment(
104
0
            basegfx::B2DPoint(rRange.getMinX(), rRange.getMaxY()),
105
0
            fDistance,
106
0
            fDistance,
107
0
            M_PI * 0.5,
108
0
            M_PI * 1.0));
109
110
0
    aRetval.setClosed(true);
111
112
0
    return aRetval;
113
0
}
114
115
// helper primitive to create/show the overlay geometry for a DynamicDiagram
116
class OverlayDiagramPrimitive final : public drawinglayer::primitive2d::DiscreteMetricDependentPrimitive2D
117
{
118
private:
119
    basegfx::B2DHomMatrix maTransformation;  // object dimensions
120
    double mfDiscreteDistance; // distance from object in pixels
121
    double mfDiscreteGap; // gap/width of visualization in pixels
122
    Color maColor;  // base color (made lighter/darker as needed, should be system selection color)
123
124
    virtual drawinglayer::primitive2d::Primitive2DReference create2DDecomposition(
125
        const drawinglayer::geometry::ViewInformation2D& rViewInformation) const override;
126
127
public:
128
    OverlayDiagramPrimitive(
129
        const basegfx::B2DHomMatrix& rTransformation,
130
        double fDiscreteDistance,
131
        double fDiscreteGap,
132
        Color const & rColor);
133
134
    virtual sal_uInt32 getPrimitive2DID() const override;
135
};
136
137
drawinglayer::primitive2d::Primitive2DReference OverlayDiagramPrimitive::create2DDecomposition(
138
    const drawinglayer::geometry::ViewInformation2D& /*rViewInformation*/) const
139
0
{
140
    // get the dimensions. Do *not* take rotation/shear into account,
141
    // this is intended to be a pure expanded/frame visualization as
142
    // needed in UI for simplified visualization
143
0
    basegfx::B2DRange aRange(0.0, 0.0, 1.0, 1.0);
144
0
    aRange.transform(maTransformation);
145
146
0
    basegfx::B2DPolyPolygon aPolyPolygon;
147
0
    const double fInnerDistance(mfDiscreteDistance * getDiscreteUnit());
148
0
    const double fOuterDistance((mfDiscreteDistance + mfDiscreteGap) * getDiscreteUnit());
149
0
    bool bCreateLap(true);
150
0
    basegfx::B2DPolyPolygon aTextAsPolyPolygon;
151
0
    double fTextWidth(0.0);
152
153
    // initially try to create lap
154
0
    if(bCreateLap)
155
0
    {
156
        // take a resource text (for now existing one that fits)
157
0
        const OUString aName(SvxResId(RID_STR_DATANAV_EDIT_ELEMENT));
158
0
        drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
159
0
        basegfx::B2DPolyPolygonVector aTarget;
160
0
        std::vector<double> aDXArray;
161
162
        // to simplify things for now, do not create a TextSimplePortionPrimitive2D
163
        // and needed FontAttribute, just get the TextOutlines as geometry
164
0
        aTextLayouter.getTextOutlines(
165
0
            aTarget,
166
0
            aName,
167
0
            0,
168
0
            aName.getLength(),
169
0
            aDXArray,
170
0
            {});
171
172
        // put into one PolyPolygon (also simplification - overlapping chars
173
        // may create XOR gaps, so these exist for a reason, but low probability)
174
0
        for (auto const& elem : aTarget)
175
0
        {
176
0
            aTextAsPolyPolygon.append(elem);
177
0
        }
178
179
        // get text dimensions & transform to destination
180
0
        const basegfx::B2DRange aTextRange(aTextAsPolyPolygon.getB2DRange());
181
0
        basegfx::B2DHomMatrix aTextTransform;
182
183
0
        aTextTransform.translate(aTextRange.getMinX(), aTextRange.getMinY());
184
0
        const double fTargetTextHeight((mfDiscreteDistance + mfDiscreteGap - 2.0) * getDiscreteUnit());
185
0
        const double fTextScale(fTargetTextHeight / aTextRange.getHeight());
186
0
        aTextTransform.scale(fTextScale, fTextScale);
187
0
        aTextTransform.translate(
188
0
            aRange.getMinX() + (fInnerDistance * 2.0),
189
0
            aRange.getMinY() + fTargetTextHeight + (fOuterDistance - fInnerDistance) - (2.0 * getDiscreteUnit()));
190
0
        aTextAsPolyPolygon.transform(aTextTransform);
191
192
        // check text size/position
193
0
        fTextWidth = aTextRange.getWidth() * fTextScale;
194
0
        const double fLapLeft(aRange.getMinX() + fInnerDistance);
195
0
        const double fLapRight(aRange.getMinX() + (aRange.getWidth() * 0.5)  - fInnerDistance);
196
197
        // if text is too big, do not create a Lap at all
198
        // to avoid trouble. It is expected that the user keeps
199
        // the object he works with big enough to do useful actions
200
0
        if(fTextWidth + (4.0 * getDiscreteUnit()) > fLapRight - fLapLeft)
201
0
            bCreateLap = false;
202
0
    }
203
204
    // create outer polygon
205
0
    aPolyPolygon.append(
206
0
        createRoundedPolygon(
207
0
            aRange,
208
0
            fOuterDistance,
209
0
            false,
210
0
            0.0));
211
212
    // create inner polygon, maybe with Lap
213
0
    aPolyPolygon.append(
214
0
        createRoundedPolygon(
215
0
            aRange,
216
0
            fInnerDistance,
217
0
            bCreateLap,
218
0
            fTextWidth));
219
220
0
    Color aFillColor(maColor);
221
0
    Color aLineColor(maColor);
222
223
0
    aFillColor.IncreaseLuminance(10);
224
0
    aLineColor.DecreaseLuminance(30);
225
226
0
    const drawinglayer::attribute::LineAttribute aLineAttribute(
227
0
        aLineColor.getBColor(),
228
0
        1.0 * getDiscreteUnit());
229
230
0
    drawinglayer::primitive2d::Primitive2DContainer aContainer;
231
232
    // filled polygon as BG (may get transparence for better look ?)
233
0
    aContainer.push_back(
234
0
        new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
235
0
            aPolyPolygon,
236
0
            aFillColor.getBColor()));
237
238
    // outline polygon for visibility (may be accentuated shaded
239
    // top/left, would require alternative creation)
240
0
    aContainer.push_back(
241
0
        new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
242
0
            std::move(aPolyPolygon),
243
0
            aLineAttribute));
244
245
    // top-left line pattern (as grep-here-sign to signal
246
    // that this construct may be also dragged by the user)
247
0
    const double fLapLeft(aRange.getMinX() + fInnerDistance);
248
0
    const double fLapRight(aRange.getMinX() + (aRange.getWidth() * 0.5)  - fInnerDistance);
249
0
    const double  fLapUp(aRange.getMinY() - ((mfDiscreteDistance + mfDiscreteDistance * 0.666) * getDiscreteUnit()));
250
0
    const double  fLapDown(aRange.getMinY() - ((mfDiscreteDistance + mfDiscreteDistance * 0.333) * getDiscreteUnit()));
251
0
    basegfx::B2DPolygon aPolygonLapUp;
252
0
    aPolygonLapUp.append(basegfx::B2DPoint(fLapLeft, fLapUp));
253
0
    aPolygonLapUp.append(basegfx::B2DPoint(fLapRight, fLapUp));
254
0
    basegfx::B2DPolygon aPolygonLapDown;
255
0
    aPolygonLapDown.append(basegfx::B2DPoint(fLapLeft, fLapDown));
256
0
    aPolygonLapDown.append(basegfx::B2DPoint(fLapRight, fLapDown));
257
0
    drawinglayer::attribute::StrokeAttribute aStrokeAttribute({ 2.0 * getDiscreteUnit(), 2.0 * getDiscreteUnit() });
258
259
0
    aContainer.push_back(
260
0
        new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
261
0
            std::move(aPolygonLapUp),
262
0
            aLineAttribute,
263
0
            aStrokeAttribute));
264
265
0
    aContainer.push_back(
266
0
        new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
267
0
            std::move(aPolygonLapDown),
268
0
            aLineAttribute,
269
0
            std::move(aStrokeAttribute)));
270
271
    // add text last. May use darker text color, go for same color
272
    // as accentuation line for now
273
0
    if(bCreateLap && 0 != aTextAsPolyPolygon.count())
274
0
    {
275
0
        aContainer.push_back(
276
0
            new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
277
0
                std::move(aTextAsPolyPolygon),
278
0
                aLineColor.getBColor()));
279
0
    }
280
0
    return new drawinglayer::primitive2d::GroupPrimitive2D(std::move(aContainer));
281
0
}
282
283
OverlayDiagramPrimitive::OverlayDiagramPrimitive(
284
    const basegfx::B2DHomMatrix& rTransformation,
285
    double fDiscreteDistance,
286
    double fDiscreteGap,
287
    Color const & rColor)
288
0
: drawinglayer::primitive2d::DiscreteMetricDependentPrimitive2D()
289
0
, maTransformation(rTransformation)
290
0
, mfDiscreteDistance(fDiscreteDistance)
291
0
, mfDiscreteGap(fDiscreteGap)
292
0
, maColor(rColor)
293
0
{
294
0
}
295
296
sal_uInt32 OverlayDiagramPrimitive::getPrimitive2DID() const
297
0
{
298
0
    return PRIMITIVE2D_ID_OVERLAYDIAGRAMPRIMITIVE2D;
299
0
}
300
301
// helper object for DiagramOverlay
302
class OverlayDiagramFrame final : public sdr::overlay::OverlayObject
303
{
304
private:
305
    basegfx::B2DHomMatrix maTransformation; // object dimensions
306
    Color maColor; // base color
307
308
    virtual drawinglayer::primitive2d::Primitive2DContainer createOverlayObjectPrimitive2DSequence() override;
309
310
public:
311
    explicit OverlayDiagramFrame(
312
        const basegfx::B2DHomMatrix& rTransformation,
313
        Color const & rColor);
314
};
315
316
OverlayDiagramFrame::OverlayDiagramFrame(
317
    const basegfx::B2DHomMatrix& rTransformation,
318
    const Color& rColor)
319
0
: sdr::overlay::OverlayObject(rColor)
320
0
, maTransformation(rTransformation)
321
0
, maColor(rColor)
322
0
{
323
0
}
324
325
drawinglayer::primitive2d::Primitive2DContainer OverlayDiagramFrame::createOverlayObjectPrimitive2DSequence()
326
0
{
327
0
    drawinglayer::primitive2d::Primitive2DContainer aReturnContainer;
328
329
0
    if ( !officecfg::Office::Common::Misc::ExperimentalMode::get() )
330
0
        return aReturnContainer;
331
332
0
    if (getOverlayManager())
333
0
    {
334
0
        aReturnContainer = drawinglayer::primitive2d::Primitive2DContainer {
335
0
            new OverlayDiagramPrimitive(
336
0
                maTransformation,
337
0
                8.0, // distance from geometry in pixels
338
0
                8.0, // gap/width of visualization in pixels
339
0
                maColor) };
340
0
    }
341
342
0
    return aReturnContainer;
343
0
}
344
345
} // end of anonymous namespace
346
347
namespace svx { namespace diagram {
348
349
void DiagramFrameHdl::clicked(const Point& /*rPnt*/)
350
0
{
351
    // this may check for a direct hit at the text later
352
    // and only then take action. That would require
353
    // to evaluate & keep that (maybe during creation).
354
    // For now, just trigger to open the Dialog
355
0
    comphelper::dispatchCommand(u".uno:EditDiagram"_ustr, {});
356
0
}
357
358
void DiagramFrameHdl::CreateB2dIAObject()
359
0
{
360
    // first throw away old one
361
0
    GetRidOfIAObject();
362
363
0
    SdrMarkView* pView = m_pHdlList->GetView();
364
365
0
    if(!pView || pView->areMarkHandlesHidden())
366
0
        return;
367
368
0
    SdrPageView* pPageView = pView->GetSdrPageView();
369
370
0
    if(!pPageView)
371
0
        return;
372
373
0
    for(sal_uInt32 b(0); b < pPageView->PageWindowCount(); b++)
374
0
    {
375
0
        const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(b);
376
377
0
        if(rPageWindow.GetPaintWindow().OutputToWindow())
378
0
        {
379
0
            const rtl::Reference< sdr::overlay::OverlayManager >& xManager = rPageWindow.GetOverlayManager();
380
0
            if (xManager.is())
381
0
            {
382
0
                OutputDevice& rOutDev(rPageWindow.GetPaintWindow().GetOutputDevice());
383
0
                const StyleSettings& rStyles(rOutDev.GetSettings().GetStyleSettings());
384
0
                Color aFillColor(rStyles.GetHighlightColor());
385
0
                std::unique_ptr<sdr::overlay::OverlayObject> pNewOverlayObject(
386
0
                    new OverlayDiagramFrame(
387
0
                        maTransformation,
388
0
                        aFillColor));
389
390
                // OVERLAYMANAGER
391
0
                insertNewlyCreatedOverlayObjectForSdrHdl(
392
0
                    std::move(pNewOverlayObject),
393
0
                    rPageWindow.GetObjectContact(),
394
0
                    *xManager);
395
0
            }
396
0
        }
397
0
    }
398
0
}
399
400
DiagramFrameHdl::DiagramFrameHdl(const basegfx::B2DHomMatrix& rTransformation)
401
0
: SdrHdl(Point(), SdrHdlKind::Move)
402
0
, maTransformation(rTransformation)
403
0
{
404
0
}
405
406
IDiagramHelper::IDiagramHelper(bool bSelfCreated)
407
135
: mbUseDiagramThemeData(false)
408
135
, mbUseDiagramModelData(true)
409
135
, mbForceThemePtrRecreation(false)
410
135
, mbSelfCreated(bSelfCreated)
411
135
{
412
135
}
413
414
135
IDiagramHelper::~IDiagramHelper() {}
415
416
void IDiagramHelper::anchorToSdrObjGroup(SdrObjGroup& rTarget)
417
135
{
418
135
    rTarget.mp_DiagramHelper.reset(this);
419
135
}
420
421
void IDiagramHelper::AddAdditionalVisualization(const SdrObjGroup& rTarget, SdrHdlList& rHdlList)
422
0
{
423
    // create an extra frame visualization here
424
0
    basegfx::B2DHomMatrix aTransformation;
425
0
    basegfx::B2DPolyPolygon aPolyPolygon;
426
0
    rTarget.TRGetBaseGeometry(aTransformation, aPolyPolygon);
427
428
0
    std::unique_ptr<SdrHdl> pHdl(new DiagramFrameHdl(aTransformation));
429
0
    rHdlList.AddHdl(std::move(pHdl));
430
0
}
431
432
}} // end of namespace
433
434
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */