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 <com/sun/star/awt/MouseButton.hpp>
22 #include <com/sun/star/awt/MouseEvent.hpp>
23 
24 #include <basegfx/point/b2dpoint.hxx>
25 #include <basegfx/polygon/b2dpolygon.hxx>
26 #include <basegfx/matrix/b2dhommatrix.hxx>
27 #include <cppcanvas/basegfxfactory.hxx>
28 #include <comphelper/diagnose_ex.hxx>
29 
30 #include <slideshowcontext.hxx>
31 #include "userpaintoverlay.hxx"
32 #include <mouseeventhandler.hxx>
33 #include <eventmultiplexer.hxx>
34 #include <screenupdater.hxx>
35 #include <vieweventhandler.hxx>
36 
37 #include <slide.hxx>
38 #include <cursormanager.hxx>
39 
40 using namespace ::com::sun::star;
41 
42 namespace slideshow::internal
43 {
44         class PaintOverlayHandler : public MouseEventHandler,
45                                     public ViewEventHandler,
46                     public UserPaintEventHandler
47         {
48         public:
49             PaintOverlayHandler( const RGBColor&          rStrokeColor,
50                                  double                   nStrokeWidth,
51                                  ScreenUpdater&           rScreenUpdater,
52                                  const UnoViewContainer&  rViews,
53                                  Slide&                   rSlide,
54                                  PolyPolygonVector&&      rPolygons,
55                                  bool                     bActive ) :
56                 mrScreenUpdater( rScreenUpdater ),
57                 maViews(),
58                 maPolygons( std::move(rPolygons) ),
59                 maStrokeColor( rStrokeColor ),
60                 mnStrokeWidth( nStrokeWidth ),
61                 maLastPoint(),
62                 maLastMouseDownPos(),
63                 mbIsLastPointValid( false ),
64                 mbIsLastMouseDownPosValid( false ),
65                 //handle the "remove all ink from slide" mode of erasing
66                 mbIsEraseAllModeActivated( false ),
67                 //handle the "remove stroke by stroke" mode of erasing
68                 mbIsEraseModeActivated( false ),
69                 mrSlide(rSlide),
70                 mnSize(100),
71                 mbActive( bActive )
72             {
73                 for( const auto& rView : rViews )
74                     viewAdded( rView );
75 
76                 drawPolygons();
77             }
78 
79             void dispose()
80             {
81                 maViews.clear();
82             }
83 
84             // ViewEventHandler methods
85             virtual void viewAdded( const UnoViewSharedPtr& rView ) override
86             {
87                 maViews.push_back( rView );
88             }
89 
90             virtual void viewRemoved( const UnoViewSharedPtr& rView ) override
91             {
92                 maViews.erase( ::std::remove( maViews.begin(),
93                                               maViews.end(),
94                                               rView ) );
95             }
96 
97             virtual void viewChanged( const UnoViewSharedPtr& /*rView*/ ) override
98             {
99                 // TODO(F2): for persistent drawings, need to store
100                 // polygon and repaint here.
101             }
102 
103             virtual void viewsChanged() override
104             {
105                 // TODO(F2): for persistent drawings, need to store
106                 // polygon and repaint here.
107             }
108 
109             bool colorChanged( RGBColor const& rUserColor ) override
110             {
111                 mbIsLastPointValid = false;
112                 mbActive = true;
113                 maStrokeColor = rUserColor;
114                 mbIsEraseModeActivated = false;
115                 return true;
116             }
117 
118             bool widthChanged( double nUserStrokeWidth ) override
119             {
120                 mnStrokeWidth = nUserStrokeWidth;
121                 mbIsEraseModeActivated = false;
122                 return true;
123             }
124 
125             void repaintWithoutPolygons()
126             {
127                     // must get access to the instance to erase all polygon
128                     for( const auto& rxView : maViews )
129                     {
130                         // fully clear view content to background color
131                         //rxView->getCanvas()->clear();
132 
133                         //get via SlideImpl instance the bitmap of the slide unmodified to redraw it
134                         SlideBitmapSharedPtr         pBitmap( mrSlide.getCurrentSlideBitmap( rxView ) );
135                         ::cppcanvas::CanvasSharedPtr pCanvas( rxView->getCanvas() );
136 
137                         const ::basegfx::B2DHomMatrix   aViewTransform( rxView->getTransformation() );
138                         const ::basegfx::B2DPoint       aOutPosPixel( aViewTransform * ::basegfx::B2DPoint() );
139 
140                         // setup a canvas with device coordinate space, the slide
141                         // bitmap already has the correct dimension.
142                         ::cppcanvas::CanvasSharedPtr pDevicePixelCanvas( pCanvas->clone() );
143 
144                         pDevicePixelCanvas->setTransformation( ::basegfx::B2DHomMatrix() );
145 
146                         // render at given output position
147                         pBitmap->move( aOutPosPixel );
148 
149                         // clear clip (might have been changed, e.g. from comb
150                         // transition)
151                         pBitmap->clip( ::basegfx::B2DPolyPolygon() );
152                         pBitmap->draw( pDevicePixelCanvas );
153 
154                         mrScreenUpdater.notifyUpdate(rxView,true);
155                     }
156             }
157 
158             bool eraseAllInkChanged( bool bEraseAllInk ) override
159             {
160                 mbIsEraseAllModeActivated = bEraseAllInk;
161                 // if the erase all mode is activated it will remove all ink from slide,
162                 // therefore destroy all the polygons stored
163                 if(mbIsEraseAllModeActivated)
164                 {
165                     // The Erase Mode should be deactivated
166                     mbIsEraseModeActivated = false;
167                     repaintWithoutPolygons();
168                     maPolygons.clear();
169                 }
170                 mbIsEraseAllModeActivated=false;
171                 return true;
172             }
173 
174             bool eraseInkWidthChanged( sal_Int32 rEraseInkSize ) override
175             {
176                 // Change the size
177                 mnSize=rEraseInkSize;
178                 // Changed to mode Erase
179                 mbIsEraseModeActivated = true;
180                 return true;
181             }
182 
183             bool switchPenMode() override
184             {
185                 mbIsLastPointValid = false;
186                 mbActive = true;
187                 mbIsEraseModeActivated = false;
188                 return true;
189             }
190 
191             bool switchEraserMode() override
192             {
193                 mbIsLastPointValid = false;
194                 mbActive = true;
195                 mbIsEraseModeActivated = true;
196                 return true;
197             }
198 
199             bool disable() override
200             {
201                 mbIsLastPointValid = false;
202                 mbIsLastMouseDownPosValid = false;
203                 mbActive = false;
204                 return true;
205             }
206 
207             //Draw all registered polygons.
208             void drawPolygons()
209             {
210                 for( const auto& rxPolygon : maPolygons )
211                 {
212                     rxPolygon->draw();
213                 }
214                 // screen update necessary to show painting
215                 mrScreenUpdater.notifyUpdate();
216             }
217 
218             //Retrieve all registered polygons.
219             const PolyPolygonVector& getPolygons() const
220             {
221                 return maPolygons;
222             }
223 
224             // MouseEventHandler methods
225             virtual bool handleMousePressed( const awt::MouseEvent& e ) override
226             {
227                 if( !mbActive )
228                     return false;
229 
230                 if (e.Buttons == awt::MouseButton::RIGHT)
231                 {
232                     mbIsLastPointValid = false;
233                     return false;
234                 }
235 
236                 if (e.Buttons != awt::MouseButton::LEFT)
237                     return false;
238 
239                 maLastMouseDownPos.setX( e.X );
240                 maLastMouseDownPos.setY( e.Y );
241                 mbIsLastMouseDownPosValid = true;
242 
243                 // eat mouse click (though we don't process it
244                 // _directly_, it enables the drag mode
245                 return true;
246             }
247 
248             virtual bool handleMouseReleased( const awt::MouseEvent& e ) override
249             {
250                 if( !mbActive )
251                     return false;
252 
253                 if (e.Buttons == awt::MouseButton::RIGHT)
254                 {
255                     mbIsLastPointValid = false;
256                     return false;
257                 }
258 
259                 if (e.Buttons != awt::MouseButton::LEFT)
260                     return false;
261 
262                 // check, whether up- and down press are on exactly
263                 // the same pixel. If that's the case, ignore the
264                 // click, and pass on the event to low-prio
265                 // handlers. This effectively permits effect
266                 // advancements via clicks also when user paint is
267                 // enabled.
268                 if( mbIsLastMouseDownPosValid &&
269                     ::basegfx::B2DPoint( e.X,
270                                          e.Y ) == maLastMouseDownPos )
271                 {
272                     mbIsLastMouseDownPosValid = false;
273                     return false;
274                 }
275 
276                 // invalidate, next downpress will have to start a new
277                 // polygon.
278                 mbIsLastPointValid = false;
279 
280                 // eat mouse click (though we don't process it
281                 // _directly_, it enables the drag mode
282                 return true;
283             }
284 
285             virtual bool handleMouseDragged( const awt::MouseEvent& e ) override
286             {
287                 if( !mbActive )
288                     return false;
289 
290                 if (e.Buttons == awt::MouseButton::RIGHT)
291                 {
292                     mbIsLastPointValid = false;
293                     return false;
294                 }
295 
296                 if(mbIsEraseModeActivated)
297                 {
298                     //define the last point as an object
299                     //we suppose that there's no way this point could be valid
300                     ::basegfx::B2DPolygon aPoly;
301 
302                     maLastPoint.setX( e.X-mnSize );
303                     maLastPoint.setY( e.Y-mnSize );
304 
305                     aPoly.append( maLastPoint );
306 
307                     maLastPoint.setX( e.X-mnSize );
308                     maLastPoint.setY( e.Y+mnSize );
309 
310                     aPoly.append( maLastPoint );
311                     maLastPoint.setX( e.X+mnSize );
312                     maLastPoint.setY( e.Y+mnSize );
313 
314                     aPoly.append( maLastPoint );
315                     maLastPoint.setX( e.X+mnSize );
316                     maLastPoint.setY( e.Y-mnSize );
317 
318                     aPoly.append( maLastPoint );
319                     maLastPoint.setX( e.X-mnSize );
320                     maLastPoint.setY( e.Y-mnSize );
321 
322                     aPoly.append( maLastPoint );
323 
324                     //now we have defined a Polygon that is closed
325 
326                     //The point is to redraw the LastPoint the way it was originally on the bitmap,
327                     //of the slide
328                     for (const auto& rxView : maViews)
329                     {
330 
331                         //get via SlideImpl instance the bitmap of the slide unmodified to redraw it
332                         SlideBitmapSharedPtr         pBitmap( mrSlide.getCurrentSlideBitmap( rxView ) );
333                         ::cppcanvas::CanvasSharedPtr pCanvas( rxView->getCanvas() );
334 
335                         ::basegfx::B2DHomMatrix     aViewTransform( rxView->getTransformation() );
336                         const ::basegfx::B2DPoint       aOutPosPixel( aViewTransform * ::basegfx::B2DPoint() );
337 
338                         // setup a canvas with device coordinate space, the slide
339                         // bitmap already has the correct dimension.
340                         ::cppcanvas::CanvasSharedPtr pDevicePixelCanvas( pCanvas->clone() );
341 
342                         pDevicePixelCanvas->setTransformation( ::basegfx::B2DHomMatrix() );
343 
344                         // render at given output position
345                         pBitmap->move( aOutPosPixel );
346 
347                         ::basegfx::B2DPolyPolygon aPolyPoly(aPoly);
348                         aViewTransform.translate(-aOutPosPixel.getX(), -aOutPosPixel.getY());
349                         aPolyPoly.transform(aViewTransform);
350                         // set clip so that we just redraw a part of the canvas
351                         pBitmap->clip(aPolyPoly);
352                         pBitmap->draw( pDevicePixelCanvas );
353 
354                         mrScreenUpdater.notifyUpdate(rxView,true);
355                     }
356 
357                 }
358                 else
359                 {
360                     if( !mbIsLastPointValid )
361                     {
362                         mbIsLastPointValid = true;
363                         maLastPoint.setX( e.X );
364                         maLastPoint.setY( e.Y );
365                     }
366                     else
367                     {
368                         ::basegfx::B2DPolygon aPoly;
369                         aPoly.append( maLastPoint );
370 
371                         maLastPoint.setX( e.X );
372                         maLastPoint.setY( e.Y );
373 
374                         aPoly.append( maLastPoint );
375 
376                         // paint to all views
377                         for (const auto& rxView : maViews)
378                         {
379                             ::cppcanvas::PolyPolygonSharedPtr pPolyPoly(
380                                 ::cppcanvas::BaseGfxFactory::createPolyPolygon( rxView->getCanvas(),
381                                                                                 aPoly ) );
382 
383                             if( pPolyPoly )
384                             {
385                                 pPolyPoly->setStrokeWidth(mnStrokeWidth);
386                                 pPolyPoly->setRGBALineColor( maStrokeColor.getIntegerColor() );
387                                 pPolyPoly->draw();
388                                 maPolygons.push_back(pPolyPoly);
389                             }
390                         }
391 
392                         // screen update necessary to show painting
393                         mrScreenUpdater.notifyUpdate();
394                     }
395                 }
396                 // mouse events captured
397                 return true;
398             }
399 
400             virtual bool handleMouseMoved( const awt::MouseEvent& /*e*/ ) override
401             {
402                 // not used here
403                 return false; // did not handle the event
404             }
405 
406         private:
407             ScreenUpdater&          mrScreenUpdater;
408             UnoViewVector           maViews;
409             PolyPolygonVector       maPolygons;
410             RGBColor                maStrokeColor;
411             double                  mnStrokeWidth;
412             basegfx::B2DPoint       maLastPoint;
413             basegfx::B2DPoint       maLastMouseDownPos;
414             bool                    mbIsLastPointValid;
415             bool                    mbIsLastMouseDownPosValid;
416             // added bool for erasing purpose :
417             bool                    mbIsEraseAllModeActivated;
418             bool                    mbIsEraseModeActivated;
419             Slide&                  mrSlide;
420             sal_Int32               mnSize;
421             bool                    mbActive;
422         };
423 
424         UserPaintOverlaySharedPtr UserPaintOverlay::create( const RGBColor&          rStrokeColor,
425                                                             double                   nStrokeWidth,
426                                                             const SlideShowContext&  rContext,
427                                                             PolyPolygonVector&&      rPolygons,
428                                                             bool                     bActive )
429         {
430             UserPaintOverlaySharedPtr pRet( new UserPaintOverlay( rStrokeColor,
431                                                                   nStrokeWidth,
432                                                                   rContext,
433                                                                   std::move(rPolygons),
434                                                                   bActive));
435 
436             return pRet;
437         }
438 
439         UserPaintOverlay::UserPaintOverlay( const RGBColor&          rStrokeColor,
440                                             double                   nStrokeWidth,
441                                             const SlideShowContext&  rContext,
442                                             PolyPolygonVector&&      rPolygons,
443                                             bool                     bActive ) :
444             mpHandler( std::make_shared<PaintOverlayHandler>( rStrokeColor,
445                                                 nStrokeWidth,
446                                                 rContext.mrScreenUpdater,
447                                                 rContext.mrViewContainer,
448                                                 //adding a link to Slide
449                                                 dynamic_cast<Slide&>(rContext.mrCursorManager),
450                                                 std::move(rPolygons), bActive )),
451             mrMultiplexer( rContext.mrEventMultiplexer )
452         {
453             mrMultiplexer.addClickHandler( mpHandler, 3.0 );
454             mrMultiplexer.addMouseMoveHandler( mpHandler, 3.0 );
455             mrMultiplexer.addViewHandler( mpHandler );
456             mrMultiplexer.addUserPaintHandler(mpHandler);
457         }
458 
459         PolyPolygonVector const & UserPaintOverlay::getPolygons() const
460         {
461             return mpHandler->getPolygons();
462         }
463 
464         void UserPaintOverlay::drawPolygons()
465         {
466             mpHandler->drawPolygons();
467         }
468 
469         UserPaintOverlay::~UserPaintOverlay()
470         {
471             try
472             {
473                 mrMultiplexer.removeMouseMoveHandler( mpHandler );
474                 mrMultiplexer.removeClickHandler( mpHandler );
475                 mrMultiplexer.removeViewHandler( mpHandler );
476                 mpHandler->dispose();
477             }
478             catch (const uno::Exception&)
479             {
480                 TOOLS_WARN_EXCEPTION("slideshow", "");
481             }
482         }
483 }
484 
485 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
486