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
