LibreOffice Module chart2 (master) 1
SelectionHelper.cxx
Go to the documentation of this file.
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 <SelectionHelper.hxx>
21#include <ObjectIdentifier.hxx>
22#include <DiagramHelper.hxx>
23#include <Diagram.hxx>
24#include <ChartModelHelper.hxx>
25#include <ChartModel.hxx>
26
27#include <svx/svdpage.hxx>
28#include <svx/svditer.hxx>
29#include <svx/obj3d.hxx>
30#include <svx/svdopath.hxx>
31#include <vcl/svapp.hxx>
33#include <osl/diagnose.h>
34
35namespace chart
36{
37using namespace ::com::sun::star;
38
39namespace
40{
41
42OUString lcl_getObjectName( SdrObject const * pObj )
43{
44 if(pObj)
45 return pObj->GetName();
46 return OUString();
47}
48
49void impl_selectObject( SdrObject* pObjectToSelect, DrawViewWrapper& rDrawViewWrapper )
50{
51 SolarMutexGuard aSolarGuard;
52
53 if(pObjectToSelect)
54 {
55 SelectionHelper aSelectionHelper( pObjectToSelect );
56 SdrObject* pMarkObj = aSelectionHelper.getObjectToMark();
57 rDrawViewWrapper.setMarkHandleProvider(&aSelectionHelper);
58 rDrawViewWrapper.MarkObject(pMarkObj);
59 rDrawViewWrapper.setMarkHandleProvider(nullptr);
60 }
61}
62
63}//anonymous namespace
64
66{
67 return m_aSelectedOID.isValid();
68}
69
70OUString const & Selection::getSelectedCID() const
71{
73}
74
76{
78}
79
80bool Selection::setSelection( const OUString& rCID )
81{
82 if ( rCID != m_aSelectedOID.getObjectCID() )
83 {
85 return true;
86 }
87 return false;
88}
89
91{
92 if ( !( xShape == m_aSelectedOID.getAdditionalShape() ) )
93 {
96 return true;
97 }
98 return false;
99}
100
102{
106}
107
109{
112 {
115 return true;
116 }
117 return false;
118}
119
121{
123 {
125 }
126}
127
129{
131}
132
134{
136}
137
139{
140 if( !pDrawViewWrapper )
141 return;
142
143 {
144 SolarMutexGuard aSolarGuard;
145 pDrawViewWrapper->UnmarkAll();
146 }
147 SdrObject* pObjectToSelect = nullptr;
149 {
150 pObjectToSelect = pDrawViewWrapper->getNamedSdrObject( m_aSelectedOID.getObjectCID() );
151 }
153 {
155 }
156
157 impl_selectObject( pObjectToSelect, *pDrawViewWrapper );
158}
159
160void Selection::adaptSelectionToNewPos( const Point& rMousePos, DrawViewWrapper const * pDrawViewWrapper
161 , bool bIsRightMouse, bool bWaitingForDoubleClick )
162{
163 if( !pDrawViewWrapper )
164 return;
165
166 //do not toggle multiclick selection if right clicked on the selected object or waiting for double click
167 bool bAllowMultiClickSelectionChange = !bIsRightMouse && !bWaitingForDoubleClick;
168
169 ObjectIdentifier aLastSelectedObject( m_aSelectedOID );
170
171 SolarMutexGuard aSolarGuard;
172
173 //bAllowMultiClickSelectionChange==true -> a second click on the same object can lead to a changed selection (e.g. series -> single data point)
174
175 //get object to select:
176 {
178
179 //the search for the object to select starts with the hit object deepest in the grouping hierarchy (a leaf in the tree)
180 //further we travel along the grouping hierarchy from child to parent
181 SdrObject* pNewObj = pDrawViewWrapper->getHitObject(rMousePos);
182 m_aSelectedOID = ObjectIdentifier( lcl_getObjectName( pNewObj ) );//name of pNewObj
183
184 //ignore handle only objects for hit test
185 while( pNewObj && m_aSelectedOID.getObjectCID().match( "HandlesOnly" ) )
186 {
187 pNewObj->SetMarkProtect(true);
188 pNewObj = pDrawViewWrapper->getHitObject(rMousePos);
189 m_aSelectedOID = ObjectIdentifier( lcl_getObjectName( pNewObj ) );
190 }
191
192 //accept only named objects while searching for the object to select
193 //this call may change m_aSelectedOID
194 if ( SelectionHelper::findNamedParent( pNewObj, m_aSelectedOID, true ) )
195 {
196 //if the so far found object is a multi click object further steps are necessary
198 {
199 bool bSameObjectAsLastSelected = ( aLastSelectedObject == m_aSelectedOID );
200 if( bSameObjectAsLastSelected )
201 {
202 //if the same child is clicked again don't go up further
203 break;
204 }
206 {
207 //if a sibling of the last selected object is clicked don't go up further
208 break;
209 }
210 ObjectIdentifier aLastChild = m_aSelectedOID;
211 if ( !SelectionHelper::findNamedParent( pNewObj, m_aSelectedOID, false ) )
212 {
213 //take the one found so far
214 break;
215 }
216 //if the last selected object is found don't go up further
217 //but take the last child if selection change is allowed
218 if ( aLastSelectedObject == m_aSelectedOID )
219 {
220 if( bAllowMultiClickSelectionChange )
221 {
222 m_aSelectedOID = aLastChild;
223 }
224 else
226 break;
227 }
228 }
229
230 OSL_ENSURE(m_aSelectedOID.isValid(), "somehow lost selected object");
231 }
232 else
233 {
234 //maybe an additional shape was hit
235 if ( pNewObj )
236 {
238 }
239 else
240 {
242 }
243 }
244
246 {
247 OUString aPageCID( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_PAGE, u"" ) );//@todo read CID from model
248
250 {
251 m_aSelectedOID = ObjectIdentifier( aPageCID );
252 }
253
254 //check whether the diagram was hit but not selected (e.g. because it has no filling):
255 OUString aDiagramCID = ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM, OUString::number( 0 ) );
256 OUString aWallCID( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM_WALL, u"" ) );//@todo read CID from model
257 bool bBackGroundHit = m_aSelectedOID.getObjectCID() == aPageCID || m_aSelectedOID.getObjectCID() == aWallCID || !m_aSelectedOID.isAutoGeneratedObject();
258 if( bBackGroundHit )
259 {
260 //todo: if more than one diagram is available in future do check the list of all diagrams here
261 SdrObject* pDiagram = pDrawViewWrapper->getNamedSdrObject( aDiagramCID );
262 if( pDiagram )
263 {
264 if( DrawViewWrapper::IsObjectHit( pDiagram, rMousePos ) )
265 {
266 m_aSelectedOID = ObjectIdentifier( aDiagramCID );
267 }
268 }
269 }
270 //check whether the legend was hit but not selected (e.g. because it has no filling):
271 if( bBackGroundHit || m_aSelectedOID.getObjectCID() == aDiagramCID )
272 {
273 OUString aLegendCID( ObjectIdentifier::createClassifiedIdentifierForParticle( ObjectIdentifier::createParticleForLegend(nullptr) ) );//@todo read CID from model
274 SdrObject* pLegend = pDrawViewWrapper->getNamedSdrObject( aLegendCID );
275 if( pLegend )
276 {
277 if( DrawViewWrapper::IsObjectHit( pLegend, rMousePos ) )
278 {
279 m_aSelectedOID = ObjectIdentifier( aLegendCID );
280 }
281 }
282 }
283 }
284 }
285
287 {
289 }
290}
291
293{
295 switch( eObjectType )
296 {
299 case OBJECTTYPE_SHAPE:
301 return true;
302 default:
303 return false;
304 }
305}
306
308{
310}
311
313{
315}
316
318{
320}
321
323 , OUString& rOutName
324 , bool bGivenObjectMayBeResult )
325{
326 SolarMutexGuard aSolarGuard;
327 //find the deepest named group
328 SdrObject* pObj = pInOutObject;
329 OUString aName;
330 if( bGivenObjectMayBeResult )
331 aName = lcl_getObjectName( pObj );
332
333 while( pObj && !ObjectIdentifier::isCID( aName ) )
334 {
336 if( !pObjList )
337 return false;
339 if( !pOwner )
340 return false;
341 pObj = pOwner;
342 aName = lcl_getObjectName( pObj );
343 }
344
345 if(!pObj)
346 return false;
347 if(aName.isEmpty())
348 return false;
349
350 pInOutObject = pObj;
351 rOutName = aName;
352 return true;
353}
354
356 , ObjectIdentifier& rOutObject
357 , bool bGivenObjectMayBeResult )
358{
359 OUString aName;
360 if ( findNamedParent( pInOutObject, aName, bGivenObjectMayBeResult ) )
361 {
362 rOutObject = ObjectIdentifier( aName );
363 return true;
364 }
365 return false;
366}
367
369 , const OUString& rNameOfSelectedObject
370 , const DrawViewWrapper& rDrawViewWrapper )
371{
372 if(rNameOfSelectedObject.isEmpty())
373 return false;
374 if( !ObjectIdentifier::isDragableObject(rNameOfSelectedObject) )
375 return false;
376 SolarMutexGuard aSolarGuard;
377 SdrObject* pObj = rDrawViewWrapper.getNamedSdrObject( rNameOfSelectedObject );
378 return DrawViewWrapper::IsObjectHit( pObj, rMPos );
379}
380
382 const Point& rMPos,
383 DrawViewWrapper const & rDrawViewWrapper,
384 bool bGetDiagramInsteadOf_Wall )
385{
386 SolarMutexGuard aSolarGuard;
387 OUString aRet;
388
389 SdrObject* pNewObj = rDrawViewWrapper.getHitObject(rMPos);
390 aRet = lcl_getObjectName( pNewObj );//name of pNewObj
391
392 //ignore handle only objects for hit test
393 while( pNewObj && aRet.match("HandlesOnly") )
394 {
395 pNewObj->SetMarkProtect(true);
396 pNewObj = rDrawViewWrapper.getHitObject(rMPos);
397 aRet = lcl_getObjectName( pNewObj );
398 }
399
400 //accept only named objects while searching for the object to select
401 if( !findNamedParent( pNewObj, aRet, true ) )
402 {
403 aRet.clear();
404 }
405
406 OUString aPageCID( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_PAGE, u"" ) );//@todo read CID from model
407 //get page when nothing was hit
408 if( aRet.isEmpty() && !pNewObj )
409 {
410 aRet = aPageCID;
411 }
412
413 //get diagram instead wall or page if hit inside diagram
414 if( !aRet.isEmpty() )
415 {
416 if( aRet == aPageCID )
417 {
418 OUString aDiagramCID = ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM, OUString::number( 0 ) );
419 //todo: if more than one diagram is available in future do check the list of all diagrams here
420 SdrObject* pDiagram = rDrawViewWrapper.getNamedSdrObject( aDiagramCID );
421 if( pDiagram )
422 {
423 if( DrawViewWrapper::IsObjectHit( pDiagram, rMPos ) )
424 {
425 aRet = aDiagramCID;
426 }
427 }
428 }
429 else if( bGetDiagramInsteadOf_Wall )
430 {
431 OUString aWallCID( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM_WALL, u"" ) );//@todo read CID from model
432
433 if( aRet == aWallCID )
434 {
435 OUString aDiagramCID = ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM, OUString::number( 0 ) );
436 aRet = aDiagramCID;
437 }
438 }
439 }
440
441 return aRet;
442 // \\- solar mutex
443}
444
445bool SelectionHelper::isRotateableObject( std::u16string_view rCID
446 , const rtl::Reference<::chart::ChartModel>& xChartModel )
447{
449 return false;
450
451 sal_Int32 nDimensionCount = xChartModel->getFirstChartDiagram()->getDimension();
452
453 return nDimensionCount == 3;
454}
455
457 : m_pSelectedObj( pSelectedObj ), m_pMarkObj(nullptr)
458{
459
460}
462{
463}
464
466{
467 //true == green == surrounding handles
468 return DynCastE3dObject( m_pSelectedObj) == nullptr;
469}
470
472{
473 if(!pObj)
474 return nullptr;
475 OUString aName( lcl_getObjectName( pObj ) );
476 if( aName.match("MarkHandles") || aName.match("HandlesOnly") )
477 return pObj;
478 if( !aName.isEmpty() )//don't get the markhandles of a different object
479 return nullptr;
480
481 //search for a child with name "MarkHandles" or "HandlesOnly"
482 SolarMutexGuard aSolarGuard;
483 SdrObjList* pSubList = pObj->GetSubList();
484 if(pSubList)
485 {
486 SdrObjListIter aIterator(pSubList, SdrIterMode::Flat);
487 while (aIterator.IsMore())
488 {
489 SdrObject* pMarkHandles = SelectionHelper::getMarkHandlesObject( aIterator.Next() );
490 if( pMarkHandles )
491 return pMarkHandles;
492 }
493 }
494 return nullptr;
495}
496
498{
499 //return the selected object itself
500 //or a specific other object if that exists
502 m_pMarkObj = pObj;
503
504 //search for a child with name "MarkHandles" or "HandlesOnly"
505 if(pObj)
506 {
507 SolarMutexGuard aSolarGuard;
508 SdrObjList* pSubList = pObj->GetSubList();
509 if(pSubList)
510 {
511 SdrObjListIter aIterator(pSubList, SdrIterMode::Flat);
512 while (aIterator.IsMore())
513 {
514 SdrObject* pMarkHandles = SelectionHelper::getMarkHandlesObject( aIterator.Next() );
515 if( pMarkHandles )
516 {
517 m_pMarkObj = pMarkHandles;
518 break;
519 }
520 }
521 }
522 }
523 return m_pMarkObj;
524}
525
527{
528 //search whether the object or one of its children is a 3D object
529 //if so, return the accessory 3DScene
530
531 E3dObject* pRotateable = nullptr;
532
533 if(pObj)
534 {
535 pRotateable = DynCastE3dObject(pObj);
536 if( !pRotateable )
537 {
538 SolarMutexGuard aSolarGuard;
539 SdrObjList* pSubList = pObj->GetSubList();
540 if(pSubList)
541 {
542 SdrObjListIter aIterator(pSubList, SdrIterMode::DeepWithGroups);
543 while( aIterator.IsMore() && !pRotateable )
544 {
545 pRotateable = DynCastE3dObject(aIterator.Next());
546 }
547 }
548 }
549 }
550
551 E3dScene* pScene(nullptr);
552
553 if(pRotateable)
554 {
555 SolarMutexGuard aSolarGuard;
556 pScene = pRotateable->getRootE3dSceneFromE3dObject();
557 }
558
559 return pScene;
560}
561
563{
564 SolarMutexGuard aSolarGuard;
565
566 //@todo -> more flexible handle creation
567 //2 scenarios possible:
568 //1. add an additional invisible shape as a child to the selected object
569 //this child needs to be named somehow and handles need to be generated there from...
570 //or 2. offer a central service per view where renderer and so can register for handle creation for a special shape
571 //.. or 3. feature from drawinglayer to create handles for each shape... (bad performance... ?) ?
572
573 //scenario 1 is now used:
574 //if a child with name MarkHandles exists
575 //this child is marked instead of the logical selected object
576
577/*
578 //if a special mark object was found
579 //that object should be used for marking only
580 if( m_pMarkObj != m_pSelectedObj)
581 return false;
582*/
583 //if a special mark object was found
584 //that object should be used to create handles from
586 {
587 rHdlList.Clear();
588 if( auto pPathObj = dynamic_cast<const SdrPathObj*>( m_pMarkObj) )
589 {
590 //if th object is a polygon
591 //from each point a handle is generated
592 const ::basegfx::B2DPolyPolygon& rPolyPolygon = pPathObj->GetPathPoly();
593 for( sal_uInt32 nN = 0; nN < rPolyPolygon.count(); nN++)
594 {
595 const ::basegfx::B2DPolygon& aPolygon(rPolyPolygon.getB2DPolygon(nN));
596 for( sal_uInt32 nM = 0; nM < aPolygon.count(); nM++)
597 {
598 const ::basegfx::B2DPoint aPoint(aPolygon.getB2DPoint(nM));
599 rHdlList.AddHdl(std::make_unique<SdrHdl>(Point(basegfx::fround(aPoint.getX()), basegfx::fround(aPoint.getY())), SdrHdlKind::Poly));
600 }
601 }
602 return true;
603 }
604 else
605 return false; //use the special MarkObject for marking
606 }
607
608 //@todo:
609 //add and document good marking defaults ...
610
611 rHdlList.Clear();
612
614 if(!pObj)
615 return false;
616 SdrObjList* pSubList = pObj->GetSubList();
617 if( !pSubList )//no group object !pObj->IsGroupObject()
618 return false;
619
620 OUString aName( lcl_getObjectName( pObj ) );
622 if( eObjectType == OBJECTTYPE_DATA_POINT
623 || eObjectType == OBJECTTYPE_DATA_LABEL
624 || eObjectType == OBJECTTYPE_LEGEND_ENTRY
625 || eObjectType == OBJECTTYPE_AXIS_UNITLABEL )
626 {
627 return false;
628 }
629
630 SdrObjListIter aIterator(pSubList, SdrIterMode::Flat);
631
632 while (aIterator.IsMore())
633 {
634 SdrObject* pSubObj = aIterator.Next();
635 if( eObjectType == OBJECTTYPE_DATA_SERIES )
636 {
637 OUString aSubName( lcl_getObjectName( pSubObj ) );
638 ObjectType eSubObjectType( ObjectIdentifier::getObjectType( aSubName ) );
639 if( eSubObjectType!=OBJECTTYPE_DATA_POINT )
640 return false;
641 }
642
643 Point aPos = pSubObj->GetCurrentBoundRect().Center();
644 rHdlList.AddHdl(std::make_unique<SdrHdl>(aPos,SdrHdlKind::Poly));
645 }
646 return true;
647}
648
649} //namespace chart
650
651/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
virtual E3dScene * getRootE3dSceneFromE3dObject() const
void Clear()
void AddHdl(std::unique_ptr< SdrHdl > pHdl)
SdrObject * Next()
bool IsMore() const
virtual SdrObject * getSdrObjectFromSdrObjList() const
void SetMarkProtect(bool bProt)
virtual css::uno::Reference< css::drawing::XShape > getUnoShape()
virtual SdrObjList * GetSubList() const
virtual const tools::Rectangle & GetCurrentBoundRect() const
SdrObjList * getParentSdrObjListFromSdrObject() const
virtual const OUString & GetName() const
void UnmarkAll()
SdrObject * getHitObject(const Point &rPnt) const
static SdrObject * getSdrObject(const css::uno::Reference< css::drawing::XShape > &xShape)
SdrObject * getNamedSdrObject(const OUString &rName) const
static bool IsObjectHit(SdrObject const *pObj, const Point &rPnt)
static OUString createClassifiedIdentifierForParticle(std::u16string_view rParticle)
static bool areSiblings(std::u16string_view rCID1, std::u16string_view rCID2)
static bool isDragableObject(std::u16string_view rClassifiedIdentifier)
static OUString createParticleForLegend(const rtl::Reference<::chart::ChartModel > &xChartModel)
static bool isCID(std::u16string_view rName)
static bool isMultiClickObject(std::u16string_view rClassifiedIdentifier)
const OUString & getObjectCID() const
static bool isRotateableObject(std::u16string_view rClassifiedIdentifier)
static ObjectType getObjectType(std::u16string_view rCID)
static OUString createClassifiedIdentifier(enum ObjectType eObjectType, std::u16string_view rParticleID)
const css::uno::Reference< css::drawing::XShape > & getAdditionalShape() const
ObjectType getObjectType() const
static OUString getHitObjectCID(const Point &rMPos, DrawViewWrapper const &rDrawViewWrapper, bool bGetDiagramInsteadOf_Wall=false)
static SdrObject * getMarkHandlesObject(SdrObject *pObj)
static bool findNamedParent(SdrObject *&pInOutObject, OUString &rOutName, bool bGivenObjectMayBeResult)
static bool isRotateableObject(std::u16string_view rCID, const rtl::Reference<::chart::ChartModel > &xChartModel)
SelectionHelper(SdrObject *pSelectedObj)
virtual bool getMarkHandles(SdrHdlList &rHdlList) override
static E3dScene * getSceneToRotate(SdrObject *pObj)
static bool isDragableObjectHitTwice(const Point &rMPos, const OUString &rNameOfSelectedObject, const DrawViewWrapper &rDrawViewWrapper)
virtual bool getFrameDragSingles() override
void remindSelectionBeforeMouseDown()
void resetPossibleSelectionAfterSingleClickWasEnsured()
bool isDragableObjectSelected() const
bool hasSelection() const
bool isRotateableObjectSelected(const rtl::Reference<::chart::ChartModel > &xChartModel) const
ObjectIdentifier m_aSelectedOID_selectOnlyIfNoDoubleClickIsFollowing
bool isAdditionalShapeSelected() const
ObjectIdentifier m_aSelectedOID
OUString const & getSelectedCID() const
bool maybeSwitchSelectionAfterSingleClickWasEnsured()
ObjectIdentifier m_aSelectedOID_beforeMouseDown
void adaptSelectionToNewPos(const Point &rMousePos, DrawViewWrapper const *pDrawViewWrapper, bool bIsRightMouse, bool bWaitingForDoubleClick)
bool isSelectionDifferentFromBeforeMouseDown() const
bool isResizeableObjectSelected() const
void applySelection(DrawViewWrapper *pDrawViewWrapper)
bool setSelection(const OUString &rCID)
css::uno::Reference< css::drawing::XShape > const & getSelectedAdditionalShape() const
constexpr Point Center() const
float u
OUString aName
SvLinkSource * pOwner
B2IRange fround(const B2DRange &rRange)
@ OBJECTTYPE_DATA_SERIES
@ OBJECTTYPE_DIAGRAM
@ OBJECTTYPE_LEGEND_ENTRY
@ OBJECTTYPE_DATA_POINT
@ OBJECTTYPE_AXIS_UNITLABEL
@ OBJECTTYPE_DATA_LABEL
@ OBJECTTYPE_DIAGRAM_WALL
SVXCORE_DLLPUBLIC E3dObject * DynCastE3dObject(SdrObject *)