1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_slideshow.hxx"
30 
31 #include <boost/current_function.hpp>
32 #include <rtl/ustrbuf.hxx>
33 #include <vcl/svapp.hxx>
34 #include <vcl/gdimtf.hxx>
35 #include <vcl/virdev.hxx>
36 #include <vcl/metric.hxx>
37 #include <cppcanvas/vclfactory.hxx>
38 #include <cppcanvas/basegfxfactory.hxx>
39 #include <basegfx/range/b2drange.hxx>
40 
41 #include <comphelper/anytostring.hxx>
42 #include <cppuhelper/exc_hlp.hxx>
43 
44 #include <com/sun/star/awt/MouseButton.hpp>
45 #include <com/sun/star/awt/MouseEvent.hpp>
46 #include <com/sun/star/rendering/XBitmap.hpp>
47 
48 #include "eventqueue.hxx"
49 #include "screenupdater.hxx"
50 #include "eventmultiplexer.hxx"
51 #include "activitiesqueue.hxx"
52 #include "slideshowcontext.hxx"
53 #include "mouseeventhandler.hxx"
54 #include "rehearsetimingsactivity.hxx"
55 
56 #include <boost/bind.hpp>
57 #include <algorithm>
58 
59 using namespace com::sun::star;
60 using namespace com::sun::star::uno;
61 
62 namespace slideshow {
63 namespace internal {
64 
65 class RehearseTimingsActivity::WakeupEvent : public Event,
66                                              private ::boost::noncopyable
67 {
68 public:
69     WakeupEvent( boost::shared_ptr< ::canvas::tools::ElapsedTime > const& pTimeBase,
70                  ActivitySharedPtr const&                                 rActivity,
71                  ActivitiesQueue &                                        rActivityQueue ) :
72 #if OSL_DEBUG_LEVEL > 1
73         Event(::rtl::OUString::createFromAscii("WakeupEvent")),
74 #endif
75         maTimer(pTimeBase),
76         mnNextTime(0.0),
77         mpActivity(rActivity),
78         mrActivityQueue( rActivityQueue )
79     {}
80 
81     virtual void dispose() {}
82     virtual bool fire()
83     {
84         ActivitySharedPtr pActivity( mpActivity.lock() );
85         if( !pActivity )
86             return false;
87 
88         return mrActivityQueue.addActivity( pActivity );
89     }
90 
91     virtual bool isCharged() const { return true; }
92     virtual double getActivationTime( double nCurrentTime ) const
93     {
94         const double nElapsedTime( maTimer.getElapsedTime() );
95 
96         return ::std::max( nCurrentTime,
97                            nCurrentTime - nElapsedTime + mnNextTime );
98     }
99 
100     /// Start the internal timer
101     void start() { maTimer.reset(); }
102 
103     /** Set the next timeout this object should generate.
104 
105         @param nextTime
106         Absolute time, measured from the last start() call,
107         when this event should wakeup the Activity again. If
108         your time is relative, simply call start() just before
109         every setNextTimeout() call.
110     */
111     void setNextTimeout( double nextTime ) { mnNextTime = nextTime; }
112 
113 private:
114     ::canvas::tools::ElapsedTime    maTimer;
115     double                          mnNextTime;
116     boost::weak_ptr<Activity>       mpActivity;
117     ActivitiesQueue&                mrActivityQueue;
118 };
119 
120 class RehearseTimingsActivity::MouseHandler : public MouseEventHandler,
121                                               private boost::noncopyable
122 {
123 public:
124     explicit MouseHandler( RehearseTimingsActivity& rta );
125 
126     void reset();
127     bool hasBeenClicked() const { return mbHasBeenClicked; }
128 
129     // MouseEventHandler
130     virtual bool handleMousePressed( awt::MouseEvent const & evt );
131     virtual bool handleMouseReleased( awt::MouseEvent const & evt );
132     virtual bool handleMouseEntered( awt::MouseEvent const & evt );
133     virtual bool handleMouseExited( awt::MouseEvent const & evt );
134     virtual bool handleMouseDragged( awt::MouseEvent const & evt );
135     virtual bool handleMouseMoved( awt::MouseEvent const & evt );
136 
137 private:
138     bool isInArea( com::sun::star::awt::MouseEvent const & evt ) const;
139     void updatePressedState( const bool pressedState ) const;
140 
141     RehearseTimingsActivity& mrActivity;
142     bool                     mbHasBeenClicked;
143     bool                     mbMouseStartedInArea;
144 };
145 
146 const sal_Int32 LEFT_BORDER_SPACE  = 10;
147 const sal_Int32 LOWER_BORDER_SPACE = 30;
148 
149 RehearseTimingsActivity::RehearseTimingsActivity( const SlideShowContext& rContext ) :
150     mrEventQueue(rContext.mrEventQueue),
151     mrScreenUpdater(rContext.mrScreenUpdater),
152     mrEventMultiplexer(rContext.mrEventMultiplexer),
153     mrActivitiesQueue(rContext.mrActivitiesQueue),
154     maElapsedTime( rContext.mrEventQueue.getTimer() ),
155     maViews(),
156     maSpriteRectangle(),
157     maFont( Application::GetSettings().GetStyleSettings().GetInfoFont() ),
158     mpWakeUpEvent(),
159     mpMouseHandler(),
160     maSpriteSizePixel(),
161     mnYOffset(0),
162     mbActive(false),
163     mbDrawPressed(false)
164 {
165     maFont.SetHeight( maFont.GetHeight() * 2 );
166     maFont.SetWidth( maFont.GetWidth() * 2 );
167     maFont.SetAlign( ALIGN_BASELINE );
168     maFont.SetColor( COL_BLACK );
169 
170     // determine sprite size (in pixel):
171     VirtualDevice blackHole;
172     blackHole.EnableOutput(false);
173     blackHole.SetFont( maFont );
174     blackHole.SetMapMode( MAP_PIXEL );
175     Rectangle rect;
176     const FontMetric metric( blackHole.GetFontMetric() );
177     blackHole.GetTextBoundRect(
178         rect, String(RTL_CONSTASCII_USTRINGPARAM("XX:XX:XX")) );
179     maSpriteSizePixel.setX( rect.getWidth() * 12 / 10 );
180     maSpriteSizePixel.setY( metric.GetLineHeight() * 11 / 10 );
181     mnYOffset = (metric.GetAscent() + (metric.GetLineHeight() / 20));
182 
183     std::for_each( rContext.mrViewContainer.begin(),
184                    rContext.mrViewContainer.end(),
185                    boost::bind( &RehearseTimingsActivity::viewAdded,
186                                 this,
187                                 _1 ));
188 }
189 
190 RehearseTimingsActivity::~RehearseTimingsActivity()
191 {
192     try
193     {
194         stop();
195     }
196     catch (uno::Exception &)
197     {
198         OSL_ENSURE( false, rtl::OUStringToOString(
199                         comphelper::anyToString(
200                             cppu::getCaughtException() ),
201                         RTL_TEXTENCODING_UTF8 ).getStr() );
202     }
203 }
204 
205 boost::shared_ptr<RehearseTimingsActivity> RehearseTimingsActivity::create(
206     const SlideShowContext& rContext )
207 {
208     boost::shared_ptr<RehearseTimingsActivity> pActivity(
209         new RehearseTimingsActivity( rContext ));
210 
211     pActivity->mpMouseHandler.reset(
212         new MouseHandler(*pActivity.get()) );
213     pActivity->mpWakeUpEvent.reset(
214         new WakeupEvent( rContext.mrEventQueue.getTimer(),
215                          pActivity,
216                          rContext.mrActivitiesQueue ));
217 
218     rContext.mrEventMultiplexer.addViewHandler( pActivity );
219 
220     return pActivity;
221 }
222 
223 void RehearseTimingsActivity::start()
224 {
225     maElapsedTime.reset();
226     mbDrawPressed = false;
227     mbActive = true;
228 
229     // paint and show all sprites:
230     paintAllSprites();
231     for_each_sprite( boost::bind( &cppcanvas::Sprite::show, _1 ) );
232 
233     mrActivitiesQueue.addActivity( shared_from_this() );
234 
235     mpMouseHandler->reset();
236     mrEventMultiplexer.addClickHandler(
237         mpMouseHandler, 42 /* highest prio of all, > 3.0 */ );
238     mrEventMultiplexer.addMouseMoveHandler(
239         mpMouseHandler, 42 /* highest prio of all, > 3.0 */ );
240 }
241 
242 double RehearseTimingsActivity::stop()
243 {
244     mrEventMultiplexer.removeMouseMoveHandler( mpMouseHandler );
245     mrEventMultiplexer.removeClickHandler( mpMouseHandler );
246 
247     mbActive = false; // will be removed from queue
248 
249     for_each_sprite( boost::bind( &cppcanvas::Sprite::hide, _1 ) );
250 
251     return maElapsedTime.getElapsedTime();
252 }
253 
254 bool RehearseTimingsActivity::hasBeenClicked() const
255 {
256     if (mpMouseHandler)
257         return mpMouseHandler->hasBeenClicked();
258     return false;
259 }
260 
261 // Disposable:
262 void RehearseTimingsActivity::dispose()
263 {
264     stop();
265 
266     mpWakeUpEvent.reset();
267     mpMouseHandler.reset();
268 
269     ViewsVecT().swap( maViews );
270 }
271 
272 // Activity:
273 double RehearseTimingsActivity::calcTimeLag() const
274 {
275     return 0.0;
276 }
277 
278 bool RehearseTimingsActivity::perform()
279 {
280     if( !isActive() )
281         return false;
282 
283     if( !mpWakeUpEvent )
284         return false;
285 
286     mpWakeUpEvent->start();
287     mpWakeUpEvent->setNextTimeout( 0.5 );
288     mrEventQueue.addEvent( mpWakeUpEvent );
289 
290     paintAllSprites();
291 
292     // sprites changed, need screen update
293     mrScreenUpdater.notifyUpdate();
294 
295     return false; // don't reinsert, WakeupEvent will perform
296                   // that after the given timeout
297 }
298 
299 bool RehearseTimingsActivity::isActive() const
300 {
301     return mbActive;
302 }
303 
304 void RehearseTimingsActivity::dequeued()
305 {
306     // not used here
307 }
308 
309 void RehearseTimingsActivity::end()
310 {
311     if (isActive())
312     {
313         stop();
314         mbActive = false;
315     }
316 }
317 
318 basegfx::B2DRange RehearseTimingsActivity::calcSpriteRectangle( UnoViewSharedPtr const& rView ) const
319 {
320     const Reference<rendering::XBitmap> xBitmap( rView->getCanvas()->getUNOCanvas(),
321                                                  UNO_QUERY );
322     if( !xBitmap.is() )
323         return basegfx::B2DRange();
324 
325     const geometry::IntegerSize2D realSize( xBitmap->getSize() );
326     // pixel:
327     basegfx::B2DPoint spritePos(
328         std::min<sal_Int32>( realSize.Width, LEFT_BORDER_SPACE ),
329         std::max<sal_Int32>( 0, realSize.Height - maSpriteSizePixel.getY()
330                                                 - LOWER_BORDER_SPACE ) );
331     basegfx::B2DHomMatrix transformation( rView->getTransformation() );
332     transformation.invert();
333     spritePos *= transformation;
334     basegfx::B2DSize spriteSize( maSpriteSizePixel.getX(),
335                                  maSpriteSizePixel.getY() );
336     spriteSize *= transformation;
337     return basegfx::B2DRange(
338         spritePos.getX(), spritePos.getY(),
339         spritePos.getX() + spriteSize.getX(),
340         spritePos.getY() + spriteSize.getY() );
341 }
342 
343 void RehearseTimingsActivity::viewAdded( const UnoViewSharedPtr& rView )
344 {
345     cppcanvas::CustomSpriteSharedPtr sprite(
346         rView->createSprite( basegfx::B2DSize(
347                                  maSpriteSizePixel.getX()+2,
348                                  maSpriteSizePixel.getY()+2 ),
349                              1001.0 )); // sprite should be in front of all
350                                         // other sprites
351     sprite->setAlpha( 0.8 );
352     const basegfx::B2DRange spriteRectangle(
353         calcSpriteRectangle( rView ) );
354     sprite->move( basegfx::B2DPoint(
355                       spriteRectangle.getMinX(),
356                       spriteRectangle.getMinY() ) );
357 
358     if( maViews.empty() )
359         maSpriteRectangle = spriteRectangle;
360 
361     maViews.push_back( ViewsVecT::value_type( rView, sprite ) );
362 
363     if (isActive())
364         sprite->show();
365 }
366 
367 void RehearseTimingsActivity::viewRemoved( const UnoViewSharedPtr& rView )
368 {
369     maViews.erase(
370         std::remove_if(
371             maViews.begin(), maViews.end(),
372             boost::bind(
373                 std::equal_to<UnoViewSharedPtr>(),
374                 rView,
375                 // select view:
376                 boost::bind( std::select1st<ViewsVecT::value_type>(), _1 ))),
377         maViews.end() );
378 }
379 
380 void RehearseTimingsActivity::viewChanged( const UnoViewSharedPtr& rView )
381 {
382     // find entry corresponding to modified view
383     ViewsVecT::iterator aModifiedEntry(
384         std::find_if(
385             maViews.begin(),
386             maViews.end(),
387             boost::bind(
388                 std::equal_to<UnoViewSharedPtr>(),
389                 rView,
390                 // select view:
391                 boost::bind( std::select1st<ViewsVecT::value_type>(), _1 ))));
392 
393     OSL_ASSERT( aModifiedEntry != maViews.end() );
394     if( aModifiedEntry == maViews.end() )
395         return;
396 
397     // new sprite pos, transformation might have changed:
398     maSpriteRectangle = calcSpriteRectangle( rView );
399 
400     // reposition sprite:
401     aModifiedEntry->second->move( maSpriteRectangle.getMinimum() );
402 
403     // sprites changed, need screen update
404     mrScreenUpdater.notifyUpdate( rView );
405 }
406 
407 void RehearseTimingsActivity::viewsChanged()
408 {
409     if( !maViews.empty() )
410     {
411         // new sprite pos, transformation might have changed:
412         maSpriteRectangle = calcSpriteRectangle( maViews.front().first );
413 
414         // reposition sprites
415         for_each_sprite( boost::bind( &cppcanvas::Sprite::move,
416                                       _1,
417                                       boost::cref(maSpriteRectangle.getMinimum())) );
418 
419         // sprites changed, need screen update
420         mrScreenUpdater.notifyUpdate();
421     }
422 }
423 
424 void RehearseTimingsActivity::paintAllSprites() const
425 {
426     for_each_sprite(
427         boost::bind( &RehearseTimingsActivity::paint, this,
428                      // call getContentCanvas() on each sprite:
429                      boost::bind(
430                          &cppcanvas::CustomSprite::getContentCanvas, _1 ) ) );
431 }
432 
433 void RehearseTimingsActivity::paint( cppcanvas::CanvasSharedPtr const & canvas ) const
434 {
435     // build timer string:
436     const sal_Int32 nTimeSecs =
437         static_cast<sal_Int32>(maElapsedTime.getElapsedTime());
438     rtl::OUStringBuffer buf;
439     sal_Int32 n = (nTimeSecs / 3600);
440     if (n < 10)
441         buf.append( static_cast<sal_Unicode>('0') );
442     buf.append( n );
443     buf.append( static_cast<sal_Unicode>(':') );
444     n = ((nTimeSecs % 3600) / 60);
445     if (n < 10)
446         buf.append( static_cast<sal_Unicode>('0') );
447     buf.append( n );
448     buf.append( static_cast<sal_Unicode>(':') );
449     n = (nTimeSecs % 60);
450     if (n < 10)
451         buf.append( static_cast<sal_Unicode>('0') );
452     buf.append( n );
453     const rtl::OUString time = buf.makeStringAndClear();
454 
455 	// create the MetaFile:
456 	GDIMetaFile metaFile;
457 	VirtualDevice blackHole;
458 	metaFile.Record( &blackHole );
459     metaFile.SetPrefSize( Size( 1, 1 ) );
460 	blackHole.EnableOutput(false);
461     blackHole.SetMapMode( MAP_PIXEL );
462     blackHole.SetFont( maFont );
463     Rectangle rect = Rectangle( 0,0,
464                                 maSpriteSizePixel.getX(),
465                                 maSpriteSizePixel.getY());
466     if (mbDrawPressed)
467     {
468         blackHole.SetTextColor( COL_BLACK );
469         blackHole.SetFillColor( COL_LIGHTGRAY );
470         blackHole.SetLineColor( COL_GRAY );
471     }
472     else
473     {
474         blackHole.SetTextColor( COL_BLACK );
475         blackHole.SetFillColor( COL_WHITE );
476         blackHole.SetLineColor( COL_GRAY );
477     }
478     blackHole.DrawRect( rect );
479     blackHole.GetTextBoundRect( rect, time );
480     blackHole.DrawText(
481         Point( (maSpriteSizePixel.getX() - rect.getWidth()) / 2,
482                mnYOffset ), time );
483 
484 	metaFile.Stop();
485 	metaFile.WindStart();
486 
487     cppcanvas::RendererSharedPtr renderer(
488         cppcanvas::VCLFactory::getInstance().createRenderer(
489             canvas, metaFile, cppcanvas::Renderer::Parameters() ) );
490     const bool succ = renderer->draw();
491     OSL_ASSERT( succ );
492     (void)succ;
493 }
494 
495 
496 RehearseTimingsActivity::MouseHandler::MouseHandler( RehearseTimingsActivity& rta ) :
497     mrActivity(rta),
498     mbHasBeenClicked(false),
499     mbMouseStartedInArea(false)
500 {}
501 
502 void RehearseTimingsActivity::MouseHandler::reset()
503 {
504     mbHasBeenClicked = false;
505     mbMouseStartedInArea = false;
506 }
507 
508 bool RehearseTimingsActivity::MouseHandler::isInArea(
509     awt::MouseEvent const & evt ) const
510 {
511     return mrActivity.maSpriteRectangle.isInside(
512         basegfx::B2DPoint( evt.X, evt.Y ) );
513 }
514 
515 void RehearseTimingsActivity::MouseHandler::updatePressedState(
516     const bool pressedState ) const
517 {
518     if( pressedState != mrActivity.mbDrawPressed )
519     {
520         mrActivity.mbDrawPressed = pressedState;
521         mrActivity.paintAllSprites();
522 
523         mrActivity.mrScreenUpdater.notifyUpdate();
524     }
525 }
526 
527 // MouseEventHandler
528 bool RehearseTimingsActivity::MouseHandler::handleMousePressed(
529     awt::MouseEvent const & evt )
530 {
531     if( evt.Buttons == awt::MouseButton::LEFT && isInArea(evt) )
532     {
533         mbMouseStartedInArea = true;
534         updatePressedState(true);
535         return true; // consume event
536     }
537     return false;
538 }
539 
540 bool RehearseTimingsActivity::MouseHandler::handleMouseReleased(
541     awt::MouseEvent const & evt )
542 {
543     if( evt.Buttons == awt::MouseButton::LEFT && mbMouseStartedInArea )
544     {
545         mbHasBeenClicked = isInArea(evt); // fini if in
546         mbMouseStartedInArea = false;
547         updatePressedState(false);
548         if( !mbHasBeenClicked )
549             return true; // consume event, else next slide (manual advance)
550     }
551     return false;
552 }
553 
554 bool RehearseTimingsActivity::MouseHandler::handleMouseEntered(
555     awt::MouseEvent const & /*evt*/ )
556 {
557     return false;
558 }
559 
560 bool RehearseTimingsActivity::MouseHandler::handleMouseExited(
561     awt::MouseEvent const & /*evt*/ )
562 {
563     return false;
564 }
565 
566 bool RehearseTimingsActivity::MouseHandler::handleMouseDragged(
567     awt::MouseEvent const & evt )
568 {
569     if( mbMouseStartedInArea )
570         updatePressedState( isInArea(evt) );
571     return false;
572 }
573 
574 bool RehearseTimingsActivity::MouseHandler::handleMouseMoved(
575     awt::MouseEvent const & /*evt*/ )
576 {
577     return false;
578 }
579 
580 } // namespace internal
581 } // namespace presentation
582