1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_sd.hxx"
26 
27 #include "sddll.hxx"
28 
29 #include <com/sun/star/frame/XFrame.hpp>
30 #include <sfx2/imagemgr.hxx>
31 #include <sfx2/viewfrm.hxx>
32 #include <sfx2/bindings.hxx>
33 #include <sfx2/app.hxx>
34 #include <sfx2/request.hxx>
35 #include <sfx2/dispatch.hxx>
36 
37 #include <tools/rcid.h>
38 
39 #include <vcl/help.hxx>
40 #include <vcl/imagerepository.hxx>
41 #include <vcl/lazydelete.hxx>
42 
43 #include <svx/sdrpagewindow.hxx>
44 #include <svx/sdrpaintwindow.hxx>
45 #include <svx/sdr/overlay/overlayanimatedbitmapex.hxx>
46 #include <svx/sdr/overlay/overlaybitmapex.hxx>
47 #include <svx/sdr/overlay/overlaymanager.hxx>
48 #include <svx/svxids.hrc>
49 
50 #include "view/viewoverlaymanager.hxx"
51 
52 #include "res_bmp.hrc"
53 #include "DrawDocShell.hxx"
54 #include "DrawViewShell.hxx"
55 #include "DrawController.hxx"
56 #include "glob.hrc"
57 #include "strings.hrc"
58 #include "sdresid.hxx"
59 #include "EventMultiplexer.hxx"
60 #include "ViewShellManager.hxx"
61 #include "helpids.h"
62 #include "sdpage.hxx"
63 #include "drawdoc.hxx"
64 #include "smarttag.hxx"
65 
66 using ::rtl::OUString;
67 using namespace ::com::sun::star::uno;
68 using namespace ::com::sun::star::frame;
69 
70 namespace sd {
71 
72 class ImageButtonHdl;
73 
74 // --------------------------------------------------------------------
75 
76 static sal_uInt16 gButtonSlots[] = { SID_INSERT_TABLE, SID_INSERT_DIAGRAM, SID_INSERT_GRAPHIC, SID_INSERT_AVMEDIA };
77 static sal_uInt16 gButtonToolTips[] = { STR_INSERT_TABLE, STR_INSERT_CHART, STR_INSERT_PICTURE, STR_INSERT_MOVIE };
78 
79 // --------------------------------------------------------------------
80 
loadImageResource(sal_uInt16 nId)81 static BitmapEx loadImageResource( sal_uInt16 nId )
82 {
83 	SdResId aResId( nId );
84 	aResId.SetRT( RSC_BITMAP );
85 
86 	return BitmapEx( aResId );
87 }
88 
89 // --------------------------------------------------------------------
90 
getButtonImage(int index,bool large)91 static BitmapEx* getButtonImage( int index, bool large )
92 {
93 	static vcl::DeleteOnDeinit< BitmapEx > gSmallButtonImages[BMP_PLACEHOLDER_SMALL_END - BMP_PLACEHOLDER_SMALL_START] = { 0, 0, 0, 0, 0, 0, 0, 0 };
94 	static vcl::DeleteOnDeinit< BitmapEx > gLargeButtonImages[BMP_PLACEHOLDER_LARGE_END - BMP_PLACEHOLDER_LARGE_START] = { 0, 0, 0, 0, 0, 0, 0, 0 };
95 
96 	if( !gSmallButtonImages[0].get() )
97 	{
98 		for( sal_uInt16 i = 0; i < (BMP_PLACEHOLDER_SMALL_END-BMP_PLACEHOLDER_SMALL_START); i++ )
99 		{
100 			gSmallButtonImages[i].set( new BitmapEx( loadImageResource( BMP_PLACEHOLDER_SMALL_START + i ) ) );
101 			gLargeButtonImages[i].set( new BitmapEx( loadImageResource( BMP_PLACEHOLDER_LARGE_START + i ) ) );
102 		}
103 	}
104 
105 	if( large )
106 	{
107 		return gLargeButtonImages[index].get();
108 	}
109 	else
110 	{
111 		return gSmallButtonImages[index].get();
112 	}
113 }
114 
115 // --------------------------------------------------------------------
116 
117 const sal_uInt32 SMART_TAG_HDL_NUM = SAL_MAX_UINT32;
118 
119 class ChangePlaceholderTag : public SmartTag
120 {
121     friend class ImageButtonHdl;
122 public:
123 	ChangePlaceholderTag( ViewOverlayManager& rManager, ::sd::View& rView, SdrObject& rPlaceholderObj );
124 	virtual ~ChangePlaceholderTag();
125 
126 	/** returns true if the SmartTag handled the event. */
127 	virtual bool MouseButtonDown( const MouseEvent&, SmartHdl& );
128 
129 	/** returns true if the SmartTag consumes this event. */
130 	virtual bool KeyInput( const KeyEvent& rKEvt );
131 
132 	BitmapEx createOverlayImage( int nHighlight = -1 );
133 
134 protected:
135 	virtual void addCustomHandles( SdrHdlList& rHandlerList );
136 	virtual void disposing();
137 	virtual void select();
138 	virtual void deselect();
139 
140 private:
141 	ViewOverlayManager& mrManager;
142 	SdrObjectWeakRef    mxPlaceholderObj;
143 };
144 
145 class ImageButtonHdl : public SmartHdl
146 {
147 public:
148 	ImageButtonHdl( const SmartTagReference& xTag, /* sal_uInt16 nSID, const Image& rImage, const Image& rImageMO, */ const Point& rPnt );
149 	virtual ~ImageButtonHdl();
150 	virtual void CreateB2dIAObject();
151 	virtual sal_Bool IsFocusHdl() const;
152 	virtual Pointer GetPointer() const;
153 	virtual bool isMarkable() const;
154 
155 	virtual void onMouseEnter(const MouseEvent& rMEvt);
156 	virtual void onMouseLeave();
157 
getHighlightId() const158 	int getHighlightId() const { return mnHighlightId; }
159 
160 	void HideTip();
161 
162 private:
163 	rtl::Reference< ChangePlaceholderTag > mxTag;
164 
165 	int mnHighlightId;
166 	Size maImageSize;
167 	sal_uLong mnTip;
168 };
169 
170 // --------------------------------------------------------------------
171 
ImageButtonHdl(const SmartTagReference & xTag,const Point & rPnt)172 ImageButtonHdl::ImageButtonHdl( const SmartTagReference& xTag /*, sal_uInt16 nSID, const Image& rImage, const Image& rImageMO*/, const Point& rPnt )
173 : SmartHdl( xTag, rPnt )
174 , mxTag( dynamic_cast< ChangePlaceholderTag* >( xTag.get() ) )
175 , mnHighlightId( -1 )
176 , maImageSize( 42, 42 )
177 , mnTip( 0 )
178 {
179 }
180 
181 // --------------------------------------------------------------------
182 
~ImageButtonHdl()183 ImageButtonHdl::~ImageButtonHdl()
184 {
185 	HideTip();
186 }
187 
188 // --------------------------------------------------------------------
189 
HideTip()190 void ImageButtonHdl::HideTip()
191 {
192 	if( mnTip )
193 	{
194 		Help::HideTip( mnTip );
195 		mnTip = 0;
196 	}
197 }
198 
199 // --------------------------------------------------------------------
200 
201 extern ::rtl::OUString ImplRetrieveLabelFromCommand( const Reference< XFrame >& xFrame, const OUString& aCmdURL );
202 
onMouseEnter(const MouseEvent & rMEvt)203 void ImageButtonHdl::onMouseEnter(const MouseEvent& rMEvt)
204 {
205 	int nHighlightId = 0;
206 
207 	if( pHdlList && pHdlList->GetView())
208 	{
209 		OutputDevice* pDev = pHdlList->GetView()->GetFirstOutputDevice();
210 		if( pDev == 0 )
211 			pDev = Application::GetDefaultDevice();
212 
213 		Point aMDPos( rMEvt.GetPosPixel() );
214 		aMDPos -= pDev->LogicToPixel( GetPos() );
215 
216 		nHighlightId += aMDPos.X() > maImageSize.Width() ? 1 : 0;
217 		nHighlightId += aMDPos.Y() > maImageSize.Height() ? 2 : 0;
218 
219 		if( mnHighlightId != nHighlightId )
220 		{
221 			HideTip();
222 
223 			mnHighlightId = nHighlightId;
224 
225 			if( pHdlList )
226 			{
227 				SdResId aResId( gButtonToolTips[mnHighlightId] );
228 				aResId.SetRT( RSC_STRING );
229 
230 				String aHelpText( aResId );
231 				Rectangle aScreenRect( pDev->LogicToPixel( GetPos() ), maImageSize );
232 				mnTip = Help::ShowTip( static_cast< ::Window* >( pHdlList->GetView()->GetFirstOutputDevice() ), aScreenRect, aHelpText, 0 ) ;
233 			}
234 			Touch();
235 		}
236 	}
237 }
238 
239 // --------------------------------------------------------------------
240 
onMouseLeave()241 void ImageButtonHdl::onMouseLeave()
242 {
243 	mnHighlightId = -1;
244 	HideTip();
245 	Touch();
246 }
247 
248 // --------------------------------------------------------------------
249 
CreateB2dIAObject()250 void ImageButtonHdl::CreateB2dIAObject()
251 {
252 	// first throw away old one
253 	GetRidOfIAObject();
254 
255 	const Point aTagPos( GetPos() );
256 	basegfx::B2DPoint aPosition( aTagPos.X(), aTagPos.Y() );
257 
258 	BitmapEx aBitmapEx( mxTag->createOverlayImage( mnHighlightId ) ); // maImageMO.GetBitmapEx() : maImage.GetBitmapEx() );
259 	maImageSize = aBitmapEx.GetSizePixel();
260 	maImageSize.Width() >>= 1;
261 	maImageSize.Height() >>= 1;
262 
263 	if(pHdlList)
264 	{
265 		SdrMarkView* pView = pHdlList->GetView();
266 
267 		if(pView && !pView->areMarkHandlesHidden())
268 		{
269 			SdrPageView* pPageView = pView->GetSdrPageView();
270 
271 			if(pPageView)
272 			{
273 				for(sal_uInt32 b = 0; b < pPageView->PageWindowCount(); b++)
274 				{
275 					const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(b);
276 
277 					SdrPaintWindow& rPaintWindow = rPageWindow.GetPaintWindow();
278 					if(rPaintWindow.OutputToWindow() && rPageWindow.GetOverlayManager() )
279 					{
280 						::sdr::overlay::OverlayObject* pOverlayObject = 0;
281 
282                         pOverlayObject = new ::sdr::overlay::OverlayBitmapEx( aPosition, aBitmapEx, 0, 0 );
283 						rPageWindow.GetOverlayManager()->add(*pOverlayObject);
284 						maOverlayGroup.append(*pOverlayObject);
285 					}
286 				}
287 			}
288 		}
289 	}
290 }
291 
292 // --------------------------------------------------------------------
293 
IsFocusHdl() const294 sal_Bool ImageButtonHdl::IsFocusHdl() const
295 {
296 	return false;
297 }
298 
299 // --------------------------------------------------------------------
300 
isMarkable() const301 bool ImageButtonHdl::isMarkable() const
302 {
303 	return false;
304 }
305 
306 // --------------------------------------------------------------------
307 
GetPointer() const308 Pointer ImageButtonHdl::GetPointer() const
309 {
310 	return Pointer( POINTER_ARROW );
311 }
312 
313 // ====================================================================
314 
ChangePlaceholderTag(ViewOverlayManager & rManager,::sd::View & rView,SdrObject & rPlaceholderObj)315 ChangePlaceholderTag::ChangePlaceholderTag( ViewOverlayManager& rManager, ::sd::View& rView, SdrObject& rPlaceholderObj )
316 : SmartTag( rView )
317 , mrManager( rManager )
318 , mxPlaceholderObj( &rPlaceholderObj )
319 {
320 }
321 
322 // --------------------------------------------------------------------
323 
~ChangePlaceholderTag()324 ChangePlaceholderTag::~ChangePlaceholderTag()
325 {
326 }
327 
328 // --------------------------------------------------------------------
329 
330 /** returns true if the ChangePlaceholderTag handled the event. */
MouseButtonDown(const MouseEvent &,SmartHdl & rHdl)331 bool ChangePlaceholderTag::MouseButtonDown( const MouseEvent& /*rMEvt*/, SmartHdl& rHdl )
332 {
333 	int nHighlightId = static_cast< ImageButtonHdl& >(rHdl).getHighlightId();
334 	if( nHighlightId >= 0 )
335 	{
336 		sal_uInt16 nSID = gButtonSlots[nHighlightId];
337 
338 		if( mxPlaceholderObj.get() )
339 		{
340 			// mark placeholder if it is not currently marked (or if also others are marked)
341 			if( !mrView.IsObjMarked( mxPlaceholderObj.get() ) || (mrView.GetMarkedObjectList().GetMarkCount() != 1) )
342 			{
343 				SdrPageView* pPV = mrView.GetSdrPageView();
344 				mrView.UnmarkAllObj(pPV );
345 				mrView.MarkObj(mxPlaceholderObj.get(), pPV, sal_False);
346 			}
347 		}
348 
349 		mrView.GetViewShell()->GetViewFrame()->GetDispatcher()->Execute( nSID, SFX_CALLMODE_ASYNCHRON);
350 	}
351 	return false;
352 }
353 
354 // --------------------------------------------------------------------
355 
356 /** returns true if the SmartTag consumes this event. */
KeyInput(const KeyEvent & rKEvt)357 bool ChangePlaceholderTag::KeyInput( const KeyEvent& rKEvt )
358 {
359 	sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
360 	switch( nCode )
361 	{
362 	case KEY_DOWN:
363 	case KEY_UP:
364 	case KEY_LEFT:
365 	case KEY_RIGHT:
366 	case KEY_ESCAPE:
367 	case KEY_TAB:
368     case KEY_RETURN:
369    	case KEY_SPACE:
370 	default:
371 		return false;
372 	}
373 }
374 
375 // --------------------------------------------------------------------
376 
createOverlayImage(int nHighlight)377 BitmapEx ChangePlaceholderTag::createOverlayImage( int nHighlight )
378 {
379 	BitmapEx aRet;
380     if( mxPlaceholderObj.is() )
381     {
382         SdrObject* pPlaceholder = mxPlaceholderObj.get();
383 	    SmartTagReference xThis( this );
384 		const Rectangle& rSnapRect = pPlaceholder->GetSnapRect();
385 	    const Point aPoint;
386 
387 		OutputDevice* pDev = mrView.GetFirstOutputDevice();
388 		if( pDev == 0 )
389 			pDev = Application::GetDefaultDevice();
390 
391         Size aShapeSizePix = pDev->LogicToPixel(rSnapRect.GetSize());
392         long nShapeSizePix = std::min(aShapeSizePix.Width(),aShapeSizePix.Height());
393 
394 		bool bLarge = nShapeSizePix > 250;
395 
396 		Size aSize( getButtonImage( 0, bLarge )->GetSizePixel() );
397 
398 		aRet.SetSizePixel( Size( aSize.Width() << 1, aSize.Height() << 1 ) );
399 
400 		const Rectangle aRectSrc( Point( 0, 0 ), aSize );
401 
402 		aRet = *(getButtonImage((nHighlight == 0) ? 4 : 0, bLarge));
403 		aRet.Expand( aSize.Width(), aSize.Height(), NULL, sal_True );
404 
405 		aRet.CopyPixel( Rectangle( Point( aSize.Width(), 0				), aSize ), aRectSrc, getButtonImage((nHighlight == 1) ? 5 : 1, bLarge) );
406 		aRet.CopyPixel( Rectangle( Point( 0,			 aSize.Height() ), aSize ), aRectSrc, getButtonImage((nHighlight == 2) ? 6 : 2, bLarge) );
407 		aRet.CopyPixel( Rectangle( Point( aSize.Width(), aSize.Height() ), aSize ), aRectSrc, getButtonImage((nHighlight == 3) ? 7 : 3, bLarge) );
408 	}
409 
410 	return aRet;
411 }
412 
addCustomHandles(SdrHdlList & rHandlerList)413 void ChangePlaceholderTag::addCustomHandles( SdrHdlList& rHandlerList )
414 {
415     if( mxPlaceholderObj.is() )
416     {
417         SdrObject* pPlaceholder = mxPlaceholderObj.get();
418 	    SmartTagReference xThis( this );
419 		const Rectangle& rSnapRect = pPlaceholder->GetSnapRect();
420 	    const Point aPoint;
421 
422 		OutputDevice* pDev = mrView.GetFirstOutputDevice();
423 		if( pDev == 0 )
424 			pDev = Application::GetDefaultDevice();
425 
426         Size aShapeSizePix = pDev->LogicToPixel(rSnapRect.GetSize());
427         long nShapeSizePix = std::min(aShapeSizePix.Width(),aShapeSizePix.Height());
428         if( 50 > nShapeSizePix )
429             return;
430 
431         bool bLarge = nShapeSizePix > 250;
432 
433 		Size aButtonSize( pDev->PixelToLogic( getButtonImage(0, bLarge )->GetSizePixel()) );
434 
435 		const int nColumns = 2;
436 		const int nRows = 2;
437 
438 		long all_width = nColumns * aButtonSize.Width();
439 		long all_height = nRows * aButtonSize.Height();
440 
441 		Point aPos( rSnapRect.Center() );
442 		aPos.X() -= all_width >> 1;
443 		aPos.Y() -= all_height >> 1;
444 
445 		ImageButtonHdl* pHdl = new ImageButtonHdl( xThis, aPoint );
446 		pHdl->SetObjHdlNum( SMART_TAG_HDL_NUM );
447 		pHdl->SetPageView( mrView.GetSdrPageView() );
448 
449 		pHdl->SetPos( aPos );
450 
451 		rHandlerList.AddHdl( pHdl );
452     }
453 }
454 
455 // --------------------------------------------------------------------
456 
disposing()457 void ChangePlaceholderTag::disposing()
458 {
459 	SmartTag::disposing();
460 }
461 
462 // --------------------------------------------------------------------
463 
select()464 void ChangePlaceholderTag::select()
465 {
466 	SmartTag::select();
467 }
468 
469 // --------------------------------------------------------------------
470 
deselect()471 void ChangePlaceholderTag::deselect()
472 {
473 	SmartTag::deselect();
474 }
475 
476 // --------------------------------------------------------------------
477 
ViewOverlayManager(ViewShellBase & rViewShellBase)478 ViewOverlayManager::ViewOverlayManager( ViewShellBase& rViewShellBase )
479 : mrBase( rViewShellBase )
480 , mnUpdateTagsEvent( 0 )
481 {
482 	Link aLink( LINK(this,ViewOverlayManager,EventMultiplexerListener) );
483     mrBase.GetEventMultiplexer()->AddEventListener(aLink, tools::EventMultiplexerEvent::EID_CURRENT_PAGE
484 		| tools::EventMultiplexerEvent::EID_MAIN_VIEW_ADDED
485 		| tools::EventMultiplexerEvent::EID_VIEW_ADDED
486 		| tools::EventMultiplexerEvent::EID_BEGIN_TEXT_EDIT
487 		| tools::EventMultiplexerEvent::EID_END_TEXT_EDIT );
488 
489     StartListening( *mrBase.GetDocShell() );
490 }
491 
492 // --------------------------------------------------------------------
493 
~ViewOverlayManager()494 ViewOverlayManager::~ViewOverlayManager()
495 {
496     Link aLink( LINK(this,ViewOverlayManager,EventMultiplexerListener) );
497     mrBase.GetEventMultiplexer()->RemoveEventListener( aLink );
498 
499     if( mnUpdateTagsEvent )
500     {
501         Application::RemoveUserEvent( mnUpdateTagsEvent );
502         mnUpdateTagsEvent = 0;
503     }
504 
505 	DisposeTags();
506 }
507 
508 // --------------------------------------------------------------------
509 
Notify(SfxBroadcaster &,const SfxHint & rHint)510 void ViewOverlayManager::Notify(SfxBroadcaster&, const SfxHint& rHint)
511 {
512     const SfxSimpleHint* pSimpleHint = dynamic_cast<const SfxSimpleHint*>(&rHint);
513     if (pSimpleHint != NULL)
514     {
515         if (pSimpleHint->GetId() == SFX_HINT_DOCCHANGED)
516         {
517             UpdateTags();
518         }
519     }
520 }
521 
onZoomChanged()522 void ViewOverlayManager::onZoomChanged()
523 {
524     if( !maTagVector.empty() )
525     {
526         UpdateTags();
527     }
528 }
529 
UpdateTags()530 void ViewOverlayManager::UpdateTags()
531 {
532     if( !mnUpdateTagsEvent )
533         mnUpdateTagsEvent = Application::PostUserEvent( LINK( this, ViewOverlayManager, UpdateTagsHdl ) );
534 }
535 
IMPL_LINK(ViewOverlayManager,UpdateTagsHdl,void *,EMPTYARG)536 IMPL_LINK(ViewOverlayManager,UpdateTagsHdl, void *, EMPTYARG)
537 {
538 	OSL_TRACE("ViewOverlayManager::UpdateTagsHdl");
539 
540     mnUpdateTagsEvent  = 0;
541     bool bChanges = DisposeTags();
542     bChanges |= CreateTags();
543 
544     if( bChanges && mrBase.GetDrawView() )
545         static_cast< ::sd::View* >( mrBase.GetDrawView() )->updateHandles();
546     return 0;
547 }
548 
CreateTags()549 bool ViewOverlayManager::CreateTags()
550 {
551     bool bChanges = false;
552 
553     SdPage* pPage = mrBase.GetMainViewShell()->getCurrentPage();
554 
555     if( pPage && !pPage->IsMasterPage() && (pPage->GetPageKind() == PK_STANDARD) )
556     {
557 		const std::list< SdrObject* >& rShapes = pPage->GetPresentationShapeList().getList();
558 
559     	for( std::list< SdrObject* >::const_iterator iter( rShapes.begin() ); iter != rShapes.end(); iter++ )
560     	{
561     	    if( (*iter)->IsEmptyPresObj() && ((*iter)->GetObjIdentifier() == OBJ_OUTLINETEXT) && (mrBase.GetDrawView()->GetTextEditObject() != (*iter)) )
562     	    {
563     	        rtl::Reference< SmartTag > xTag( new ChangePlaceholderTag( *this, *mrBase.GetMainViewShell()->GetView(), *(*iter) ) );
564     	        maTagVector.push_back(xTag);
565 				bChanges = true;
566     	    }
567     	}
568     }
569 
570     return bChanges;
571 }
572 
573 // --------------------------------------------------------------------
574 
DisposeTags()575 bool ViewOverlayManager::DisposeTags()
576 {
577 	if( !maTagVector.empty() )
578 	{
579 	    ViewTagVector vec;
580 	    vec.swap( maTagVector );
581 
582 		ViewTagVector::iterator iter = vec.begin();
583 		do
584 		{
585 			(*iter++)->Dispose();
586 		}
587 		while( iter != vec.end() );
588 		return true;
589 	}
590 
591 	return false;
592 }
593 
594 // --------------------------------------------------------------------
595 
IMPL_LINK(ViewOverlayManager,EventMultiplexerListener,tools::EventMultiplexerEvent *,pEvent)596 IMPL_LINK(ViewOverlayManager,EventMultiplexerListener,
597     tools::EventMultiplexerEvent*,pEvent)
598 {
599     switch (pEvent->meEventId)
600     {
601 		case tools::EventMultiplexerEvent::EID_MAIN_VIEW_ADDED:
602 		case tools::EventMultiplexerEvent::EID_VIEW_ADDED:
603 		case tools::EventMultiplexerEvent::EID_BEGIN_TEXT_EDIT:
604 		case tools::EventMultiplexerEvent::EID_END_TEXT_EDIT:
605         case tools::EventMultiplexerEvent::EID_CURRENT_PAGE:
606             UpdateTags();
607             break;
608     }
609     return 0;
610 }
611 
612 }
613