Line data Source code
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 <uielement/newmenucontroller.hxx>
21 :
22 : #include "services.h"
23 : #include <classes/resource.hrc>
24 : #include <classes/fwkresid.hxx>
25 : #include <framework/bmkmenu.hxx>
26 : #include <framework/imageproducer.hxx>
27 : #include <framework/menuconfiguration.hxx>
28 :
29 : #include <com/sun/star/awt/XDevice.hpp>
30 : #include <com/sun/star/beans/PropertyValue.hpp>
31 : #include <com/sun/star/awt/MenuItemStyle.hpp>
32 : #include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp>
33 : #include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp>
34 : #include <com/sun/star/ui/GlobalAcceleratorConfiguration.hpp>
35 : #include <com/sun/star/frame/ModuleManager.hpp>
36 :
37 : #include <vcl/svapp.hxx>
38 : #include <vcl/i18nhelp.hxx>
39 : #include <vcl/settings.hxx>
40 : #include <rtl/ustrbuf.hxx>
41 : #include <cppuhelper/implbase1.hxx>
42 : #include <osl/file.hxx>
43 : #include <svtools/menuoptions.hxx>
44 : #include <svtools/acceleratorexecute.hxx>
45 : #include <unotools/moduleoptions.hxx>
46 : #include <osl/mutex.hxx>
47 :
48 : // Defines
49 :
50 : using namespace com::sun::star::uno;
51 : using namespace com::sun::star::lang;
52 : using namespace com::sun::star::frame;
53 : using namespace com::sun::star::beans;
54 : using namespace com::sun::star::util;
55 : using namespace com::sun::star::container;
56 : using namespace com::sun::star::ui;
57 :
58 : namespace framework
59 : {
60 :
61 0 : DEFINE_XSERVICEINFO_MULTISERVICE_2 ( NewMenuController ,
62 : OWeakObject ,
63 : SERVICENAME_POPUPMENUCONTROLLER ,
64 : IMPLEMENTATIONNAME_NEWMENUCONTROLLER
65 : )
66 :
67 0 : DEFINE_INIT_SERVICE ( NewMenuController, {} )
68 :
69 0 : void NewMenuController::setMenuImages( PopupMenu* pPopupMenu, bool bSetImages )
70 : {
71 0 : sal_uInt16 nItemCount = pPopupMenu->GetItemCount();
72 0 : Image aImage;
73 0 : Reference< XFrame > xFrame( m_xFrame );
74 :
75 0 : for ( sal_uInt16 i = 0; i < nItemCount; i++ )
76 : {
77 0 : sal_uInt16 nItemId = pPopupMenu->GetItemId( sal::static_int_cast<sal_uInt16>( i ));
78 0 : if ( nItemId != 0 )
79 : {
80 0 : if ( bSetImages )
81 : {
82 0 : bool bImageSet( false );
83 0 : OUString aImageId;
84 :
85 0 : AddInfoForId::const_iterator pInfo = m_aAddInfoForItem.find( nItemId );
86 0 : if ( pInfo != m_aAddInfoForItem.end() )
87 0 : aImageId = pInfo->second.aImageId; // Retrieve image id for menu item
88 :
89 0 : if ( !aImageId.isEmpty() )
90 : {
91 0 : aImage = GetImageFromURL( xFrame, aImageId, false );
92 0 : if ( !!aImage )
93 : {
94 0 : bImageSet = true;
95 0 : pPopupMenu->SetItemImage( nItemId, aImage );
96 : }
97 : }
98 :
99 0 : if ( !bImageSet )
100 : {
101 0 : OUString aCmd( pPopupMenu->GetItemCommand( nItemId ) );
102 0 : if ( !aCmd.isEmpty() )
103 0 : aImage = GetImageFromURL( xFrame, aCmd, false );
104 :
105 0 : if ( !!aImage )
106 0 : pPopupMenu->SetItemImage( nItemId, aImage );
107 0 : }
108 : }
109 : else
110 0 : pPopupMenu->SetItemImage( nItemId, aImage );
111 : }
112 0 : }
113 0 : }
114 :
115 0 : void NewMenuController::determineAndSetNewDocAccel( PopupMenu* pPopupMenu, const KeyCode& rKeyCode )
116 : {
117 0 : sal_uInt16 nCount( pPopupMenu->GetItemCount() );
118 0 : sal_uInt16 nId( 0 );
119 0 : bool bFound( false );
120 0 : OUString aCommand;
121 :
122 0 : if ( !m_aEmptyDocURL.isEmpty() )
123 : {
124 : // Search for the empty document URL
125 :
126 0 : for ( sal_uInt32 i = 0; i < sal_uInt32( nCount ); i++ )
127 : {
128 0 : nId = pPopupMenu->GetItemId( sal_uInt16( i ));
129 0 : if ( nId != 0 && pPopupMenu->GetItemType( nId ) != MENUITEM_SEPARATOR )
130 : {
131 0 : aCommand = pPopupMenu->GetItemCommand( nId );
132 0 : if ( aCommand.startsWith( m_aEmptyDocURL ) )
133 : {
134 0 : pPopupMenu->SetAccelKey( nId, rKeyCode );
135 0 : bFound = true;
136 0 : break;
137 : }
138 : }
139 : }
140 : }
141 :
142 0 : if ( !bFound )
143 : {
144 : // Search for the default module name
145 0 : OUString aDefaultModuleName( SvtModuleOptions().GetDefaultModuleName() );
146 0 : if ( !aDefaultModuleName.isEmpty() )
147 : {
148 0 : for ( sal_uInt32 i = 0; i < sal_uInt32( nCount ); i++ )
149 : {
150 0 : nId = pPopupMenu->GetItemId( sal_uInt16( i ));
151 0 : if ( nId != 0 && pPopupMenu->GetItemType( nId ) != MENUITEM_SEPARATOR )
152 : {
153 0 : aCommand = pPopupMenu->GetItemCommand( nId );
154 0 : if ( aCommand.indexOf( aDefaultModuleName ) >= 0 )
155 : {
156 0 : pPopupMenu->SetAccelKey( nId, rKeyCode );
157 0 : break;
158 : }
159 : }
160 : }
161 0 : }
162 0 : }
163 0 : }
164 :
165 0 : void NewMenuController::setAccelerators( PopupMenu* pPopupMenu )
166 : {
167 0 : if ( m_bModuleIdentified )
168 : {
169 0 : Reference< XAcceleratorConfiguration > xDocAccelCfg( m_xDocAcceleratorManager );
170 0 : Reference< XAcceleratorConfiguration > xModuleAccelCfg( m_xModuleAcceleratorManager );
171 0 : Reference< XAcceleratorConfiguration > xGlobalAccelCfg( m_xGlobalAcceleratorManager );
172 :
173 0 : if ( !m_bAcceleratorCfg )
174 : {
175 : // Retrieve references on demand
176 0 : m_bAcceleratorCfg = true;
177 0 : if ( !xDocAccelCfg.is() )
178 : {
179 0 : Reference< XController > xController = m_xFrame->getController();
180 0 : Reference< XModel > xModel;
181 0 : if ( xController.is() )
182 : {
183 0 : xModel = xController->getModel();
184 0 : if ( xModel.is() )
185 : {
186 0 : Reference< XUIConfigurationManagerSupplier > xSupplier( xModel, UNO_QUERY );
187 0 : if ( xSupplier.is() )
188 : {
189 0 : Reference< XUIConfigurationManager > xDocUICfgMgr( xSupplier->getUIConfigurationManager(), UNO_QUERY );
190 0 : if ( xDocUICfgMgr.is() )
191 : {
192 0 : xDocAccelCfg = xDocUICfgMgr->getShortCutManager();
193 0 : m_xDocAcceleratorManager = xDocAccelCfg;
194 0 : }
195 0 : }
196 : }
197 0 : }
198 : }
199 :
200 0 : if ( !xModuleAccelCfg.is() )
201 : {
202 : Reference< XModuleUIConfigurationManagerSupplier > xModuleCfgMgrSupplier =
203 0 : theModuleUIConfigurationManagerSupplier::get( m_xContext );
204 0 : Reference< XUIConfigurationManager > xUICfgMgr = xModuleCfgMgrSupplier->getUIConfigurationManager( m_aModuleIdentifier );
205 0 : if ( xUICfgMgr.is() )
206 : {
207 0 : xModuleAccelCfg = xUICfgMgr->getShortCutManager();
208 0 : m_xModuleAcceleratorManager = xModuleAccelCfg;
209 0 : }
210 : }
211 :
212 0 : if ( !xGlobalAccelCfg.is() )
213 : {
214 0 : xGlobalAccelCfg = GlobalAcceleratorConfiguration::create( m_xContext );
215 0 : m_xGlobalAcceleratorManager = xGlobalAccelCfg;
216 : }
217 : }
218 :
219 0 : KeyCode aEmptyKeyCode;
220 0 : sal_uInt32 nItemCount( pPopupMenu->GetItemCount() );
221 0 : std::vector< KeyCode > aMenuShortCuts;
222 0 : std::vector< OUString > aCmds;
223 0 : std::vector< sal_uInt32 > aIds;
224 0 : for ( sal_uInt32 i = 0; i < nItemCount; i++ )
225 : {
226 0 : sal_uInt16 nId( pPopupMenu->GetItemId( sal_uInt16( i )));
227 0 : if ( nId && ( pPopupMenu->GetItemType( nId ) != MENUITEM_SEPARATOR ))
228 : {
229 0 : aIds.push_back( nId );
230 0 : aMenuShortCuts.push_back( aEmptyKeyCode );
231 0 : aCmds.push_back( pPopupMenu->GetItemCommand( nId ));
232 : }
233 : }
234 :
235 0 : sal_uInt32 nSeqCount( aIds.size() );
236 :
237 0 : if ( m_bNewMenu )
238 0 : nSeqCount+=1;
239 :
240 0 : Sequence< OUString > aSeq( nSeqCount );
241 :
242 : // Add a special command for our "New" menu.
243 0 : if ( m_bNewMenu )
244 : {
245 0 : aSeq[nSeqCount-1] = m_aCommandURL;
246 0 : aMenuShortCuts.push_back( aEmptyKeyCode );
247 : }
248 :
249 0 : const sal_uInt32 nCount = aCmds.size();
250 0 : for ( sal_uInt32 i = 0; i < nCount; i++ )
251 0 : aSeq[i] = aCmds[i];
252 :
253 0 : if ( m_xGlobalAcceleratorManager.is() )
254 0 : retrieveShortcutsFromConfiguration( xGlobalAccelCfg, aSeq, aMenuShortCuts );
255 0 : if ( m_xModuleAcceleratorManager.is() )
256 0 : retrieveShortcutsFromConfiguration( xModuleAccelCfg, aSeq, aMenuShortCuts );
257 0 : if ( m_xDocAcceleratorManager.is() )
258 0 : retrieveShortcutsFromConfiguration( xGlobalAccelCfg, aSeq, aMenuShortCuts );
259 :
260 0 : const sal_uInt32 nCount2 = aIds.size();
261 0 : for ( sal_uInt32 i = 0; i < nCount2; i++ )
262 0 : pPopupMenu->SetAccelKey( sal_uInt16( aIds[i] ), aMenuShortCuts[i] );
263 :
264 : // Special handling for "New" menu short-cut should be set at the
265 : // document which will be opened using it.
266 0 : if ( m_bNewMenu )
267 : {
268 0 : if ( aMenuShortCuts[nSeqCount-1] != aEmptyKeyCode )
269 0 : determineAndSetNewDocAccel( pPopupMenu, aMenuShortCuts[nSeqCount-1] );
270 0 : }
271 : }
272 0 : }
273 :
274 0 : void NewMenuController::retrieveShortcutsFromConfiguration(
275 : const Reference< XAcceleratorConfiguration >& rAccelCfg,
276 : const Sequence< OUString >& rCommands,
277 : std::vector< KeyCode >& aMenuShortCuts )
278 : {
279 0 : if ( rAccelCfg.is() )
280 : {
281 : try
282 : {
283 0 : com::sun::star::awt::KeyEvent aKeyEvent;
284 0 : Sequence< Any > aSeqKeyCode = rAccelCfg->getPreferredKeyEventsForCommandList( rCommands );
285 0 : for ( sal_Int32 i = 0; i < aSeqKeyCode.getLength(); i++ )
286 : {
287 0 : if ( aSeqKeyCode[i] >>= aKeyEvent )
288 0 : aMenuShortCuts[i] = svt::AcceleratorExecute::st_AWTKey2VCLKey( aKeyEvent );
289 0 : }
290 : }
291 0 : catch ( const IllegalArgumentException& )
292 : {
293 : }
294 : }
295 0 : }
296 :
297 0 : NewMenuController::NewMenuController( const ::com::sun::star::uno::Reference< ::com::sun::star::uno::XComponentContext >& xContext ) :
298 : svt::PopupMenuControllerBase( xContext ),
299 : m_bShowImages( true ),
300 : m_bNewMenu( false ),
301 : m_bModuleIdentified( false ),
302 : m_bAcceleratorCfg( false ),
303 : m_aTargetFrame( "_default" ),
304 0 : m_xContext( xContext )
305 : {
306 0 : }
307 :
308 0 : NewMenuController::~NewMenuController()
309 : {
310 0 : }
311 :
312 : // private function
313 0 : void NewMenuController::fillPopupMenu( Reference< css::awt::XPopupMenu >& rPopupMenu )
314 : {
315 0 : VCLXPopupMenu* pPopupMenu = (VCLXPopupMenu *)VCLXMenu::GetImplementation( rPopupMenu );
316 0 : PopupMenu* pVCLPopupMenu = 0;
317 :
318 0 : SolarMutexGuard aSolarMutexGuard;
319 :
320 0 : resetPopupMenu( rPopupMenu );
321 0 : if ( pPopupMenu )
322 0 : pVCLPopupMenu = (PopupMenu *)pPopupMenu->GetMenu();
323 :
324 0 : if ( pVCLPopupMenu )
325 : {
326 0 : MenuConfiguration aMenuCfg( m_xContext );
327 0 : BmkMenu* pSubMenu( 0 );
328 :
329 0 : if ( m_bNewMenu )
330 0 : pSubMenu = (BmkMenu*)aMenuCfg.CreateBookmarkMenu( m_xFrame, BOOKMARK_NEWMENU );
331 : else
332 0 : pSubMenu = (BmkMenu*)aMenuCfg.CreateBookmarkMenu( m_xFrame, BOOKMARK_WIZARDMENU );
333 :
334 : // copy entries as we have to use the provided popup menu
335 0 : *pVCLPopupMenu = *pSubMenu;
336 :
337 0 : Image aImage;
338 0 : AddInfo aAddInfo;
339 :
340 : // retrieve additional parameters from bookmark menu and
341 : // store it in a boost::unordered_map.
342 0 : for ( sal_uInt16 i = 0; i < pSubMenu->GetItemCount(); i++ )
343 : {
344 0 : sal_uInt16 nItemId = pSubMenu->GetItemId( sal::static_int_cast<sal_uInt16>( i ) );
345 0 : if (( nItemId != 0 ) &&
346 0 : ( pSubMenu->GetItemType( nItemId ) != MENUITEM_SEPARATOR ))
347 : {
348 0 : MenuConfiguration::Attributes* pBmkAttributes = (MenuConfiguration::Attributes *)(pSubMenu->GetUserValue( nItemId ));
349 0 : if ( pBmkAttributes != 0 )
350 : {
351 0 : aAddInfo.aTargetFrame = pBmkAttributes->aTargetFrame;
352 0 : aAddInfo.aImageId = pBmkAttributes->aImageId;
353 :
354 0 : m_aAddInfoForItem.insert( AddInfoForId::value_type( nItemId, aAddInfo ));
355 : }
356 : }
357 : }
358 :
359 0 : if ( m_bShowImages )
360 0 : setMenuImages( pVCLPopupMenu, m_bShowImages );
361 :
362 0 : delete pSubMenu;
363 0 : }
364 0 : }
365 :
366 : // XEventListener
367 0 : void SAL_CALL NewMenuController::disposing( const EventObject& ) throw ( RuntimeException, std::exception )
368 : {
369 0 : Reference< css::awt::XMenuListener > xHolder(( OWeakObject *)this, UNO_QUERY );
370 :
371 0 : osl::MutexGuard aLock( m_aMutex );
372 0 : m_xFrame.clear();
373 0 : m_xDispatch.clear();
374 0 : m_xContext.clear();
375 :
376 0 : if ( m_xPopupMenu.is() )
377 0 : m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(( OWeakObject *)this, UNO_QUERY ));
378 0 : m_xPopupMenu.clear();
379 0 : }
380 :
381 : // XStatusListener
382 0 : void SAL_CALL NewMenuController::statusChanged( const FeatureStateEvent& ) throw ( RuntimeException, std::exception )
383 : {
384 0 : }
385 :
386 : // XMenuListener
387 0 : void SAL_CALL NewMenuController::itemSelected( const css::awt::MenuEvent& rEvent ) throw (RuntimeException, std::exception)
388 : {
389 0 : Reference< css::awt::XPopupMenu > xPopupMenu;
390 0 : Reference< XDispatch > xDispatch;
391 0 : Reference< XDispatchProvider > xDispatchProvider;
392 0 : Reference< XComponentContext > xContext;
393 0 : Reference< XURLTransformer > xURLTransformer;
394 :
395 0 : osl::ClearableMutexGuard aLock( m_aMutex );
396 0 : xPopupMenu = m_xPopupMenu;
397 0 : xDispatchProvider = Reference< XDispatchProvider >( m_xFrame, UNO_QUERY );
398 0 : xContext = m_xContext;
399 0 : xURLTransformer = m_xURLTransformer;
400 0 : aLock.clear();
401 :
402 0 : css::util::URL aTargetURL;
403 0 : Sequence< PropertyValue > aArgsList( 1 );
404 :
405 0 : if ( xPopupMenu.is() && xDispatchProvider.is() )
406 : {
407 0 : VCLXPopupMenu* pPopupMenu = (VCLXPopupMenu *)VCLXPopupMenu::GetImplementation( xPopupMenu );
408 0 : if ( pPopupMenu )
409 : {
410 : {
411 0 : SolarMutexGuard aSolarMutexGuard;
412 0 : PopupMenu* pVCLPopupMenu = (PopupMenu *)pPopupMenu->GetMenu();
413 0 : aTargetURL.Complete = pVCLPopupMenu->GetItemCommand( rEvent.MenuId );
414 : }
415 :
416 0 : xURLTransformer->parseStrict( aTargetURL );
417 :
418 0 : aArgsList[0].Name = "Referer";
419 0 : aArgsList[0].Value = makeAny( OUString( "private:user" ));
420 :
421 0 : OUString aTargetFrame( m_aTargetFrame );
422 0 : AddInfoForId::const_iterator pItem = m_aAddInfoForItem.find( rEvent.MenuId );
423 0 : if ( pItem != m_aAddInfoForItem.end() )
424 0 : aTargetFrame = pItem->second.aTargetFrame;
425 :
426 0 : xDispatch = xDispatchProvider->queryDispatch( aTargetURL, aTargetFrame, 0 );
427 : }
428 : }
429 :
430 0 : if ( xDispatch.is() )
431 : {
432 : // Call dispatch asychronously as we can be destroyed while dispatch is
433 : // executed. VCL is not able to survive this as it wants to call listeners
434 : // after select!!!
435 0 : NewDocument* pNewDocument = new NewDocument;
436 0 : pNewDocument->xDispatch = xDispatch;
437 0 : pNewDocument->aTargetURL = aTargetURL;
438 0 : pNewDocument->aArgSeq = aArgsList;
439 0 : Application::PostUserEvent( STATIC_LINK(0, NewMenuController, ExecuteHdl_Impl), pNewDocument );
440 0 : }
441 0 : }
442 :
443 0 : void SAL_CALL NewMenuController::itemActivated( const css::awt::MenuEvent& ) throw (RuntimeException, std::exception)
444 : {
445 0 : SolarMutexGuard aSolarMutexGuard;
446 0 : if ( m_xFrame.is() && m_xPopupMenu.is() )
447 : {
448 0 : VCLXPopupMenu* pPopupMenu = (VCLXPopupMenu *)VCLXPopupMenu::GetImplementation( m_xPopupMenu );
449 0 : if ( pPopupMenu )
450 : {
451 0 : const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
452 0 : bool bShowImages( rSettings.GetUseImagesInMenus() );
453 :
454 0 : PopupMenu* pVCLPopupMenu = (PopupMenu *)pPopupMenu->GetMenu();
455 :
456 0 : if ( m_bShowImages != bShowImages )
457 : {
458 0 : m_bShowImages = bShowImages;
459 0 : setMenuImages( pVCLPopupMenu, m_bShowImages );
460 : }
461 :
462 0 : setAccelerators( pVCLPopupMenu );
463 : }
464 0 : }
465 0 : }
466 :
467 : // XPopupMenuController
468 0 : void NewMenuController::impl_setPopupMenu()
469 : {
470 :
471 0 : if ( m_xPopupMenu.is() )
472 0 : fillPopupMenu( m_xPopupMenu );
473 :
474 : // Identify module that we are attach to. It's our context that we need to know.
475 0 : Reference< XModuleManager2 > xModuleManager = ModuleManager::create( m_xContext );
476 : try
477 : {
478 0 : m_aModuleIdentifier = xModuleManager->identify( m_xFrame );
479 0 : m_bModuleIdentified = true;
480 :
481 0 : if ( !m_aModuleIdentifier.isEmpty() )
482 : {
483 0 : Sequence< PropertyValue > aSeq;
484 :
485 0 : if ( xModuleManager->getByName( m_aModuleIdentifier ) >>= aSeq )
486 : {
487 0 : for ( sal_Int32 y = 0; y < aSeq.getLength(); y++ )
488 : {
489 0 : if ( aSeq[y].Name == "ooSetupFactoryEmptyDocumentURL" )
490 : {
491 0 : aSeq[y].Value >>= m_aEmptyDocURL;
492 0 : break;
493 : }
494 : }
495 0 : }
496 : }
497 : }
498 0 : catch ( const RuntimeException& )
499 : {
500 0 : throw;
501 : }
502 0 : catch ( const Exception& )
503 : {
504 0 : }
505 0 : }
506 :
507 : // XInitialization
508 0 : void SAL_CALL NewMenuController::initialize( const Sequence< Any >& aArguments ) throw ( Exception, RuntimeException, std::exception )
509 : {
510 0 : osl::MutexGuard aLock( m_aMutex );
511 :
512 0 : bool bInitalized( m_bInitialized );
513 0 : if ( !bInitalized )
514 : {
515 0 : svt::PopupMenuControllerBase::initialize( aArguments );
516 :
517 0 : if ( m_bInitialized )
518 : {
519 0 : const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
520 :
521 0 : m_bShowImages = rSettings.GetUseImagesInMenus();
522 0 : m_bNewMenu = m_aCommandURL == ".uno:AddDirect";
523 : }
524 0 : }
525 0 : }
526 :
527 0 : IMPL_STATIC_LINK_NOINSTANCE( NewMenuController, ExecuteHdl_Impl, NewDocument*, pNewDocument )
528 : {
529 : /* i62706: Don't catch all exceptions. We hide all problems here and are not able
530 : to handle them on higher levels.
531 : try
532 : {
533 : */
534 : // Asynchronous execution as this can lead to our own destruction!
535 : // Framework can recycle our current frame and the layout manager disposes all user interface
536 : // elements if a component gets detached from its frame!
537 0 : pNewDocument->xDispatch->dispatch( pNewDocument->aTargetURL, pNewDocument->aArgSeq );
538 0 : delete pNewDocument;
539 0 : return 0;
540 : }
541 :
542 : }
543 :
544 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|