office-gobmx/slideshow/source/engine/rehearsetimingsactivity.cxx
Noel Grandin b6652dae45 loplugin:ostr in slideshow
Change-Id: I87b391b95a75a1ddc4e26dcd0bbd557f8bd3b17b
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/167578
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
Tested-by: Jenkins
2024-05-13 14:44:04 +02:00

543 lines
16 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <rtl/ustrbuf.hxx>
#include <vcl/svapp.hxx>
#include <vcl/gdimtf.hxx>
#include <vcl/virdev.hxx>
#include <vcl/metric.hxx>
#include <vcl/settings.hxx>
#include <cppcanvas/vclfactory.hxx>
#include <basegfx/range/b2drange.hxx>
#include <osl/diagnose.h>
#include <comphelper/diagnose_ex.hxx>
#include <com/sun/star/awt/MouseButton.hpp>
#include <com/sun/star/awt/MouseEvent.hpp>
#include <com/sun/star/rendering/XBitmap.hpp>
#include <com/sun/star/rendering/XCanvas.hpp>
#include <eventqueue.hxx>
#include <screenupdater.hxx>
#include <eventmultiplexer.hxx>
#include <activitiesqueue.hxx>
#include <slideshowcontext.hxx>
#include <mouseeventhandler.hxx>
#include "rehearsetimingsactivity.hxx"
#include <algorithm>
using namespace com::sun::star;
using namespace com::sun::star::uno;
namespace slideshow::internal {
class RehearseTimingsActivity::WakeupEvent : public Event
{
public:
WakeupEvent( std::shared_ptr< ::canvas::tools::ElapsedTime > const& pTimeBase,
ActivitySharedPtr const& rActivity,
ActivitiesQueue & rActivityQueue ) :
Event(u"WakeupEvent"_ustr),
maTimer(pTimeBase),
mnNextTime(0.0),
mpActivity(rActivity),
mrActivityQueue( rActivityQueue )
{}
WakeupEvent( const WakeupEvent& ) = delete;
WakeupEvent& operator=( const WakeupEvent& ) = delete;
virtual void dispose() override {}
virtual bool fire() override
{
ActivitySharedPtr pActivity( mpActivity.lock() );
if( !pActivity )
return false;
return mrActivityQueue.addActivity( pActivity );
}
virtual bool isCharged() const override { return true; }
virtual double getActivationTime( double nCurrentTime ) const override
{
const double nElapsedTime( maTimer.getElapsedTime() );
return ::std::max( nCurrentTime,
nCurrentTime - nElapsedTime + mnNextTime );
}
/// Start the internal timer
void start() { maTimer.reset(); }
/** Set the next timeout this object should generate.
@param nextTime
Absolute time, measured from the last start() call,
when this event should wakeup the Activity again. If
your time is relative, simply call start() just before
every setNextTimeout() call.
*/
void setNextTimeout( double nextTime ) { mnNextTime = nextTime; }
private:
::canvas::tools::ElapsedTime maTimer;
double mnNextTime;
std::weak_ptr<Activity> mpActivity;
ActivitiesQueue& mrActivityQueue;
};
class RehearseTimingsActivity::MouseHandler : public MouseEventHandler
{
public:
explicit MouseHandler( RehearseTimingsActivity& rta );
MouseHandler( const MouseHandler& ) = delete;
MouseHandler& operator=( const MouseHandler& ) = delete;
void reset();
bool hasBeenClicked() const { return mbHasBeenClicked; }
// MouseEventHandler
virtual bool handleMousePressed( awt::MouseEvent const & evt ) override;
virtual bool handleMouseReleased( awt::MouseEvent const & evt ) override;
virtual bool handleMouseDragged( awt::MouseEvent const & evt ) override;
virtual bool handleMouseMoved( awt::MouseEvent const & evt ) override;
private:
bool isInArea( css::awt::MouseEvent const & evt ) const;
void updatePressedState( const bool pressedState ) const;
RehearseTimingsActivity& mrActivity;
bool mbHasBeenClicked;
bool mbMouseStartedInArea;
};
const sal_Int32 LEFT_BORDER_SPACE = 10;
const sal_Int32 LOWER_BORDER_SPACE = 30;
RehearseTimingsActivity::RehearseTimingsActivity( const SlideShowContext& rContext ) :
mrEventQueue(rContext.mrEventQueue),
mrScreenUpdater(rContext.mrScreenUpdater),
mrEventMultiplexer(rContext.mrEventMultiplexer),
mrActivitiesQueue(rContext.mrActivitiesQueue),
maElapsedTime( rContext.mrEventQueue.getTimer() ),
maViews(),
maSpriteRectangle(),
maFont( Application::GetSettings().GetStyleSettings().GetLabelFont() ),
mpWakeUpEvent(),
mpMouseHandler(),
maSpriteSizePixel(),
mnYOffset(0),
mbActive(false),
mbDrawPressed(false)
{
maFont.SetFontHeight( maFont.GetFontHeight() * 2 );
maFont.SetAverageFontWidth( maFont.GetAverageFontWidth() * 2 );
maFont.SetAlignment( ALIGN_BASELINE );
maFont.SetColor( COL_BLACK );
// determine sprite size (in pixel):
ScopedVclPtrInstance< VirtualDevice > blackHole;
blackHole->EnableOutput(false);
blackHole->SetFont( maFont );
blackHole->SetMapMode(MapMode(MapUnit::MapPixel));
tools::Rectangle rect;
const FontMetric metric( blackHole->GetFontMetric() );
blackHole->GetTextBoundRect( rect, u"XX:XX:XX"_ustr );
maSpriteSizePixel.setX( rect.getOpenWidth() * 12 / 10 );
maSpriteSizePixel.setY( metric.GetLineHeight() * 11 / 10 );
mnYOffset = (metric.GetAscent() + (metric.GetLineHeight() / 20));
for( const auto& rView : rContext.mrViewContainer )
viewAdded( rView );
}
RehearseTimingsActivity::~RehearseTimingsActivity()
{
try
{
stop();
}
catch (const uno::Exception&)
{
TOOLS_WARN_EXCEPTION("slideshow", "");
}
}
std::shared_ptr<RehearseTimingsActivity> RehearseTimingsActivity::create(
const SlideShowContext& rContext )
{
std::shared_ptr<RehearseTimingsActivity> pActivity(
new RehearseTimingsActivity( rContext ));
pActivity->mpMouseHandler =
std::make_shared<MouseHandler>(*pActivity);
pActivity->mpWakeUpEvent =
std::make_shared<WakeupEvent>( rContext.mrEventQueue.getTimer(),
pActivity,
rContext.mrActivitiesQueue );
rContext.mrEventMultiplexer.addViewHandler( pActivity );
return pActivity;
}
void RehearseTimingsActivity::start()
{
maElapsedTime.reset();
mbDrawPressed = false;
mbActive = true;
// paint and show all sprites:
paintAllSprites();
for_each_sprite( []( const ::cppcanvas::CustomSpriteSharedPtr& pSprite )
{ return pSprite->show(); } );
mrActivitiesQueue.addActivity( std::dynamic_pointer_cast<Activity>(shared_from_this()) );
mpMouseHandler->reset();
mrEventMultiplexer.addClickHandler(
mpMouseHandler, 42 /* highest prio of all, > 3.0 */ );
mrEventMultiplexer.addMouseMoveHandler(
mpMouseHandler, 42 /* highest prio of all, > 3.0 */ );
}
double RehearseTimingsActivity::stop()
{
mrEventMultiplexer.removeMouseMoveHandler( mpMouseHandler );
mrEventMultiplexer.removeClickHandler( mpMouseHandler );
mbActive = false; // will be removed from queue
for_each_sprite( []( const ::cppcanvas::CustomSpriteSharedPtr& pSprite )
{ return pSprite->hide(); } );
return maElapsedTime.getElapsedTime();
}
bool RehearseTimingsActivity::hasBeenClicked() const
{
if (mpMouseHandler)
return mpMouseHandler->hasBeenClicked();
return false;
}
// Disposable:
void RehearseTimingsActivity::dispose()
{
stop();
mpWakeUpEvent.reset();
mpMouseHandler.reset();
ViewsVecT().swap( maViews );
}
// Activity:
double RehearseTimingsActivity::calcTimeLag() const
{
return 0.0;
}
bool RehearseTimingsActivity::perform()
{
if( !isActive() )
return false;
if( !mpWakeUpEvent )
return false;
mpWakeUpEvent->start();
mpWakeUpEvent->setNextTimeout( 0.5 );
mrEventQueue.addEvent( mpWakeUpEvent );
paintAllSprites();
// sprites changed, need screen update
mrScreenUpdater.notifyUpdate();
return false; // don't reinsert, WakeupEvent will perform
// that after the given timeout
}
bool RehearseTimingsActivity::isActive() const
{
return mbActive;
}
void RehearseTimingsActivity::dequeued()
{
// not used here
}
void RehearseTimingsActivity::end()
{
if (isActive())
{
stop();
mbActive = false;
}
}
basegfx::B2DRange RehearseTimingsActivity::calcSpriteRectangle( UnoViewSharedPtr const& rView ) const
{
const Reference<rendering::XBitmap> xBitmap( rView->getCanvas()->getUNOCanvas(),
UNO_QUERY );
if( !xBitmap.is() )
return basegfx::B2DRange();
const geometry::IntegerSize2D realSize( xBitmap->getSize() );
// pixel:
basegfx::B2DPoint spritePos(
std::min<sal_Int32>( realSize.Width, LEFT_BORDER_SPACE ),
std::max<sal_Int32>( 0, realSize.Height - maSpriteSizePixel.getY()
- LOWER_BORDER_SPACE ) );
basegfx::B2DHomMatrix transformation( rView->getTransformation() );
transformation.invert();
spritePos *= transformation;
basegfx::B2DSize spriteSize( maSpriteSizePixel.getX(),
maSpriteSizePixel.getY() );
spriteSize *= transformation;
return basegfx::B2DRange(
spritePos.getX(), spritePos.getY(),
spritePos.getX() + spriteSize.getWidth(),
spritePos.getY() + spriteSize.getHeight() );
}
void RehearseTimingsActivity::viewAdded( const UnoViewSharedPtr& rView )
{
cppcanvas::CustomSpriteSharedPtr sprite(
rView->createSprite( basegfx::B2DSize(
maSpriteSizePixel.getX()+2,
maSpriteSizePixel.getY()+2 ),
1001.0 )); // sprite should be in front of all
// other sprites
sprite->setAlpha( 0.8 );
const basegfx::B2DRange spriteRectangle(
calcSpriteRectangle( rView ) );
sprite->move( basegfx::B2DPoint(
spriteRectangle.getMinX(),
spriteRectangle.getMinY() ) );
if( maViews.empty() )
maSpriteRectangle = spriteRectangle;
maViews.emplace_back( rView, sprite );
if (isActive())
sprite->show();
}
void RehearseTimingsActivity::viewRemoved( const UnoViewSharedPtr& rView )
{
std::erase_if(
maViews,
[&rView]
( const ViewsVecT::value_type& cp )
{ return rView == cp.first; } );
}
void RehearseTimingsActivity::viewChanged( const UnoViewSharedPtr& rView )
{
// find entry corresponding to modified view
ViewsVecT::iterator aModifiedEntry(
std::find_if(
maViews.begin(),
maViews.end(),
[&rView]
( const ViewsVecT::value_type& cp )
{ return rView == cp.first; } )
);
OSL_ASSERT( aModifiedEntry != maViews.end() );
if( aModifiedEntry == maViews.end() )
return;
// new sprite pos, transformation might have changed:
maSpriteRectangle = calcSpriteRectangle( rView );
// reposition sprite:
aModifiedEntry->second->move( maSpriteRectangle.getMinimum() );
// sprites changed, need screen update
mrScreenUpdater.notifyUpdate( rView, false );
}
void RehearseTimingsActivity::viewsChanged()
{
if( maViews.empty() )
return;
// new sprite pos, transformation might have changed:
maSpriteRectangle = calcSpriteRectangle( maViews.front().first );
::basegfx::B2DPoint nMin = maSpriteRectangle.getMinimum();
// reposition sprites
for_each_sprite( [nMin]( const ::cppcanvas::CustomSpriteSharedPtr& pSprite )
{ return pSprite->move( nMin ); } );
// sprites changed, need screen update
mrScreenUpdater.notifyUpdate();
}
void RehearseTimingsActivity::paintAllSprites() const
{
for_each_sprite(
[this]( const ::cppcanvas::CustomSpriteSharedPtr& pSprite )
{ return this->paint( pSprite->getContentCanvas() ); } );
}
void RehearseTimingsActivity::paint( cppcanvas::CanvasSharedPtr const & canvas ) const
{
// build timer string:
const sal_Int32 nTimeSecs =
static_cast<sal_Int32>(maElapsedTime.getElapsedTime());
OUStringBuffer buf;
sal_Int32 n = nTimeSecs / 3600;
if (n < 10)
buf.append( '0' );
buf.append( OUString::number(n) + ":" );
n = ((nTimeSecs % 3600) / 60);
if (n < 10)
buf.append( '0' );
buf.append( OUString::number(n) + ":" );
n = (nTimeSecs % 60);
if (n < 10)
buf.append( '0' );
buf.append( n );
const OUString time = buf.makeStringAndClear();
// create the MetaFile:
GDIMetaFile metaFile;
ScopedVclPtrInstance< VirtualDevice > blackHole;
metaFile.Record( blackHole );
metaFile.SetPrefSize( Size( 1, 1 ) );
blackHole->EnableOutput(false);
blackHole->SetMapMode(MapMode(MapUnit::MapPixel));
blackHole->SetFont( maFont );
tools::Rectangle rect( 0,0,
maSpriteSizePixel.getX(),
maSpriteSizePixel.getY());
if (mbDrawPressed)
{
blackHole->SetTextColor( COL_BLACK );
blackHole->SetFillColor( COL_LIGHTGRAY );
blackHole->SetLineColor( COL_GRAY );
}
else
{
blackHole->SetTextColor( COL_BLACK );
blackHole->SetFillColor( COL_WHITE );
blackHole->SetLineColor( COL_GRAY );
}
blackHole->DrawRect( rect );
blackHole->GetTextBoundRect( rect, time );
blackHole->DrawText(
Point( (maSpriteSizePixel.getX() - rect.getOpenWidth()) / 2,
mnYOffset ), time );
metaFile.Stop();
metaFile.WindStart();
cppcanvas::RendererSharedPtr renderer(
cppcanvas::VCLFactory::createRenderer(
canvas, metaFile, cppcanvas::Renderer::Parameters() ) );
const bool succ = renderer->draw();
OSL_ASSERT( succ );
}
RehearseTimingsActivity::MouseHandler::MouseHandler( RehearseTimingsActivity& rta ) :
mrActivity(rta),
mbHasBeenClicked(false),
mbMouseStartedInArea(false)
{}
void RehearseTimingsActivity::MouseHandler::reset()
{
mbHasBeenClicked = false;
mbMouseStartedInArea = false;
}
bool RehearseTimingsActivity::MouseHandler::isInArea(
awt::MouseEvent const & evt ) const
{
return mrActivity.maSpriteRectangle.isInside(
basegfx::B2DPoint( evt.X, evt.Y ) );
}
void RehearseTimingsActivity::MouseHandler::updatePressedState(
const bool pressedState ) const
{
if( pressedState != mrActivity.mbDrawPressed )
{
mrActivity.mbDrawPressed = pressedState;
mrActivity.paintAllSprites();
mrActivity.mrScreenUpdater.notifyUpdate();
}
}
// MouseEventHandler
bool RehearseTimingsActivity::MouseHandler::handleMousePressed(
awt::MouseEvent const & evt )
{
if( evt.Buttons == awt::MouseButton::LEFT && isInArea(evt) )
{
mbMouseStartedInArea = true;
updatePressedState(true);
return true; // consume event
}
return false;
}
bool RehearseTimingsActivity::MouseHandler::handleMouseReleased(
awt::MouseEvent const & evt )
{
if( evt.Buttons == awt::MouseButton::LEFT && mbMouseStartedInArea )
{
mbHasBeenClicked = isInArea(evt); // fini if in
mbMouseStartedInArea = false;
updatePressedState(false);
if( !mbHasBeenClicked )
return true; // consume event, else next slide (manual advance)
}
return false;
}
bool RehearseTimingsActivity::MouseHandler::handleMouseDragged(
awt::MouseEvent const & evt )
{
if( mbMouseStartedInArea )
updatePressedState( isInArea(evt) );
return false;
}
bool RehearseTimingsActivity::MouseHandler::handleMouseMoved(
awt::MouseEvent const & /*evt*/ )
{
return false;
}
} // namespace presentation
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */