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