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