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 <svx/def3d.hxx>
22 #include <svx/dlgctl3d.hxx>
23 #include <svx/strings.hrc>
24 #include <svx/view3d.hxx>
25 #include <svx/fmmodel.hxx>
26 #include <svl/itempool.hxx>
27 #include <svx/fmpage.hxx>
28 #include <svx/sphere3d.hxx>
29 #include <svx/cube3d.hxx>
30 #include <svx/scene3d.hxx>
31 #include <vcl/svapp.hxx>
32 #include <vcl/builderfactory.hxx>
33 #include <svx/helperhittest3d.hxx>
34 #include <basegfx/polygon/b2dpolygontools.hxx>
35 #include <svx/polygn3d.hxx>
36 #include <svx/xfillit0.hxx>
37 #include <svx/xflclit.hxx>
38 #include <svx/xlineit0.hxx>
39 #include <svx/xlnclit.hxx>
40 #include <svx/xlnwtit.hxx>
41 #include <helpids.h>
42 #include <algorithm>
43 #include <svx/dialmgr.hxx>
44 #include <tools/helpers.hxx>
45 #include <vcl/settings.hxx>
46 
47 using namespace com::sun::star;
48 
Svx3DPreviewControl(vcl::Window * pParent,WinBits nStyle)49 Svx3DPreviewControl::Svx3DPreviewControl(vcl::Window* pParent, WinBits nStyle)
50 :   Control(pParent, nStyle),
51     mpFmPage(nullptr),
52     mpScene(nullptr),
53     mp3DObj(nullptr),
54     mnObjectType(SvxPreviewObjectType::SPHERE)
55 {
56     Construct();
57 
58     // do not paint background self, DrawingLayer paints this buffered and as page
59     SetControlBackground();
60     SetBackground();
61 }
62 
GetOptimalSize() const63 Size Svx3DPreviewControl::GetOptimalSize() const
64 {
65     return LogicToPixel(Size(80, 100), MapMode(MapUnit::MapAppFont));
66 }
67 
VCL_BUILDER_FACTORY(Svx3DPreviewControl)68 VCL_BUILDER_FACTORY(Svx3DPreviewControl)
69 
70 Svx3DPreviewControl::~Svx3DPreviewControl()
71 {
72     disposeOnce();
73 }
74 
dispose()75 void Svx3DPreviewControl::dispose()
76 {
77     mp3DView.reset();
78     mpModel.reset();
79     Control::dispose();
80 }
81 
Construct()82 void Svx3DPreviewControl::Construct()
83 {
84     // Do never mirror the preview window.  This explicitly includes right
85     // to left writing environments.
86     EnableRTL (false);
87     SetMapMode(MapMode(MapUnit::Map100thMM));
88 
89     // Model
90     mpModel.reset(new FmFormModel());
91     mpModel->GetItemPool().FreezeIdRanges();
92 
93     // Page
94     mpFmPage = new FmFormPage( *mpModel );
95     mpModel->InsertPage( mpFmPage, 0 );
96 
97     // 3D View
98     mp3DView.reset(new E3dView(*mpModel, this ));
99     mp3DView->SetBufferedOutputAllowed(true);
100     mp3DView->SetBufferedOverlayAllowed(true);
101 
102     // 3D Scene
103     mpScene = new E3dScene(*mpModel);
104 
105     // initially create object
106     SetObjectType(SvxPreviewObjectType::SPHERE);
107 
108     // camera and perspective
109     Camera3D rCamera  = mpScene->GetCamera();
110     const basegfx::B3DRange& rVolume = mpScene->GetBoundVolume();
111     double fW = rVolume.getWidth();
112     double fH = rVolume.getHeight();
113     double fCamZ = rVolume.getMaxZ() + ((fW + fH) / 2.0);
114 
115     rCamera.SetAutoAdjustProjection(false);
116     rCamera.SetViewWindow(- fW / 2, - fH / 2, fW, fH);
117     basegfx::B3DPoint aLookAt;
118     double fDefaultCamPosZ = mp3DView->GetDefaultCamPosZ();
119     basegfx::B3DPoint aCamPos(0.0, 0.0, fCamZ < fDefaultCamPosZ ? fDefaultCamPosZ : fCamZ);
120     rCamera.SetPosAndLookAt(aCamPos, aLookAt);
121     double fDefaultCamFocal = mp3DView->GetDefaultCamFocal();
122     rCamera.SetFocalLength(fDefaultCamFocal);
123 
124     mpScene->SetCamera( rCamera );
125     mpFmPage->InsertObject( mpScene );
126 
127     basegfx::B3DHomMatrix aRotation;
128     aRotation.rotate(DEG2RAD( 25 ), 0.0, 0.0);
129     aRotation.rotate(0.0, DEG2RAD( 40 ), 0.0);
130     mpScene->SetTransform(aRotation * mpScene->GetTransform());
131 
132     // invalidate SnapRects of objects
133     mpScene->SetRectsDirty();
134 
135     SfxItemSet aSet( mpModel->GetItemPool(),
136         svl::Items<XATTR_LINESTYLE, XATTR_LINESTYLE,
137         XATTR_FILL_FIRST, XATTR_FILLBITMAP>{} );
138     aSet.Put( XLineStyleItem( drawing::LineStyle_NONE ) );
139     aSet.Put( XFillStyleItem( drawing::FillStyle_SOLID ) );
140     aSet.Put( XFillColorItem( "", COL_WHITE ) );
141 
142     mpScene->SetMergedItemSet(aSet);
143 
144     // PageView
145     SdrPageView* pPageView = mp3DView->ShowSdrPage( mpFmPage );
146     mp3DView->hideMarkHandles();
147 
148     // mark scene
149     mp3DView->MarkObj( mpScene, pPageView );
150 }
151 
Resize()152 void Svx3DPreviewControl::Resize()
153 {
154     // size of page
155     Size aSize( GetSizePixel() );
156     aSize = PixelToLogic( aSize );
157     mpFmPage->SetSize( aSize );
158 
159     // set size
160     Size aObjSize( aSize.Width()*5/6, aSize.Height()*5/6 );
161     Point aObjPoint( (aSize.Width() - aObjSize.Width()) / 2,
162         (aSize.Height() - aObjSize.Height()) / 2);
163     tools::Rectangle aRect( aObjPoint, aObjSize);
164     mpScene->SetSnapRect( aRect );
165 }
166 
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle & rRect)167 void Svx3DPreviewControl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
168 {
169     mp3DView->CompleteRedraw(&rRenderContext, vcl::Region(rRect));
170 }
171 
MouseButtonDown(const MouseEvent & rMEvt)172 void Svx3DPreviewControl::MouseButtonDown(const MouseEvent& rMEvt)
173 {
174     Control::MouseButtonDown(rMEvt);
175 
176     if( rMEvt.IsShift() && rMEvt.IsMod1() )
177     {
178         if(SvxPreviewObjectType::SPHERE == GetObjectType())
179         {
180             SetObjectType(SvxPreviewObjectType::CUBE);
181         }
182         else
183         {
184             SetObjectType(SvxPreviewObjectType::SPHERE);
185         }
186     }
187 }
188 
SetObjectType(SvxPreviewObjectType nType)189 void Svx3DPreviewControl::SetObjectType(SvxPreviewObjectType nType)
190 {
191     if( mnObjectType != nType || !mp3DObj)
192     {
193         SfxItemSet aSet(mpModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END>{});
194         mnObjectType = nType;
195 
196         if( mp3DObj )
197         {
198             aSet.Put(mp3DObj->GetMergedItemSet());
199             mpScene->RemoveObject( mp3DObj->GetOrdNum() );
200             // always use SdrObject::Free(...) for SdrObjects (!)
201             SdrObject* pTemp(mp3DObj);
202             SdrObject::Free(pTemp);
203         }
204 
205         switch( nType )
206         {
207             case SvxPreviewObjectType::SPHERE:
208             {
209                 mp3DObj = new E3dSphereObj(
210                     *mpModel,
211                     mp3DView->Get3DDefaultAttributes(),
212                     basegfx::B3DPoint( 0, 0, 0 ),
213                     basegfx::B3DVector( 5000, 5000, 5000 ));
214             }
215             break;
216 
217             case SvxPreviewObjectType::CUBE:
218             {
219                 mp3DObj = new E3dCubeObj(
220                     *mpModel,
221                     mp3DView->Get3DDefaultAttributes(),
222                     basegfx::B3DPoint( -2500, -2500, -2500 ),
223                     basegfx::B3DVector( 5000, 5000, 5000 ));
224             }
225             break;
226         }
227 
228         if (mp3DObj)
229         {
230             mpScene->InsertObject( mp3DObj );
231             mp3DObj->SetMergedItemSet(aSet);
232         }
233 
234         Resize();
235     }
236 }
237 
Get3DAttributes() const238 SfxItemSet const & Svx3DPreviewControl::Get3DAttributes() const
239 {
240     return mp3DObj->GetMergedItemSet();
241 }
242 
Set3DAttributes(const SfxItemSet & rAttr)243 void Svx3DPreviewControl::Set3DAttributes( const SfxItemSet& rAttr )
244 {
245     mp3DObj->SetMergedItemSet(rAttr, true);
246     Resize();
247 }
248 
PreviewControl3D()249 PreviewControl3D::PreviewControl3D()
250     : mpFmPage(nullptr)
251     , mpScene(nullptr)
252     , mp3DObj(nullptr)
253     , mnObjectType(SvxPreviewObjectType::SPHERE)
254 {
255 }
256 
SetDrawingArea(weld::DrawingArea * pDrawingArea)257 void PreviewControl3D::SetDrawingArea(weld::DrawingArea* pDrawingArea)
258 {
259     Size aSize(pDrawingArea->get_ref_device().LogicToPixel(Size(80, 100), MapMode(MapUnit::MapAppFont)));
260     pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
261     CustomWidgetController::SetDrawingArea(pDrawingArea);
262     SetOutputSizePixel(aSize);
263 
264     Construct();
265 }
266 
~PreviewControl3D()267 PreviewControl3D::~PreviewControl3D()
268 {
269     mp3DView.reset();
270     mpModel.reset();
271 }
272 
Construct()273 void PreviewControl3D::Construct()
274 {
275     // Do never mirror the preview window.  This explicitly includes right
276     // to left writing environments.
277     EnableRTL (false);
278     OutputDevice& rDevice = GetDrawingArea()->get_ref_device();
279     rDevice.SetMapMode(MapMode(MapUnit::Map100thMM));
280 
281     // Model
282     mpModel.reset(new FmFormModel());
283     mpModel->GetItemPool().FreezeIdRanges();
284 
285     // Page
286     mpFmPage = new FmFormPage( *mpModel );
287     mpModel->InsertPage( mpFmPage, 0 );
288 
289     // 3D View
290     mp3DView.reset(new E3dView(*mpModel, &rDevice));
291     mp3DView->SetBufferedOutputAllowed(true);
292     mp3DView->SetBufferedOverlayAllowed(true);
293 
294     // 3D Scene
295     mpScene = new E3dScene(*mpModel);
296 
297     // initially create object
298     SetObjectType(SvxPreviewObjectType::SPHERE);
299 
300     // camera and perspective
301     Camera3D rCamera  = mpScene->GetCamera();
302     const basegfx::B3DRange& rVolume = mpScene->GetBoundVolume();
303     double fW = rVolume.getWidth();
304     double fH = rVolume.getHeight();
305     double fCamZ = rVolume.getMaxZ() + ((fW + fH) / 2.0);
306 
307     rCamera.SetAutoAdjustProjection(false);
308     rCamera.SetViewWindow(- fW / 2, - fH / 2, fW, fH);
309     basegfx::B3DPoint aLookAt;
310     double fDefaultCamPosZ = mp3DView->GetDefaultCamPosZ();
311     basegfx::B3DPoint aCamPos(0.0, 0.0, fCamZ < fDefaultCamPosZ ? fDefaultCamPosZ : fCamZ);
312     rCamera.SetPosAndLookAt(aCamPos, aLookAt);
313     double fDefaultCamFocal = mp3DView->GetDefaultCamFocal();
314     rCamera.SetFocalLength(fDefaultCamFocal);
315 
316     mpScene->SetCamera( rCamera );
317     mpFmPage->InsertObject( mpScene );
318 
319     basegfx::B3DHomMatrix aRotation;
320     aRotation.rotate(DEG2RAD( 25 ), 0.0, 0.0);
321     aRotation.rotate(0.0, DEG2RAD( 40 ), 0.0);
322     mpScene->SetTransform(aRotation * mpScene->GetTransform());
323 
324     // invalidate SnapRects of objects
325     mpScene->SetRectsDirty();
326 
327     SfxItemSet aSet( mpModel->GetItemPool(),
328         svl::Items<XATTR_LINESTYLE, XATTR_LINESTYLE,
329         XATTR_FILL_FIRST, XATTR_FILLBITMAP>{} );
330     aSet.Put( XLineStyleItem( drawing::LineStyle_NONE ) );
331     aSet.Put( XFillStyleItem( drawing::FillStyle_SOLID ) );
332     aSet.Put( XFillColorItem( "", COL_WHITE ) );
333 
334     mpScene->SetMergedItemSet(aSet);
335 
336     // PageView
337     SdrPageView* pPageView = mp3DView->ShowSdrPage( mpFmPage );
338     mp3DView->hideMarkHandles();
339 
340     // mark scene
341     mp3DView->MarkObj( mpScene, pPageView );
342 }
343 
Resize()344 void PreviewControl3D::Resize()
345 {
346     // size of page
347     Size aSize(GetOutputSizePixel());
348     aSize = GetDrawingArea()->get_ref_device().PixelToLogic(aSize);
349     mpFmPage->SetSize(aSize);
350 
351     // set size
352     Size aObjSize( aSize.Width()*5/6, aSize.Height()*5/6 );
353     Point aObjPoint( (aSize.Width() - aObjSize.Width()) / 2,
354         (aSize.Height() - aObjSize.Height()) / 2);
355     tools::Rectangle aRect( aObjPoint, aObjSize);
356     mpScene->SetSnapRect( aRect );
357 }
358 
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle & rRect)359 void PreviewControl3D::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
360 {
361     mp3DView->CompleteRedraw(&rRenderContext, vcl::Region(rRect));
362 }
363 
MouseButtonDown(const MouseEvent & rMEvt)364 bool PreviewControl3D::MouseButtonDown(const MouseEvent& rMEvt)
365 {
366     if (rMEvt.IsShift() && rMEvt.IsMod1())
367     {
368         if(SvxPreviewObjectType::SPHERE == GetObjectType())
369         {
370             SetObjectType(SvxPreviewObjectType::CUBE);
371         }
372         else
373         {
374             SetObjectType(SvxPreviewObjectType::SPHERE);
375         }
376     }
377     return false;
378 }
379 
SetObjectType(SvxPreviewObjectType nType)380 void PreviewControl3D::SetObjectType(SvxPreviewObjectType nType)
381 {
382     if( mnObjectType != nType || !mp3DObj)
383     {
384         SfxItemSet aSet(mpModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END>{});
385         mnObjectType = nType;
386 
387         if( mp3DObj )
388         {
389             aSet.Put(mp3DObj->GetMergedItemSet());
390             mpScene->RemoveObject( mp3DObj->GetOrdNum() );
391             // always use SdrObject::Free(...) for SdrObjects (!)
392             SdrObject* pTemp(mp3DObj);
393             SdrObject::Free(pTemp);
394         }
395 
396         switch( nType )
397         {
398             case SvxPreviewObjectType::SPHERE:
399             {
400                 mp3DObj = new E3dSphereObj(
401                     *mpModel,
402                     mp3DView->Get3DDefaultAttributes(),
403                     basegfx::B3DPoint( 0, 0, 0 ),
404                     basegfx::B3DVector( 5000, 5000, 5000 ));
405             }
406             break;
407 
408             case SvxPreviewObjectType::CUBE:
409             {
410                 mp3DObj = new E3dCubeObj(
411                     *mpModel,
412                     mp3DView->Get3DDefaultAttributes(),
413                     basegfx::B3DPoint( -2500, -2500, -2500 ),
414                     basegfx::B3DVector( 5000, 5000, 5000 ));
415             }
416             break;
417         }
418 
419         if (mp3DObj)
420         {
421             mpScene->InsertObject( mp3DObj );
422             mp3DObj->SetMergedItemSet(aSet);
423         }
424 
425         Invalidate();
426     }
427 }
428 
Get3DAttributes() const429 SfxItemSet const & PreviewControl3D::Get3DAttributes() const
430 {
431     return mp3DObj->GetMergedItemSet();
432 }
433 
Set3DAttributes(const SfxItemSet & rAttr)434 void PreviewControl3D::Set3DAttributes( const SfxItemSet& rAttr )
435 {
436     mp3DObj->SetMergedItemSet(rAttr, true);
437     Resize();
438 }
439 
440 #define RADIUS_LAMP_PREVIEW_SIZE    (4500.0)
441 #define RADIUS_LAMP_SMALL           (600.0)
442 #define RADIUS_LAMP_BIG             (1000.0)
443 #define NO_LIGHT_SELECTED           (0xffffffff)
444 #define MAX_NUMBER_LIGHTS              (8)
445 
446 static const sal_Int32 g_nInteractionStartDistance = 5 * 5 * 2;
447 
Svx3DLightControl(vcl::Window * pParent,WinBits nStyle)448 Svx3DLightControl::Svx3DLightControl(vcl::Window* pParent, WinBits nStyle)
449 :   Svx3DPreviewControl(pParent, nStyle),
450     maChangeCallback(),
451     maSelectionChangeCallback(),
452     maSelectedLight(NO_LIGHT_SELECTED),
453     mpExpansionObject(nullptr),
454     mpLampBottomObject(nullptr),
455     mpLampShaftObject(nullptr),
456     maLightObjects(MAX_NUMBER_LIGHTS, nullptr),
457     mfRotateX(-20.0),
458     mfRotateY(45.0),
459     mfRotateZ(0.0),
460     maActionStartPoint(),
461     mfSaveActionStartHor(0.0),
462     mfSaveActionStartVer(0.0),
463     mfSaveActionStartRotZ(0.0),
464     mbMouseMoved(false),
465     mbGeometrySelected(false)
466 {
467     Construct2();
468 }
469 
Construct2()470 void Svx3DLightControl::Construct2()
471 {
472     {
473         // hide all page stuff, use control background (normally gray)
474         const Color aDialogColor(Application::GetSettings().GetStyleSettings().GetDialogColor());
475         mp3DView->SetPageVisible(false);
476         mp3DView->SetApplicationBackgroundColor(aDialogColor);
477         mp3DView->SetApplicationDocumentColor(aDialogColor);
478     }
479 
480     {
481         // create invisible expansion object
482         const double fMaxExpansion(RADIUS_LAMP_BIG + RADIUS_LAMP_PREVIEW_SIZE);
483         mpExpansionObject = new E3dCubeObj(
484             *mpModel,
485             mp3DView->Get3DDefaultAttributes(),
486             basegfx::B3DPoint(-fMaxExpansion, -fMaxExpansion, -fMaxExpansion),
487             basegfx::B3DVector(2.0 * fMaxExpansion, 2.0 * fMaxExpansion, 2.0 * fMaxExpansion));
488         mpScene->InsertObject( mpExpansionObject );
489         SfxItemSet aSet(mpModel->GetItemPool());
490         aSet.Put( XLineStyleItem( drawing::LineStyle_NONE ) );
491         aSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) );
492         mpExpansionObject->SetMergedItemSet(aSet);
493     }
494 
495     {
496         // create lamp control object (Yellow lined object)
497         // base circle
498         const basegfx::B2DPolygon a2DCircle(basegfx::utils::createPolygonFromCircle(basegfx::B2DPoint(0.0, 0.0), RADIUS_LAMP_PREVIEW_SIZE));
499         basegfx::B3DPolygon a3DCircle(basegfx::utils::createB3DPolygonFromB2DPolygon(a2DCircle));
500         basegfx::B3DHomMatrix aTransform;
501 
502         aTransform.rotate(F_PI2, 0.0, 0.0);
503         aTransform.translate(0.0, -RADIUS_LAMP_PREVIEW_SIZE, 0.0);
504         a3DCircle.transform(aTransform);
505 
506         // create object for it
507         mpLampBottomObject = new E3dPolygonObj(
508             *mpModel,
509             basegfx::B3DPolyPolygon(a3DCircle));
510         mpScene->InsertObject( mpLampBottomObject );
511 
512         // half circle with stand
513         basegfx::B2DPolygon a2DHalfCircle;
514         a2DHalfCircle.append(basegfx::B2DPoint(RADIUS_LAMP_PREVIEW_SIZE, 0.0));
515         a2DHalfCircle.append(basegfx::B2DPoint(RADIUS_LAMP_PREVIEW_SIZE, -RADIUS_LAMP_PREVIEW_SIZE));
516         a2DHalfCircle.append(basegfx::utils::createPolygonFromEllipseSegment(
517             basegfx::B2DPoint(0.0, 0.0), RADIUS_LAMP_PREVIEW_SIZE, RADIUS_LAMP_PREVIEW_SIZE, F_2PI - F_PI2, F_PI2));
518         basegfx::B3DPolygon a3DHalfCircle(basegfx::utils::createB3DPolygonFromB2DPolygon(a2DHalfCircle));
519 
520         // create object for it
521         mpLampShaftObject = new E3dPolygonObj(
522             *mpModel,
523             basegfx::B3DPolyPolygon(a3DHalfCircle));
524         mpScene->InsertObject( mpLampShaftObject );
525 
526         // initially invisible
527         SfxItemSet aSet(mpModel->GetItemPool());
528         aSet.Put( XLineStyleItem( drawing::LineStyle_NONE ) );
529         aSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) );
530 
531         mpLampBottomObject->SetMergedItemSet(aSet);
532         mpLampShaftObject->SetMergedItemSet(aSet);
533     }
534 
535     {
536         // change camera settings
537         Camera3D rCamera  = mpScene->GetCamera();
538         const basegfx::B3DRange& rVolume = mpScene->GetBoundVolume();
539         double fW = rVolume.getWidth();
540         double fH = rVolume.getHeight();
541         double fCamZ = rVolume.getMaxZ() + ((fW + fH) / 2.0);
542 
543         rCamera.SetAutoAdjustProjection(false);
544         rCamera.SetViewWindow(- fW / 2, - fH / 2, fW, fH);
545         basegfx::B3DPoint aLookAt;
546         double fDefaultCamPosZ = mp3DView->GetDefaultCamPosZ();
547         basegfx::B3DPoint aCamPos(0.0, 0.0, fCamZ < fDefaultCamPosZ ? fDefaultCamPosZ : fCamZ);
548         rCamera.SetPosAndLookAt(aCamPos, aLookAt);
549         double fDefaultCamFocal = mp3DView->GetDefaultCamFocal();
550         rCamera.SetFocalLength(fDefaultCamFocal);
551 
552         mpScene->SetCamera( rCamera );
553 
554         basegfx::B3DHomMatrix aNeutral;
555         mpScene->SetTransform(aNeutral);
556     }
557 
558     // invalidate SnapRects of objects
559     mpScene->SetRectsDirty();
560 }
561 
ConstructLightObjects()562 void Svx3DLightControl::ConstructLightObjects()
563 {
564     for(sal_uInt32 a(0); a < MAX_NUMBER_LIGHTS; a++)
565     {
566         // get rid of possible existing light object
567         if(maLightObjects[a])
568         {
569             mpScene->RemoveObject(maLightObjects[a]->GetOrdNum());
570             // always use SdrObject::Free(...) for SdrObjects (!)
571             SdrObject* pTemp(maLightObjects[a]);
572             SdrObject::Free(pTemp);
573             maLightObjects[a] = nullptr;
574         }
575 
576         if(GetLightOnOff(a))
577         {
578             const bool bIsSelectedLight(a == maSelectedLight);
579             basegfx::B3DVector aDirection(GetLightDirection(a));
580             aDirection.normalize();
581             aDirection *= RADIUS_LAMP_PREVIEW_SIZE;
582 
583             const double fLampSize(bIsSelectedLight ? RADIUS_LAMP_BIG : RADIUS_LAMP_SMALL);
584             E3dObject* pNewLight = new E3dSphereObj(
585                 *mpModel,
586                 mp3DView->Get3DDefaultAttributes(),
587                 basegfx::B3DPoint( 0, 0, 0 ),
588                 basegfx::B3DVector( fLampSize, fLampSize, fLampSize));
589             mpScene->InsertObject(pNewLight);
590 
591             basegfx::B3DHomMatrix aTransform;
592             aTransform.translate(aDirection.getX(), aDirection.getY(), aDirection.getZ());
593             pNewLight->SetTransform(aTransform);
594 
595             SfxItemSet aSet(mpModel->GetItemPool());
596             aSet.Put( XLineStyleItem( drawing::LineStyle_NONE ) );
597             aSet.Put( XFillStyleItem( drawing::FillStyle_SOLID ) );
598             aSet.Put( XFillColorItem(OUString(), GetLightColor(a)));
599             pNewLight->SetMergedItemSet(aSet);
600 
601             maLightObjects[a] = pNewLight;
602         }
603     }
604 }
605 
AdaptToSelectedLight()606 void Svx3DLightControl::AdaptToSelectedLight()
607 {
608     if(NO_LIGHT_SELECTED == maSelectedLight)
609     {
610         // make mpLampBottomObject/mpLampShaftObject invisible
611         SfxItemSet aSet(mpModel->GetItemPool());
612         aSet.Put( XLineStyleItem( drawing::LineStyle_NONE ) );
613         aSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) );
614         mpLampBottomObject->SetMergedItemSet(aSet);
615         mpLampShaftObject->SetMergedItemSet(aSet);
616     }
617     else
618     {
619         basegfx::B3DVector aDirection(GetLightDirection(maSelectedLight));
620         aDirection.normalize();
621 
622         // make mpLampBottomObject/mpLampShaftObject visible (yellow hairline)
623         SfxItemSet aSet(mpModel->GetItemPool());
624         aSet.Put( XLineStyleItem( drawing::LineStyle_SOLID ) );
625         aSet.Put( XLineColorItem(OUString(), COL_YELLOW));
626         aSet.Put( XLineWidthItem(0));
627         aSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) );
628         mpLampBottomObject->SetMergedItemSet(aSet);
629         mpLampShaftObject->SetMergedItemSet(aSet);
630 
631         // adapt transformation of mpLampShaftObject
632         basegfx::B3DHomMatrix aTransform;
633         double fRotateY(0.0);
634 
635         if(!basegfx::fTools::equalZero(aDirection.getZ()) || !basegfx::fTools::equalZero(aDirection.getX()))
636         {
637             fRotateY = atan2(-aDirection.getZ(), aDirection.getX());
638         }
639 
640         aTransform.rotate(0.0, fRotateY, 0.0);
641         mpLampShaftObject->SetTransform(aTransform);
642 
643         // adapt transformation of selected light
644         E3dObject* pSelectedLight = maLightObjects[sal_Int32(maSelectedLight)];
645 
646         if(pSelectedLight)
647         {
648             aTransform.identity();
649             aTransform.translate(
650                 aDirection.getX() * RADIUS_LAMP_PREVIEW_SIZE,
651                 aDirection.getY() * RADIUS_LAMP_PREVIEW_SIZE,
652                 aDirection.getZ() * RADIUS_LAMP_PREVIEW_SIZE);
653             pSelectedLight->SetTransform(aTransform);
654         }
655     }
656 }
657 
TrySelection(Point aPosPixel)658 void Svx3DLightControl::TrySelection(Point aPosPixel)
659 {
660     if(mpScene)
661     {
662         const Point aPosLogic(PixelToLogic(aPosPixel));
663         const basegfx::B2DPoint aPoint(aPosLogic.X(), aPosLogic.Y());
664         std::vector< const E3dCompoundObject* > aResult;
665         getAllHit3DObjectsSortedFrontToBack(aPoint, *mpScene, aResult);
666 
667         if(!aResult.empty())
668         {
669             // exclude expansion object which will be part of
670             // the hits. It's invisible, but for HitTest, it's included
671             const E3dCompoundObject* pResult = nullptr;
672 
673             for(auto const & b: aResult)
674             {
675                 if(b && b != mpExpansionObject)
676                 {
677                     pResult = b;
678                     break;
679                 }
680             }
681 
682             if(pResult == mp3DObj)
683             {
684                 if(!mbGeometrySelected)
685                 {
686                     mbGeometrySelected = true;
687                     maSelectedLight = NO_LIGHT_SELECTED;
688                     ConstructLightObjects();
689                     AdaptToSelectedLight();
690                     Invalidate();
691 
692                     if(maSelectionChangeCallback.IsSet())
693                     {
694                         maSelectionChangeCallback.Call(this);
695                     }
696                 }
697             }
698             else
699             {
700                 sal_uInt32 aNewSelectedLight(NO_LIGHT_SELECTED);
701 
702                 for(sal_uInt32 a(0); a < MAX_NUMBER_LIGHTS; a++)
703                 {
704                     if(maLightObjects[a] && maLightObjects[a] == pResult)
705                     {
706                         aNewSelectedLight = a;
707                     }
708                 }
709 
710                 if(aNewSelectedLight != maSelectedLight)
711                 {
712                     SelectLight(aNewSelectedLight);
713 
714                     if(maSelectionChangeCallback.IsSet())
715                     {
716                         maSelectionChangeCallback.Call(this);
717                     }
718                 }
719             }
720         }
721     }
722 }
723 
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle & rRect)724 void Svx3DLightControl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
725 {
726     Svx3DPreviewControl::Paint(rRenderContext, rRect);
727 }
728 
MouseButtonDown(const MouseEvent & rMEvt)729 void Svx3DLightControl::MouseButtonDown( const MouseEvent& rMEvt )
730 {
731     bool bCallParent(true);
732 
733     // switch state
734     if(rMEvt.IsLeft())
735     {
736         if(IsSelectionValid() || mbGeometrySelected)
737         {
738             mbMouseMoved = false;
739             bCallParent = false;
740             maActionStartPoint = rMEvt.GetPosPixel();
741             StartTracking();
742         }
743         else
744         {
745             // Single click without moving much trying to do a selection
746             TrySelection(rMEvt.GetPosPixel());
747             bCallParent = false;
748         }
749     }
750 
751     // call parent
752     if(bCallParent)
753     {
754         Svx3DPreviewControl::MouseButtonDown(rMEvt);
755     }
756 }
757 
Tracking(const TrackingEvent & rTEvt)758 void Svx3DLightControl::Tracking( const TrackingEvent& rTEvt )
759 {
760     if(rTEvt.IsTrackingEnded())
761     {
762         if(rTEvt.IsTrackingCanceled())
763         {
764             if(mbMouseMoved)
765             {
766                 // interrupt tracking
767                 mbMouseMoved = false;
768 
769                 if(mbGeometrySelected)
770                 {
771                     SetRotation(mfSaveActionStartVer, mfSaveActionStartHor, mfSaveActionStartRotZ);
772                 }
773                 else
774                 {
775                     SetPosition(mfSaveActionStartHor, mfSaveActionStartVer);
776                 }
777 
778                 if(maChangeCallback.IsSet())
779                 {
780                     maChangeCallback.Call(this);
781                 }
782             }
783         }
784         else
785         {
786             const MouseEvent& rMEvt = rTEvt.GetMouseEvent();
787 
788             if(mbMouseMoved)
789             {
790                 // was change interactively
791             }
792             else
793             {
794                 // simple click without much movement, try selection
795                 TrySelection(rMEvt.GetPosPixel());
796             }
797         }
798     }
799     else
800     {
801         const MouseEvent& rMEvt = rTEvt.GetMouseEvent();
802         Point aDeltaPos = rMEvt.GetPosPixel() - maActionStartPoint;
803 
804         if(!mbMouseMoved)
805         {
806             if(sal_Int32(aDeltaPos.X() * aDeltaPos.X() + aDeltaPos.Y() * aDeltaPos.Y()) > g_nInteractionStartDistance)
807             {
808                 if(mbGeometrySelected)
809                 {
810                     GetRotation(mfSaveActionStartVer, mfSaveActionStartHor, mfSaveActionStartRotZ);
811                 }
812                 else
813                 {
814                     // interaction start, save values
815                     GetPosition(mfSaveActionStartHor, mfSaveActionStartVer);
816                 }
817 
818                 mbMouseMoved = true;
819             }
820         }
821 
822         if(mbMouseMoved)
823         {
824             if(mbGeometrySelected)
825             {
826                 double fNewRotX = mfSaveActionStartVer - basegfx::deg2rad(aDeltaPos.Y());
827                 double fNewRotY = mfSaveActionStartHor + basegfx::deg2rad(aDeltaPos.X());
828 
829                 // cut horizontal
830                 while(fNewRotY < 0.0)
831                 {
832                     fNewRotY += F_2PI;
833                 }
834 
835                 while(fNewRotY >= F_2PI)
836                 {
837                     fNewRotY -= F_2PI;
838                 }
839 
840                 // cut vertical
841                 if(fNewRotX < -F_PI2)
842                 {
843                     fNewRotX = -F_PI2;
844                 }
845 
846                 if(fNewRotX > F_PI2)
847                 {
848                     fNewRotX = F_PI2;
849                 }
850 
851                 SetRotation(fNewRotX, fNewRotY, mfSaveActionStartRotZ);
852 
853                 if(maChangeCallback.IsSet())
854                 {
855                     maChangeCallback.Call(this);
856                 }
857             }
858             else
859             {
860                 // interaction in progress
861                 double fNewPosHor = mfSaveActionStartHor + static_cast<double>(aDeltaPos.X());
862                 double fNewPosVer = mfSaveActionStartVer - static_cast<double>(aDeltaPos.Y());
863 
864                 // cut horizontal
865                 fNewPosHor = NormAngle360(fNewPosHor);
866 
867                 // cut vertical
868                 if(fNewPosVer < -90.0)
869                 {
870                     fNewPosVer = -90.0;
871                 }
872 
873                 if(fNewPosVer > 90.0)
874                 {
875                     fNewPosVer = 90.0;
876                 }
877 
878                 SetPosition(fNewPosHor, fNewPosVer);
879 
880                 if(maChangeCallback.IsSet())
881                 {
882                     maChangeCallback.Call(this);
883                 }
884             }
885         }
886     }
887 }
888 
Resize()889 void Svx3DLightControl::Resize()
890 {
891     // set size of page
892     const Size aSize(PixelToLogic(GetSizePixel()));
893     mpFmPage->SetSize(aSize);
894 
895     // set position and size of scene
896     mpScene->SetSnapRect(tools::Rectangle(Point(0, 0), aSize));
897 }
898 
SetObjectType(SvxPreviewObjectType nType)899 void Svx3DLightControl::SetObjectType(SvxPreviewObjectType nType)
900 {
901     // call parent
902     Svx3DPreviewControl::SetObjectType(nType);
903 
904     // apply object rotation
905     if(mp3DObj)
906     {
907         basegfx::B3DHomMatrix aObjectRotation;
908         aObjectRotation.rotate(mfRotateX, mfRotateY, mfRotateZ);
909         mp3DObj->SetTransform(aObjectRotation);
910     }
911 }
912 
IsSelectionValid()913 bool Svx3DLightControl::IsSelectionValid()
914 {
915     return (NO_LIGHT_SELECTED != maSelectedLight) && GetLightOnOff(maSelectedLight);
916 }
917 
GetPosition(double & rHor,double & rVer)918 void Svx3DLightControl::GetPosition(double& rHor, double& rVer)
919 {
920     if(IsSelectionValid())
921     {
922         basegfx::B3DVector aDirection(GetLightDirection(maSelectedLight));
923         aDirection.normalize();
924         rHor = basegfx::rad2deg(atan2(-aDirection.getX(), -aDirection.getZ()) + F_PI); // 0..360.0
925         rVer = basegfx::rad2deg(atan2(aDirection.getY(), aDirection.getXZLength())); // -90.0..90.0
926     }
927     if(IsGeometrySelected())
928     {
929         rHor = basegfx::rad2deg(mfRotateY); // 0..360.0
930         rVer = basegfx::rad2deg(mfRotateX); // -90.0..90.0
931     }
932 }
933 
SetPosition(double fHor,double fVer)934 void Svx3DLightControl::SetPosition(double fHor, double fVer)
935 {
936     if(IsSelectionValid())
937     {
938         // set selected light's direction
939         fHor = basegfx::deg2rad(fHor) - F_PI; // -PI..PI
940         fVer = basegfx::deg2rad(fVer); // -PI2..PI2
941         basegfx::B3DVector aDirection(cos(fVer) * -sin(fHor), sin(fVer), cos(fVer) * -cos(fHor));
942         aDirection.normalize();
943 
944         if(!aDirection.equal(GetLightDirection(maSelectedLight)))
945         {
946             // set changed light direction at SdrScene
947             SfxItemSet aSet(mpModel->GetItemPool());
948 
949             switch(maSelectedLight)
950             {
951                 case 0: aSet.Put(makeSvx3DLightDirection1Item(aDirection)); break;
952                 case 1: aSet.Put(makeSvx3DLightDirection2Item(aDirection)); break;
953                 case 2: aSet.Put(makeSvx3DLightDirection3Item(aDirection)); break;
954                 case 3: aSet.Put(makeSvx3DLightDirection4Item(aDirection)); break;
955                 case 4: aSet.Put(makeSvx3DLightDirection5Item(aDirection)); break;
956                 case 5: aSet.Put(makeSvx3DLightDirection6Item(aDirection)); break;
957                 case 6: aSet.Put(makeSvx3DLightDirection7Item(aDirection)); break;
958                 default:
959                 case 7: aSet.Put(makeSvx3DLightDirection8Item(aDirection)); break;
960             }
961 
962             mpScene->SetMergedItemSet(aSet);
963 
964             // correct 3D light's and LampFrame's geometries
965             AdaptToSelectedLight();
966             Invalidate();
967         }
968     }
969     if(IsGeometrySelected())
970     {
971         if(mfRotateX != fVer || mfRotateY != fHor)
972         {
973             mfRotateX = basegfx::deg2rad(fVer);
974             mfRotateY = basegfx::deg2rad(fHor);
975 
976             if(mp3DObj)
977             {
978                 basegfx::B3DHomMatrix aObjectRotation;
979                 aObjectRotation.rotate(mfRotateX, mfRotateY, mfRotateZ);
980                 mp3DObj->SetTransform(aObjectRotation);
981 
982                 Invalidate();
983             }
984         }
985     }
986 }
987 
SetRotation(double fRotX,double fRotY,double fRotZ)988 void Svx3DLightControl::SetRotation(double fRotX, double fRotY, double fRotZ)
989 {
990     if(IsGeometrySelected())
991     {
992         if(fRotX != mfRotateX || fRotY != mfRotateY || fRotZ != mfRotateZ)
993         {
994             mfRotateX = fRotX;
995             mfRotateY = fRotY;
996             mfRotateZ = fRotZ;
997 
998             if(mp3DObj)
999             {
1000                 basegfx::B3DHomMatrix aObjectRotation;
1001                 aObjectRotation.rotate(mfRotateX, mfRotateY, mfRotateZ);
1002                 mp3DObj->SetTransform(aObjectRotation);
1003 
1004                 Invalidate();
1005             }
1006         }
1007     }
1008 }
1009 
GetRotation(double & rRotX,double & rRotY,double & rRotZ)1010 void Svx3DLightControl::GetRotation(double& rRotX, double& rRotY, double& rRotZ)
1011 {
1012     rRotX = mfRotateX;
1013     rRotY = mfRotateY;
1014     rRotZ = mfRotateZ;
1015 }
1016 
Set3DAttributes(const SfxItemSet & rAttr)1017 void Svx3DLightControl::Set3DAttributes( const SfxItemSet& rAttr )
1018 {
1019     // call parent
1020     Svx3DPreviewControl::Set3DAttributes(rAttr);
1021 
1022     if(maSelectedLight != NO_LIGHT_SELECTED && !GetLightOnOff(maSelectedLight))
1023     {
1024         // selected light is no more active, select new one
1025         maSelectedLight = NO_LIGHT_SELECTED;
1026     }
1027 
1028     // local updates
1029     ConstructLightObjects();
1030     AdaptToSelectedLight();
1031     Invalidate();
1032 }
1033 
SelectLight(sal_uInt32 nLightNumber)1034 void Svx3DLightControl::SelectLight(sal_uInt32 nLightNumber)
1035 {
1036     if(nLightNumber > 7)
1037     {
1038         nLightNumber = NO_LIGHT_SELECTED;
1039     }
1040 
1041     if(NO_LIGHT_SELECTED != nLightNumber)
1042     {
1043         if(!GetLightOnOff(nLightNumber))
1044         {
1045             nLightNumber = NO_LIGHT_SELECTED;
1046         }
1047     }
1048 
1049     if(nLightNumber != maSelectedLight)
1050     {
1051         maSelectedLight = nLightNumber;
1052         mbGeometrySelected = false;
1053         ConstructLightObjects();
1054         AdaptToSelectedLight();
1055         Invalidate();
1056     }
1057 }
1058 
GetLightOnOff(sal_uInt32 nNum) const1059 bool Svx3DLightControl::GetLightOnOff(sal_uInt32 nNum) const
1060 {
1061     if(nNum <= 7)
1062     {
1063         const SfxItemSet aLightItemSet(Get3DAttributes());
1064 
1065         switch(nNum)
1066         {
1067             case 0 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_1).GetValue();
1068             case 1 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_2).GetValue();
1069             case 2 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_3).GetValue();
1070             case 3 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_4).GetValue();
1071             case 4 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_5).GetValue();
1072             case 5 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_6).GetValue();
1073             case 6 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_7).GetValue();
1074             case 7 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_8).GetValue();
1075         }
1076     }
1077 
1078     return false;
1079 }
1080 
GetLightColor(sal_uInt32 nNum) const1081 Color Svx3DLightControl::GetLightColor(sal_uInt32 nNum) const
1082 {
1083     if(nNum <= 7)
1084     {
1085         const SfxItemSet aLightItemSet(Get3DAttributes());
1086 
1087         switch(nNum)
1088         {
1089             case 0 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_1).GetValue();
1090             case 1 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_2).GetValue();
1091             case 2 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_3).GetValue();
1092             case 3 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_4).GetValue();
1093             case 4 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_5).GetValue();
1094             case 5 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_6).GetValue();
1095             case 6 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_7).GetValue();
1096             case 7 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_8).GetValue();
1097         }
1098     }
1099 
1100     return COL_BLACK;
1101 }
1102 
GetLightDirection(sal_uInt32 nNum) const1103 basegfx::B3DVector Svx3DLightControl::GetLightDirection(sal_uInt32 nNum) const
1104 {
1105     if(nNum <= 7)
1106     {
1107         const SfxItemSet aLightItemSet(Get3DAttributes());
1108 
1109         switch(nNum)
1110         {
1111             case 0 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_1).GetValue();
1112             case 1 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_2).GetValue();
1113             case 2 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_3).GetValue();
1114             case 3 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_4).GetValue();
1115             case 4 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_5).GetValue();
1116             case 5 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_6).GetValue();
1117             case 6 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_7).GetValue();
1118             case 7 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_8).GetValue();
1119         }
1120     }
1121 
1122     return basegfx::B3DVector();
1123 }
1124 
LightControl3D()1125 LightControl3D::LightControl3D()
1126 :   maChangeCallback(),
1127     maSelectionChangeCallback(),
1128     maSelectedLight(NO_LIGHT_SELECTED),
1129     mpExpansionObject(nullptr),
1130     mpLampBottomObject(nullptr),
1131     mpLampShaftObject(nullptr),
1132     maLightObjects(MAX_NUMBER_LIGHTS, nullptr),
1133     mfRotateX(-20.0),
1134     mfRotateY(45.0),
1135     mfRotateZ(0.0),
1136     maActionStartPoint(),
1137     mfSaveActionStartHor(0.0),
1138     mfSaveActionStartVer(0.0),
1139     mfSaveActionStartRotZ(0.0),
1140     mbMouseMoved(false),
1141     mbMouseCaptured(false),
1142     mbGeometrySelected(false)
1143 {
1144 }
1145 
SetDrawingArea(weld::DrawingArea * pDrawingArea)1146 void LightControl3D::SetDrawingArea(weld::DrawingArea* pDrawingArea)
1147 {
1148     PreviewControl3D::SetDrawingArea(pDrawingArea);
1149     Construct2();
1150 }
1151 
Construct2()1152 void LightControl3D::Construct2()
1153 {
1154     {
1155         // hide all page stuff, use control background (normally gray)
1156         const Color aDialogColor(Application::GetSettings().GetStyleSettings().GetDialogColor());
1157         mp3DView->SetPageVisible(false);
1158         mp3DView->SetApplicationBackgroundColor(aDialogColor);
1159         mp3DView->SetApplicationDocumentColor(aDialogColor);
1160     }
1161 
1162     {
1163         // create invisible expansion object
1164         const double fMaxExpansion(RADIUS_LAMP_BIG + RADIUS_LAMP_PREVIEW_SIZE);
1165         mpExpansionObject = new E3dCubeObj(
1166             *mpModel,
1167             mp3DView->Get3DDefaultAttributes(),
1168             basegfx::B3DPoint(-fMaxExpansion, -fMaxExpansion, -fMaxExpansion),
1169             basegfx::B3DVector(2.0 * fMaxExpansion, 2.0 * fMaxExpansion, 2.0 * fMaxExpansion));
1170         mpScene->InsertObject( mpExpansionObject );
1171         SfxItemSet aSet(mpModel->GetItemPool());
1172         aSet.Put( XLineStyleItem( drawing::LineStyle_NONE ) );
1173         aSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) );
1174         mpExpansionObject->SetMergedItemSet(aSet);
1175     }
1176 
1177     {
1178         // create lamp control object (Yellow lined object)
1179         // base circle
1180         const basegfx::B2DPolygon a2DCircle(basegfx::utils::createPolygonFromCircle(basegfx::B2DPoint(0.0, 0.0), RADIUS_LAMP_PREVIEW_SIZE));
1181         basegfx::B3DPolygon a3DCircle(basegfx::utils::createB3DPolygonFromB2DPolygon(a2DCircle));
1182         basegfx::B3DHomMatrix aTransform;
1183 
1184         aTransform.rotate(F_PI2, 0.0, 0.0);
1185         aTransform.translate(0.0, -RADIUS_LAMP_PREVIEW_SIZE, 0.0);
1186         a3DCircle.transform(aTransform);
1187 
1188         // create object for it
1189         mpLampBottomObject = new E3dPolygonObj(
1190             *mpModel,
1191             basegfx::B3DPolyPolygon(a3DCircle));
1192         mpScene->InsertObject( mpLampBottomObject );
1193 
1194         // half circle with stand
1195         basegfx::B2DPolygon a2DHalfCircle;
1196         a2DHalfCircle.append(basegfx::B2DPoint(RADIUS_LAMP_PREVIEW_SIZE, 0.0));
1197         a2DHalfCircle.append(basegfx::B2DPoint(RADIUS_LAMP_PREVIEW_SIZE, -RADIUS_LAMP_PREVIEW_SIZE));
1198         a2DHalfCircle.append(basegfx::utils::createPolygonFromEllipseSegment(
1199             basegfx::B2DPoint(0.0, 0.0), RADIUS_LAMP_PREVIEW_SIZE, RADIUS_LAMP_PREVIEW_SIZE, F_2PI - F_PI2, F_PI2));
1200         basegfx::B3DPolygon a3DHalfCircle(basegfx::utils::createB3DPolygonFromB2DPolygon(a2DHalfCircle));
1201 
1202         // create object for it
1203         mpLampShaftObject = new E3dPolygonObj(
1204             *mpModel,
1205             basegfx::B3DPolyPolygon(a3DHalfCircle));
1206         mpScene->InsertObject( mpLampShaftObject );
1207 
1208         // initially invisible
1209         SfxItemSet aSet(mpModel->GetItemPool());
1210         aSet.Put( XLineStyleItem( drawing::LineStyle_NONE ) );
1211         aSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) );
1212 
1213         mpLampBottomObject->SetMergedItemSet(aSet);
1214         mpLampShaftObject->SetMergedItemSet(aSet);
1215     }
1216 
1217     {
1218         // change camera settings
1219         Camera3D rCamera  = mpScene->GetCamera();
1220         const basegfx::B3DRange& rVolume = mpScene->GetBoundVolume();
1221         double fW = rVolume.getWidth();
1222         double fH = rVolume.getHeight();
1223         double fCamZ = rVolume.getMaxZ() + ((fW + fH) / 2.0);
1224 
1225         rCamera.SetAutoAdjustProjection(false);
1226         rCamera.SetViewWindow(- fW / 2, - fH / 2, fW, fH);
1227         basegfx::B3DPoint aLookAt;
1228         double fDefaultCamPosZ = mp3DView->GetDefaultCamPosZ();
1229         basegfx::B3DPoint aCamPos(0.0, 0.0, fCamZ < fDefaultCamPosZ ? fDefaultCamPosZ : fCamZ);
1230         rCamera.SetPosAndLookAt(aCamPos, aLookAt);
1231         double fDefaultCamFocal = mp3DView->GetDefaultCamFocal();
1232         rCamera.SetFocalLength(fDefaultCamFocal);
1233 
1234         mpScene->SetCamera( rCamera );
1235 
1236         basegfx::B3DHomMatrix aNeutral;
1237         mpScene->SetTransform(aNeutral);
1238     }
1239 
1240     // invalidate SnapRects of objects
1241     mpScene->SetRectsDirty();
1242 }
1243 
ConstructLightObjects()1244 void LightControl3D::ConstructLightObjects()
1245 {
1246     for(sal_uInt32 a(0); a < MAX_NUMBER_LIGHTS; a++)
1247     {
1248         // get rid of possible existing light object
1249         if(maLightObjects[a])
1250         {
1251             mpScene->RemoveObject(maLightObjects[a]->GetOrdNum());
1252             // always use SdrObject::Free(...) for SdrObjects (!)
1253             SdrObject* pTemp(maLightObjects[a]);
1254             SdrObject::Free(pTemp);
1255             maLightObjects[a] = nullptr;
1256         }
1257 
1258         if(GetLightOnOff(a))
1259         {
1260             const bool bIsSelectedLight(a == maSelectedLight);
1261             basegfx::B3DVector aDirection(GetLightDirection(a));
1262             aDirection.normalize();
1263             aDirection *= RADIUS_LAMP_PREVIEW_SIZE;
1264 
1265             const double fLampSize(bIsSelectedLight ? RADIUS_LAMP_BIG : RADIUS_LAMP_SMALL);
1266             E3dObject* pNewLight = new E3dSphereObj(
1267                 *mpModel,
1268                 mp3DView->Get3DDefaultAttributes(),
1269                 basegfx::B3DPoint( 0, 0, 0 ),
1270                 basegfx::B3DVector( fLampSize, fLampSize, fLampSize));
1271             mpScene->InsertObject(pNewLight);
1272 
1273             basegfx::B3DHomMatrix aTransform;
1274             aTransform.translate(aDirection.getX(), aDirection.getY(), aDirection.getZ());
1275             pNewLight->SetTransform(aTransform);
1276 
1277             SfxItemSet aSet(mpModel->GetItemPool());
1278             aSet.Put( XLineStyleItem( drawing::LineStyle_NONE ) );
1279             aSet.Put( XFillStyleItem( drawing::FillStyle_SOLID ) );
1280             aSet.Put( XFillColorItem(OUString(), GetLightColor(a)));
1281             pNewLight->SetMergedItemSet(aSet);
1282 
1283             maLightObjects[a] = pNewLight;
1284         }
1285     }
1286 }
1287 
AdaptToSelectedLight()1288 void LightControl3D::AdaptToSelectedLight()
1289 {
1290     if(NO_LIGHT_SELECTED == maSelectedLight)
1291     {
1292         // make mpLampBottomObject/mpLampShaftObject invisible
1293         SfxItemSet aSet(mpModel->GetItemPool());
1294         aSet.Put( XLineStyleItem( drawing::LineStyle_NONE ) );
1295         aSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) );
1296         mpLampBottomObject->SetMergedItemSet(aSet);
1297         mpLampShaftObject->SetMergedItemSet(aSet);
1298     }
1299     else
1300     {
1301         basegfx::B3DVector aDirection(GetLightDirection(maSelectedLight));
1302         aDirection.normalize();
1303 
1304         // make mpLampBottomObject/mpLampShaftObject visible (yellow hairline)
1305         SfxItemSet aSet(mpModel->GetItemPool());
1306         aSet.Put( XLineStyleItem( drawing::LineStyle_SOLID ) );
1307         aSet.Put( XLineColorItem(OUString(), COL_YELLOW));
1308         aSet.Put( XLineWidthItem(0));
1309         aSet.Put( XFillStyleItem( drawing::FillStyle_NONE ) );
1310         mpLampBottomObject->SetMergedItemSet(aSet);
1311         mpLampShaftObject->SetMergedItemSet(aSet);
1312 
1313         // adapt transformation of mpLampShaftObject
1314         basegfx::B3DHomMatrix aTransform;
1315         double fRotateY(0.0);
1316 
1317         if(!basegfx::fTools::equalZero(aDirection.getZ()) || !basegfx::fTools::equalZero(aDirection.getX()))
1318         {
1319             fRotateY = atan2(-aDirection.getZ(), aDirection.getX());
1320         }
1321 
1322         aTransform.rotate(0.0, fRotateY, 0.0);
1323         mpLampShaftObject->SetTransform(aTransform);
1324 
1325         // adapt transformation of selected light
1326         E3dObject* pSelectedLight = maLightObjects[sal_Int32(maSelectedLight)];
1327 
1328         if(pSelectedLight)
1329         {
1330             aTransform.identity();
1331             aTransform.translate(
1332                 aDirection.getX() * RADIUS_LAMP_PREVIEW_SIZE,
1333                 aDirection.getY() * RADIUS_LAMP_PREVIEW_SIZE,
1334                 aDirection.getZ() * RADIUS_LAMP_PREVIEW_SIZE);
1335             pSelectedLight->SetTransform(aTransform);
1336         }
1337     }
1338 }
1339 
TrySelection(Point aPosPixel)1340 void LightControl3D::TrySelection(Point aPosPixel)
1341 {
1342     if(mpScene)
1343     {
1344         const Point aPosLogic(GetDrawingArea()->get_ref_device().PixelToLogic(aPosPixel));
1345         const basegfx::B2DPoint aPoint(aPosLogic.X(), aPosLogic.Y());
1346         std::vector< const E3dCompoundObject* > aResult;
1347         getAllHit3DObjectsSortedFrontToBack(aPoint, *mpScene, aResult);
1348 
1349         if(!aResult.empty())
1350         {
1351             // exclude expansion object which will be part of
1352             // the hits. It's invisible, but for HitTest, it's included
1353             const E3dCompoundObject* pResult = nullptr;
1354 
1355             for(auto const & b: aResult)
1356             {
1357                 if(b && b != mpExpansionObject)
1358                 {
1359                     pResult = b;
1360                     break;
1361                 }
1362             }
1363 
1364             if(pResult == mp3DObj)
1365             {
1366                 if(!mbGeometrySelected)
1367                 {
1368                     mbGeometrySelected = true;
1369                     maSelectedLight = NO_LIGHT_SELECTED;
1370                     ConstructLightObjects();
1371                     AdaptToSelectedLight();
1372                     Invalidate();
1373 
1374                     if(maSelectionChangeCallback.IsSet())
1375                     {
1376                         maSelectionChangeCallback.Call(this);
1377                     }
1378                 }
1379             }
1380             else
1381             {
1382                 sal_uInt32 aNewSelectedLight(NO_LIGHT_SELECTED);
1383 
1384                 for(sal_uInt32 a(0); a < MAX_NUMBER_LIGHTS; a++)
1385                 {
1386                     if(maLightObjects[a] && maLightObjects[a] == pResult)
1387                     {
1388                         aNewSelectedLight = a;
1389                     }
1390                 }
1391 
1392                 if(aNewSelectedLight != maSelectedLight)
1393                 {
1394                     SelectLight(aNewSelectedLight);
1395 
1396                     if(maSelectionChangeCallback.IsSet())
1397                     {
1398                         maSelectionChangeCallback.Call(this);
1399                     }
1400                 }
1401             }
1402         }
1403     }
1404 }
1405 
Paint(vcl::RenderContext & rRenderContext,const tools::Rectangle & rRect)1406 void LightControl3D::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
1407 {
1408     PreviewControl3D::Paint(rRenderContext, rRect);
1409 }
1410 
GetFocusRect()1411 tools::Rectangle LightControl3D::GetFocusRect()
1412 {
1413     if (!HasFocus())
1414         return tools::Rectangle();
1415     Size aFocusSize = GetOutputSizePixel();
1416     aFocusSize.AdjustWidth( -4 );
1417     aFocusSize.AdjustHeight( -4 );
1418     return tools::Rectangle(Point(2, 2), aFocusSize);
1419 }
1420 
MouseButtonDown(const MouseEvent & rMEvt)1421 bool LightControl3D::MouseButtonDown( const MouseEvent& rMEvt )
1422 {
1423     bool bCallParent(true);
1424 
1425     // switch state
1426     if(rMEvt.IsLeft())
1427     {
1428         if(IsSelectionValid() || mbGeometrySelected)
1429         {
1430             mbMouseMoved = false;
1431             bCallParent = false;
1432             maActionStartPoint = rMEvt.GetPosPixel();
1433             CaptureMouse();
1434             mbMouseCaptured = true;
1435         }
1436         else
1437         {
1438             // Single click without moving much trying to do a selection
1439             TrySelection(rMEvt.GetPosPixel());
1440             bCallParent = false;
1441         }
1442     }
1443 
1444     // call parent
1445     if (bCallParent)
1446         return PreviewControl3D::MouseButtonDown(rMEvt);
1447     return true;
1448 }
1449 
MouseMove(const MouseEvent & rMEvt)1450 bool LightControl3D::MouseMove(const MouseEvent& rMEvt)
1451 {
1452     if (!mbMouseCaptured)
1453         return false;
1454 
1455     Point aDeltaPos = rMEvt.GetPosPixel() - maActionStartPoint;
1456 
1457     if(!mbMouseMoved)
1458     {
1459         if(sal_Int32(aDeltaPos.X() * aDeltaPos.X() + aDeltaPos.Y() * aDeltaPos.Y()) > g_nInteractionStartDistance)
1460         {
1461             if(mbGeometrySelected)
1462             {
1463                 GetRotation(mfSaveActionStartVer, mfSaveActionStartHor, mfSaveActionStartRotZ);
1464             }
1465             else
1466             {
1467                 // interaction start, save values
1468                 GetPosition(mfSaveActionStartHor, mfSaveActionStartVer);
1469             }
1470 
1471             mbMouseMoved = true;
1472         }
1473     }
1474 
1475     if(mbMouseMoved)
1476     {
1477         if(mbGeometrySelected)
1478         {
1479             double fNewRotX = mfSaveActionStartVer - basegfx::deg2rad(aDeltaPos.Y());
1480             double fNewRotY = mfSaveActionStartHor + basegfx::deg2rad(aDeltaPos.X());
1481 
1482             // cut horizontal
1483             while(fNewRotY < 0.0)
1484             {
1485                 fNewRotY += F_2PI;
1486             }
1487 
1488             while(fNewRotY >= F_2PI)
1489             {
1490                 fNewRotY -= F_2PI;
1491             }
1492 
1493             // cut vertical
1494             if(fNewRotX < -F_PI2)
1495             {
1496                 fNewRotX = -F_PI2;
1497             }
1498 
1499             if(fNewRotX > F_PI2)
1500             {
1501                 fNewRotX = F_PI2;
1502             }
1503 
1504             SetRotation(fNewRotX, fNewRotY, mfSaveActionStartRotZ);
1505 
1506             if(maChangeCallback.IsSet())
1507             {
1508                 maChangeCallback.Call(this);
1509             }
1510         }
1511         else
1512         {
1513             // interaction in progress
1514             double fNewPosHor = mfSaveActionStartHor + static_cast<double>(aDeltaPos.X());
1515             double fNewPosVer = mfSaveActionStartVer - static_cast<double>(aDeltaPos.Y());
1516 
1517             // cut horizontal
1518             fNewPosHor = NormAngle360(fNewPosHor);
1519 
1520             // cut vertical
1521             if(fNewPosVer < -90.0)
1522             {
1523                 fNewPosVer = -90.0;
1524             }
1525 
1526             if(fNewPosVer > 90.0)
1527             {
1528                 fNewPosVer = 90.0;
1529             }
1530 
1531             SetPosition(fNewPosHor, fNewPosVer);
1532 
1533             if(maChangeCallback.IsSet())
1534             {
1535                 maChangeCallback.Call(this);
1536             }
1537         }
1538     }
1539     return true;
1540 }
1541 
MouseButtonUp(const MouseEvent & rMEvt)1542 bool LightControl3D::MouseButtonUp(const MouseEvent& rMEvt)
1543 {
1544     if (!mbMouseCaptured)
1545         return false;
1546     ReleaseMouse();
1547     mbMouseCaptured = false;
1548 
1549     if (mbMouseMoved)
1550     {
1551         // was change interactively
1552     }
1553     else
1554     {
1555         // simple click without much movement, try selection
1556         TrySelection(rMEvt.GetPosPixel());
1557     }
1558 
1559     return true;
1560 }
1561 
Resize()1562 void LightControl3D::Resize()
1563 {
1564     // set size of page
1565     const Size aSize(GetDrawingArea()->get_ref_device().PixelToLogic(GetOutputSizePixel()));
1566     mpFmPage->SetSize(aSize);
1567 
1568     // set position and size of scene
1569     mpScene->SetSnapRect(tools::Rectangle(Point(0, 0), aSize));
1570 }
1571 
SetObjectType(SvxPreviewObjectType nType)1572 void LightControl3D::SetObjectType(SvxPreviewObjectType nType)
1573 {
1574     // call parent
1575     PreviewControl3D::SetObjectType(nType);
1576 
1577     // apply object rotation
1578     if(mp3DObj)
1579     {
1580         basegfx::B3DHomMatrix aObjectRotation;
1581         aObjectRotation.rotate(mfRotateX, mfRotateY, mfRotateZ);
1582         mp3DObj->SetTransform(aObjectRotation);
1583     }
1584 }
1585 
IsSelectionValid()1586 bool LightControl3D::IsSelectionValid()
1587 {
1588     return (NO_LIGHT_SELECTED != maSelectedLight) && GetLightOnOff(maSelectedLight);
1589 }
1590 
GetPosition(double & rHor,double & rVer)1591 void LightControl3D::GetPosition(double& rHor, double& rVer)
1592 {
1593     if(IsSelectionValid())
1594     {
1595         basegfx::B3DVector aDirection(GetLightDirection(maSelectedLight));
1596         aDirection.normalize();
1597         rHor = basegfx::rad2deg(atan2(-aDirection.getX(), -aDirection.getZ()) + F_PI); // 0..360.0
1598         rVer = basegfx::rad2deg(atan2(aDirection.getY(), aDirection.getXZLength())); // -90.0..90.0
1599     }
1600     if(IsGeometrySelected())
1601     {
1602         rHor = basegfx::rad2deg(mfRotateY); // 0..360.0
1603         rVer = basegfx::rad2deg(mfRotateX); // -90.0..90.0
1604     }
1605 }
1606 
SetPosition(double fHor,double fVer)1607 void LightControl3D::SetPosition(double fHor, double fVer)
1608 {
1609     if(IsSelectionValid())
1610     {
1611         // set selected light's direction
1612         fHor = basegfx::deg2rad(fHor) - F_PI; // -PI..PI
1613         fVer = basegfx::deg2rad(fVer); // -PI2..PI2
1614         basegfx::B3DVector aDirection(cos(fVer) * -sin(fHor), sin(fVer), cos(fVer) * -cos(fHor));
1615         aDirection.normalize();
1616 
1617         if(!aDirection.equal(GetLightDirection(maSelectedLight)))
1618         {
1619             // set changed light direction at SdrScene
1620             SfxItemSet aSet(mpModel->GetItemPool());
1621 
1622             switch(maSelectedLight)
1623             {
1624                 case 0: aSet.Put(makeSvx3DLightDirection1Item(aDirection)); break;
1625                 case 1: aSet.Put(makeSvx3DLightDirection2Item(aDirection)); break;
1626                 case 2: aSet.Put(makeSvx3DLightDirection3Item(aDirection)); break;
1627                 case 3: aSet.Put(makeSvx3DLightDirection4Item(aDirection)); break;
1628                 case 4: aSet.Put(makeSvx3DLightDirection5Item(aDirection)); break;
1629                 case 5: aSet.Put(makeSvx3DLightDirection6Item(aDirection)); break;
1630                 case 6: aSet.Put(makeSvx3DLightDirection7Item(aDirection)); break;
1631                 default:
1632                 case 7: aSet.Put(makeSvx3DLightDirection8Item(aDirection)); break;
1633             }
1634 
1635             mpScene->SetMergedItemSet(aSet);
1636 
1637             // correct 3D light's and LampFrame's geometries
1638             AdaptToSelectedLight();
1639             Invalidate();
1640         }
1641     }
1642     if(IsGeometrySelected())
1643     {
1644         if(mfRotateX != fVer || mfRotateY != fHor)
1645         {
1646             mfRotateX = basegfx::deg2rad(fVer);
1647             mfRotateY = basegfx::deg2rad(fHor);
1648 
1649             if(mp3DObj)
1650             {
1651                 basegfx::B3DHomMatrix aObjectRotation;
1652                 aObjectRotation.rotate(mfRotateX, mfRotateY, mfRotateZ);
1653                 mp3DObj->SetTransform(aObjectRotation);
1654 
1655                 Invalidate();
1656             }
1657         }
1658     }
1659 }
1660 
SetRotation(double fRotX,double fRotY,double fRotZ)1661 void LightControl3D::SetRotation(double fRotX, double fRotY, double fRotZ)
1662 {
1663     if(IsGeometrySelected())
1664     {
1665         if(fRotX != mfRotateX || fRotY != mfRotateY || fRotZ != mfRotateZ)
1666         {
1667             mfRotateX = fRotX;
1668             mfRotateY = fRotY;
1669             mfRotateZ = fRotZ;
1670 
1671             if(mp3DObj)
1672             {
1673                 basegfx::B3DHomMatrix aObjectRotation;
1674                 aObjectRotation.rotate(mfRotateX, mfRotateY, mfRotateZ);
1675                 mp3DObj->SetTransform(aObjectRotation);
1676 
1677                 Invalidate();
1678             }
1679         }
1680     }
1681 }
1682 
GetRotation(double & rRotX,double & rRotY,double & rRotZ)1683 void LightControl3D::GetRotation(double& rRotX, double& rRotY, double& rRotZ)
1684 {
1685     rRotX = mfRotateX;
1686     rRotY = mfRotateY;
1687     rRotZ = mfRotateZ;
1688 }
1689 
Set3DAttributes(const SfxItemSet & rAttr)1690 void LightControl3D::Set3DAttributes( const SfxItemSet& rAttr )
1691 {
1692     // call parent
1693     PreviewControl3D::Set3DAttributes(rAttr);
1694 
1695     if(maSelectedLight != NO_LIGHT_SELECTED && !GetLightOnOff(maSelectedLight))
1696     {
1697         // selected light is no more active, select new one
1698         maSelectedLight = NO_LIGHT_SELECTED;
1699     }
1700 
1701     // local updates
1702     ConstructLightObjects();
1703     AdaptToSelectedLight();
1704     Invalidate();
1705 }
1706 
SelectLight(sal_uInt32 nLightNumber)1707 void LightControl3D::SelectLight(sal_uInt32 nLightNumber)
1708 {
1709     if(nLightNumber > 7)
1710     {
1711         nLightNumber = NO_LIGHT_SELECTED;
1712     }
1713 
1714     if(NO_LIGHT_SELECTED != nLightNumber)
1715     {
1716         if(!GetLightOnOff(nLightNumber))
1717         {
1718             nLightNumber = NO_LIGHT_SELECTED;
1719         }
1720     }
1721 
1722     if(nLightNumber != maSelectedLight)
1723     {
1724         maSelectedLight = nLightNumber;
1725         mbGeometrySelected = false;
1726         ConstructLightObjects();
1727         AdaptToSelectedLight();
1728         Invalidate();
1729     }
1730 }
1731 
GetLightOnOff(sal_uInt32 nNum) const1732 bool LightControl3D::GetLightOnOff(sal_uInt32 nNum) const
1733 {
1734     if(nNum <= 7)
1735     {
1736         const SfxItemSet aLightItemSet(Get3DAttributes());
1737 
1738         switch(nNum)
1739         {
1740             case 0 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_1).GetValue();
1741             case 1 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_2).GetValue();
1742             case 2 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_3).GetValue();
1743             case 3 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_4).GetValue();
1744             case 4 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_5).GetValue();
1745             case 5 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_6).GetValue();
1746             case 6 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_7).GetValue();
1747             case 7 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTON_8).GetValue();
1748         }
1749     }
1750 
1751     return false;
1752 }
1753 
GetLightColor(sal_uInt32 nNum) const1754 Color LightControl3D::GetLightColor(sal_uInt32 nNum) const
1755 {
1756     if(nNum <= 7)
1757     {
1758         const SfxItemSet aLightItemSet(Get3DAttributes());
1759 
1760         switch(nNum)
1761         {
1762             case 0 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_1).GetValue();
1763             case 1 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_2).GetValue();
1764             case 2 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_3).GetValue();
1765             case 3 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_4).GetValue();
1766             case 4 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_5).GetValue();
1767             case 5 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_6).GetValue();
1768             case 6 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_7).GetValue();
1769             case 7 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTCOLOR_8).GetValue();
1770         }
1771     }
1772 
1773     return COL_BLACK;
1774 }
1775 
GetLightDirection(sal_uInt32 nNum) const1776 basegfx::B3DVector LightControl3D::GetLightDirection(sal_uInt32 nNum) const
1777 {
1778     if(nNum <= 7)
1779     {
1780         const SfxItemSet aLightItemSet(Get3DAttributes());
1781 
1782         switch(nNum)
1783         {
1784             case 0 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_1).GetValue();
1785             case 1 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_2).GetValue();
1786             case 2 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_3).GetValue();
1787             case 3 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_4).GetValue();
1788             case 4 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_5).GetValue();
1789             case 5 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_6).GetValue();
1790             case 6 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_7).GetValue();
1791             case 7 : return aLightItemSet.Get(SDRATTR_3DSCENE_LIGHTDIRECTION_8).GetValue();
1792         }
1793     }
1794 
1795     return basegfx::B3DVector();
1796 }
1797 
SvxLightCtl3D(vcl::Window * pParent)1798 SvxLightCtl3D::SvxLightCtl3D( vcl::Window* pParent)
1799 :   Control(pParent, WB_BORDER | WB_TABSTOP),
1800     maLightControl(VclPtr<Svx3DLightControl>::Create(this, 0)),
1801     maHorScroller(VclPtr<ScrollBar>::Create(this, WB_HORZ | WB_DRAG)),
1802     maVerScroller(VclPtr<ScrollBar>::Create(this, WB_VERT | WB_DRAG)),
1803     maSwitcher(VclPtr<PushButton>::Create(this, 0))
1804 {
1805     // init members
1806     Init();
1807 }
1808 
GetOptimalSize() const1809 Size SvxLightCtl3D::GetOptimalSize() const
1810 {
1811     return LogicToPixel(Size(80, 100), MapMode(MapUnit::MapAppFont));
1812 }
1813 
VCL_BUILDER_FACTORY(SvxLightCtl3D)1814 VCL_BUILDER_FACTORY(SvxLightCtl3D)
1815 
1816 void SvxLightCtl3D::Init()
1817 {
1818     // #i58240# set HelpIDs for scrollbars and switcher
1819     maHorScroller->SetHelpId(HID_CTRL3D_HSCROLL);
1820     maVerScroller->SetHelpId(HID_CTRL3D_VSCROLL);
1821     maSwitcher->SetHelpId(HID_CTRL3D_SWITCHER);
1822     maSwitcher->SetAccessibleName(SvxResId(STR_SWITCH));
1823 
1824     // Light preview
1825     maLightControl->Show();
1826     maLightControl->SetChangeCallback( LINK(this, SvxLightCtl3D, InternalInteractiveChange) );
1827     maLightControl->SetSelectionChangeCallback( LINK(this, SvxLightCtl3D, InternalSelectionChange) );
1828 
1829     // Horiz Scrollbar
1830     maHorScroller->Show();
1831     maHorScroller->SetRange(Range(0, 36000));
1832     maHorScroller->SetLineSize(100);
1833     maHorScroller->SetPageSize(1000);
1834     maHorScroller->SetScrollHdl( LINK(this, SvxLightCtl3D, ScrollBarMove) );
1835 
1836     // Vert Scrollbar
1837     maVerScroller->Show();
1838     maVerScroller->SetRange(Range(0, 18000));
1839     maVerScroller->SetLineSize(100);
1840     maVerScroller->SetPageSize(1000);
1841     maVerScroller->SetScrollHdl( LINK(this, SvxLightCtl3D, ScrollBarMove) );
1842 
1843     // Switch Button
1844     maSwitcher->Show();
1845     maSwitcher->SetClickHdl( LINK(this, SvxLightCtl3D, ButtonPress) );
1846 
1847     // check selection
1848     CheckSelection();
1849 
1850     // new layout
1851     NewLayout();
1852 }
1853 
~SvxLightCtl3D()1854 SvxLightCtl3D::~SvxLightCtl3D()
1855 {
1856     disposeOnce();
1857 }
1858 
dispose()1859 void SvxLightCtl3D::dispose()
1860 {
1861     maLightControl.disposeAndClear();
1862     maHorScroller.disposeAndClear();
1863     maVerScroller.disposeAndClear();
1864     maSwitcher.disposeAndClear();
1865     Control::dispose();
1866 }
1867 
Resize()1868 void SvxLightCtl3D::Resize()
1869 {
1870     // call parent
1871     Control::Resize();
1872 
1873     // new layout
1874     NewLayout();
1875 }
1876 
NewLayout()1877 void SvxLightCtl3D::NewLayout()
1878 {
1879     // Layout members
1880     const Size aSize(GetOutputSizePixel());
1881     const sal_Int32 nScrollSize(maHorScroller->GetSizePixel().Height());
1882 
1883     // Preview control
1884     Point aPoint(0, 0);
1885     Size aDestSize(aSize.Width() - nScrollSize, aSize.Height() - nScrollSize);
1886     maLightControl->SetPosSizePixel(aPoint, aDestSize);
1887 
1888     // hor scrollbar
1889     aPoint.setY( aSize.Height() - nScrollSize );
1890     aDestSize.setHeight( nScrollSize );
1891     maHorScroller->SetPosSizePixel(aPoint, aDestSize);
1892 
1893     // vert scrollbar
1894     aPoint.setX( aSize.Width() - nScrollSize );
1895     aPoint.setY( 0 );
1896     aDestSize.setWidth( nScrollSize );
1897     aDestSize.setHeight( aSize.Height() - nScrollSize );
1898     maVerScroller->SetPosSizePixel(aPoint, aDestSize);
1899 
1900     // button
1901     aPoint.setY( aSize.Height() - nScrollSize );
1902     aDestSize.setHeight( nScrollSize );
1903     maSwitcher->SetPosSizePixel(aPoint, aDestSize);
1904 }
1905 
CheckSelection()1906 void SvxLightCtl3D::CheckSelection()
1907 {
1908     const bool bSelectionValid(maLightControl->IsSelectionValid() || maLightControl->IsGeometrySelected());
1909     maHorScroller->Enable(bSelectionValid);
1910     maVerScroller->Enable(bSelectionValid);
1911 
1912     if(bSelectionValid)
1913     {
1914         double fHor(0.0), fVer(0.0);
1915         maLightControl->GetPosition(fHor, fVer);
1916         maHorScroller->SetThumbPos( sal_Int32(fHor * 100.0) );
1917         maVerScroller->SetThumbPos( 18000 - sal_Int32((fVer + 90.0) * 100.0) );
1918     }
1919 }
1920 
move(double fDeltaHor,double fDeltaVer)1921 void SvxLightCtl3D::move( double fDeltaHor, double fDeltaVer )
1922 {
1923     double fHor(0.0), fVer(0.0);
1924 
1925     maLightControl->GetPosition(fHor, fVer);
1926     fHor += fDeltaHor;
1927     fVer += fDeltaVer;
1928 
1929     if( fVer > 90.0 )
1930         return;
1931 
1932     if ( fVer < -90.0 )
1933         return;
1934 
1935     maLightControl->SetPosition(fHor, fVer);
1936     maHorScroller->SetThumbPos( sal_Int32(fHor * 100.0) );
1937     maVerScroller->SetThumbPos( 18000 - sal_Int32((fVer + 90.0) * 100.0) );
1938 }
1939 
KeyInput(const KeyEvent & rKEvt)1940 void SvxLightCtl3D::KeyInput( const KeyEvent& rKEvt )
1941 {
1942     const vcl::KeyCode aCode(rKEvt.GetKeyCode());
1943 
1944     if( aCode.GetModifier() )
1945     {
1946         Control::KeyInput( rKEvt );
1947         return;
1948     }
1949 
1950     switch ( aCode.GetCode() )
1951     {
1952         case KEY_SPACE:
1953         {
1954             break;
1955         }
1956         case KEY_LEFT:
1957         {
1958             move(  -4.0,  0.0 ); // #i58242# changed move direction in X
1959             break;
1960         }
1961         case KEY_RIGHT:
1962         {
1963             move( 4.0,  0.0 ); // #i58242# changed move direction in X
1964             break;
1965         }
1966         case KEY_UP:
1967         {
1968             move(  0.0,  4.0 );
1969             break;
1970         }
1971         case KEY_DOWN:
1972         {
1973             move(  0.0, -4.0 );
1974             break;
1975         }
1976         case KEY_PAGEUP:
1977         {
1978             sal_Int32 nLight(maLightControl->GetSelectedLight() - 1);
1979 
1980             while((nLight >= 0) && !maLightControl->GetLightOnOff(nLight))
1981             {
1982                 nLight--;
1983             }
1984 
1985             if(nLight < 0)
1986             {
1987                 nLight = 7;
1988 
1989                 while((nLight >= 0) && !maLightControl->GetLightOnOff(nLight))
1990                 {
1991                     nLight--;
1992                 }
1993             }
1994 
1995             if(nLight >= 0)
1996             {
1997                 maLightControl->SelectLight(nLight);
1998                 CheckSelection();
1999 
2000                 if(maUserSelectionChangeCallback.IsSet())
2001                 {
2002                     maUserSelectionChangeCallback.Call(this);
2003                 }
2004             }
2005 
2006             break;
2007         }
2008         case KEY_PAGEDOWN:
2009         {
2010             sal_Int32 nLight(maLightControl->GetSelectedLight() - 1);
2011 
2012             while(nLight <= 7 && !maLightControl->GetLightOnOff(nLight))
2013             {
2014                 nLight++;
2015             }
2016 
2017             if(nLight > 7)
2018             {
2019                 nLight = 0;
2020 
2021                 while(nLight <= 7 && !maLightControl->GetLightOnOff(nLight))
2022                 {
2023                     nLight++;
2024                 }
2025             }
2026 
2027             if(nLight <= 7)
2028             {
2029                 maLightControl->SelectLight(nLight);
2030                 CheckSelection();
2031 
2032                 if(maUserSelectionChangeCallback.IsSet())
2033                 {
2034                     maUserSelectionChangeCallback.Call(this);
2035                 }
2036             }
2037 
2038             break;
2039         }
2040         default:
2041         {
2042             Control::KeyInput( rKEvt );
2043             break;
2044         }
2045     }
2046 }
2047 
GetFocus()2048 void SvxLightCtl3D::GetFocus()
2049 {
2050     Control::GetFocus();
2051 
2052     if(HasFocus() && IsEnabled())
2053     {
2054         CheckSelection();
2055 
2056         Size aFocusSize = maLightControl->GetOutputSizePixel();
2057 
2058         aFocusSize.AdjustWidth( -4 );
2059         aFocusSize.AdjustHeight( -4 );
2060 
2061         tools::Rectangle aFocusRect( Point( 2, 2 ), aFocusSize );
2062 
2063         aFocusRect = maLightControl->PixelToLogic( aFocusRect );
2064 
2065         maLightControl->ShowFocus( aFocusRect );
2066     }
2067 }
2068 
LoseFocus()2069 void SvxLightCtl3D::LoseFocus()
2070 {
2071     Control::LoseFocus();
2072 
2073     maLightControl->HideFocus();
2074 }
2075 
IMPL_LINK_NOARG(SvxLightCtl3D,ScrollBarMove,ScrollBar *,void)2076 IMPL_LINK_NOARG(SvxLightCtl3D, ScrollBarMove, ScrollBar*, void)
2077 {
2078     const sal_Int32 nHor(maHorScroller->GetThumbPos());
2079     const sal_Int32 nVer(maVerScroller->GetThumbPos());
2080 
2081     maLightControl->SetPosition(
2082         static_cast<double>(nHor) / 100.0,
2083         static_cast<double>((18000 - nVer) - 9000) / 100.0);
2084 }
2085 
IMPL_LINK_NOARG(SvxLightCtl3D,ButtonPress,Button *,void)2086 IMPL_LINK_NOARG(SvxLightCtl3D, ButtonPress, Button*, void)
2087 {
2088     if(SvxPreviewObjectType::SPHERE == GetSvx3DLightControl().GetObjectType())
2089     {
2090         GetSvx3DLightControl().SetObjectType(SvxPreviewObjectType::CUBE);
2091     }
2092     else
2093     {
2094         GetSvx3DLightControl().SetObjectType(SvxPreviewObjectType::SPHERE);
2095     }
2096 }
2097 
IMPL_LINK_NOARG(SvxLightCtl3D,InternalInteractiveChange,Svx3DLightControl *,void)2098 IMPL_LINK_NOARG(SvxLightCtl3D, InternalInteractiveChange, Svx3DLightControl*, void)
2099 {
2100     double fHor(0.0), fVer(0.0);
2101 
2102     maLightControl->GetPosition(fHor, fVer);
2103     maHorScroller->SetThumbPos( sal_Int32(fHor * 100.0) );
2104     maVerScroller->SetThumbPos( 18000 - sal_Int32((fVer + 90.0) * 100.0) );
2105 }
2106 
IMPL_LINK_NOARG(SvxLightCtl3D,InternalSelectionChange,Svx3DLightControl *,void)2107 IMPL_LINK_NOARG(SvxLightCtl3D, InternalSelectionChange, Svx3DLightControl*, void)
2108 {
2109     CheckSelection();
2110 
2111     if(maUserSelectionChangeCallback.IsSet())
2112     {
2113         maUserSelectionChangeCallback.Call(this);
2114     }
2115 }
2116 
LightCtl3D(LightControl3D & rLightControl,weld::Scale & rHori,weld::Scale & rVert,weld::Button & rSwitcher)2117 LightCtl3D::LightCtl3D(LightControl3D& rLightControl, weld::Scale& rHori,
2118                        weld::Scale& rVert, weld::Button& rSwitcher)
2119     : mrLightControl(rLightControl)
2120     , mrHorScroller(rHori)
2121     , mrVerScroller(rVert)
2122     , mrSwitcher(rSwitcher)
2123 {
2124     // init members
2125     Init();
2126 }
2127 
Init()2128 void LightCtl3D::Init()
2129 {
2130     Size aSize(mrLightControl.GetDrawingArea()->get_ref_device().LogicToPixel(Size(80, 100), MapMode(MapUnit::MapAppFont)));
2131     mrLightControl.set_size_request(aSize.Width(), aSize.Height());
2132 
2133     // #i58240# set HelpIDs for scrollbars and switcher
2134     mrHorScroller.set_help_id(HID_CTRL3D_HSCROLL);
2135     mrVerScroller.set_help_id(HID_CTRL3D_VSCROLL);
2136     mrSwitcher.set_help_id(HID_CTRL3D_SWITCHER);
2137     mrSwitcher.set_accessible_name(SvxResId(STR_SWITCH));
2138 
2139     // Light preview
2140     mrLightControl.Show();
2141     mrLightControl.SetChangeCallback( LINK(this, LightCtl3D, InternalInteractiveChange) );
2142     mrLightControl.SetSelectionChangeCallback( LINK(this, LightCtl3D, InternalSelectionChange) );
2143 
2144     // Horiz Scrollbar
2145     mrHorScroller.show();
2146     mrHorScroller.set_range(0, 36000);
2147     mrHorScroller.connect_value_changed( LINK(this, LightCtl3D, ScrollBarMove) );
2148 
2149     // Vert Scrollbar
2150     mrVerScroller.show();
2151     mrVerScroller.set_range(0, 18000);
2152     mrVerScroller.connect_value_changed( LINK(this, LightCtl3D, ScrollBarMove) );
2153 
2154     // Switch Button
2155     mrSwitcher.show();
2156     mrSwitcher.connect_clicked( LINK(this, LightCtl3D, ButtonPress) );
2157 
2158     weld::DrawingArea* pArea = mrLightControl.GetDrawingArea();
2159     pArea->connect_key_press(Link<const KeyEvent&, bool>()); //acknowledge we first remove the old one
2160     pArea->connect_key_press(LINK(this, LightCtl3D, KeyInput));
2161 
2162     pArea->connect_focus_in(Link<weld::Widget&, void>()); //acknowledge we first remove the old one
2163     pArea->connect_focus_in(LINK(this, LightCtl3D, FocusIn));
2164 
2165     // check selection
2166     CheckSelection();
2167 }
2168 
~LightCtl3D()2169 LightCtl3D::~LightCtl3D()
2170 {
2171 }
2172 
CheckSelection()2173 void LightCtl3D::CheckSelection()
2174 {
2175     const bool bSelectionValid(mrLightControl.IsSelectionValid() || mrLightControl.IsGeometrySelected());
2176     mrHorScroller.set_sensitive(bSelectionValid);
2177     mrVerScroller.set_sensitive(bSelectionValid);
2178 
2179     if (bSelectionValid)
2180     {
2181         double fHor(0.0), fVer(0.0);
2182         mrLightControl.GetPosition(fHor, fVer);
2183         mrHorScroller.set_value( sal_Int32(fHor * 100.0) );
2184         mrVerScroller.set_value( 18000 - sal_Int32((fVer + 90.0) * 100.0) );
2185     }
2186 }
2187 
move(double fDeltaHor,double fDeltaVer)2188 void LightCtl3D::move( double fDeltaHor, double fDeltaVer )
2189 {
2190     double fHor(0.0), fVer(0.0);
2191 
2192     mrLightControl.GetPosition(fHor, fVer);
2193     fHor += fDeltaHor;
2194     fVer += fDeltaVer;
2195 
2196     if( fVer > 90.0 )
2197         return;
2198 
2199     if ( fVer < -90.0 )
2200         return;
2201 
2202     mrLightControl.SetPosition(fHor, fVer);
2203     mrHorScroller.set_value( sal_Int32(fHor * 100.0) );
2204     mrVerScroller.set_value( 18000 - sal_Int32((fVer + 90.0) * 100.0) );
2205 
2206     if(maUserInteractiveChangeCallback.IsSet())
2207     {
2208         maUserInteractiveChangeCallback.Call(this);
2209     }
2210 }
2211 
IMPL_LINK(LightCtl3D,KeyInput,const KeyEvent &,rKEvt,bool)2212 IMPL_LINK(LightCtl3D, KeyInput, const KeyEvent&, rKEvt, bool)
2213 {
2214     const vcl::KeyCode aCode(rKEvt.GetKeyCode());
2215 
2216     if (aCode.GetModifier())
2217         return false;
2218 
2219     bool bHandled = true;
2220 
2221     switch ( aCode.GetCode() )
2222     {
2223         case KEY_SPACE:
2224         {
2225             break;
2226         }
2227         case KEY_LEFT:
2228         {
2229             move(  -4.0,  0.0 ); // #i58242# changed move direction in X
2230             break;
2231         }
2232         case KEY_RIGHT:
2233         {
2234             move( 4.0,  0.0 ); // #i58242# changed move direction in X
2235             break;
2236         }
2237         case KEY_UP:
2238         {
2239             move(  0.0,  4.0 );
2240             break;
2241         }
2242         case KEY_DOWN:
2243         {
2244             move(  0.0, -4.0 );
2245             break;
2246         }
2247         case KEY_PAGEUP:
2248         {
2249             sal_Int32 nLight(mrLightControl.GetSelectedLight() - 1);
2250 
2251             while((nLight >= 0) && !mrLightControl.GetLightOnOff(nLight))
2252             {
2253                 nLight--;
2254             }
2255 
2256             if(nLight < 0)
2257             {
2258                 nLight = 7;
2259 
2260                 while((nLight >= 0) && !mrLightControl.GetLightOnOff(nLight))
2261                 {
2262                     nLight--;
2263                 }
2264             }
2265 
2266             if(nLight >= 0)
2267             {
2268                 mrLightControl.SelectLight(nLight);
2269                 CheckSelection();
2270 
2271                 if(maUserSelectionChangeCallback.IsSet())
2272                 {
2273                     maUserSelectionChangeCallback.Call(this);
2274                 }
2275             }
2276 
2277             break;
2278         }
2279         case KEY_PAGEDOWN:
2280         {
2281             sal_Int32 nLight(mrLightControl.GetSelectedLight() - 1);
2282 
2283             while(nLight <= 7 && !mrLightControl.GetLightOnOff(nLight))
2284             {
2285                 nLight++;
2286             }
2287 
2288             if(nLight > 7)
2289             {
2290                 nLight = 0;
2291 
2292                 while(nLight <= 7 && !mrLightControl.GetLightOnOff(nLight))
2293                 {
2294                     nLight++;
2295                 }
2296             }
2297 
2298             if(nLight <= 7)
2299             {
2300                 mrLightControl.SelectLight(nLight);
2301                 CheckSelection();
2302 
2303                 if(maUserSelectionChangeCallback.IsSet())
2304                 {
2305                     maUserSelectionChangeCallback.Call(this);
2306                 }
2307             }
2308 
2309             break;
2310         }
2311         default:
2312         {
2313             bHandled = false;
2314             break;
2315         }
2316     }
2317     return bHandled;
2318 }
2319 
IMPL_LINK_NOARG(LightCtl3D,FocusIn,weld::Widget &,void)2320 IMPL_LINK_NOARG(LightCtl3D, FocusIn, weld::Widget&, void)
2321 {
2322     if (mrLightControl.IsEnabled())
2323     {
2324         CheckSelection();
2325     }
2326 }
2327 
IMPL_LINK_NOARG(LightCtl3D,ScrollBarMove,weld::Scale &,void)2328 IMPL_LINK_NOARG(LightCtl3D, ScrollBarMove, weld::Scale&, void)
2329 {
2330     const sal_Int32 nHor(mrHorScroller.get_value());
2331     const sal_Int32 nVer(mrVerScroller.get_value());
2332 
2333     mrLightControl.SetPosition(
2334         static_cast<double>(nHor) / 100.0,
2335         static_cast<double>((18000 - nVer) - 9000) / 100.0);
2336 
2337     if (maUserInteractiveChangeCallback.IsSet())
2338     {
2339         maUserInteractiveChangeCallback.Call(this);
2340     }
2341 }
2342 
IMPL_LINK_NOARG(LightCtl3D,ButtonPress,weld::Button &,void)2343 IMPL_LINK_NOARG(LightCtl3D, ButtonPress, weld::Button&, void)
2344 {
2345     if(SvxPreviewObjectType::SPHERE == GetSvx3DLightControl().GetObjectType())
2346     {
2347         GetSvx3DLightControl().SetObjectType(SvxPreviewObjectType::CUBE);
2348     }
2349     else
2350     {
2351         GetSvx3DLightControl().SetObjectType(SvxPreviewObjectType::SPHERE);
2352     }
2353 }
2354 
IMPL_LINK_NOARG(LightCtl3D,InternalInteractiveChange,LightControl3D *,void)2355 IMPL_LINK_NOARG(LightCtl3D, InternalInteractiveChange, LightControl3D*, void)
2356 {
2357     double fHor(0.0), fVer(0.0);
2358 
2359     mrLightControl.GetPosition(fHor, fVer);
2360     mrHorScroller.set_value( sal_Int32(fHor * 100.0) );
2361     mrVerScroller.set_value( 18000 - sal_Int32((fVer + 90.0) * 100.0) );
2362 
2363     if(maUserInteractiveChangeCallback.IsSet())
2364     {
2365         maUserInteractiveChangeCallback.Call(this);
2366     }
2367 }
2368 
IMPL_LINK_NOARG(LightCtl3D,InternalSelectionChange,LightControl3D *,void)2369 IMPL_LINK_NOARG(LightCtl3D, InternalSelectionChange, LightControl3D*, void)
2370 {
2371     CheckSelection();
2372 
2373     if(maUserSelectionChangeCallback.IsSet())
2374     {
2375         maUserSelectionChangeCallback.Call(this);
2376     }
2377 }
2378 
2379 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2380