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