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 "GroupManager.hxx"
21 #include <com/sun/star/beans/XFastPropertySet.hpp>
22 #include <com/sun/star/form/FormComponentType.hpp>
23 #include <comphelper/property.hxx>
24 #include <comphelper/types.hxx>
25 #include <osl/diagnose.h>
26 #include <tools/solar.h>
27 
28 #include <frm_strings.hxx>
29 
30 #include <algorithm>
31 
32 namespace frm
33 {
34 using namespace ::com::sun::star::uno;
35 using namespace ::com::sun::star::sdbc;
36 using namespace ::com::sun::star::beans;
37 using namespace ::com::sun::star::container;
38 using namespace ::com::sun::star::form;
39 using namespace ::com::sun::star::awt;
40 using namespace ::com::sun::star::lang;
41 using namespace ::comphelper;
42 
43 namespace
44 {
isRadioButton(const Reference<XPropertySet> & _rxComponent)45     bool isRadioButton( const Reference< XPropertySet >& _rxComponent )
46     {
47         bool bIs = false;
48         if ( hasProperty( PROPERTY_CLASSID, _rxComponent ) )
49         {
50             sal_Int16 nClassId = FormComponentType::CONTROL;
51             _rxComponent->getPropertyValue( PROPERTY_CLASSID ) >>= nClassId;
52             if ( nClassId == FormComponentType::RADIOBUTTON )
53                 bIs = true;
54         }
55         return bIs;
56     }
57 }
58 
OGroupCompAcc(const Reference<XPropertySet> & rxElement,const OGroupComp & _rGroupComp)59 OGroupCompAcc::OGroupCompAcc(const Reference<XPropertySet>& rxElement, const OGroupComp& _rGroupComp )
60                :m_xComponent( rxElement )
61                ,m_aGroupComp( _rGroupComp )
62 {
63 }
64 
operator ==(const OGroupCompAcc & rCompAcc) const65 bool OGroupCompAcc::operator==( const OGroupCompAcc& rCompAcc ) const
66 {
67     return m_xComponent == rCompAcc.m_xComponent;
68 }
69 
70 class OGroupCompAccLess
71 {
72 public:
operator ()(const OGroupCompAcc & lhs,const OGroupCompAcc & rhs) const73     bool operator() (const OGroupCompAcc& lhs, const OGroupCompAcc& rhs) const
74     {
75         return
76             reinterpret_cast<sal_Int64>(lhs.m_xComponent.get())
77         <   reinterpret_cast<sal_Int64>(rhs.m_xComponent.get());
78     }
79 };
80 
OGroupComp()81 OGroupComp::OGroupComp()
82     :m_nPos( -1 )
83     ,m_nTabIndex( 0 )
84 {
85 }
86 
OGroupComp(const Reference<XPropertySet> & rxSet,sal_Int32 nInsertPos)87 OGroupComp::OGroupComp(const Reference<XPropertySet>& rxSet, sal_Int32 nInsertPos )
88     : m_xComponent( rxSet )
89     , m_xControlModel(rxSet,UNO_QUERY)
90     , m_nPos( nInsertPos )
91     , m_nTabIndex(0)
92 {
93     if (m_xComponent.is())
94     {
95         if (hasProperty( PROPERTY_TABINDEX, m_xComponent ) )
96             // Indices smaller than 0 are treated like 0
97             m_nTabIndex = std::max(getINT16(m_xComponent->getPropertyValue( PROPERTY_TABINDEX )) , sal_Int16(0));
98     }
99 }
100 
operator ==(const OGroupComp & rComp) const101 bool OGroupComp::operator==( const OGroupComp& rComp ) const
102 {
103     return m_nTabIndex == rComp.GetTabIndex() && m_nPos == rComp.GetPos();
104 }
105 
106 class OGroupCompLess
107 {
108 public:
operator ()(const OGroupComp & lhs,const OGroupComp & rhs) const109     bool operator() (const OGroupComp& lhs, const OGroupComp& rhs) const
110     {
111         bool bResult;
112         // TabIndex of 0 will be added at the end
113         if (lhs.m_nTabIndex == rhs.GetTabIndex())
114             bResult = lhs.m_nPos < rhs.GetPos();
115         else if (lhs.m_nTabIndex && rhs.GetTabIndex())
116             bResult = lhs.m_nTabIndex < rhs.GetTabIndex();
117         else
118             bResult = lhs.m_nTabIndex != 0;
119         return bResult;
120     }
121 };
122 
OGroup(const OUString & rGroupName)123 OGroup::OGroup( const OUString& rGroupName )
124         :m_aGroupName( rGroupName )
125         ,m_nInsertPos(0)
126 {
127 }
128 
~OGroup()129 OGroup::~OGroup()
130 {
131 }
132 
InsertComponent(const Reference<XPropertySet> & xSet)133 void OGroup::InsertComponent( const Reference<XPropertySet>& xSet )
134 {
135     OGroupComp aNewGroupComp( xSet, m_nInsertPos );
136     sal_Int32 nPosInserted = insert_sorted(m_aCompArray, aNewGroupComp, OGroupCompLess());
137 
138     OGroupCompAcc aNewGroupCompAcc( xSet, m_aCompArray[nPosInserted] );
139     insert_sorted(m_aCompAccArray, aNewGroupCompAcc, OGroupCompAccLess());
140     m_nInsertPos++;
141 }
142 
RemoveComponent(const Reference<XPropertySet> & rxElement)143 void OGroup::RemoveComponent( const Reference<XPropertySet>& rxElement )
144 {
145     sal_Int32 nGroupCompAccPos;
146     OGroupCompAcc aSearchCompAcc( rxElement, OGroupComp() );
147     if ( seek_entry(m_aCompAccArray, aSearchCompAcc, nGroupCompAccPos, OGroupCompAccLess()) )
148     {
149         OGroupCompAcc& aGroupCompAcc = m_aCompAccArray[nGroupCompAccPos];
150         const OGroupComp& aGroupComp = aGroupCompAcc.GetGroupComponent();
151 
152         sal_Int32 nGroupCompPos;
153         if ( seek_entry(m_aCompArray, aGroupComp, nGroupCompPos, OGroupCompLess()) )
154         {
155             m_aCompAccArray.erase( m_aCompAccArray.begin() + nGroupCompAccPos );
156             m_aCompArray.erase( m_aCompArray.begin() + nGroupCompPos );
157 
158             /*
159              * By removing the GroupComp the insertion position has become invalid.
160              * We do not to change it here, however, because it's passed on continuously
161              * and ascending distinctively.
162              */
163         }
164         else
165         {
166             OSL_FAIL( "OGroup::RemoveComponent: Component not in Group" );
167         }
168     }
169     else
170     {
171         OSL_FAIL( "OGroup::RemoveComponent: Component not in Group" );
172     }
173 }
174 
GetControlModels() const175 Sequence< Reference<XControlModel>  > OGroup::GetControlModels() const
176 {
177     Sequence<Reference<XControlModel> > aControlModelSeq( m_aCompArray.size() );
178     Reference<XControlModel>* pModels = aControlModelSeq.getArray();
179 
180     for (auto const& rGroupComp : m_aCompArray)
181     {
182         *pModels++ = rGroupComp.GetControlModel();
183     }
184     return aControlModelSeq;
185 }
186 
OGroupManager(const Reference<XContainer> & _rxContainer)187 OGroupManager::OGroupManager(const Reference< XContainer >& _rxContainer)
188     :m_pCompGroup( new OGroup( "AllComponentGroup" ) )
189     ,m_xContainer(_rxContainer)
190 {
191     osl_atomic_increment(&m_refCount);
192     {
193         _rxContainer->addContainerListener(this);
194     }
195     osl_atomic_decrement(&m_refCount);
196 }
197 
~OGroupManager()198 OGroupManager::~OGroupManager()
199 {
200 }
201 
202 // XPropertyChangeListener
disposing(const EventObject & evt)203 void OGroupManager::disposing(const EventObject& evt)
204 {
205     Reference<XContainer>  xContainer(evt.Source, UNO_QUERY);
206     if (xContainer.get() == m_xContainer.get())
207     {
208         m_pCompGroup.reset();
209 
210         // delete group
211         m_aGroupArr.clear();
212         m_xContainer.clear();
213     }
214 }
215 
removeFromGroupMap(const OUString & _sGroupName,const Reference<XPropertySet> & _xSet)216 void OGroupManager::removeFromGroupMap(const OUString& _sGroupName,const Reference<XPropertySet>& _xSet)
217 {
218     // remove Component from CompGroup
219     m_pCompGroup->RemoveComponent( _xSet );
220 
221     OGroupArr::iterator aFind = m_aGroupArr.find(_sGroupName);
222 
223     if ( aFind != m_aGroupArr.end() )
224     {
225         // group exists
226         aFind->second.RemoveComponent( _xSet );
227 
228         // If the count of Group elements == 1 -> deactivate Group
229         sal_Int32 nCount = aFind->second.Count();
230         if ( nCount == 1 || nCount == 0 )
231         {
232             OActiveGroups::iterator aActiveFind = ::std::find(
233                 m_aActiveGroupMap.begin(),
234                 m_aActiveGroupMap.end(),
235                 aFind
236             );
237             if ( aActiveFind != m_aActiveGroupMap.end() )
238             {
239                 // the group is active. Deactivate it if the remaining component
240                 // is *no* radio button
241                 if ( nCount == 0 || !isRadioButton( aFind->second.GetObject( 0 ) ) )
242                     m_aActiveGroupMap.erase( aActiveFind );
243             }
244         }
245     }
246 
247 
248     // Deregister as PropertyChangeListener at Component
249     _xSet->removePropertyChangeListener( PROPERTY_NAME, this );
250     if (hasProperty(PROPERTY_GROUP_NAME, _xSet))
251         _xSet->removePropertyChangeListener( PROPERTY_GROUP_NAME, this );
252     if (hasProperty(PROPERTY_TABINDEX, _xSet))
253         _xSet->removePropertyChangeListener( PROPERTY_TABINDEX, this );
254 }
255 
propertyChange(const PropertyChangeEvent & evt)256 void SAL_CALL OGroupManager::propertyChange(const PropertyChangeEvent& evt)
257 {
258     Reference<XPropertySet>  xSet(evt.Source, UNO_QUERY);
259 
260     // remove Component from group
261     OUString     sGroupName;
262     if (hasProperty( PROPERTY_GROUP_NAME, xSet ))
263         xSet->getPropertyValue( PROPERTY_GROUP_NAME ) >>= sGroupName;
264     if (evt.PropertyName == PROPERTY_NAME) {
265         if (!sGroupName.isEmpty())
266             return; // group hasn't changed; ignore this name change.
267         // no GroupName; use Name as GroupName
268         evt.OldValue >>= sGroupName;
269     }
270     else if (evt.PropertyName == PROPERTY_GROUP_NAME) {
271         evt.OldValue >>= sGroupName;
272         if (sGroupName.isEmpty()) {
273             // No prior GroupName; fallback to Name
274             xSet->getPropertyValue( PROPERTY_NAME ) >>= sGroupName;
275         }
276     }
277     else
278         sGroupName = GetGroupName( xSet );
279 
280     removeFromGroupMap(sGroupName,xSet);
281 
282     // Re-insert Component
283     InsertElement( xSet );
284 }
285 
286 // XContainerListener
elementInserted(const ContainerEvent & Event)287 void SAL_CALL OGroupManager::elementInserted(const ContainerEvent& Event)
288 {
289     Reference< XPropertySet > xProps;
290     Event.Element >>= xProps;
291     if ( xProps.is() )
292         InsertElement( xProps );
293 }
294 
elementRemoved(const ContainerEvent & Event)295 void SAL_CALL OGroupManager::elementRemoved(const ContainerEvent& Event)
296 {
297     Reference<XPropertySet> xProps;
298     Event.Element >>= xProps;
299     if ( xProps.is() )
300         RemoveElement( xProps );
301 }
302 
elementReplaced(const ContainerEvent & Event)303 void SAL_CALL OGroupManager::elementReplaced(const ContainerEvent& Event)
304 {
305     Reference<XPropertySet> xProps;
306     Event.ReplacedElement >>= xProps;
307     if ( xProps.is() )
308         RemoveElement( xProps );
309 
310     xProps.clear();
311     Event.Element >>= xProps;
312     if ( xProps.is() )
313         InsertElement( xProps );
314 }
315 
316 // Other functions
getControlModels() const317 Sequence<Reference<XControlModel> > OGroupManager::getControlModels() const
318 {
319     return m_pCompGroup->GetControlModels();
320 }
321 
getGroupCount() const322 sal_Int32 OGroupManager::getGroupCount() const
323 {
324     return m_aActiveGroupMap.size();
325 }
326 
getGroup(sal_Int32 nGroup,Sequence<Reference<XControlModel>> & _rGroup,OUString & _rName)327 void OGroupManager::getGroup(sal_Int32 nGroup, Sequence< Reference<XControlModel> >& _rGroup, OUString& _rName)
328 {
329     OSL_ENSURE(nGroup >= 0 && static_cast<size_t>(nGroup) < m_aActiveGroupMap.size(),"OGroupManager::getGroup: Invalid group index!");
330     OGroupArr::iterator aGroupPos   = m_aActiveGroupMap[nGroup];
331     _rName                          = aGroupPos->second.GetGroupName();
332     _rGroup                         = aGroupPos->second.GetControlModels();
333 }
334 
getGroupByName(const OUString & _rName,Sequence<Reference<XControlModel>> & _rGroup)335 void OGroupManager::getGroupByName(const OUString& _rName, Sequence< Reference<XControlModel>  >& _rGroup)
336 {
337     OGroupArr::iterator aFind = m_aGroupArr.find(_rName);
338     if ( aFind != m_aGroupArr.end() )
339         _rGroup = aFind->second.GetControlModels();
340 }
341 
InsertElement(const Reference<XPropertySet> & xSet)342 void OGroupManager::InsertElement( const Reference<XPropertySet>& xSet )
343 {
344     // Only ControlModels
345     Reference<XControlModel>  xControl(xSet, UNO_QUERY);
346     if (!xControl.is() )
347         return;
348 
349     // Add Component to CompGroup
350     m_pCompGroup->InsertComponent( xSet );
351 
352     // Add Component to Group
353     OUString sGroupName( GetGroupName( xSet ) );
354 
355     OGroupArr::iterator aFind = m_aGroupArr.find(sGroupName);
356 
357     if ( aFind == m_aGroupArr.end() )
358     {
359         aFind = m_aGroupArr.emplace(sGroupName,OGroup(sGroupName)).first;
360     }
361 
362     aFind->second.InsertComponent( xSet );
363 
364     // if we have at least 2 elements in the group, then this is an "active group"
365     bool bActivateGroup = aFind->second.Count() == 2;
366 
367     // Additionally, if the component is a radio button, then it's group becomes active,
368     // too. With this, we ensure that in a container with n radio buttons which all are
369     // in different groups the selection still works reliably (means that all radios can be
370     // clicked independently)
371     if ( aFind->second.Count() == 1 )
372     {
373         if ( isRadioButton( xSet ) )
374             bActivateGroup = true;
375     }
376 
377     if ( bActivateGroup )
378     {
379         OActiveGroups::iterator aAlreadyExistent = ::std::find(
380             m_aActiveGroupMap.begin(),
381             m_aActiveGroupMap.end(),
382             aFind
383         );
384         if ( aAlreadyExistent == m_aActiveGroupMap.end() )
385             m_aActiveGroupMap.push_back(  aFind );
386     }
387 
388     // Register as PropertyChangeListener at Component
389     xSet->addPropertyChangeListener( PROPERTY_NAME, this );
390     if (hasProperty(PROPERTY_GROUP_NAME, xSet))
391         xSet->addPropertyChangeListener( PROPERTY_GROUP_NAME, this );
392 
393     // Not everyone needs to support Tabindex
394     if (hasProperty(PROPERTY_TABINDEX, xSet))
395         xSet->addPropertyChangeListener( PROPERTY_TABINDEX, this );
396 }
397 
RemoveElement(const Reference<XPropertySet> & xSet)398 void OGroupManager::RemoveElement( const Reference<XPropertySet>& xSet )
399 {
400     // Only ControlModels
401     Reference<XControlModel>  xControl(xSet, UNO_QUERY);
402     if (!xControl.is() )
403         return;
404 
405     // Remove Component from Group
406     OUString     sGroupName( GetGroupName( xSet ) );
407 
408     removeFromGroupMap(sGroupName,xSet);
409 }
410 
GetGroupName(const css::uno::Reference<css::beans::XPropertySet> & xComponent)411 OUString OGroupManager::GetGroupName( const css::uno::Reference< css::beans::XPropertySet>& xComponent )
412 {
413     if (!xComponent.is())
414         return OUString();
415     OUString sGroupName;
416     if (hasProperty( PROPERTY_GROUP_NAME, xComponent )) {
417         xComponent->getPropertyValue( PROPERTY_GROUP_NAME ) >>= sGroupName;
418         if (sGroupName.isEmpty())
419             xComponent->getPropertyValue( PROPERTY_NAME ) >>= sGroupName;
420     }
421     else
422         xComponent->getPropertyValue( PROPERTY_NAME ) >>= sGroupName;
423     return sGroupName;
424 }
425 }   // namespace frm
426 
427 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
428