Coverage Report

Created: 2025-12-31 10:39

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