Avoid potential negative array index access to cached text.
[LibreOffice.git] / framework / source / uiconfiguration / windowstateconfiguration.cxx
blobbe600fae3c460e2af4079bf30d3424cbca268eed
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <uiconfiguration/windowstateproperties.hxx>
21 #include <helper/mischelper.hxx>
23 #include <com/sun/star/beans/PropertyValue.hpp>
24 #include <com/sun/star/beans/XPropertySet.hpp>
25 #include <com/sun/star/configuration/theDefaultProvider.hpp>
26 #include <com/sun/star/container/XNameAccess.hpp>
27 #include <com/sun/star/container/XNameContainer.hpp>
28 #include <com/sun/star/container/XContainer.hpp>
29 #include <com/sun/star/lang/XServiceInfo.hpp>
30 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
31 #include <com/sun/star/frame/ModuleManager.hpp>
32 #include <com/sun/star/frame/XModuleManager2.hpp>
33 #include <com/sun/star/awt/Point.hpp>
34 #include <com/sun/star/awt/Size.hpp>
35 #include <com/sun/star/ui/DockingArea.hpp>
36 #include <com/sun/star/util/XChangesBatch.hpp>
37 #include <com/sun/star/uno/XComponentContext.hpp>
39 #include <comphelper/compbase.hxx>
40 #include <comphelper/string.hxx>
41 #include <cppuhelper/implbase.hxx>
42 #include <cppuhelper/supportsservice.hxx>
43 #include <comphelper/propertysequence.hxx>
44 #include <comphelper/sequence.hxx>
45 #include <sal/log.hxx>
46 #include <o3tl/string_view.hxx>
48 #include <mutex>
49 #include <string_view>
50 #include <unordered_map>
51 #include <vector>
53 using namespace com::sun::star::uno;
54 using namespace com::sun::star::lang;
55 using namespace com::sun::star::beans;
56 using namespace com::sun::star::util;
57 using namespace com::sun::star::configuration;
58 using namespace com::sun::star::container;
59 using namespace ::com::sun::star::frame;
60 using namespace ::com::sun::star::ui;
61 using namespace framework;
63 namespace {
65 // Zero based indexes, order must be the same as WindowStateMask && CONFIGURATION_PROPERTIES!
66 const sal_Int16 PROPERTY_LOCKED = 0;
67 const sal_Int16 PROPERTY_DOCKED = 1;
68 const sal_Int16 PROPERTY_VISIBLE = 2;
69 const sal_Int16 PROPERTY_CONTEXT = 3;
70 const sal_Int16 PROPERTY_HIDEFROMMENU = 4;
71 const sal_Int16 PROPERTY_NOCLOSE = 5;
72 const sal_Int16 PROPERTY_SOFTCLOSE = 6;
73 const sal_Int16 PROPERTY_CONTEXTACTIVE = 7;
74 const sal_Int16 PROPERTY_DOCKINGAREA = 8;
75 const sal_Int16 PROPERTY_POS = 9;
76 const sal_Int16 PROPERTY_SIZE = 10;
77 const sal_Int16 PROPERTY_UINAME = 11;
78 const sal_Int16 PROPERTY_INTERNALSTATE = 12;
79 const sal_Int16 PROPERTY_STYLE = 13;
80 const sal_Int16 PROPERTY_DOCKPOS = 14;
81 const sal_Int16 PROPERTY_DOCKSIZE = 15;
83 // Order must be the same as WindowStateMask!!
84 constexpr OUString CONFIGURATION_PROPERTIES[]
86 WINDOWSTATE_PROPERTY_LOCKED,
87 WINDOWSTATE_PROPERTY_DOCKED,
88 WINDOWSTATE_PROPERTY_VISIBLE,
89 WINDOWSTATE_PROPERTY_CONTEXT,
90 WINDOWSTATE_PROPERTY_HIDEFROMENU,
91 WINDOWSTATE_PROPERTY_NOCLOSE,
92 WINDOWSTATE_PROPERTY_SOFTCLOSE,
93 WINDOWSTATE_PROPERTY_CONTEXTACTIVE,
94 WINDOWSTATE_PROPERTY_DOCKINGAREA,
95 WINDOWSTATE_PROPERTY_POS,
96 WINDOWSTATE_PROPERTY_SIZE,
97 WINDOWSTATE_PROPERTY_UINAME,
98 WINDOWSTATE_PROPERTY_INTERNALSTATE,
99 WINDOWSTATE_PROPERTY_STYLE,
100 WINDOWSTATE_PROPERTY_DOCKPOS,
101 WINDOWSTATE_PROPERTY_DOCKSIZE
104 // Configuration access class for WindowState supplier implementation
106 class ConfigurationAccess_WindowState : public ::cppu::WeakImplHelper< XNameContainer, XContainerListener >
108 public:
109 ConfigurationAccess_WindowState( std::u16string_view aWindowStateConfigFile, const Reference< XComponentContext >& rxContext );
110 virtual ~ConfigurationAccess_WindowState() override;
112 // XNameAccess
113 virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override;
115 virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override;
117 virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override;
119 // XNameContainer
120 virtual void SAL_CALL removeByName( const OUString& sName ) override;
122 virtual void SAL_CALL insertByName( const OUString& sName, const css::uno::Any& aPropertySet ) override;
124 // XNameReplace
125 virtual void SAL_CALL replaceByName( const OUString& sName, const css::uno::Any& aPropertySet ) override;
127 // XElementAccess
128 virtual css::uno::Type SAL_CALL getElementType() override;
130 virtual sal_Bool SAL_CALL hasElements() override;
132 // container.XContainerListener
133 virtual void SAL_CALL elementInserted( const ContainerEvent& aEvent ) override;
134 virtual void SAL_CALL elementRemoved ( const ContainerEvent& aEvent ) override;
135 virtual void SAL_CALL elementReplaced( const ContainerEvent& aEvent ) override;
137 // lang.XEventListener
138 virtual void SAL_CALL disposing( const EventObject& aEvent ) override;
140 protected:
141 enum // WindowStateMask
143 WINDOWSTATE_MASK_DOCKINGAREA = 256,
144 WINDOWSTATE_MASK_POS = 512,
145 WINDOWSTATE_MASK_SIZE = 1024,
146 WINDOWSTATE_MASK_UINAME = 2048,
147 WINDOWSTATE_MASK_INTERNALSTATE = 4096,
148 WINDOWSTATE_MASK_STYLE = 8192,
149 WINDOWSTATE_MASK_DOCKPOS = 16384,
150 WINDOWSTATE_MASK_DOCKSIZE = 32768
153 // Cache structure. Valid values are described by the eMask member. All other values should not be
154 // provided to outside code!
155 struct WindowStateInfo
157 WindowStateInfo()
158 : bLocked(false)
159 , bDocked(false)
160 , bVisible(false)
161 , bContext(false)
162 , bHideFromMenu(false)
163 , bNoClose(false)
164 , bSoftClose(false)
165 , bContextActive(false)
166 , aDockingArea(css::ui::DockingArea_DOCKINGAREA_TOP)
167 , aDockPos(0, 0)
168 , aPos(0, 0)
169 , aSize(0, 0)
170 , nInternalState(0)
171 , nStyle(0)
172 , nMask(0)
176 bool bLocked : 1,
177 bDocked : 1,
178 bVisible : 1,
179 bContext : 1,
180 bHideFromMenu : 1,
181 bNoClose : 1,
182 bSoftClose : 1,
183 bContextActive : 1;
184 css::ui::DockingArea aDockingArea;
185 css::awt::Point aDockPos;
186 css::awt::Size aDockSize;
187 css::awt::Point aPos;
188 css::awt::Size aSize;
189 OUString aUIName;
190 sal_uInt32 nInternalState;
191 sal_uInt16 nStyle;
192 sal_uInt32 nMask; // see WindowStateMask
195 void impl_putPropertiesFromStruct( const WindowStateInfo& rWinStateInfo, Reference< XPropertySet > const & xPropSet );
196 Any impl_insertCacheAndReturnSequence( const OUString& rResourceURL, Reference< XNameAccess > const & rNameAccess );
197 WindowStateInfo& impl_insertCacheAndReturnWinState( const OUString& rResourceURL, Reference< XNameAccess > const & rNameAccess );
198 Any impl_getSequenceFromStruct( const WindowStateInfo& rWinStateInfo );
199 void impl_fillStructFromSequence( WindowStateInfo& rWinStateInfo, const Sequence< PropertyValue >& rSeq );
200 Any impl_getWindowStateFromResourceURL( const OUString& rResourceURL );
201 void impl_initializeConfigAccess();
203 private:
204 typedef std::unordered_map< OUString,
205 WindowStateInfo > ResourceURLToInfoCache;
207 std::mutex m_aMutex;
208 OUString m_aConfigWindowAccess;
209 Reference< XMultiServiceFactory > m_xConfigProvider;
210 Reference< XNameAccess > m_xConfigAccess;
211 Reference< XContainerListener > m_xConfigListener;
212 ResourceURLToInfoCache m_aResourceURLToInfoCache;
213 bool m_bConfigAccessInitialized : 1,
214 m_bModified : 1;
215 std::vector< OUString > m_aPropArray;
218 ConfigurationAccess_WindowState::ConfigurationAccess_WindowState( std::u16string_view aModuleName, const Reference< XComponentContext >& rxContext ) :
219 // Create configuration hierarchical access name
220 m_aConfigWindowAccess(
221 OUString::Concat("/org.openoffice.Office.UI.") + aModuleName + "/UIElements/States"),
222 m_xConfigProvider(theDefaultProvider::get( rxContext )),
223 m_bConfigAccessInitialized( false ),
224 m_bModified( false )
226 // Initialize access array with property names.
227 for (const OUString & s : CONFIGURATION_PROPERTIES )
228 m_aPropArray.push_back(s);
231 ConfigurationAccess_WindowState::~ConfigurationAccess_WindowState()
233 // SAFE
234 std::unique_lock g(m_aMutex);
235 Reference< XContainer > xContainer( m_xConfigAccess, UNO_QUERY );
236 if ( xContainer.is() )
237 xContainer->removeContainerListener(m_xConfigListener);
240 // XNameAccess
241 Any SAL_CALL ConfigurationAccess_WindowState::getByName( const OUString& rResourceURL )
243 // SAFE
244 std::unique_lock g(m_aMutex);
246 ResourceURLToInfoCache::const_iterator pIter = m_aResourceURLToInfoCache.find( rResourceURL );
247 if ( pIter != m_aResourceURLToInfoCache.end() )
248 return impl_getSequenceFromStruct( pIter->second );
249 else
251 Any a( impl_getWindowStateFromResourceURL( rResourceURL ) );
252 if ( a == Any() )
253 throw NoSuchElementException();
254 return a;
258 Sequence< OUString > SAL_CALL ConfigurationAccess_WindowState::getElementNames()
260 // SAFE
261 std::unique_lock g(m_aMutex);
263 if ( !m_bConfigAccessInitialized )
265 impl_initializeConfigAccess();
266 m_bConfigAccessInitialized = true;
269 if ( m_xConfigAccess.is() )
270 return m_xConfigAccess->getElementNames();
271 else
272 return Sequence< OUString > ();
275 sal_Bool SAL_CALL ConfigurationAccess_WindowState::hasByName( const OUString& rResourceURL )
277 // SAFE
278 std::unique_lock g(m_aMutex);
280 ResourceURLToInfoCache::const_iterator pIter = m_aResourceURLToInfoCache.find( rResourceURL );
281 if ( pIter != m_aResourceURLToInfoCache.end() )
282 return true;
283 else
285 Any a( impl_getWindowStateFromResourceURL( rResourceURL ) );
286 if ( a == Any() )
287 return false;
288 else
289 return true;
293 // XElementAccess
294 Type SAL_CALL ConfigurationAccess_WindowState::getElementType()
296 return cppu::UnoType<Sequence< PropertyValue >>::get();
299 sal_Bool SAL_CALL ConfigurationAccess_WindowState::hasElements()
301 // SAFE
302 std::unique_lock g(m_aMutex);
304 if ( !m_bConfigAccessInitialized )
306 impl_initializeConfigAccess();
307 m_bConfigAccessInitialized = true;
310 if ( m_xConfigAccess.is() )
311 return m_xConfigAccess->hasElements();
312 else
313 return false;
316 // XNameContainer
317 void SAL_CALL ConfigurationAccess_WindowState::removeByName( const OUString& rResourceURL )
319 // SAFE
320 std::unique_lock g(m_aMutex);
322 ResourceURLToInfoCache::iterator pIter = m_aResourceURLToInfoCache.find( rResourceURL );
323 if ( pIter != m_aResourceURLToInfoCache.end() )
324 m_aResourceURLToInfoCache.erase( pIter );
326 if ( !m_bConfigAccessInitialized )
328 impl_initializeConfigAccess();
329 m_bConfigAccessInitialized = true;
334 // Remove must be write-through => remove element from configuration
335 Reference< XNameContainer > xNameContainer( m_xConfigAccess, UNO_QUERY );
336 if ( xNameContainer.is() )
338 g.unlock();
340 xNameContainer->removeByName( rResourceURL );
341 Reference< XChangesBatch > xFlush( m_xConfigAccess, UNO_QUERY );
342 if ( xFlush.is() )
343 xFlush->commitChanges();
346 catch ( const css::lang::WrappedTargetException& )
351 void SAL_CALL ConfigurationAccess_WindowState::insertByName( const OUString& rResourceURL, const css::uno::Any& aPropertySet )
353 // SAFE
354 std::unique_lock g(m_aMutex);
356 Sequence< PropertyValue > aPropSet;
357 if ( !(aPropertySet >>= aPropSet) )
358 throw IllegalArgumentException();
360 ResourceURLToInfoCache::const_iterator pIter = m_aResourceURLToInfoCache.find( rResourceURL );
361 if ( pIter != m_aResourceURLToInfoCache.end() )
362 throw ElementExistException();
364 if ( !m_bConfigAccessInitialized )
366 impl_initializeConfigAccess();
367 m_bConfigAccessInitialized = true;
370 // Try to ask our configuration access
371 if ( !m_xConfigAccess.is() )
372 return;
374 if ( m_xConfigAccess->hasByName( rResourceURL ) )
375 throw ElementExistException();
377 WindowStateInfo aWinStateInfo;
378 impl_fillStructFromSequence( aWinStateInfo, aPropSet );
379 m_aResourceURLToInfoCache.emplace( rResourceURL, aWinStateInfo );
381 // insert must be write-through => insert element into configuration
382 Reference< XNameContainer > xNameContainer( m_xConfigAccess, UNO_QUERY );
383 if ( !xNameContainer.is() )
384 return;
386 Reference< XSingleServiceFactory > xFactory( m_xConfigAccess, UNO_QUERY );
387 g.unlock();
391 Reference< XPropertySet > xPropSet( xFactory->createInstance(), UNO_QUERY );
392 if ( xPropSet.is() )
394 Any a;
395 impl_putPropertiesFromStruct( aWinStateInfo, xPropSet );
396 a <<= xPropSet;
397 xNameContainer->insertByName( rResourceURL, a );
398 Reference< XChangesBatch > xFlush( xFactory, UNO_QUERY );
399 if ( xFlush.is() )
400 xFlush->commitChanges();
403 catch ( const Exception& )
408 // XNameReplace
409 void SAL_CALL ConfigurationAccess_WindowState::replaceByName( const OUString& rResourceURL, const css::uno::Any& aPropertySet )
411 // SAFE
412 std::unique_lock g(m_aMutex);
414 Sequence< PropertyValue > aPropSet;
415 if ( !(aPropertySet >>= aPropSet) )
416 throw IllegalArgumentException();
418 ResourceURLToInfoCache::iterator pIter = m_aResourceURLToInfoCache.find( rResourceURL );
419 if ( pIter != m_aResourceURLToInfoCache.end() )
421 WindowStateInfo& rWinStateInfo = pIter->second;
422 impl_fillStructFromSequence( rWinStateInfo, aPropSet );
423 m_bModified = true;
425 else
427 if ( !m_bConfigAccessInitialized )
429 impl_initializeConfigAccess();
430 m_bConfigAccessInitialized = true;
433 // Try to ask our configuration access
434 Reference< XNameAccess > xNameAccess;
435 Any a( m_xConfigAccess->getByName( rResourceURL ));
437 if ( !(a >>= xNameAccess) )
438 throw NoSuchElementException();
440 WindowStateInfo& rWinStateInfo( impl_insertCacheAndReturnWinState( rResourceURL, xNameAccess ));
441 impl_fillStructFromSequence( rWinStateInfo, aPropSet );
442 m_bModified = true;
443 pIter = m_aResourceURLToInfoCache.find( rResourceURL );
447 if ( !(m_bModified && pIter != m_aResourceURLToInfoCache.end()) )
448 return;
450 Reference< XNameContainer > xNameContainer( m_xConfigAccess, UNO_QUERY );
451 if ( !xNameContainer.is() )
452 return;
454 WindowStateInfo aWinStateInfo( pIter->second );
455 OUString aResourceURL( pIter->first );
456 m_bModified = false;
457 g.unlock();
461 Reference< XPropertySet > xPropSet;
462 if ( xNameContainer->getByName( aResourceURL ) >>= xPropSet )
464 impl_putPropertiesFromStruct( aWinStateInfo, xPropSet );
466 Reference< XChangesBatch > xFlush( m_xConfigAccess, UNO_QUERY );
467 if ( xFlush.is() )
468 xFlush->commitChanges();
471 catch ( const Exception& )
477 // container.XContainerListener
478 void SAL_CALL ConfigurationAccess_WindowState::elementInserted( const ContainerEvent& )
480 // do nothing - next time someone wants to retrieve this node we will find it in the configuration
483 void SAL_CALL ConfigurationAccess_WindowState::elementRemoved ( const ContainerEvent& )
487 void SAL_CALL ConfigurationAccess_WindowState::elementReplaced( const ContainerEvent& )
491 // lang.XEventListener
492 void SAL_CALL ConfigurationAccess_WindowState::disposing( const EventObject& aEvent )
494 // SAFE
495 // remove our reference to the config access
496 std::unique_lock g(m_aMutex);
498 Reference< XInterface > xIfac1( aEvent.Source, UNO_QUERY );
499 Reference< XInterface > xIfac2( m_xConfigAccess, UNO_QUERY );
500 if ( xIfac1 == xIfac2 )
501 m_xConfigAccess.clear();
504 // private helper methods
505 Any ConfigurationAccess_WindowState::impl_getSequenceFromStruct( const WindowStateInfo& rWinStateInfo )
507 sal_Int32 i( 0 );
508 sal_Int32 nCount( m_aPropArray.size() );
509 std::vector< PropertyValue > aPropVec;
511 for ( i = 0; i < nCount; i++ )
513 if ( rWinStateInfo.nMask & ( 1 << i ))
515 // put value into the return sequence
516 PropertyValue pv;
517 pv.Name = m_aPropArray[i];
519 switch ( i )
521 case PROPERTY_LOCKED:
522 pv.Value <<= rWinStateInfo.bLocked; break;
523 case PROPERTY_DOCKED:
524 pv.Value <<= rWinStateInfo.bDocked; break;
525 case PROPERTY_VISIBLE:
526 pv.Value <<= rWinStateInfo.bVisible; break;
527 case PROPERTY_CONTEXT:
528 pv.Value <<= rWinStateInfo.bContext; break;
529 case PROPERTY_HIDEFROMMENU:
530 pv.Value <<= rWinStateInfo.bHideFromMenu; break;
531 case PROPERTY_NOCLOSE:
532 pv.Value <<= rWinStateInfo.bNoClose; break;
533 case PROPERTY_SOFTCLOSE:
534 pv.Value <<= rWinStateInfo.bSoftClose; break;
535 case PROPERTY_CONTEXTACTIVE:
536 pv.Value <<= rWinStateInfo.bContextActive; break;
537 case PROPERTY_DOCKINGAREA:
538 pv.Value <<= rWinStateInfo.aDockingArea; break;
539 case PROPERTY_POS:
540 pv.Value <<= rWinStateInfo.aPos; break;
541 case PROPERTY_SIZE:
542 pv.Value <<= rWinStateInfo.aSize; break;
543 case PROPERTY_UINAME:
544 pv.Value <<= rWinStateInfo.aUIName; break;
545 case PROPERTY_INTERNALSTATE:
546 pv.Value <<= sal_Int32( rWinStateInfo.nInternalState ); break;
547 case PROPERTY_STYLE:
548 pv.Value <<= sal_Int16( rWinStateInfo.nStyle ); break;
549 case PROPERTY_DOCKPOS:
550 pv.Value <<= rWinStateInfo.aDockPos; break;
551 case PROPERTY_DOCKSIZE:
552 pv.Value <<= rWinStateInfo.aDockSize; break;
553 default:
554 assert( false && "Wrong value for ConfigurationAccess_WindowState. Who has forgotten to add this new property!" );
556 aPropVec.push_back(pv);
560 return Any( comphelper::containerToSequence(aPropVec) );
563 Any ConfigurationAccess_WindowState::impl_insertCacheAndReturnSequence( const OUString& rResourceURL, Reference< XNameAccess > const & xNameAccess )
565 sal_Int32 nMask( 0 );
566 sal_Int32 nCount( m_aPropArray.size() );
567 sal_Int32 i( 0 );
568 std::vector< PropertyValue > aPropVec;
569 WindowStateInfo aWindowStateInfo;
571 for ( i = 0; i < nCount; i++ )
575 bool bAddToSeq( false );
576 Any a( xNameAccess->getByName( m_aPropArray[i] ) );
577 switch ( i )
579 case PROPERTY_LOCKED:
580 case PROPERTY_DOCKED:
581 case PROPERTY_VISIBLE:
582 case PROPERTY_CONTEXT:
583 case PROPERTY_HIDEFROMMENU:
584 case PROPERTY_NOCLOSE:
585 case PROPERTY_SOFTCLOSE:
586 case PROPERTY_CONTEXTACTIVE:
588 bool bValue;
589 if ( a >>= bValue )
591 sal_Int32 nValue( 1 << i );
592 nMask |= nValue;
593 bAddToSeq = true;
594 switch ( i )
596 case PROPERTY_LOCKED:
597 aWindowStateInfo.bLocked = bValue; break;
598 case PROPERTY_DOCKED:
599 aWindowStateInfo.bDocked = bValue; break;
600 case PROPERTY_VISIBLE:
601 aWindowStateInfo.bVisible = bValue; break;
602 case PROPERTY_CONTEXT:
603 aWindowStateInfo.bContext = bValue; break;
604 case PROPERTY_HIDEFROMMENU:
605 aWindowStateInfo.bHideFromMenu = bValue; break;
606 case PROPERTY_NOCLOSE:
607 aWindowStateInfo.bNoClose = bValue; break;
608 case PROPERTY_SOFTCLOSE:
609 aWindowStateInfo.bSoftClose = bValue; break;
610 case PROPERTY_CONTEXTACTIVE:
611 aWindowStateInfo.bContextActive = bValue; break;
615 break;
617 case PROPERTY_DOCKINGAREA:
619 sal_Int32 nDockingArea = 0;
620 if ( a >>= nDockingArea )
622 if (( nDockingArea >= 0 ) &&
623 ( nDockingArea <= sal_Int32( DockingArea_DOCKINGAREA_RIGHT )))
625 aWindowStateInfo.aDockingArea = static_cast<DockingArea>(nDockingArea);
626 nMask |= WINDOWSTATE_MASK_DOCKINGAREA;
627 a <<= aWindowStateInfo.aDockingArea;
628 bAddToSeq = true;
632 break;
634 case PROPERTY_POS:
635 case PROPERTY_DOCKPOS:
637 OUString aString;
638 if ( a >>= aString )
640 sal_Int32 nToken( 0 );
641 std::u16string_view aXStr = o3tl::getToken(aString, 0, ',', nToken );
642 if ( nToken > 0 )
644 css::awt::Point aPos;
645 aPos.X = o3tl::toInt32(aXStr);
646 aPos.Y = o3tl::toInt32(o3tl::getToken(aString, 0, ',', nToken ));
648 if ( i == PROPERTY_POS )
650 aWindowStateInfo.aPos = aPos;
651 nMask |= WINDOWSTATE_MASK_POS;
653 else
655 aWindowStateInfo.aDockPos = aPos;
656 nMask |= WINDOWSTATE_MASK_DOCKPOS;
659 a <<= aPos;
660 bAddToSeq = true;
664 break;
666 case PROPERTY_SIZE:
667 case PROPERTY_DOCKSIZE:
669 OUString aString;
670 if ( a >>= aString )
672 sal_Int32 nToken( 0 );
673 std::u16string_view aStr = o3tl::getToken(aString, 0, ',', nToken );
674 if ( nToken > 0 )
676 css::awt::Size aSize;
677 aSize.Width = o3tl::toInt32(aStr);
678 aSize.Height = o3tl::toInt32(o3tl::getToken(aString, 0, ',', nToken ));
679 if ( i == PROPERTY_SIZE )
681 aWindowStateInfo.aSize = aSize;
682 nMask |= WINDOWSTATE_MASK_SIZE;
684 else
686 aWindowStateInfo.aDockSize = aSize;
687 nMask |= WINDOWSTATE_MASK_DOCKSIZE;
690 a <<= aSize;
691 bAddToSeq = true;
695 break;
697 case PROPERTY_UINAME:
699 OUString aValue;
700 if ( a >>= aValue )
702 nMask |= WINDOWSTATE_MASK_UINAME;
703 aWindowStateInfo.aUIName = aValue;
704 bAddToSeq = true;
707 break;
709 case PROPERTY_INTERNALSTATE:
711 sal_uInt32 nValue = 0;
712 if ( a >>= nValue )
714 nMask |= WINDOWSTATE_MASK_INTERNALSTATE;
715 aWindowStateInfo.nInternalState = nValue;
716 bAddToSeq = true;
719 break;
721 case PROPERTY_STYLE:
723 sal_Int32 nValue = 0;
724 if ( a >>= nValue )
726 nMask |= WINDOWSTATE_MASK_STYLE;
727 aWindowStateInfo.nStyle = sal_uInt16( nValue );
728 bAddToSeq = true;
731 break;
733 default:
734 assert( false && "Wrong value for ConfigurationAccess_WindowState. Who has forgotten to add this new property!" );
737 if ( bAddToSeq )
739 // put value into the return sequence
740 PropertyValue pv;
741 pv.Name = m_aPropArray[i];
742 pv.Value = a;
743 aPropVec.push_back(pv);
746 catch( const css::container::NoSuchElementException& )
749 catch ( const css::lang::WrappedTargetException& )
754 aWindowStateInfo.nMask = nMask;
755 m_aResourceURLToInfoCache.emplace( rResourceURL, aWindowStateInfo );
756 return Any( comphelper::containerToSequence(aPropVec) );
759 ConfigurationAccess_WindowState::WindowStateInfo& ConfigurationAccess_WindowState::impl_insertCacheAndReturnWinState( const OUString& rResourceURL, Reference< XNameAccess > const & rNameAccess )
761 sal_Int32 nMask( 0 );
762 sal_Int32 nCount( m_aPropArray.size() );
763 sal_Int32 i( 0 );
764 WindowStateInfo aWindowStateInfo;
766 for ( i = 0; i < nCount; i++ )
770 Any a( rNameAccess->getByName( m_aPropArray[i] ) );
771 switch ( i )
773 case PROPERTY_LOCKED:
774 case PROPERTY_DOCKED:
775 case PROPERTY_VISIBLE:
776 case PROPERTY_CONTEXT:
777 case PROPERTY_HIDEFROMMENU:
778 case PROPERTY_NOCLOSE:
779 case PROPERTY_SOFTCLOSE:
780 case PROPERTY_CONTEXTACTIVE:
782 bool bValue;
783 if ( a >>= bValue )
785 sal_Int32 nValue( 1 << i );
786 nMask |= nValue;
787 switch ( i )
789 case PROPERTY_LOCKED:
790 aWindowStateInfo.bLocked = bValue; break;
791 case PROPERTY_DOCKED:
792 aWindowStateInfo.bDocked = bValue; break;
793 case PROPERTY_VISIBLE:
794 aWindowStateInfo.bVisible = bValue; break;
795 case PROPERTY_CONTEXT:
796 aWindowStateInfo.bContext = bValue; break;
797 case PROPERTY_HIDEFROMMENU:
798 aWindowStateInfo.bHideFromMenu = bValue; break;
799 case PROPERTY_NOCLOSE:
800 aWindowStateInfo.bNoClose = bValue; break;
801 case PROPERTY_SOFTCLOSE:
802 aWindowStateInfo.bNoClose = bValue; break;
803 case PROPERTY_CONTEXTACTIVE:
804 aWindowStateInfo.bContextActive = bValue; break;
805 default:
806 SAL_WARN( "fwk.uiconfiguration", "Unknown boolean property in WindowState found!" );
810 break;
812 case PROPERTY_DOCKINGAREA:
814 sal_Int32 nDockingArea = 0;
815 if ( a >>= nDockingArea )
817 if (( nDockingArea >= 0 ) &&
818 ( nDockingArea <= sal_Int32( DockingArea_DOCKINGAREA_RIGHT )))
820 aWindowStateInfo.aDockingArea = static_cast<DockingArea>(nDockingArea);
821 nMask |= WINDOWSTATE_MASK_DOCKINGAREA;
825 break;
827 case PROPERTY_POS:
828 case PROPERTY_DOCKPOS:
830 OUString aString;
831 if ( a >>= aString )
833 sal_Int32 nToken( 0 );
834 std::u16string_view aXStr = o3tl::getToken(aString, 0, ',', nToken );
835 if ( nToken > 0 )
837 css::awt::Point aPos;
838 aPos.X = o3tl::toInt32(aXStr);
839 aPos.Y = o3tl::toInt32(o3tl::getToken(aString, 0, ',', nToken ));
841 if ( i == PROPERTY_POS )
843 aWindowStateInfo.aPos = aPos;
844 nMask |= WINDOWSTATE_MASK_POS;
846 else
848 aWindowStateInfo.aDockPos = aPos;
849 nMask |= WINDOWSTATE_MASK_DOCKPOS;
854 break;
856 case PROPERTY_SIZE:
857 case PROPERTY_DOCKSIZE:
859 OUString aString;
860 if ( a >>= aString )
862 sal_Int32 nToken( 0 );
863 std::u16string_view aStr = o3tl::getToken(aString, 0, ',', nToken );
864 if ( nToken > 0 )
866 css::awt::Size aSize;
867 aSize.Width = o3tl::toInt32(aStr);
868 aSize.Height = o3tl::toInt32(o3tl::getToken(aString, 0, ',', nToken ));
869 if ( i == PROPERTY_SIZE )
871 aWindowStateInfo.aSize = aSize;
872 nMask |= WINDOWSTATE_MASK_SIZE;
874 else
876 aWindowStateInfo.aDockSize = aSize;
877 nMask |= WINDOWSTATE_MASK_DOCKSIZE;
882 break;
884 case PROPERTY_UINAME:
886 OUString aValue;
887 if ( a >>= aValue )
889 nMask |= WINDOWSTATE_MASK_UINAME;
890 aWindowStateInfo.aUIName = aValue;
893 break;
895 case PROPERTY_INTERNALSTATE:
897 sal_Int32 nValue = 0;
898 if ( a >>= nValue )
900 nMask |= WINDOWSTATE_MASK_INTERNALSTATE;
901 aWindowStateInfo.nInternalState = sal_uInt32( nValue );
904 break;
906 case PROPERTY_STYLE:
908 sal_Int32 nValue = 0;
909 if ( a >>= nValue )
911 nMask |= WINDOWSTATE_MASK_STYLE;
912 aWindowStateInfo.nStyle = sal_uInt16( nValue );
915 break;
917 default:
918 assert( false && "Wrong value for ConfigurationAccess_WindowState. Who has forgotten to add this new property!" );
921 catch( const css::container::NoSuchElementException& )
924 catch ( const css::lang::WrappedTargetException& )
929 aWindowStateInfo.nMask = nMask;
930 ResourceURLToInfoCache::iterator pIter = m_aResourceURLToInfoCache.emplace( rResourceURL, aWindowStateInfo ).first;
931 return pIter->second;
934 Any ConfigurationAccess_WindowState::impl_getWindowStateFromResourceURL( const OUString& rResourceURL )
936 if ( !m_bConfigAccessInitialized )
938 impl_initializeConfigAccess();
939 m_bConfigAccessInitialized = true;
944 // Try to ask our configuration access
945 if ( m_xConfigAccess.is() && m_xConfigAccess->hasByName( rResourceURL ) )
948 Reference< XNameAccess > xNameAccess( m_xConfigAccess->getByName( rResourceURL ), UNO_QUERY );
949 if ( xNameAccess.is() )
950 return impl_insertCacheAndReturnSequence( rResourceURL, xNameAccess );
953 catch( const css::container::NoSuchElementException& )
956 catch ( const css::lang::WrappedTargetException& )
960 return Any();
963 void ConfigurationAccess_WindowState::impl_fillStructFromSequence( WindowStateInfo& rWinStateInfo, const Sequence< PropertyValue >& rSeq )
965 sal_Int32 nCompareCount( m_aPropArray.size() );
966 sal_Int32 nCount( rSeq.getLength() );
967 sal_Int32 i( 0 );
969 for ( i = 0; i < nCount; i++ )
971 for ( sal_Int32 j = 0; j < nCompareCount; j++ )
973 if ( rSeq[i].Name == m_aPropArray[j] )
975 switch ( j )
977 case PROPERTY_LOCKED:
978 case PROPERTY_DOCKED:
979 case PROPERTY_VISIBLE:
980 case PROPERTY_CONTEXT:
981 case PROPERTY_HIDEFROMMENU:
982 case PROPERTY_NOCLOSE:
983 case PROPERTY_SOFTCLOSE:
984 case PROPERTY_CONTEXTACTIVE:
986 bool bValue;
987 if ( rSeq[i].Value >>= bValue )
989 sal_Int32 nValue( 1 << j );
990 rWinStateInfo.nMask |= nValue;
991 switch ( j )
993 case PROPERTY_LOCKED:
994 rWinStateInfo.bLocked = bValue;
995 break;
996 case PROPERTY_DOCKED:
997 rWinStateInfo.bDocked = bValue;
998 break;
999 case PROPERTY_VISIBLE:
1000 rWinStateInfo.bVisible = bValue;
1001 break;
1002 case PROPERTY_CONTEXT:
1003 rWinStateInfo.bContext = bValue;
1004 break;
1005 case PROPERTY_HIDEFROMMENU:
1006 rWinStateInfo.bHideFromMenu = bValue;
1007 break;
1008 case PROPERTY_NOCLOSE:
1009 rWinStateInfo.bNoClose = bValue;
1010 break;
1011 case PROPERTY_SOFTCLOSE:
1012 rWinStateInfo.bSoftClose = bValue;
1013 break;
1014 case PROPERTY_CONTEXTACTIVE:
1015 rWinStateInfo.bContextActive = bValue;
1016 break;
1020 break;
1022 case PROPERTY_DOCKINGAREA:
1024 css::ui::DockingArea eDockingArea;
1025 if ( rSeq[i].Value >>= eDockingArea )
1027 rWinStateInfo.aDockingArea = eDockingArea;
1028 rWinStateInfo.nMask |= WINDOWSTATE_MASK_DOCKINGAREA;
1031 break;
1033 case PROPERTY_POS:
1034 case PROPERTY_DOCKPOS:
1036 css::awt::Point aPoint;
1037 if ( rSeq[i].Value >>= aPoint )
1039 if ( j == PROPERTY_POS )
1041 rWinStateInfo.aPos = aPoint;
1042 rWinStateInfo.nMask |= WINDOWSTATE_MASK_POS;
1044 else
1046 rWinStateInfo.aDockPos = aPoint;
1047 rWinStateInfo.nMask |= WINDOWSTATE_MASK_DOCKPOS;
1051 break;
1053 case PROPERTY_SIZE:
1054 case PROPERTY_DOCKSIZE:
1056 css::awt::Size aSize;
1057 if ( rSeq[i].Value >>= aSize )
1059 if ( j == PROPERTY_SIZE )
1061 rWinStateInfo.aSize = aSize;
1062 rWinStateInfo.nMask |= WINDOWSTATE_MASK_SIZE;
1064 else
1066 rWinStateInfo.aDockSize = aSize;
1067 rWinStateInfo.nMask |= WINDOWSTATE_MASK_DOCKSIZE;
1071 break;
1073 case PROPERTY_UINAME:
1075 OUString aValue;
1076 if ( rSeq[i].Value >>= aValue )
1078 rWinStateInfo.aUIName = aValue;
1079 rWinStateInfo.nMask |= WINDOWSTATE_MASK_UINAME;
1082 break;
1084 case PROPERTY_INTERNALSTATE:
1086 sal_Int32 nValue = 0;
1087 if ( rSeq[i].Value >>= nValue )
1089 rWinStateInfo.nInternalState = sal_uInt32( nValue );
1090 rWinStateInfo.nMask |= WINDOWSTATE_MASK_INTERNALSTATE;
1093 break;
1095 case PROPERTY_STYLE:
1097 sal_Int32 nValue = 0;
1098 if ( rSeq[i].Value >>= nValue )
1100 rWinStateInfo.nStyle = sal_uInt16( nValue );
1101 rWinStateInfo.nMask |= WINDOWSTATE_MASK_STYLE;
1104 break;
1106 default:
1107 assert( false && "Wrong value for ConfigurationAccess_WindowState. Who has forgotten to add this new property!" );
1110 break;
1116 void ConfigurationAccess_WindowState::impl_putPropertiesFromStruct( const WindowStateInfo& rWinStateInfo, Reference< XPropertySet > const & xPropSet )
1118 sal_Int32 i( 0 );
1119 sal_Int32 nCount( m_aPropArray.size() );
1120 OUString aDelim( "," );
1122 for ( i = 0; i < nCount; i++ )
1124 if ( rWinStateInfo.nMask & ( 1 << i ))
1128 // put values into the property set
1129 switch ( i )
1131 case PROPERTY_LOCKED:
1132 xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bLocked ) ); break;
1133 case PROPERTY_DOCKED:
1134 xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bDocked ) ); break;
1135 case PROPERTY_VISIBLE:
1136 xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bVisible ) ); break;
1137 case PROPERTY_CONTEXT:
1138 xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bContext ) ); break;
1139 case PROPERTY_HIDEFROMMENU:
1140 xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bHideFromMenu ) ); break;
1141 case PROPERTY_NOCLOSE:
1142 xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bNoClose ) ); break;
1143 case PROPERTY_SOFTCLOSE:
1144 xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bSoftClose ) ); break;
1145 case PROPERTY_CONTEXTACTIVE:
1146 xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bContextActive ) ); break;
1147 case PROPERTY_DOCKINGAREA:
1148 xPropSet->setPropertyValue( m_aPropArray[i], Any( sal_Int16( rWinStateInfo.aDockingArea ) ) ); break;
1149 case PROPERTY_POS:
1150 case PROPERTY_DOCKPOS:
1152 OUString aPosStr;
1153 if ( i == PROPERTY_POS )
1154 aPosStr = OUString::number( rWinStateInfo.aPos.X );
1155 else
1156 aPosStr = OUString::number( rWinStateInfo.aDockPos.X );
1157 aPosStr += aDelim;
1158 if ( i == PROPERTY_POS )
1159 aPosStr += OUString::number( rWinStateInfo.aPos.Y );
1160 else
1161 aPosStr += OUString::number( rWinStateInfo.aDockPos.Y );
1162 xPropSet->setPropertyValue( m_aPropArray[i], Any( aPosStr ) );
1163 break;
1165 case PROPERTY_SIZE:
1166 case PROPERTY_DOCKSIZE:
1168 OUString aSizeStr;
1169 if ( i == PROPERTY_SIZE )
1170 aSizeStr = OUString::number( rWinStateInfo.aSize.Width );
1171 else
1172 aSizeStr = OUString::number( rWinStateInfo.aDockSize.Width );
1173 aSizeStr += aDelim;
1174 if ( i == PROPERTY_SIZE )
1175 aSizeStr += OUString::number( rWinStateInfo.aSize.Height );
1176 else
1177 aSizeStr += OUString::number( rWinStateInfo.aDockSize.Height );
1178 xPropSet->setPropertyValue( m_aPropArray[i], Any( aSizeStr ) );
1179 break;
1181 case PROPERTY_UINAME:
1182 xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.aUIName ) ); break;
1183 case PROPERTY_INTERNALSTATE:
1184 xPropSet->setPropertyValue( m_aPropArray[i], Any( sal_Int32( rWinStateInfo.nInternalState )) ); break;
1185 case PROPERTY_STYLE:
1186 xPropSet->setPropertyValue( m_aPropArray[i], Any( sal_Int32( rWinStateInfo.nStyle )) ); break;
1187 default:
1188 assert( false && "Wrong value for ConfigurationAccess_WindowState. Who has forgotten to add this new property!" );
1191 catch( const Exception& )
1198 void ConfigurationAccess_WindowState::impl_initializeConfigAccess()
1202 Sequence<Any> aArgs(comphelper::InitAnyPropertySequence(
1204 {"nodepath", Any(m_aConfigWindowAccess)}
1205 }));
1206 m_xConfigAccess.set( m_xConfigProvider->createInstanceWithArguments(
1207 "com.sun.star.configuration.ConfigurationUpdateAccess", aArgs ), UNO_QUERY );
1208 if ( m_xConfigAccess.is() )
1210 // Add as container listener
1211 Reference< XContainer > xContainer( m_xConfigAccess, UNO_QUERY );
1212 if ( xContainer.is() )
1214 m_xConfigListener = new WeakContainerListener(this);
1215 xContainer->addContainerListener(m_xConfigListener);
1219 catch ( const WrappedTargetException& )
1222 catch ( const Exception& )
1227 typedef comphelper::WeakComponentImplHelper< css::container::XNameAccess,
1228 css::lang::XServiceInfo> WindowStateConfiguration_BASE;
1230 class WindowStateConfiguration : public WindowStateConfiguration_BASE
1232 public:
1233 explicit WindowStateConfiguration( const css::uno::Reference< css::uno::XComponentContext >& rxContext );
1234 virtual ~WindowStateConfiguration() override;
1236 virtual OUString SAL_CALL getImplementationName() override
1238 return "com.sun.star.comp.framework.WindowStateConfiguration";
1241 virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
1243 return cppu::supportsService(this, ServiceName);
1246 virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
1248 return {"com.sun.star.ui.WindowStateConfiguration"};
1251 // XNameAccess
1252 virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override;
1254 virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override;
1256 virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override;
1258 // XElementAccess
1259 virtual css::uno::Type SAL_CALL getElementType() override;
1260 virtual sal_Bool SAL_CALL hasElements() override;
1262 typedef std::unordered_map< OUString,
1263 OUString > ModuleToWindowStateFileMap;
1265 typedef std::unordered_map< OUString,
1266 css::uno::Reference< css::container::XNameAccess > > ModuleToWindowStateConfigHashMap;
1268 private:
1269 css::uno::Reference< css::uno::XComponentContext> m_xContext;
1270 ModuleToWindowStateFileMap m_aModuleToFileHashMap;
1271 ModuleToWindowStateConfigHashMap m_aModuleToWindowStateHashMap;
1274 WindowStateConfiguration::WindowStateConfiguration( const Reference< XComponentContext >& rxContext ) :
1275 m_xContext( rxContext )
1277 css::uno::Reference< css::frame::XModuleManager2 > xModuleManager =
1278 ModuleManager::create( m_xContext );
1279 Reference< XNameAccess > xEmptyNameAccess;
1280 Sequence< OUString > aElementNames;
1283 aElementNames = xModuleManager->getElementNames();
1285 catch (const css::uno::RuntimeException &)
1288 Sequence< PropertyValue > aSeq;
1290 for ( OUString const & aModuleIdentifier : std::as_const(aElementNames) )
1292 if ( xModuleManager->getByName( aModuleIdentifier ) >>= aSeq )
1294 OUString aWindowStateFileStr;
1295 for ( PropertyValue const & rProp : std::as_const(aSeq) )
1297 if ( rProp.Name == "ooSetupFactoryWindowStateConfigRef" )
1299 rProp.Value >>= aWindowStateFileStr;
1300 break;
1304 if ( !aWindowStateFileStr.isEmpty() )
1306 // Create first mapping ModuleIdentifier ==> Window state configuration file
1307 m_aModuleToFileHashMap.emplace( aModuleIdentifier, aWindowStateFileStr );
1309 // Create second mapping Command File ==> Window state configuration instance
1310 ModuleToWindowStateConfigHashMap::iterator pIter = m_aModuleToWindowStateHashMap.find( aWindowStateFileStr );
1311 if ( pIter == m_aModuleToWindowStateHashMap.end() )
1312 m_aModuleToWindowStateHashMap.emplace( aWindowStateFileStr, xEmptyNameAccess );
1318 WindowStateConfiguration::~WindowStateConfiguration()
1320 std::unique_lock g(m_aMutex);
1321 m_aModuleToFileHashMap.clear();
1322 m_aModuleToWindowStateHashMap.clear();
1325 Any SAL_CALL WindowStateConfiguration::getByName( const OUString& aModuleIdentifier )
1327 std::unique_lock g(m_aMutex);
1329 ModuleToWindowStateFileMap::const_iterator pIter = m_aModuleToFileHashMap.find( aModuleIdentifier );
1330 if ( pIter != m_aModuleToFileHashMap.end() )
1332 Any a;
1333 OUString aWindowStateConfigFile( pIter->second );
1335 ModuleToWindowStateConfigHashMap::iterator pModuleIter = m_aModuleToWindowStateHashMap.find( aWindowStateConfigFile );
1336 if ( pModuleIter != m_aModuleToWindowStateHashMap.end() )
1338 if ( pModuleIter->second.is() )
1339 a <<= pModuleIter->second;
1340 else
1342 Reference< XNameAccess > xResourceURLWindowState = new ConfigurationAccess_WindowState( aWindowStateConfigFile, m_xContext );
1343 pModuleIter->second = xResourceURLWindowState;
1344 a <<= xResourceURLWindowState;
1347 return a;
1351 throw NoSuchElementException();
1354 Sequence< OUString > SAL_CALL WindowStateConfiguration::getElementNames()
1356 std::unique_lock g(m_aMutex);
1358 return comphelper::mapKeysToSequence( m_aModuleToFileHashMap );
1361 sal_Bool SAL_CALL WindowStateConfiguration::hasByName( const OUString& aName )
1363 std::unique_lock g(m_aMutex);
1365 ModuleToWindowStateFileMap::const_iterator pIter = m_aModuleToFileHashMap.find( aName );
1366 return ( pIter != m_aModuleToFileHashMap.end() );
1369 // XElementAccess
1370 Type SAL_CALL WindowStateConfiguration::getElementType()
1372 return cppu::UnoType<XNameAccess>::get();
1375 sal_Bool SAL_CALL WindowStateConfiguration::hasElements()
1377 // We always have at least one module. So it is valid to return true!
1378 return true;
1383 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
1384 com_sun_star_comp_framework_WindowStateConfiguration_get_implementation(
1385 css::uno::XComponentContext *context,
1386 css::uno::Sequence<css::uno::Any> const &)
1388 return cppu::acquire(new WindowStateConfiguration(context));
1391 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */