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