nss: upgrade to release 3.73
[LibreOffice.git] / sfx2 / source / control / shell.cxx
blob353dde1a8d07df61ece541d237c76b335e3216ef
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 <com/sun/star/embed/VerbDescriptor.hpp>
21 #include <com/sun/star/embed/VerbAttributes.hpp>
22 #include <officecfg/Office/Common.hxx>
23 #include <rtl/ustring.hxx>
24 #include <sal/log.hxx>
25 #include <osl/diagnose.h>
26 #include <svl/itempool.hxx>
27 #include <svl/undo.hxx>
28 #include <itemdel.hxx>
29 #include <svtools/asynclink.hxx>
30 #include <unotools/configmgr.hxx>
31 #include <comphelper/lok.hxx>
32 #include <sfx2/shell.hxx>
33 #include <sfx2/bindings.hxx>
34 #include <sfx2/dispatch.hxx>
35 #include <sfx2/viewfrm.hxx>
36 #include <sfx2/objface.hxx>
37 #include <sfx2/objsh.hxx>
38 #include <sfx2/viewsh.hxx>
39 #include <sfx2/request.hxx>
40 #include <sfx2/sfxsids.hrc>
41 #include <statcach.hxx>
42 #include <sidebar/ContextChangeBroadcaster.hxx>
43 #include <com/sun/star/ui/dialogs/XSLTFilterDialog.hpp>
44 #include <tools/debug.hxx>
46 #include <memory>
47 #include <vector>
48 #include <map>
50 using namespace com::sun::star;
52 struct SfxShell_Impl: public SfxBroadcaster
54 OUString aObjectName; // Name of Sbx-Objects
55 // Maps the Which() field to a pointer to a SfxPoolItem
56 std::map<sal_uInt16, std::unique_ptr<SfxPoolItem>>
57 m_Items; // Data exchange on Item level
58 SfxViewShell* pViewSh; // SfxViewShell if Shell is
59 // ViewFrame/ViewShell/SubShell list
60 SfxViewFrame* pFrame; // Frame, if <UI-active>
61 SfxRepeatTarget* pRepeatTarget; // SbxObjectRef xParent;
62 bool bActive;
63 SfxDisableFlags nDisableFlags;
64 std::unique_ptr<svtools::AsynchronLink> pExecuter;
65 std::unique_ptr<svtools::AsynchronLink> pUpdater;
66 std::vector<std::unique_ptr<SfxSlot> > aSlotArr;
68 css::uno::Sequence < css::embed::VerbDescriptor > aVerbList;
69 ::sfx2::sidebar::ContextChangeBroadcaster maContextChangeBroadcaster;
71 SfxShell_Impl()
72 : pViewSh(nullptr)
73 , pFrame(nullptr)
74 , pRepeatTarget(nullptr)
75 , bActive(false)
76 , nDisableFlags(SfxDisableFlags::NONE)
80 virtual ~SfxShell_Impl() override { pExecuter.reset(); pUpdater.reset();}
84 void SfxShell::EmptyExecStub(SfxShell *, SfxRequest &)
88 void SfxShell::EmptyStateStub(SfxShell *, SfxItemSet &)
92 SfxShell::SfxShell()
93 : pImpl(new SfxShell_Impl),
94 pPool(nullptr),
95 pUndoMgr(nullptr)
99 SfxShell::SfxShell( SfxViewShell *pViewSh )
100 : pImpl(new SfxShell_Impl),
101 pPool(nullptr),
102 pUndoMgr(nullptr)
104 pImpl->pViewSh = pViewSh;
107 SfxShell::~SfxShell()
111 void SfxShell::SetName( const OUString &rName )
113 pImpl->aObjectName = rName;
116 const OUString& SfxShell::GetName() const
118 return pImpl->aObjectName;
121 SfxDispatcher* SfxShell::GetDispatcher() const
123 return pImpl->pFrame ? pImpl->pFrame->GetDispatcher() : nullptr;
126 SfxViewShell* SfxShell::GetViewShell() const
128 return pImpl->pViewSh;
131 SfxViewFrame* SfxShell::GetFrame() const
133 if ( pImpl->pFrame )
134 return pImpl->pFrame;
135 if ( pImpl->pViewSh )
136 return pImpl->pViewSh->GetViewFrame();
137 return nullptr;
140 const SfxPoolItem* SfxShell::GetItem
142 sal_uInt16 nSlotId // Slot-Id of the querying <SfxPoolItem>s
143 ) const
145 auto const it = pImpl->m_Items.find( nSlotId );
146 if (it != pImpl->m_Items.end())
147 return it->second.get();
148 return nullptr;
151 void SfxShell::PutItem
153 const SfxPoolItem& rItem /* Instance, of which a copy is created,
154 which is stored in the SfxShell in a list. */
157 DBG_ASSERT( dynamic_cast< const SfxSetItem* >( &rItem) == nullptr, "SetItems aren't allowed here" );
158 DBG_ASSERT( SfxItemPool::IsSlot( rItem.Which() ),
159 "items with Which-Ids aren't allowed here" );
161 // MSC made a mess here of WNT/W95, beware of changes
162 SfxPoolItem *pItem = rItem.Clone();
163 SfxPoolItemHint aItemHint( pItem );
164 sal_uInt16 nWhich = rItem.Which();
166 auto const it = pImpl->m_Items.find(nWhich);
167 if (it != pImpl->m_Items.end())
169 // Replace Item
170 pImpl->m_Items.erase( it );
171 pImpl->m_Items.insert(std::make_pair(nWhich, std::unique_ptr<SfxPoolItem>(pItem)));
173 // if active, notify Bindings
174 SfxDispatcher *pDispat = GetDispatcher();
175 if ( pDispat )
177 SfxBindings* pBindings = pDispat->GetBindings();
178 pBindings->Broadcast( aItemHint );
179 sal_uInt16 nSlotId = nWhich; //pItem->GetSlotId();
180 SfxStateCache* pCache = pBindings->GetStateCache( nSlotId );
181 if ( pCache )
183 pCache->SetState( SfxItemState::DEFAULT, pItem, true );
184 pCache->SetCachedState( true );
187 return;
189 else
191 Broadcast( aItemHint );
192 pImpl->m_Items.insert(std::make_pair(nWhich, std::unique_ptr<SfxPoolItem>(pItem)));
196 SfxInterface* SfxShell::GetInterface() const
198 return GetStaticInterface();
201 SfxUndoManager* SfxShell::GetUndoManager()
203 return pUndoMgr;
206 void SfxShell::SetUndoManager( SfxUndoManager *pNewUndoMgr )
208 OSL_ENSURE( ( pUndoMgr == nullptr ) || ( pNewUndoMgr == nullptr ) || ( pUndoMgr == pNewUndoMgr ),
209 "SfxShell::SetUndoManager: exchanging one non-NULL manager with another non-NULL manager? Suspicious!" );
210 // there's at least one client of our UndoManager - the DocumentUndoManager at the SfxBaseModel - which
211 // caches the UndoManager, and registers itself as listener. If exchanging non-NULL UndoManagers is really
212 // a supported scenario (/me thinks it is not), then we would need to notify all such clients instances.
214 pUndoMgr = pNewUndoMgr;
215 if (pUndoMgr && !utl::ConfigManager::IsFuzzing())
217 pUndoMgr->SetMaxUndoActionCount(
218 officecfg::Office::Common::Undo::Steps::get());
222 SfxRepeatTarget* SfxShell::GetRepeatTarget() const
224 return pImpl->pRepeatTarget;
227 void SfxShell::SetRepeatTarget( SfxRepeatTarget *pTarget )
229 pImpl->pRepeatTarget = pTarget;
232 void SfxShell::Invalidate
234 sal_uInt16 nId /* Invalidated Slot-Id or Which-Id.
235 If these are 0 (default), then all
236 by this Shell currently handled Slot-Ids are
237 invalidated. */
240 if ( !GetViewShell() )
242 OSL_FAIL( "wrong Invalidate method called!" );
243 return;
246 Invalidate_Impl( GetViewShell()->GetViewFrame()->GetBindings(), nId );
249 void SfxShell::Invalidate_Impl( SfxBindings& rBindings, sal_uInt16 nId )
251 if ( nId == 0 )
253 rBindings.InvalidateShell( *this );
255 else
257 const SfxInterface *pIF = GetInterface();
260 const SfxSlot *pSlot = pIF->GetSlot(nId);
261 if ( pSlot )
263 // Invalidate the Slot itself
264 rBindings.Invalidate( pSlot->GetSlotId() );
265 return;
268 pIF = pIF->GetGenoType();
271 while ( pIF );
273 SAL_INFO( "sfx.control", "W3: invalidating slot-id unknown in shell" );
277 void SfxShell::HandleOpenXmlFilterSettings(SfxRequest & rReq)
281 uno::Reference < ui::dialogs::XExecutableDialog > xDialog = ui::dialogs::XSLTFilterDialog::create( ::comphelper::getProcessComponentContext() );
282 xDialog->execute();
284 catch (const uno::Exception&)
287 rReq.Ignore ();
290 void SfxShell::DoActivate_Impl( SfxViewFrame *pFrame, bool bMDI )
292 #ifdef DBG_UTIL
293 const SfxInterface *p_IF = GetInterface();
294 if ( !p_IF )
295 return;
296 #endif
297 SAL_INFO(
298 "sfx.control",
299 "SfxShell::DoActivate() " << this << " " << GetInterface()->GetClassName()
300 << " bMDI " << (bMDI ? "MDI" : ""));
302 if ( bMDI )
304 // Remember Frame, in which it was activated
305 pImpl->pFrame = pFrame;
306 pImpl->bActive = true;
309 // Notify Subclass
310 Activate(bMDI);
313 void SfxShell::DoDeactivate_Impl( SfxViewFrame const *pFrame, bool bMDI )
315 #ifdef DBG_UTIL
316 const SfxInterface *p_IF = GetInterface();
317 if ( !p_IF )
318 return;
319 #endif
320 SAL_INFO(
321 "sfx.control",
322 "SfxShell::DoDeactivate()" << this << " " << GetInterface()->GetClassName()
323 << " bMDI " << (bMDI ? "MDI" : ""));
325 // Only when it comes from a Frame
326 // (not when for instance by popping BASIC-IDE from AppDisp)
327 if ( bMDI && pImpl->pFrame == pFrame )
329 // deliver
330 pImpl->pFrame = nullptr;
331 pImpl->bActive = false;
334 // Notify Subclass
335 Deactivate(bMDI);
338 bool SfxShell::IsActive() const
340 return pImpl->bActive;
343 void SfxShell::Activate
345 bool /*bMDI*/ /* TRUE
346 the <SfxDispatcher>, on which the SfxShell is
347 located, is activated or the SfxShell instance
348 was pushed on an active SfxDispatcher.
349 (compare with SystemWindow::IsMDIActivate())
351 FALSE
352 the <SfxViewFrame>, on which SfxDispatcher
353 the SfxShell instance is located, was
354 activated. (for example by a closing dialog) */
357 BroadcastContextForActivation(true);
360 void SfxShell::Deactivate
362 bool /*bMDI*/ /* TRUE
363 the <SfxDispatcher>, on which the SfxShell is
364 located, is inactivated or the SfxShell instance
365 was popped on an active SfxDispatcher.
366 (compare with SystemWindow::IsMDIActivate())
368 FALSE
369 the <SfxViewFrame>, on which SfxDispatcher
370 the SfxShell instance is located, was
371 deactivated. (for example by a dialog) */
374 BroadcastContextForActivation(false);
377 bool SfxShell::CanExecuteSlot_Impl( const SfxSlot &rSlot )
379 // Get Slot status
380 SfxItemPool &rPool = GetPool();
381 const sal_uInt16 nId = rSlot.GetWhich( rPool );
382 SfxItemSet aSet(rPool, {{nId, nId}});
383 SfxStateFunc pFunc = rSlot.GetStateFnc();
384 CallState( pFunc, aSet );
385 return aSet.GetItemState(nId) != SfxItemState::DISABLED;
388 bool SfxShell::IsConditionalFastCall( const SfxRequest &rReq )
390 sal_uInt16 nId = rReq.GetSlot();
391 bool bRet = false;
393 if (nId == SID_UNDO || nId == SID_REDO)
395 const SfxItemSet* pArgs = rReq.GetArgs();
396 if (pArgs && pArgs->HasItem(SID_REPAIRPACKAGE))
397 bRet = true;
399 return bRet;
403 static void ShellCall_Impl( void* pObj, void* pArg )
405 static_cast<SfxShell*>(pObj)->ExecuteSlot( *static_cast<SfxRequest*>(pArg) );
408 void SfxShell::ExecuteSlot( SfxRequest& rReq, bool bAsync )
410 if( !bAsync )
411 ExecuteSlot( rReq );
412 else
414 if( !pImpl->pExecuter )
415 pImpl->pExecuter.reset( new svtools::AsynchronLink(
416 Link<void*,void>( this, ShellCall_Impl ) ) );
417 pImpl->pExecuter->Call( new SfxRequest( rReq ) );
421 const SfxPoolItem* SfxShell::ExecuteSlot
423 SfxRequest &rReq, // the relayed <SfxRequest>
424 const SfxInterface* pIF // default = 0 means get virtually
427 if ( !pIF )
428 pIF = GetInterface();
430 sal_uInt16 nSlot = rReq.GetSlot();
431 const SfxSlot* pSlot = nullptr;
432 if ( nSlot >= SID_VERB_START && nSlot <= SID_VERB_END )
433 pSlot = GetVerbSlot_Impl(nSlot);
434 if ( !pSlot )
435 pSlot = pIF->GetSlot(nSlot);
436 DBG_ASSERT( pSlot, "slot not supported" );
438 SfxExecFunc pFunc = pSlot->GetExecFnc();
439 if ( pFunc )
440 CallExec( pFunc, rReq );
442 return rReq.GetReturnValue();
445 const SfxPoolItem* SfxShell::GetSlotState
447 sal_uInt16 nSlotId, // Slot-Id to the Slots in question
448 const SfxInterface* pIF, // default = 0 means get virtually
449 SfxItemSet* pStateSet // SfxItemSet of the Slot-State method
452 // Get Slot on the given Interface
453 if ( !pIF )
454 pIF = GetInterface();
455 SfxItemState eState = SfxItemState::UNKNOWN;
456 SfxItemPool &rPool = GetPool();
458 const SfxSlot* pSlot = nullptr;
459 if ( nSlotId >= SID_VERB_START && nSlotId <= SID_VERB_END )
460 pSlot = GetVerbSlot_Impl(nSlotId);
461 if ( !pSlot )
462 pSlot = pIF->GetSlot(nSlotId);
463 if ( pSlot )
464 // Map on Which-Id if possible
465 nSlotId = pSlot->GetWhich( rPool );
467 // Get Item and Item status
468 const SfxPoolItem *pItem = nullptr;
469 SfxItemSet aSet( rPool, {{nSlotId, nSlotId}} ); // else pItem dies too soon
470 if ( pSlot )
472 // Call Status method
473 SfxStateFunc pFunc = pSlot->GetStateFnc();
474 if ( pFunc )
475 CallState( pFunc, aSet );
476 eState = aSet.GetItemState( nSlotId, true, &pItem );
478 // get default Item if possible
479 if ( eState == SfxItemState::DEFAULT )
481 if ( SfxItemPool::IsWhich(nSlotId) )
482 pItem = &rPool.GetDefaultItem(nSlotId);
483 else
484 eState = SfxItemState::DONTCARE;
487 else
488 eState = SfxItemState::UNKNOWN;
490 // Evaluate Item and item status and possibly maintain them in pStateSet
491 std::unique_ptr<SfxPoolItem> pRetItem;
492 if ( eState <= SfxItemState::DISABLED )
494 if ( pStateSet )
495 pStateSet->DisableItem(nSlotId);
496 return nullptr;
498 else if ( eState == SfxItemState::DONTCARE )
500 if ( pStateSet )
501 pStateSet->ClearItem(nSlotId);
502 pRetItem.reset( new SfxVoidItem(0) );
504 else
506 if ( pStateSet && pStateSet->Put( *pItem ) )
507 return &pStateSet->Get( pItem->Which() );
508 pRetItem.reset(pItem->Clone());
510 auto pTemp = pRetItem.get();
511 DeleteItemOnIdle(std::move(pRetItem));
513 return pTemp;
516 static SFX_EXEC_STUB(SfxShell, VerbExec)
517 static void SfxStubSfxShellVerbState(SfxShell *, SfxItemSet& rSet)
519 SfxShell::VerbState( rSet );
522 void SfxShell::SetVerbs(const css::uno::Sequence < css::embed::VerbDescriptor >& aVerbs)
524 SfxViewShell *pViewSh = dynamic_cast<SfxViewShell*>( this );
526 DBG_ASSERT(pViewSh, "Only call SetVerbs at the ViewShell!");
527 if ( !pViewSh )
528 return;
530 // First make all Statecaches dirty, so that no-one no longer tries to use
531 // the Slots
533 SfxBindings *pBindings =
534 pViewSh->GetViewFrame()->GetDispatcher()->GetBindings();
535 sal_uInt16 nCount = pImpl->aSlotArr.size();
536 for (sal_uInt16 n1=0; n1<nCount ; n1++)
538 sal_uInt16 nId = SID_VERB_START + n1;
539 pBindings->Invalidate(nId, false, true);
543 sal_uInt16 nr=0;
544 for (sal_Int32 n=0; n<aVerbs.getLength(); n++)
546 sal_uInt16 nSlotId = SID_VERB_START + nr++;
547 DBG_ASSERT(nSlotId <= SID_VERB_END, "Too many Verbs!");
548 if (nSlotId > SID_VERB_END)
549 break;
551 SfxSlot *pNewSlot = new SfxSlot;
552 pNewSlot->nSlotId = nSlotId;
553 pNewSlot->nGroupId = SfxGroupId::NONE;
555 // Verb slots must be executed asynchronously, so that they can be
556 // destroyed while executing.
557 pNewSlot->nFlags = SfxSlotMode::ASYNCHRON | SfxSlotMode::CONTAINER;
558 pNewSlot->nMasterSlotId = 0;
559 pNewSlot->nValue = 0;
560 pNewSlot->fnExec = SFX_STUB_PTR(SfxShell,VerbExec);
561 pNewSlot->fnState = SFX_STUB_PTR(SfxShell,VerbState);
562 pNewSlot->pType = nullptr; // HACK(SFX_TYPE(SfxVoidItem)) ???
563 pNewSlot->nArgDefCount = 0;
564 pNewSlot->pFirstArgDef = nullptr;
565 pNewSlot->pUnoName = nullptr;
567 if (!pImpl->aSlotArr.empty())
569 SfxSlot& rSlot = *pImpl->aSlotArr[0];
570 pNewSlot->pNextSlot = rSlot.pNextSlot;
571 rSlot.pNextSlot = pNewSlot;
573 else
574 pNewSlot->pNextSlot = pNewSlot;
576 pImpl->aSlotArr.insert(pImpl->aSlotArr.begin() + static_cast<sal_uInt16>(n), std::unique_ptr<SfxSlot>(pNewSlot));
579 pImpl->aVerbList = aVerbs;
581 // The status of SID_OBJECT is collected in the controller directly on
582 // the Shell, it is thus enough to encourage a new status update
583 SfxBindings* pBindings = pViewSh->GetViewFrame()->GetDispatcher()->GetBindings();
584 pBindings->Invalidate(SID_OBJECT, true, true);
587 const css::uno::Sequence < css::embed::VerbDescriptor >& SfxShell::GetVerbs() const
589 return pImpl->aVerbList;
592 void SfxShell::VerbExec(SfxRequest& rReq)
594 sal_uInt16 nId = rReq.GetSlot();
595 SfxViewShell *pViewShell = GetViewShell();
596 if ( !pViewShell )
597 return;
599 bool bReadOnly = pViewShell->GetObjectShell()->IsReadOnly();
600 const css::uno::Sequence < css::embed::VerbDescriptor > aList = pViewShell->GetVerbs();
601 sal_Int32 nVerb = 0;
602 for (const auto& rVerb : aList)
604 // check for ReadOnly verbs
605 if ( bReadOnly && !(rVerb.VerbAttributes & embed::VerbAttributes::MS_VERBATTR_NEVERDIRTIES) )
606 continue;
608 // check for verbs that shouldn't appear in the menu
609 if ( !(rVerb.VerbAttributes & embed::VerbAttributes::MS_VERBATTR_ONCONTAINERMENU) )
610 continue;
612 if (nId == SID_VERB_START + nVerb++)
614 pViewShell->DoVerb(rVerb.VerbID);
615 rReq.Done();
616 return;
621 void SfxShell::VerbState(SfxItemSet& )
625 const SfxSlot* SfxShell::GetVerbSlot_Impl(sal_uInt16 nId) const
627 css::uno::Sequence < css::embed::VerbDescriptor > rList = pImpl->aVerbList;
629 DBG_ASSERT(nId >= SID_VERB_START && nId <= SID_VERB_END,"Wrong VerbId!");
630 sal_uInt16 nIndex = nId - SID_VERB_START;
631 DBG_ASSERT(nIndex < rList.getLength(),"Wrong VerbId!");
633 if (nIndex < rList.getLength())
634 return pImpl->aSlotArr[nIndex].get();
635 else
636 return nullptr;
639 SfxObjectShell* SfxShell::GetObjectShell()
641 if ( GetViewShell() )
642 return GetViewShell()->GetViewFrame()->GetObjectShell();
643 else
644 return nullptr;
647 bool SfxShell::HasUIFeature(SfxShellFeature) const
649 return false;
652 static void DispatcherUpdate_Impl( void*, void* pArg )
654 static_cast<SfxDispatcher*>(pArg)->Update_Impl( true );
655 static_cast<SfxDispatcher*>(pArg)->GetBindings()->InvalidateAll(false);
658 void SfxShell::UIFeatureChanged()
660 SfxViewFrame *pFrame = GetFrame();
661 if ( pFrame && pFrame->IsVisible() )
663 // Also force an update, if dispatcher is already updated otherwise
664 // something my get stuck in the bunkered tools. Asynchronous call to
665 // prevent recursion.
666 if ( !pImpl->pUpdater )
667 pImpl->pUpdater.reset( new svtools::AsynchronLink( Link<void*,void>( this, DispatcherUpdate_Impl ) ) );
669 // Multiple views allowed
670 pImpl->pUpdater->Call( pFrame->GetDispatcher(), true );
674 void SfxShell::SetDisableFlags( SfxDisableFlags nFlags )
676 pImpl->nDisableFlags = nFlags;
679 SfxDisableFlags SfxShell::GetDisableFlags() const
681 return pImpl->nDisableFlags;
684 std::unique_ptr<SfxItemSet> SfxShell::CreateItemSet( sal_uInt16 )
686 return nullptr;
689 void SfxShell::ApplyItemSet( sal_uInt16, const SfxItemSet& )
693 void SfxShell::SetContextName (const OUString& rsContextName)
695 pImpl->maContextChangeBroadcaster.Initialize(rsContextName);
698 void SfxShell::SetViewShell_Impl( SfxViewShell* pView )
700 pImpl->pViewSh = pView;
703 void SfxShell::BroadcastContextForActivation (const bool bIsActivated)
705 // Avoids activation and de-activation (can be seen on switching view) from causing
706 // the sidebar to re-build. Such switching can happen as we change view to render
707 // using LOK for example, and is un-necessary for Online.
708 if (comphelper::LibreOfficeKit::isDialogPainting())
709 return;
711 SfxViewFrame* pViewFrame = GetFrame();
712 if (pViewFrame != nullptr)
714 if (bIsActivated)
715 pImpl->maContextChangeBroadcaster.Activate(pViewFrame->GetFrame().GetFrameInterface());
716 else
717 pImpl->maContextChangeBroadcaster.Deactivate(pViewFrame->GetFrame().GetFrameInterface());
721 bool SfxShell::SetContextBroadcasterEnabled (const bool bIsEnabled)
723 return pImpl->maContextChangeBroadcaster.SetBroadcasterEnabled(bIsEnabled);
726 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */