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         for_each_sprite( boost::bind( &cppcanvas::Sprite::move,
412                                       _1,
413                                       boost::cref(maSpriteRectangle.getMinimum())) );
414 
415         // sprites changed, need screen update
416         mrScreenUpdater.notifyUpdate();
417     }
418 }
419 
paintAllSprites() const420 void RehearseTimingsActivity::paintAllSprites() const
421 {
422     for_each_sprite(
423         boost::bind( &RehearseTimingsActivity::paint, this,
424                      // call getContentCanvas() on each sprite:
425                      boost::bind(
426                          &cppcanvas::CustomSprite::getContentCanvas, _1 ) ) );
427 }
428 
paint(cppcanvas::CanvasSharedPtr const & canvas) const429 void RehearseTimingsActivity::paint( cppcanvas::CanvasSharedPtr const & canvas ) const
430 {
431     // build timer string:
432     const sal_Int32 nTimeSecs =
433         static_cast<sal_Int32>(maElapsedTime.getElapsedTime());
434     rtl::OUStringBuffer buf;
435     sal_Int32 n = (nTimeSecs / 3600);
436     if (n < 10)
437         buf.append( static_cast<sal_Unicode>('0') );
438     buf.append( n );
439     buf.append( static_cast<sal_Unicode>(':') );
440     n = ((nTimeSecs % 3600) / 60);
441     if (n < 10)
442         buf.append( static_cast<sal_Unicode>('0') );
443     buf.append( n );
444     buf.append( static_cast<sal_Unicode>(':') );
445     n = (nTimeSecs % 60);
446     if (n < 10)
447         buf.append( static_cast<sal_Unicode>('0') );
448     buf.append( n );
449     const rtl::OUString time = buf.makeStringAndClear();
450 
451 	// create the MetaFile:
452 	GDIMetaFile metaFile;
453 	VirtualDevice blackHole;
454 	metaFile.Record( &blackHole );
455     metaFile.SetPrefSize( Size( 1, 1 ) );
456 	blackHole.EnableOutput(false);
457     blackHole.SetMapMode( MAP_PIXEL );
458     blackHole.SetFont( maFont );
459     Rectangle rect = Rectangle( 0,0,
460                                 maSpriteSizePixel.getX(),
461                                 maSpriteSizePixel.getY());
462     if (mbDrawPressed)
463     {
464         blackHole.SetTextColor( COL_BLACK );
465         blackHole.SetFillColor( COL_LIGHTGRAY );
466         blackHole.SetLineColor( COL_GRAY );
467     }
468     else
469     {
470         blackHole.SetTextColor( COL_BLACK );
471         blackHole.SetFillColor( COL_WHITE );
472         blackHole.SetLineColor( COL_GRAY );
473     }
474     blackHole.DrawRect( rect );
475     blackHole.GetTextBoundRect( rect, time );
476     blackHole.DrawText(
477         Point( (maSpriteSizePixel.getX() - rect.getWidth()) / 2,
478                mnYOffset ), time );
479 
480 	metaFile.Stop();
481 	metaFile.WindStart();
482 
483     cppcanvas::RendererSharedPtr renderer(
484         cppcanvas::VCLFactory::getInstance().createRenderer(
485             canvas, metaFile, cppcanvas::Renderer::Parameters() ) );
486     const bool succ = renderer->draw();
487     OSL_ASSERT( succ );
488     (void)succ;
489 }
490 
491 
MouseHandler(RehearseTimingsActivity & rta)492 RehearseTimingsActivity::MouseHandler::MouseHandler( RehearseTimingsActivity& rta ) :
493     mrActivity(rta),
494     mbHasBeenClicked(false),
495     mbMouseStartedInArea(false)
496 {}
497 
reset()498 void RehearseTimingsActivity::MouseHandler::reset()
499 {
500     mbHasBeenClicked = false;
501     mbMouseStartedInArea = false;
502 }
503 
isInArea(awt::MouseEvent const & evt) const504 bool RehearseTimingsActivity::MouseHandler::isInArea(
505     awt::MouseEvent const & evt ) const
506 {
507     return mrActivity.maSpriteRectangle.isInside(
508         basegfx::B2DPoint( evt.X, evt.Y ) );
509 }
510 
updatePressedState(const bool pressedState) const511 void RehearseTimingsActivity::MouseHandler::updatePressedState(
512     const bool pressedState ) const
513 {
514     if( pressedState != mrActivity.mbDrawPressed )
515     {
516         mrActivity.mbDrawPressed = pressedState;
517         mrActivity.paintAllSprites();
518 
519         mrActivity.mrScreenUpdater.notifyUpdate();
520     }
521 }
522 
523 // MouseEventHandler
handleMousePressed(awt::MouseEvent const & evt)524 bool RehearseTimingsActivity::MouseHandler::handleMousePressed(
525     awt::MouseEvent const & evt )
526 {
527     if( evt.Buttons == awt::MouseButton::LEFT && isInArea(evt) )
528     {
529         mbMouseStartedInArea = true;
530         updatePressedState(true);
531         return true; // consume event
532     }
533     return false;
534 }
535 
handleMouseReleased(awt::MouseEvent const & evt)536 bool RehearseTimingsActivity::MouseHandler::handleMouseReleased(
537     awt::MouseEvent const & evt )
538 {
539     if( evt.Buttons == awt::MouseButton::LEFT && mbMouseStartedInArea )
540     {
541         mbHasBeenClicked = isInArea(evt); // fini if in
542         mbMouseStartedInArea = false;
543         updatePressedState(false);
544         if( !mbHasBeenClicked )
545             return true; // consume event, else next slide (manual advance)
546     }
547     return false;
548 }
549 
handleMouseEntered(awt::MouseEvent const &)550 bool RehearseTimingsActivity::MouseHandler::handleMouseEntered(
551     awt::MouseEvent const & /*evt*/ )
552 {
553     return false;
554 }
555 
handleMouseExited(awt::MouseEvent const &)556 bool RehearseTimingsActivity::MouseHandler::handleMouseExited(
557     awt::MouseEvent const & /*evt*/ )
558 {
559     return false;
560 }
561 
handleMouseDragged(awt::MouseEvent const & evt)562 bool RehearseTimingsActivity::MouseHandler::handleMouseDragged(
563     awt::MouseEvent const & evt )
564 {
565     if( mbMouseStartedInArea )
566         updatePressedState( isInArea(evt) );
567     return false;
568 }
569 
handleMouseMoved(awt::MouseEvent const &)570 bool RehearseTimingsActivity::MouseHandler::handleMouseMoved(
571     awt::MouseEvent const & /*evt*/ )
572 {
573     return false;
574 }
575 
576 } // namespace internal
577 } // namespace presentation
578