/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: */ |