summaryrefslogtreecommitdiff
path: root/svx/source/engine3d/view3d.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'svx/source/engine3d/view3d.cxx')
-rw-r--r--svx/source/engine3d/view3d.cxx1926
1 files changed, 1926 insertions, 0 deletions
diff --git a/svx/source/engine3d/view3d.cxx b/svx/source/engine3d/view3d.cxx
new file mode 100644
index 000000000000..9ac90d408085
--- /dev/null
+++ b/svx/source/engine3d/view3d.cxx
@@ -0,0 +1,1926 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_svx.hxx"
+
+#include <vcl/wrkwin.hxx>
+#include <svx/svdogrp.hxx>
+#include <svx/svdopath.hxx>
+#include <tools/shl.hxx>
+#include "svx/svditer.hxx"
+#include <svx/svdpool.hxx>
+#include <svx/svdorect.hxx>
+#include <svx/svdmodel.hxx>
+#include <svx/svdpagv.hxx>
+#include <svx/svxids.hrc>
+#include <editeng/colritem.hxx>
+#include <svx/xtable.hxx>
+#include <svx/svdview.hxx>
+#include <svx/dialogs.hrc>
+#include <svx/dialmgr.hxx>
+#include "svx/globl3d.hxx"
+#include <svx/obj3d.hxx>
+#include <svx/lathe3d.hxx>
+#include <svx/sphere3d.hxx>
+#include <svx/extrud3d.hxx>
+#include <svx/cube3d.hxx>
+#include <svx/polysc3d.hxx>
+#include "dragmt3d.hxx"
+#include <svx/view3d.hxx>
+#include <svx/svdundo.hxx>
+#include <svx/xflclit.hxx>
+#include <svx/xlnclit.hxx>
+#include <svx/svdograf.hxx>
+#include <svx/xbtmpit.hxx>
+#include <svx/xflbmtit.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <svx/xlnwtit.hxx>
+#include <svx/sdr/overlay/overlaypolypolygon.hxx>
+#include <svx/sdr/overlay/overlaymanager.hxx>
+#include <svx/sdrpaintwindow.hxx>
+#include <svx/sdr/contact/viewcontactofe3dscene.hxx>
+#include <drawinglayer/geometry/viewinformation3d.hxx>
+#include <svx/sdrpagewindow.hxx>
+#include <svx/sdr/contact/displayinfo.hxx>
+#include <svx/sdr/contact/objectcontact.hxx>
+#include <svx/sdr/contact/viewobjectcontact.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <svx/sdr/overlay/overlayprimitive2dsequenceobject.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+
+#define ITEMVALUE(ItemSet,Id,Cast) ((const Cast&)(ItemSet).Get(Id)).GetValue()
+
+TYPEINIT1(E3dView, SdrView);
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Migrate Marking
+
+class Impl3DMirrorConstructOverlay
+{
+ // The OverlayObjects
+ ::sdr::overlay::OverlayObjectList maObjects;
+
+ // the view
+ const E3dView& mrView;
+
+ // the object count
+ sal_uInt32 mnCount;
+
+ // the unmirrored polygons
+ basegfx::B2DPolyPolygon* mpPolygons;
+
+ // the overlay geometry from selected objects
+ drawinglayer::primitive2d::Primitive2DSequence maFullOverlay;
+
+public:
+ Impl3DMirrorConstructOverlay(const E3dView& rView);
+ ~Impl3DMirrorConstructOverlay();
+
+ void SetMirrorAxis(Point aMirrorAxisA, Point aMirrorAxisB);
+};
+
+Impl3DMirrorConstructOverlay::Impl3DMirrorConstructOverlay(const E3dView& rView)
+: maObjects(),
+ mrView(rView),
+ mnCount(rView.GetMarkedObjectCount()),
+ mpPolygons(0),
+ maFullOverlay()
+{
+ if(mnCount)
+ {
+ if(mrView.IsSolidDragging())
+ {
+ SdrPageView* pPV = rView.GetSdrPageView();
+
+ if(pPV && pPV->PageWindowCount())
+ {
+ sdr::contact::ObjectContact& rOC = pPV->GetPageWindow(0)->GetObjectContact();
+ sdr::contact::DisplayInfo aDisplayInfo;
+
+ // Do not use the last ViewPort set at the OC at the last ProcessDisplay()
+ rOC.resetViewPort();
+
+ for(sal_uInt32 a(0);a < mnCount;a++)
+ {
+ SdrObject* pObject = mrView.GetMarkedObjectByIndex(a);
+
+ if(pObject)
+ {
+ sdr::contact::ViewContact& rVC = pObject->GetViewContact();
+ sdr::contact::ViewObjectContact& rVOC = rVC.GetViewObjectContact(rOC);
+
+ const drawinglayer::primitive2d::Primitive2DSequence aNewSequence(rVOC.getPrimitive2DSequenceHierarchy(aDisplayInfo));
+ drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(maFullOverlay, aNewSequence);
+ }
+ }
+ }
+ }
+ else
+ {
+ mpPolygons = new basegfx::B2DPolyPolygon[mnCount];
+
+ for(sal_uInt32 a(0); a < mnCount; a++)
+ {
+ SdrObject* pObject = mrView.GetMarkedObjectByIndex(a);
+ mpPolygons[mnCount - (a + 1)] = pObject->TakeXorPoly();
+ }
+ }
+ }
+}
+
+Impl3DMirrorConstructOverlay::~Impl3DMirrorConstructOverlay()
+{
+ // The OverlayObjects are cleared using the destructor of OverlayObjectList.
+ // That destructor calls clear() at the list which removes all objects from the
+ // OverlayManager and deletes them.
+ if(!mrView.IsSolidDragging())
+ {
+ delete[] mpPolygons;
+ }
+}
+
+void Impl3DMirrorConstructOverlay::SetMirrorAxis(Point aMirrorAxisA, Point aMirrorAxisB)
+{
+ // get rid of old overlay objects
+ maObjects.clear();
+
+ // create new ones
+ for(sal_uInt32 a(0); a < mrView.PaintWindowCount(); a++)
+ {
+ SdrPaintWindow* pCandidate = mrView.GetPaintWindow(a);
+ ::sdr::overlay::OverlayManager* pTargetOverlay = pCandidate->GetOverlayManager();
+
+ if(pTargetOverlay)
+ {
+ // buld transfoprmation: translate and rotate so that given edge is
+ // on x axis, them mirror in y and translate back
+ const basegfx::B2DVector aEdge(aMirrorAxisB.X() - aMirrorAxisA.X(), aMirrorAxisB.Y() - aMirrorAxisA.Y());
+ basegfx::B2DHomMatrix aMatrixTransform(basegfx::tools::createTranslateB2DHomMatrix(
+ -aMirrorAxisA.X(), -aMirrorAxisA.Y()));
+ aMatrixTransform.rotate(-atan2(aEdge.getY(), aEdge.getX()));
+ aMatrixTransform.scale(1.0, -1.0);
+ aMatrixTransform.rotate(atan2(aEdge.getY(), aEdge.getX()));
+ aMatrixTransform.translate(aMirrorAxisA.X(), aMirrorAxisA.Y());
+
+ if(mrView.IsSolidDragging())
+ {
+ if(maFullOverlay.hasElements())
+ {
+ drawinglayer::primitive2d::Primitive2DSequence aContent(maFullOverlay);
+
+ if(!aMatrixTransform.isIdentity())
+ {
+ // embed in transformation group
+ drawinglayer::primitive2d::Primitive2DReference aTransformPrimitive2D(new drawinglayer::primitive2d::TransformPrimitive2D(aMatrixTransform, aContent));
+ aContent = drawinglayer::primitive2d::Primitive2DSequence(&aTransformPrimitive2D, 1);
+ }
+
+ // if we have full overlay from selected objects, embed with 50% transparence, the
+ // transformation is added to the OverlayPrimitive2DSequenceObject
+ drawinglayer::primitive2d::Primitive2DReference aUnifiedTransparencePrimitive2D(new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(aContent, 0.5));
+ aContent = drawinglayer::primitive2d::Primitive2DSequence(&aUnifiedTransparencePrimitive2D, 1);
+
+ sdr::overlay::OverlayPrimitive2DSequenceObject* pNew = new sdr::overlay::OverlayPrimitive2DSequenceObject(aContent);
+
+ pTargetOverlay->add(*pNew);
+ maObjects.append(*pNew);
+ }
+ }
+ else
+ {
+ for(sal_uInt32 b(0); b < mnCount; b++)
+ {
+ // apply to polygon
+ basegfx::B2DPolyPolygon aPolyPolygon(mpPolygons[b]);
+ aPolyPolygon.transform(aMatrixTransform);
+
+ ::sdr::overlay::OverlayPolyPolygonStriped* pNew = new ::sdr::overlay::OverlayPolyPolygonStriped(aPolyPolygon);
+ pTargetOverlay->add(*pNew);
+ maObjects.append(*pNew);
+ }
+ }
+ }
+ }
+}
+
+/*************************************************************************
+|*
+|* Konstruktor 1
+|*
+\************************************************************************/
+
+E3dView::E3dView(SdrModel* pModel, OutputDevice* pOut) :
+ SdrView(pModel, pOut)
+{
+ InitView ();
+}
+
+/*************************************************************************
+|*
+|* DrawMarkedObj ueberladen, da eventuell nur einzelne 3D-Objekte
+|* gezeichnet werden sollen
+|*
+\************************************************************************/
+
+void E3dView::DrawMarkedObj(OutputDevice& rOut) const
+{
+ // Existieren 3D-Objekte, deren Szenen nicht selektiert sind?
+ sal_Bool bSpecialHandling = sal_False;
+ E3dScene *pScene = NULL;
+
+ long nCnt = GetMarkedObjectCount();
+ for(long nObjs = 0;nObjs < nCnt;nObjs++)
+ {
+ SdrObject *pObj = GetMarkedObjectByIndex(nObjs);
+ if(pObj && pObj->ISA(E3dCompoundObject))
+ {
+ // zugehoerige Szene
+ pScene = ((E3dCompoundObject*)pObj)->GetScene();
+ if(pScene && !IsObjMarked(pScene))
+ bSpecialHandling = sal_True;
+ }
+ // Alle SelectionFlags zuruecksetzen
+ if(pObj && pObj->ISA(E3dObject))
+ {
+ pScene = ((E3dObject*)pObj)->GetScene();
+ if(pScene)
+ pScene->SetSelected(sal_False);
+ }
+ }
+
+ if(bSpecialHandling)
+ {
+ // SelectionFlag bei allen zu 3D Objekten gehoerigen
+ // Szenen und deren Objekten auf nicht selektiert setzen
+ long nObjs;
+ for(nObjs = 0;nObjs < nCnt;nObjs++)
+ {
+ SdrObject *pObj = GetMarkedObjectByIndex(nObjs);
+ if(pObj && pObj->ISA(E3dCompoundObject))
+ {
+ // zugehoerige Szene
+ pScene = ((E3dCompoundObject*)pObj)->GetScene();
+ if(pScene)
+ pScene->SetSelected(sal_False);
+ }
+ }
+
+ for(nObjs = 0;nObjs < nCnt;nObjs++)
+ {
+ SdrObject *pObj = GetMarkedObjectByIndex(nObjs);
+ if(pObj && pObj->ISA(E3dObject))
+ {
+ // Objekt markieren
+ E3dObject* p3DObj = (E3dObject*)pObj;
+ p3DObj->SetSelected(sal_True);
+ pScene = p3DObj->GetScene();
+ }
+ }
+
+ if(pScene)
+ {
+ // code from parent
+ SortMarkedObjects();
+
+ pScene->SetDrawOnlySelected(sal_True);
+ pScene->SingleObjectPainter(rOut); // #110094#-17
+ pScene->SetDrawOnlySelected(sal_False);
+ }
+
+ // SelectionFlag zuruecksetzen
+ for(nObjs = 0;nObjs < nCnt;nObjs++)
+ {
+ SdrObject *pObj = GetMarkedObjectByIndex(nObjs);
+ if(pObj && pObj->ISA(E3dCompoundObject))
+ {
+ // zugehoerige Szene
+ pScene = ((E3dCompoundObject*)pObj)->GetScene();
+ if(pScene)
+ pScene->SetSelected(sal_False);
+ }
+ }
+ }
+ else
+ {
+ // call parent
+ SdrExchangeView::DrawMarkedObj(rOut);
+ }
+}
+
+/*************************************************************************
+|*
+|* Model holen ueberladen, da bei einzelnen 3D Objekten noch eine Szene
+|* untergeschoben werden muss
+|*
+\************************************************************************/
+
+SdrModel* E3dView::GetMarkedObjModel() const
+{
+ // Existieren 3D-Objekte, deren Szenen nicht selektiert sind?
+ bool bSpecialHandling(false);
+ const sal_uInt32 nCount(GetMarkedObjectCount());
+ sal_uInt32 nObjs(0);
+ E3dScene *pScene = 0;
+
+ for(nObjs = 0; nObjs < nCount; nObjs++)
+ {
+ const SdrObject* pObj = GetMarkedObjectByIndex(nObjs);
+
+ if(!bSpecialHandling && pObj && pObj->ISA(E3dCompoundObject))
+ {
+ // if the object is selected, but it's scene not,
+ // we need special handling
+ pScene = ((E3dCompoundObject*)pObj)->GetScene();
+
+ if(pScene && !IsObjMarked(pScene))
+ {
+ bSpecialHandling = true;
+ }
+ }
+
+ if(pObj && pObj->ISA(E3dObject))
+ {
+ // reset all selection flags at 3D objects
+ pScene = ((E3dObject*)pObj)->GetScene();
+
+ if(pScene)
+ {
+ pScene->SetSelected(false);
+ }
+ }
+ }
+
+ if(!bSpecialHandling)
+ {
+ // call parent
+ return SdrView::GetMarkedObjModel();
+ }
+
+ SdrModel* pNewModel = 0;
+ Rectangle aSelectedSnapRect;
+
+ // set 3d selection flags at all directly selected objects
+ // and collect SnapRect of selected objects
+ for(nObjs = 0; nObjs < nCount; nObjs++)
+ {
+ SdrObject *pObj = GetMarkedObjectByIndex(nObjs);
+
+ if(pObj && pObj->ISA(E3dCompoundObject))
+ {
+ // mark object, but not scenes
+ E3dCompoundObject* p3DObj = (E3dCompoundObject*)pObj;
+ p3DObj->SetSelected(true);
+ aSelectedSnapRect.Union(p3DObj->GetSnapRect());
+ }
+ }
+
+ // create new mark list which contains all indirectly selected3d
+ // scenes as selected objects
+ SdrMarkList aOldML(GetMarkedObjectList());
+ SdrMarkList aNewML;
+ SdrMarkList& rCurrentMarkList = ((E3dView*)this)->GetMarkedObjectListWriteAccess();
+ rCurrentMarkList = aNewML;
+
+ for(nObjs = 0; nObjs < nCount; nObjs++)
+ {
+ SdrObject *pObj = aOldML.GetMark(nObjs)->GetMarkedSdrObj();
+
+ if(pObj && pObj->ISA(E3dObject))
+ {
+ pScene = ((E3dObject*)pObj)->GetScene();
+
+ if(pScene && !IsObjMarked(pScene) && GetSdrPageView())
+ {
+ ((E3dView*)this)->MarkObj(pScene, GetSdrPageView(), sal_False, sal_True);
+ }
+ }
+ }
+
+ // call parent. This will copy all scenes and the selection flags at the 3d objectss. So
+ // it will be possible to delete all non-selected 3d objects from the cloned 3d scenes
+ pNewModel = SdrView::GetMarkedObjModel();
+
+ if(pNewModel)
+ {
+ for(sal_uInt16 nPg(0); nPg < pNewModel->GetPageCount(); nPg++)
+ {
+ const SdrPage* pSrcPg=pNewModel->GetPage(nPg);
+ const sal_uInt32 nObAnz(pSrcPg->GetObjCount());
+
+ for(sal_uInt32 nOb(0); nOb < nObAnz; nOb++)
+ {
+ const SdrObject* pSrcOb=pSrcPg->GetObj(nOb);
+
+ if(pSrcOb->ISA(E3dScene))
+ {
+ pScene = (E3dScene*)pSrcOb;
+
+ // delete all not intentionally cloned 3d objects
+ pScene->removeAllNonSelectedObjects();
+
+ // reset select flags and set SnapRect of all selected objects
+ pScene->SetSelected(false);
+ pScene->SetSnapRect(aSelectedSnapRect);
+ }
+ }
+ }
+ }
+
+ // restore old selection
+ rCurrentMarkList = aOldML;
+
+ // model zurueckgeben
+ return pNewModel;
+}
+
+/*************************************************************************
+|*
+|* Bei Paste muss - falls in eine Scene eingefuegt wird - die
+|* Objekte der Szene eingefuegt werden, die Szene selbst aber nicht
+|*
+\************************************************************************/
+
+sal_Bool E3dView::Paste(const SdrModel& rMod, const Point& rPos, SdrObjList* pLst, sal_uInt32 nOptions)
+{
+ sal_Bool bRetval = sal_False;
+
+ // Liste holen
+ Point aPos(rPos);
+ SdrObjList* pDstList = pLst;
+ ImpGetPasteObjList(aPos, pDstList);
+
+ if(!pDstList)
+ return sal_False;
+
+ // Owner der Liste holen
+ SdrObject* pOwner = pDstList->GetOwnerObj();
+ if(pOwner && pOwner->ISA(E3dScene))
+ {
+ E3dScene* pDstScene = (E3dScene*)pOwner;
+ BegUndo(SVX_RESSTR(RID_SVX_3D_UNDO_EXCHANGE_PASTE));
+
+ // Alle Objekte aus E3dScenes kopieren und direkt einfuegen
+ for(sal_uInt16 nPg(0); nPg < rMod.GetPageCount(); nPg++)
+ {
+ const SdrPage* pSrcPg=rMod.GetPage(nPg);
+ sal_uInt32 nObAnz(pSrcPg->GetObjCount());
+
+ // calculate offset for paste
+ Rectangle aR = pSrcPg->GetAllObjBoundRect();
+ Point aDist(aPos - aR.Center());
+
+ // Unterobjekte von Szenen einfuegen
+ for(sal_uInt32 nOb(0); nOb < nObAnz; nOb++)
+ {
+ const SdrObject* pSrcOb = pSrcPg->GetObj(nOb);
+ if(pSrcOb->ISA(E3dScene))
+ {
+ E3dScene* pSrcScene = (E3dScene*)pSrcOb;
+ ImpCloneAll3DObjectsToDestScene(pSrcScene, pDstScene, aDist);
+ }
+ }
+ }
+ EndUndo();
+ }
+ else
+ {
+ // call parent
+ bRetval = SdrView::Paste(rMod, rPos, pLst, nOptions);
+ }
+
+ // und Rueckgabewert liefern
+ return bRetval;
+}
+
+// #83403# Service routine used from local Clone() and from SdrCreateView::EndCreateObj(...)
+sal_Bool E3dView::ImpCloneAll3DObjectsToDestScene(E3dScene* pSrcScene, E3dScene* pDstScene, Point /*aOffset*/)
+{
+ sal_Bool bRetval(sal_False);
+
+ if(pSrcScene && pDstScene)
+ {
+ const sdr::contact::ViewContactOfE3dScene& rVCSceneDst = static_cast< sdr::contact::ViewContactOfE3dScene& >(pDstScene->GetViewContact());
+ const drawinglayer::geometry::ViewInformation3D aViewInfo3DDst(rVCSceneDst.getViewInformation3D());
+ const sdr::contact::ViewContactOfE3dScene& rVCSceneSrc = static_cast< sdr::contact::ViewContactOfE3dScene& >(pSrcScene->GetViewContact());
+ const drawinglayer::geometry::ViewInformation3D aViewInfo3DSrc(rVCSceneSrc.getViewInformation3D());
+
+ for(sal_uInt32 i(0); i < pSrcScene->GetSubList()->GetObjCount(); i++)
+ {
+ E3dCompoundObject* pCompoundObj = dynamic_cast< E3dCompoundObject* >(pSrcScene->GetSubList()->GetObj(i));
+
+ if(pCompoundObj)
+ {
+ // #116235#
+ E3dCompoundObject* pNewCompoundObj = dynamic_cast< E3dCompoundObject* >(pCompoundObj->Clone());
+
+ if(pNewCompoundObj)
+ {
+ // get dest scene's current range in 3D world coordinates
+ const basegfx::B3DHomMatrix aSceneToWorldTrans(pDstScene->GetFullTransform());
+ basegfx::B3DRange aSceneRange(pDstScene->GetBoundVolume());
+ aSceneRange.transform(aSceneToWorldTrans);
+
+ // get new object's implied object transformation
+ const basegfx::B3DHomMatrix aNewObjectTrans(pNewCompoundObj->GetTransform());
+
+ // get new object's range in 3D world coordinates in dest scene
+ // as if it were already added
+ const basegfx::B3DHomMatrix aObjectToWorldTrans(aSceneToWorldTrans * aNewObjectTrans);
+ basegfx::B3DRange aObjectRange(pNewCompoundObj->GetBoundVolume());
+ aObjectRange.transform(aObjectToWorldTrans);
+
+ // get scale adaption
+ const basegfx::B3DVector aSceneScale(aSceneRange.getRange());
+ const basegfx::B3DVector aObjectScale(aObjectRange.getRange());
+ double fScale(1.0);
+
+ // if new object's size in X,Y or Z is bigger that 80% of dest scene, adapt scale
+ // to not change the scene by the inserted object
+ const double fSizeFactor(0.5);
+
+ if(aObjectScale.getX() * fScale > aSceneScale.getX() * fSizeFactor)
+ {
+ const double fObjSize(aObjectScale.getX() * fScale);
+ const double fFactor((aSceneScale.getX() * fSizeFactor) / (basegfx::fTools::equalZero(fObjSize) ? 1.0 : fObjSize));
+ fScale *= fFactor;
+ }
+
+ if(aObjectScale.getY() * fScale > aSceneScale.getY() * fSizeFactor)
+ {
+ const double fObjSize(aObjectScale.getY() * fScale);
+ const double fFactor((aSceneScale.getY() * fSizeFactor) / (basegfx::fTools::equalZero(fObjSize) ? 1.0 : fObjSize));
+ fScale *= fFactor;
+ }
+
+ if(aObjectScale.getZ() * fScale > aSceneScale.getZ() * fSizeFactor)
+ {
+ const double fObjSize(aObjectScale.getZ() * fScale);
+ const double fFactor((aSceneScale.getZ() * fSizeFactor) / (basegfx::fTools::equalZero(fObjSize) ? 1.0 : fObjSize));
+ fScale *= fFactor;
+ }
+
+ // get translation adaption
+ const basegfx::B3DPoint aSceneCenter(aSceneRange.getCenter());
+ const basegfx::B3DPoint aObjectCenter(aObjectRange.getCenter());
+
+ // build full modification transform. The object's transformation
+ // shall be modified, so start at object coordinates; transform to 3d world coor
+ basegfx::B3DHomMatrix aModifyingTransform(aObjectToWorldTrans);
+
+ // translate to absolute center in 3d world coor
+ aModifyingTransform.translate(-aObjectCenter.getX(), -aObjectCenter.getY(), -aObjectCenter.getZ());
+
+ // scale to dest size in 3d world coor
+ aModifyingTransform.scale(fScale, fScale, fScale);
+
+ // translate to dest scene center in 3d world coor
+ aModifyingTransform.translate(aSceneCenter.getX(), aSceneCenter.getY(), aSceneCenter.getZ());
+
+ // transform from 3d world to dest object coordinates
+ basegfx::B3DHomMatrix aWorldToObject(aObjectToWorldTrans);
+ aWorldToObject.invert();
+ aModifyingTransform = aWorldToObject * aModifyingTransform;
+
+ // correct implied object transform by applying changing one in object coor
+ pNewCompoundObj->SetTransform(aModifyingTransform * aNewObjectTrans);
+
+ // fill and insert new object
+ pNewCompoundObj->SetModel(pDstScene->GetModel());
+ pNewCompoundObj->SetPage(pDstScene->GetPage());
+ pNewCompoundObj->NbcSetLayer(pCompoundObj->GetLayer());
+ pNewCompoundObj->NbcSetStyleSheet(pCompoundObj->GetStyleSheet(), sal_True);
+ pDstScene->Insert3DObj(pNewCompoundObj);
+ bRetval = sal_True;
+
+ // Undo anlegen
+ if( GetModel()->IsUndoEnabled() )
+ AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoNewObject(*pNewCompoundObj));
+ }
+ }
+ }
+ }
+
+ return bRetval;
+}
+
+/*************************************************************************
+|*
+|* 3D-Konvertierung moeglich?
+|*
+\************************************************************************/
+
+sal_Bool E3dView::IsConvertTo3DObjPossible() const
+{
+ sal_Bool bAny3D(sal_False);
+ sal_Bool bGroupSelected(sal_False);
+ sal_Bool bRetval(sal_True);
+
+ for(sal_uInt32 a=0;!bAny3D && a<GetMarkedObjectCount();a++)
+ {
+ SdrObject *pObj = GetMarkedObjectByIndex(a);
+ if(pObj)
+ {
+ ImpIsConvertTo3DPossible(pObj, bAny3D, bGroupSelected);
+ }
+ }
+
+ bRetval = !bAny3D
+ && (
+ IsConvertToPolyObjPossible(sal_False)
+ || IsConvertToPathObjPossible(sal_False)
+ || IsImportMtfPossible());
+ return bRetval;
+}
+
+void E3dView::ImpIsConvertTo3DPossible(SdrObject* pObj, sal_Bool& rAny3D,
+ sal_Bool& rGroupSelected) const
+{
+ if(pObj)
+ {
+ if(pObj->ISA(E3dObject))
+ {
+ rAny3D = sal_True;
+ }
+ else
+ {
+ if(pObj->IsGroupObject())
+ {
+ SdrObjListIter aIter(*pObj, IM_DEEPNOGROUPS);
+ while(aIter.IsMore())
+ {
+ SdrObject* pNewObj = aIter.Next();
+ ImpIsConvertTo3DPossible(pNewObj, rAny3D, rGroupSelected);
+ }
+ rGroupSelected = sal_True;
+ }
+ }
+ }
+}
+
+/*************************************************************************
+|*
+|* 3D-Konvertierung zu Extrude ausfuehren
+|*
+\************************************************************************/
+#include <editeng/eeitem.hxx>
+
+void E3dView::ImpChangeSomeAttributesFor3DConversion(SdrObject* pObj)
+{
+ if(pObj->ISA(SdrTextObj))
+ {
+ const SfxItemSet& rSet = pObj->GetMergedItemSet();
+ const SvxColorItem& rTextColorItem = (const SvxColorItem&)rSet.Get(EE_CHAR_COLOR);
+ if(rTextColorItem.GetValue() == RGB_Color(COL_BLACK))
+ {
+ // Bei schwarzen Textobjekten wird die Farbe auf grau gesetzt
+ if(pObj->GetPage())
+ {
+ // #84864# if black is only default attribute from
+ // pattern set it hard so that it is used in undo.
+ pObj->SetMergedItem(SvxColorItem(RGB_Color(COL_BLACK), EE_CHAR_COLOR));
+
+ // add undo now
+ if( GetModel()->IsUndoEnabled() )
+ AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoAttrObject(*pObj, false, false));
+ }
+
+ pObj->SetMergedItem(SvxColorItem(RGB_Color(COL_GRAY), EE_CHAR_COLOR));
+ }
+ }
+}
+
+void E3dView::ImpChangeSomeAttributesFor3DConversion2(SdrObject* pObj)
+{
+ if(pObj->ISA(SdrPathObj))
+ {
+ const SfxItemSet& rSet = pObj->GetMergedItemSet();
+ sal_Int32 nLineWidth = ((const XLineWidthItem&)(rSet.Get(XATTR_LINEWIDTH))).GetValue();
+ XLineStyle eLineStyle = (XLineStyle)((const XLineStyleItem&)rSet.Get(XATTR_LINESTYLE)).GetValue();
+ XFillStyle eFillStyle = ITEMVALUE(rSet, XATTR_FILLSTYLE, XFillStyleItem);
+
+ if(((SdrPathObj*)pObj)->IsClosed()
+ && eLineStyle == XLINE_SOLID
+ && !nLineWidth
+ && eFillStyle != XFILL_NONE)
+ {
+ if(pObj->GetPage() && GetModel()->IsUndoEnabled() )
+ AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoAttrObject(*pObj, false, false));
+ pObj->SetMergedItem(XLineStyleItem(XLINE_NONE));
+ pObj->SetMergedItem(XLineWidthItem(0L));
+ }
+ }
+}
+
+void E3dView::ImpCreateSingle3DObjectFlat(E3dScene* pScene, SdrObject* pObj, sal_Bool bExtrude, double fDepth, basegfx::B2DHomMatrix& rLatheMat)
+{
+ // Einzelnes PathObject, dieses umwanden
+ SdrPathObj* pPath = PTR_CAST(SdrPathObj, pObj);
+
+ if(pPath)
+ {
+ E3dDefaultAttributes aDefault = Get3DDefaultAttributes();
+ if(bExtrude)
+ aDefault.SetDefaultExtrudeCharacterMode(sal_True);
+ else
+ aDefault.SetDefaultLatheCharacterMode(sal_True);
+
+ // ItemSet des Ursprungsobjektes holen
+ SfxItemSet aSet(pObj->GetMergedItemSet());
+
+ XFillStyle eFillStyle = ITEMVALUE(aSet, XATTR_FILLSTYLE, XFillStyleItem);
+
+ // Linienstil ausschalten
+ aSet.Put(XLineStyleItem(XLINE_NONE));
+
+ // Feststellen, ob ein FILL_Attribut gesetzt ist.
+ if(!pPath->IsClosed() || eFillStyle == XFILL_NONE)
+ {
+ // Das SdrPathObj ist nicht gefuellt, lasse die
+ // vordere und hintere Flaeche weg. Ausserdem ist
+ // eine beidseitige Darstellung notwendig.
+ aDefault.SetDefaultExtrudeCloseFront(sal_False);
+ aDefault.SetDefaultExtrudeCloseBack(sal_False);
+
+ aSet.Put(Svx3DDoubleSidedItem(sal_True));
+
+ // Fuellattribut setzen
+ aSet.Put(XFillStyleItem(XFILL_SOLID));
+
+ // Fuellfarbe muss auf Linienfarbe, da das Objekt vorher
+ // nur eine Linie war
+ Color aColorLine = ((const XLineColorItem&)(aSet.Get(XATTR_LINECOLOR))).GetColorValue();
+ aSet.Put(XFillColorItem(String(), aColorLine));
+ }
+
+ // Neues Extrude-Objekt erzeugen
+ E3dObject* p3DObj = NULL;
+ if(bExtrude)
+ {
+ p3DObj = new E3dExtrudeObj(aDefault, pPath->GetPathPoly(), fDepth);
+ }
+ else
+ {
+ basegfx::B2DPolyPolygon aPolyPoly2D(pPath->GetPathPoly());
+ aPolyPoly2D.transform(rLatheMat);
+ p3DObj = new E3dLatheObj(aDefault, aPolyPoly2D);
+ }
+
+ // Attribute setzen
+ if(p3DObj)
+ {
+ p3DObj->NbcSetLayer(pObj->GetLayer());
+
+ p3DObj->SetMergedItemSet(aSet);
+
+ p3DObj->NbcSetStyleSheet(pObj->GetStyleSheet(), sal_True);
+
+ // Neues 3D-Objekt einfuegen
+ pScene->Insert3DObj(p3DObj);
+ }
+ }
+}
+
+void E3dView::ImpCreate3DObject(E3dScene* pScene, SdrObject* pObj, sal_Bool bExtrude, double fDepth, basegfx::B2DHomMatrix& rLatheMat)
+{
+ if(pObj)
+ {
+ // change text color attribute for not so dark colors
+ if(pObj->IsGroupObject())
+ {
+ SdrObjListIter aIter(*pObj, IM_DEEPWITHGROUPS);
+ while(aIter.IsMore())
+ {
+ SdrObject* pGroupMember = aIter.Next();
+ ImpChangeSomeAttributesFor3DConversion(pGroupMember);
+ }
+ }
+ else
+ ImpChangeSomeAttributesFor3DConversion(pObj);
+
+ // convert completely to path objects
+ SdrObject* pNewObj1 = pObj->ConvertToPolyObj(sal_False, sal_False);
+
+ if(pNewObj1)
+ {
+ // change text color attribute for not so dark colors
+ if(pNewObj1->IsGroupObject())
+ {
+ SdrObjListIter aIter(*pNewObj1, IM_DEEPWITHGROUPS);
+ while(aIter.IsMore())
+ {
+ SdrObject* pGroupMember = aIter.Next();
+ ImpChangeSomeAttributesFor3DConversion2(pGroupMember);
+ }
+ }
+ else
+ ImpChangeSomeAttributesFor3DConversion2(pNewObj1);
+
+ // convert completely to path objects
+ SdrObject* pNewObj2 = pObj->ConvertToContourObj(pNewObj1, sal_True);
+
+ if(pNewObj2)
+ {
+ // add all to flat scene
+ if(pNewObj2->IsGroupObject())
+ {
+ SdrObjListIter aIter(*pNewObj2, IM_DEEPWITHGROUPS);
+ while(aIter.IsMore())
+ {
+ SdrObject* pGroupMember = aIter.Next();
+ ImpCreateSingle3DObjectFlat(pScene, pGroupMember, bExtrude, fDepth, rLatheMat);
+ }
+ }
+ else
+ ImpCreateSingle3DObjectFlat(pScene, pNewObj2, bExtrude, fDepth, rLatheMat);
+
+ // delete zwi object
+ if(pNewObj2 != pObj && pNewObj2 != pNewObj1 && pNewObj2)
+ SdrObject::Free( pNewObj2 );
+ }
+
+ // delete zwi object
+ if(pNewObj1 != pObj && pNewObj1)
+ SdrObject::Free( pNewObj1 );
+ }
+ }
+}
+
+/*************************************************************************
+|*
+|* 3D-Konvertierung zu Extrude steuern
+|*
+\************************************************************************/
+
+void E3dView::ConvertMarkedObjTo3D(sal_Bool bExtrude, basegfx::B2DPoint aPnt1, basegfx::B2DPoint aPnt2)
+{
+ if(AreObjectsMarked())
+ {
+ // Undo anlegen
+ if(bExtrude)
+ BegUndo(SVX_RESSTR(RID_SVX_3D_UNDO_EXTRUDE));
+ else
+ BegUndo(SVX_RESSTR(RID_SVX_3D_UNDO_LATHE));
+
+ // Neue Szene fuer zu erzeugende 3D-Objekte anlegen
+ E3dScene* pScene = new E3dPolyScene(Get3DDefaultAttributes());
+
+ // Rechteck bestimmen und evtl. korrigieren
+ Rectangle aRect = GetAllMarkedRect();
+ if(aRect.GetWidth() <= 1)
+ aRect.SetSize(Size(500, aRect.GetHeight()));
+ if(aRect.GetHeight() <= 1)
+ aRect.SetSize(Size(aRect.GetWidth(), 500));
+
+ // Tiefe relativ zur Groesse der Selektion bestimmen
+ double fDepth = 0.0;
+ double fRot3D = 0.0;
+ basegfx::B2DHomMatrix aLatheMat;
+
+ if(bExtrude)
+ {
+ double fW = (double)aRect.GetWidth();
+ double fH = (double)aRect.GetHeight();
+ fDepth = sqrt(fW*fW + fH*fH) / 6.0;
+ }
+ if(!bExtrude)
+ {
+ // Transformation fuer Polygone Rotationskoerper erstellen
+ if(aPnt1 != aPnt2)
+ {
+ // Rotation um Kontrollpunkt1 mit eigestelltem Winkel
+ // fuer 3D Koordinaten
+ basegfx::B2DPoint aDiff(aPnt1 - aPnt2);
+ fRot3D = atan2(aDiff.getY(), aDiff.getX()) - F_PI2;
+
+ if(basegfx::fTools::equalZero(fabs(fRot3D)))
+ fRot3D = 0.0;
+
+ if(fRot3D != 0.0)
+ {
+ aLatheMat = basegfx::tools::createRotateAroundPoint(aPnt2, -fRot3D)
+ * aLatheMat;
+ }
+ }
+
+ if(aPnt2.getX() != 0.0)
+ {
+ // Translation auf Y=0 - Achse
+ aLatheMat.translate(-aPnt2.getX(), 0.0);
+ }
+ else
+ {
+ aLatheMat.translate((double)-aRect.Left(), 0.0);
+ }
+
+ // Inverse Matrix bilden, um die Zielausdehnung zu bestimmen
+ basegfx::B2DHomMatrix aInvLatheMat(aLatheMat);
+ aInvLatheMat.invert();
+
+ // SnapRect Ausdehnung mittels Spiegelung an der Rotationsachse
+ // erweitern
+ for(sal_uInt32 a=0;a<GetMarkedObjectCount();a++)
+ {
+ SdrMark* pMark = GetSdrMarkByIndex(a);
+ SdrObject* pObj = pMark->GetMarkedSdrObj();
+ Rectangle aTurnRect = pObj->GetSnapRect();
+ basegfx::B2DPoint aRot;
+ Point aRotPnt;
+
+ aRot = basegfx::B2DPoint(aTurnRect.Left(), -aTurnRect.Top());
+ aRot *= aLatheMat;
+ aRot.setX(-aRot.getX());
+ aRot *= aInvLatheMat;
+ aRotPnt = Point((long)(aRot.getX() + 0.5), (long)(-aRot.getY() - 0.5));
+ aRect.Union(Rectangle(aRotPnt, aRotPnt));
+
+ aRot = basegfx::B2DPoint(aTurnRect.Left(), -aTurnRect.Bottom());
+ aRot *= aLatheMat;
+ aRot.setX(-aRot.getX());
+ aRot *= aInvLatheMat;
+ aRotPnt = Point((long)(aRot.getX() + 0.5), (long)(-aRot.getY() - 0.5));
+ aRect.Union(Rectangle(aRotPnt, aRotPnt));
+
+ aRot = basegfx::B2DPoint(aTurnRect.Right(), -aTurnRect.Top());
+ aRot *= aLatheMat;
+ aRot.setX(-aRot.getX());
+ aRot *= aInvLatheMat;
+ aRotPnt = Point((long)(aRot.getX() + 0.5), (long)(-aRot.getY() - 0.5));
+ aRect.Union(Rectangle(aRotPnt, aRotPnt));
+
+ aRot = basegfx::B2DPoint(aTurnRect.Right(), -aTurnRect.Bottom());
+ aRot *= aLatheMat;
+ aRot.setX(-aRot.getX());
+ aRot *= aInvLatheMat;
+ aRotPnt = Point((long)(aRot.getX() + 0.5), (long)(-aRot.getY() - 0.5));
+ aRect.Union(Rectangle(aRotPnt, aRotPnt));
+ }
+ }
+
+ // Ueber die Selektion gehen und in 3D wandeln, komplett mit
+ // Umwandeln in SdrPathObject, auch Schriften
+ for(sal_uInt32 a=0;a<GetMarkedObjectCount();a++)
+ {
+ SdrMark* pMark = GetSdrMarkByIndex(a);
+ SdrObject* pObj = pMark->GetMarkedSdrObj();
+
+ ImpCreate3DObject(pScene, pObj, bExtrude, fDepth, aLatheMat);
+ }
+
+ if(pScene->GetSubList() && pScene->GetSubList()->GetObjCount() != 0)
+ {
+ // Alle angelegten Objekte Tiefenarrangieren
+ if(bExtrude)
+ DoDepthArrange(pScene, fDepth);
+
+ // 3D-Objekte auf die Mitte des Gesamtrechtecks zentrieren
+ basegfx::B3DPoint aCenter(pScene->GetBoundVolume().getCenter());
+ basegfx::B3DHomMatrix aMatrix;
+
+ aMatrix.translate(-aCenter.getX(), -aCenter.getY(), -aCenter.getZ());
+ pScene->SetTransform(aMatrix * pScene->GetTransform()); // #112587#
+
+ // Szene initialisieren
+ pScene->NbcSetSnapRect(aRect);
+ basegfx::B3DRange aBoundVol = pScene->GetBoundVolume();
+ InitScene(pScene, (double)aRect.GetWidth(), (double)aRect.GetHeight(), aBoundVol.getDepth());
+
+ // Szene anstelle des ersten selektierten Objektes einfuegen
+ // und alle alten Objekte weghauen
+ SdrObject* pRepObj = GetMarkedObjectByIndex(0);
+ SdrPageView* pPV = GetSdrPageViewOfMarkedByIndex(0);
+ MarkObj(pRepObj, pPV, sal_True);
+ ReplaceObjectAtView(pRepObj, *pPV, pScene, sal_False);
+ DeleteMarked();
+ MarkObj(pScene, pPV);
+
+ // Rotationskoerper um Rotationsachse drehen
+ basegfx::B3DHomMatrix aRotate;
+
+ if(!bExtrude && fRot3D != 0.0)
+ {
+ aRotate.rotate(0.0, 0.0, fRot3D);
+ }
+
+ // Default-Rotation setzen
+ {
+ double XRotateDefault = 20;
+ aRotate.rotate(DEG2RAD(XRotateDefault), 0.0, 0.0);
+ }
+
+ if(!aRotate.isIdentity())
+ {
+ pScene->SetTransform(aRotate * pScene->GetTransform());
+ }
+
+ // SnapRects der Objekte ungueltig
+ pScene->SetSnapRect(aRect);
+ }
+ else
+ {
+ // Es wurden keine 3D Objekte erzeugt, schmeiss alles weg
+ delete pScene;
+ }
+
+ // Undo abschliessen
+ EndUndo();
+ }
+}
+
+/*************************************************************************
+|*
+|* Alle enthaltenen Extrude-Objekte Tiefenarrangieren
+|*
+\************************************************************************/
+
+struct E3dDepthNeighbour
+{
+ E3dDepthNeighbour* mpNext;
+ E3dExtrudeObj* mpObj;
+ basegfx::B2DPolyPolygon maPreparedPolyPolygon;
+
+ E3dDepthNeighbour()
+ : mpNext(0),
+ mpObj(0),
+ maPreparedPolyPolygon()
+ {
+ }
+};
+
+struct E3dDepthLayer
+{
+ E3dDepthLayer* mpDown;
+ E3dDepthNeighbour* mpNext;
+
+ E3dDepthLayer()
+ : mpDown(0),
+ mpNext(0)
+ {
+ }
+
+ ~E3dDepthLayer()
+ {
+ while(mpNext)
+ {
+ E3dDepthNeighbour* pSucc = mpNext->mpNext;
+ delete mpNext;
+ mpNext = pSucc;
+ }
+ }
+};
+
+void E3dView::DoDepthArrange(E3dScene* pScene, double fDepth)
+{
+ if(pScene && pScene->GetSubList() && pScene->GetSubList()->GetObjCount() > 1)
+ {
+ SdrObjList* pSubList = pScene->GetSubList();
+ SdrObjListIter aIter(*pSubList, IM_FLAT);
+ E3dDepthLayer* pBaseLayer = NULL;
+ E3dDepthLayer* pLayer = NULL;
+ sal_Int32 nNumLayers = 0;
+
+ while(aIter.IsMore())
+ {
+ E3dExtrudeObj* pExtrudeObj = dynamic_cast< E3dExtrudeObj* >(aIter.Next());
+
+ if(pExtrudeObj)
+ {
+ const basegfx::B2DPolyPolygon aExtrudePoly(
+ basegfx::tools::prepareForPolygonOperation(pExtrudeObj->GetExtrudePolygon()));
+ const SfxItemSet& rLocalSet = pExtrudeObj->GetMergedItemSet();
+ const XFillStyle eLocalFillStyle = ITEMVALUE(rLocalSet, XATTR_FILLSTYLE, XFillStyleItem);
+ const Color aLocalColor = ((const XFillColorItem&)(rLocalSet.Get(XATTR_FILLCOLOR))).GetColorValue();
+
+ // sort in ExtrudeObj
+ if(pLayer)
+ {
+ // do we have overlap with an object of this layer?
+ bool bOverlap(false);
+ E3dDepthNeighbour* pAct = pLayer->mpNext;
+
+ while(!bOverlap && pAct)
+ {
+ // do pAct->mpObj and pExtrudeObj overlap? Check by
+ // using logical AND clipping
+ const basegfx::B2DPolyPolygon aAndPolyPolygon(
+ basegfx::tools::solvePolygonOperationAnd(
+ aExtrudePoly,
+ pAct->maPreparedPolyPolygon));
+
+ bOverlap = (0 != aAndPolyPolygon.count());
+
+ if(bOverlap)
+ {
+ // second ciriteria: is another fillstyle or color used?
+ const SfxItemSet& rCompareSet = pAct->mpObj->GetMergedItemSet();
+
+ XFillStyle eCompareFillStyle = ITEMVALUE(rCompareSet, XATTR_FILLSTYLE, XFillStyleItem);
+
+ if(eLocalFillStyle == eCompareFillStyle)
+ {
+ if(eLocalFillStyle == XFILL_SOLID)
+ {
+ Color aCompareColor = ((const XFillColorItem&)(rCompareSet.Get(XATTR_FILLCOLOR))).GetColorValue();
+
+ if(aCompareColor == aLocalColor)
+ {
+ bOverlap = sal_False;
+ }
+ }
+ else if(eLocalFillStyle == XFILL_NONE)
+ {
+ bOverlap = sal_False;
+ }
+ }
+ }
+
+ pAct = pAct->mpNext;
+ }
+
+ if(bOverlap)
+ {
+ // yes, start a new layer
+ pLayer->mpDown = new E3dDepthLayer;
+ pLayer = pLayer->mpDown;
+ nNumLayers++;
+ pLayer->mpNext = new E3dDepthNeighbour;
+ pLayer->mpNext->mpObj = pExtrudeObj;
+ pLayer->mpNext->maPreparedPolyPolygon = aExtrudePoly;
+ }
+ else
+ {
+ // no, add to current layer
+ E3dDepthNeighbour* pNewNext = new E3dDepthNeighbour;
+ pNewNext->mpObj = pExtrudeObj;
+ pNewNext->maPreparedPolyPolygon = aExtrudePoly;
+ pNewNext->mpNext = pLayer->mpNext;
+ pLayer->mpNext = pNewNext;
+ }
+ }
+ else
+ {
+ // first layer ever
+ pBaseLayer = new E3dDepthLayer;
+ pLayer = pBaseLayer;
+ nNumLayers++;
+ pLayer->mpNext = new E3dDepthNeighbour;
+ pLayer->mpNext->mpObj = pExtrudeObj;
+ pLayer->mpNext->maPreparedPolyPolygon = aExtrudePoly;
+ }
+ }
+ }
+
+ // number of layers is done
+ if(nNumLayers > 1)
+ {
+ // need to be arranged
+ double fMinDepth = fDepth * 0.8;
+ double fStep = (fDepth - fMinDepth) / (double)nNumLayers;
+ pLayer = pBaseLayer;
+
+ while(pLayer)
+ {
+ // move along layer
+ E3dDepthNeighbour* pAct = pLayer->mpNext;
+
+ while(pAct)
+ {
+ // adapt extrude value
+ pAct->mpObj->SetMergedItem(SfxUInt32Item(SDRATTR_3DOBJ_DEPTH, sal_uInt32(fMinDepth + 0.5)));
+
+ // next
+ pAct = pAct->mpNext;
+ }
+
+ // next layer
+ pLayer = pLayer->mpDown;
+ fMinDepth += fStep;
+ }
+ }
+
+ // cleanup
+ while(pBaseLayer)
+ {
+ pLayer = pBaseLayer->mpDown;
+ delete pBaseLayer;
+ pBaseLayer = pLayer;
+ }
+ }
+}
+
+/*************************************************************************
+|*
+|* Drag beginnen, vorher ggf. Drag-Methode fuer 3D-Objekte erzeugen
+|*
+\************************************************************************/
+
+sal_Bool E3dView::BegDragObj(const Point& rPnt, OutputDevice* pOut,
+ SdrHdl* pHdl, short nMinMov,
+ SdrDragMethod* pForcedMeth)
+{
+ if(Is3DRotationCreationActive() && GetMarkedObjectCount())
+ {
+ // bestimme alle selektierten Polygone und gebe die gespiegelte Hilfsfigur aus
+ mpMirrorOverlay->SetMirrorAxis(aRef1, aRef2);
+ }
+ else
+ {
+ sal_Bool bOwnActionNecessary;
+ if (pHdl == NULL)
+ {
+ bOwnActionNecessary = sal_True;
+ }
+ else if (pHdl->IsVertexHdl() || pHdl->IsCornerHdl())
+ {
+ bOwnActionNecessary = sal_True;
+ }
+ else
+ {
+ bOwnActionNecessary = sal_False;
+ }
+
+ if(bOwnActionNecessary && GetMarkedObjectCount() >= 1)
+ {
+ E3dDragConstraint eConstraint = E3DDRAG_CONSTR_XYZ;
+ sal_Bool bThereAreRootScenes = sal_False;
+ sal_Bool bThereAre3DObjects = sal_False;
+ long nCnt = GetMarkedObjectCount();
+ for(long nObjs = 0;nObjs < nCnt;nObjs++)
+ {
+ SdrObject *pObj = GetMarkedObjectByIndex(nObjs);
+ if(pObj)
+ {
+ if(pObj->ISA(E3dScene) && ((E3dScene*)pObj)->GetScene() == pObj)
+ bThereAreRootScenes = sal_True;
+ if(pObj->ISA(E3dObject))
+ bThereAre3DObjects = sal_True;
+ }
+ }
+ if( bThereAre3DObjects )
+ {
+ eDragHdl = ( pHdl == NULL ? HDL_MOVE : pHdl->GetKind() );
+ switch ( eDragMode )
+ {
+ case SDRDRAG_ROTATE:
+ case SDRDRAG_SHEAR:
+ {
+ switch ( eDragHdl )
+ {
+ case HDL_LEFT:
+ case HDL_RIGHT:
+ {
+ eConstraint = E3DDRAG_CONSTR_X;
+ }
+ break;
+
+ case HDL_UPPER:
+ case HDL_LOWER:
+ {
+ eConstraint = E3DDRAG_CONSTR_Y;
+ }
+ break;
+
+ case HDL_UPLFT:
+ case HDL_UPRGT:
+ case HDL_LWLFT:
+ case HDL_LWRGT:
+ {
+ eConstraint = E3DDRAG_CONSTR_Z;
+ }
+ break;
+ default: break;
+ }
+
+ // die nicht erlaubten Rotationen ausmaskieren
+ eConstraint = E3dDragConstraint(eConstraint& eDragConstraint);
+ pForcedMeth = new E3dDragRotate(*this, GetMarkedObjectList(), eConstraint, IsSolidDragging());
+ }
+ break;
+
+ case SDRDRAG_MOVE:
+ {
+ if(!bThereAreRootScenes)
+ {
+ pForcedMeth = new E3dDragMove(*this, GetMarkedObjectList(), eDragHdl, eConstraint, IsSolidDragging());
+ }
+ }
+ break;
+
+ // spaeter mal
+ case SDRDRAG_MIRROR:
+ case SDRDRAG_CROOK:
+ case SDRDRAG_DISTORT:
+ case SDRDRAG_TRANSPARENCE:
+ case SDRDRAG_GRADIENT:
+ default:
+ {
+ }
+ break;
+ }
+ }
+ }
+ }
+ return SdrView::BegDragObj(rPnt, pOut, pHdl, nMinMov, pForcedMeth);
+}
+
+/*************************************************************************
+|*
+|* Pruefen, obj 3D-Szene markiert ist
+|*
+\************************************************************************/
+
+sal_Bool E3dView::HasMarkedScene()
+{
+ return (GetMarkedScene() != NULL);
+}
+
+/*************************************************************************
+|*
+|* Pruefen, obj 3D-Szene markiert ist
+|*
+\************************************************************************/
+
+E3dScene* E3dView::GetMarkedScene()
+{
+ sal_uIntPtr nCnt = GetMarkedObjectCount();
+
+ for ( sal_uIntPtr i = 0; i < nCnt; i++ )
+ if ( GetMarkedObjectByIndex(i)->ISA(E3dScene) )
+ return (E3dScene*) GetMarkedObjectByIndex(i);
+
+ return NULL;
+}
+
+/*************************************************************************
+|*
+|* aktuelles 3D-Zeichenobjekt setzen, dafuer Szene erzeugen
+|*
+\************************************************************************/
+
+E3dScene* E3dView::SetCurrent3DObj(E3dObject* p3DObj)
+{
+ DBG_ASSERT(p3DObj != NULL, "Nana, wer steckt denn hier 'nen NULL-Zeiger rein?");
+ E3dScene* pScene = NULL;
+
+ // get transformed BoundVolume of the object
+ basegfx::B3DRange aVolume(p3DObj->GetBoundVolume());
+ aVolume.transform(p3DObj->GetTransform());
+ double fW(aVolume.getWidth());
+ double fH(aVolume.getHeight());
+
+ Rectangle aRect(0,0, (long) fW, (long) fH);
+
+ pScene = new E3dPolyScene(Get3DDefaultAttributes());
+
+ InitScene(pScene, fW, fH, aVolume.getMaxZ() + ((fW + fH) / 4.0));
+
+ pScene->Insert3DObj(p3DObj);
+ pScene->NbcSetSnapRect(aRect);
+
+ return pScene;
+}
+
+/*************************************************************************
+|*
+|* neu erzeugte Szene initialisieren
+|*
+\************************************************************************/
+
+void E3dView::InitScene(E3dScene* pScene, double fW, double fH, double fCamZ)
+{
+ Camera3D aCam(pScene->GetCamera());
+
+ aCam.SetAutoAdjustProjection(sal_False);
+ aCam.SetViewWindow(- fW / 2, - fH / 2, fW, fH);
+ basegfx::B3DPoint aLookAt;
+
+ double fDefaultCamPosZ = GetDefaultCamPosZ();
+ basegfx::B3DPoint aCamPos(0.0, 0.0, fCamZ < fDefaultCamPosZ ? fDefaultCamPosZ : fCamZ);
+
+ aCam.SetPosAndLookAt(aCamPos, aLookAt);
+ aCam.SetFocalLength(GetDefaultCamFocal());
+ aCam.SetDefaults(basegfx::B3DPoint(0.0, 0.0, fDefaultCamPosZ), aLookAt, GetDefaultCamFocal());
+ pScene->SetCamera(aCam);
+}
+
+/*************************************************************************
+|*
+|* startsequenz fuer die erstellung eines 3D-Rotationskoerpers
+|*
+\************************************************************************/
+
+void E3dView::Start3DCreation()
+{
+ if (GetMarkedObjectCount())
+ {
+ // irgendwelche Markierungen ermitteln und ausschalten
+ //HMHBOOL bVis = IsMarkHdlShown();
+
+ //HMHif (bVis) HideMarkHdl();
+
+ // bestimme die koordinaten fuer JOEs Mirrorachse
+ // entgegen der normalen Achse wird diese an die linke Seite des Objektes
+ // positioniert
+ long nOutMin = 0;
+ long nOutMax = 0;
+ long nMinLen = 0;
+ long nObjDst = 0;
+ long nOutHgt = 0;
+ OutputDevice* pOut = GetFirstOutputDevice(); //GetWin(0);
+
+ // erstmal Darstellungsgrenzen bestimmen
+ if (pOut != NULL)
+ {
+ nMinLen = pOut->PixelToLogic(Size(0,50)).Height();
+ nObjDst = pOut->PixelToLogic(Size(0,20)).Height();
+
+ long nDst = pOut->PixelToLogic(Size(0,10)).Height();
+
+ nOutMin = -pOut->GetMapMode().GetOrigin().Y();
+ nOutMax = pOut->GetOutputSize().Height() - 1 + nOutMin;
+ nOutMin += nDst;
+ nOutMax -= nDst;
+
+ if (nOutMax - nOutMin < nDst)
+ {
+ nOutMin += nOutMax + 1;
+ nOutMin /= 2;
+ nOutMin -= (nDst + 1) / 2;
+ nOutMax = nOutMin + nDst;
+ }
+
+ nOutHgt = nOutMax - nOutMin;
+
+ long nTemp = nOutHgt / 4;
+ if (nTemp > nMinLen) nMinLen = nTemp;
+ }
+
+ // und dann die Markierungen oben und unten an das Objekt heften
+ basegfx::B2DRange aR;
+ for(sal_uInt32 nMark(0L); nMark < GetMarkedObjectCount(); nMark++)
+ {
+ SdrObject* pMark = GetMarkedObjectByIndex(nMark);
+ basegfx::B2DPolyPolygon aXPP(pMark->TakeXorPoly());
+ aR.expand(basegfx::tools::getRange(aXPP));
+ }
+
+ basegfx::B2DPoint aCenter(aR.getCenter());
+ long nMarkHgt = basegfx::fround(aR.getHeight()) - 1;
+ long nHgt = nMarkHgt + nObjDst * 2;
+
+ if (nHgt < nMinLen) nHgt = nMinLen;
+
+ long nY1 = basegfx::fround(aCenter.getY()) - (nHgt + 1) / 2;
+ long nY2 = nY1 + nHgt;
+
+ if (pOut && (nMinLen > nOutHgt)) nMinLen = nOutHgt;
+ if (pOut)
+ {
+ if (nY1 < nOutMin)
+ {
+ nY1 = nOutMin;
+ if (nY2 < nY1 + nMinLen) nY2 = nY1 + nMinLen;
+ }
+ if (nY2 > nOutMax)
+ {
+ nY2 = nOutMax;
+ if (nY1 > nY2 - nMinLen) nY1 = nY2 - nMinLen;
+ }
+ }
+
+ aRef1.X() = basegfx::fround(aR.getMinX()); // Initial Achse um 2/100mm nach links
+ aRef1.Y() = nY1;
+ aRef2.X() = aRef1.X();
+ aRef2.Y() = nY2;
+
+ // Markierungen einschalten
+ SetMarkHandles();
+
+ //HMHif (bVis) ShowMarkHdl();
+ if (AreObjectsMarked()) MarkListHasChanged();
+
+ // SpiegelPolygone SOFORT zeigen
+ const SdrHdlList &aHdlList = GetHdlList();
+ mpMirrorOverlay = new Impl3DMirrorConstructOverlay(*this);
+ mpMirrorOverlay->SetMirrorAxis(aHdlList.GetHdl(HDL_REF1)->GetPos(), aHdlList.GetHdl(HDL_REF2)->GetPos());
+ //CreateMirrorPolygons ();
+ //ShowMirrorPolygons (aHdlList.GetHdl (HDL_REF1)->GetPos (),
+ // aHdlList.GetHdl (HDL_REF2)->GetPos ());
+ }
+}
+
+/*************************************************************************
+|*
+|* was passiert bei einer Mausbewegung, wenn das Objekt erstellt wird ?
+|*
+\************************************************************************/
+
+void E3dView::MovAction(const Point& rPnt)
+{
+ if(Is3DRotationCreationActive())
+ {
+ SdrHdl* pHdl = GetDragHdl();
+
+ if (pHdl)
+ {
+ SdrHdlKind eHdlKind = pHdl->GetKind();
+
+ // reagiere nur bei einer spiegelachse
+ if ((eHdlKind == HDL_REF1) ||
+ (eHdlKind == HDL_REF2) ||
+ (eHdlKind == HDL_MIRX))
+ {
+ const SdrHdlList &aHdlList = GetHdlList ();
+
+ // loesche das gespiegelte Polygon, spiegele das Original und zeichne es neu
+ //ShowMirrored ();
+ SdrView::MovAction (rPnt);
+ mpMirrorOverlay->SetMirrorAxis(
+ aHdlList.GetHdl (HDL_REF1)->GetPos(),
+ aHdlList.GetHdl (HDL_REF2)->GetPos());
+ }
+ }
+ else
+ {
+ SdrView::MovAction (rPnt);
+ }
+ }
+ else
+ {
+ SdrView::MovAction (rPnt);
+ }
+}
+
+/*************************************************************************
+|*
+|* Schluss. Objekt und evtl. Unterobjekte ueber ImpCreate3DLathe erstellen
+|* [FG] Mit dem Parameterwert sal_True (SDefault: sal_False) wird einfach ein
+|* Rotationskoerper erzeugt, ohne den Benutzer die Lage der
+|* Achse fetlegen zu lassen. Es reicht dieser Aufruf, falls
+|* ein Objekt selektiert ist. (keine Initialisierung noetig)
+|*
+\************************************************************************/
+
+void E3dView::End3DCreation(sal_Bool bUseDefaultValuesForMirrorAxes)
+{
+ ResetCreationActive();
+
+ if(AreObjectsMarked())
+ {
+ if(bUseDefaultValuesForMirrorAxes)
+ {
+ Rectangle aRect = GetAllMarkedRect();
+ if(aRect.GetWidth() <= 1)
+ aRect.SetSize(Size(500, aRect.GetHeight()));
+ if(aRect.GetHeight() <= 1)
+ aRect.SetSize(Size(aRect.GetWidth(), 500));
+
+ basegfx::B2DPoint aPnt1(aRect.Left(), -aRect.Top());
+ basegfx::B2DPoint aPnt2(aRect.Left(), -aRect.Bottom());
+
+ ConvertMarkedObjTo3D(sal_False, aPnt1, aPnt2);
+ }
+ else
+ {
+ // Hilfsfigur ausschalten
+ // bestimme aus den Handlepositionen und den Versatz der Punkte
+ const SdrHdlList &aHdlList = GetHdlList();
+ Point aMirrorRef1 = aHdlList.GetHdl(HDL_REF1)->GetPos();
+ Point aMirrorRef2 = aHdlList.GetHdl(HDL_REF2)->GetPos();
+
+ basegfx::B2DPoint aPnt1(aMirrorRef1.X(), -aMirrorRef1.Y());
+ basegfx::B2DPoint aPnt2(aMirrorRef2.X(), -aMirrorRef2.Y());
+
+ ConvertMarkedObjTo3D(sal_False, aPnt1, aPnt2);
+ }
+ }
+}
+
+/*************************************************************************
+|*
+|* Destruktor
+|*
+\************************************************************************/
+
+E3dView::~E3dView ()
+{
+}
+
+/*************************************************************************
+|*
+|* beende das erzeugen und loesche die polygone
+|*
+\************************************************************************/
+
+void E3dView::ResetCreationActive ()
+{
+ if(mpMirrorOverlay)
+ {
+ delete mpMirrorOverlay;
+ mpMirrorOverlay = 0L;
+ }
+}
+
+/*************************************************************************
+|*
+|* Klasse initialisieren
+|*
+\************************************************************************/
+
+void E3dView::InitView ()
+{
+ eDragConstraint = E3DDRAG_CONSTR_XYZ;
+ fDefaultScaleX =
+ fDefaultScaleY =
+ fDefaultScaleZ = 1.0;
+ fDefaultRotateX =
+ fDefaultRotateY =
+ fDefaultRotateZ = 0.0;
+ fDefaultExtrusionDeepth = 1000; // old: 2000;
+ fDefaultLightIntensity = 0.8; // old: 0.6;
+ fDefaultAmbientIntensity = 0.4;
+ nHDefaultSegments = 12;
+ nVDefaultSegments = 12;
+ aDefaultLightColor = RGB_Color(COL_WHITE);
+ aDefaultAmbientColor = RGB_Color(COL_BLACK);
+ bDoubleSided = sal_False;
+ mpMirrorOverlay = 0L;
+}
+
+/*************************************************************************
+|*
+|* Koennen die selektierten Objekte aufgebrochen werden?
+|*
+\************************************************************************/
+
+sal_Bool E3dView::IsBreak3DObjPossible() const
+{
+ sal_uIntPtr nCount = GetMarkedObjectCount();
+
+ if (nCount > 0)
+ {
+ sal_uIntPtr i = 0;
+
+ while (i < nCount)
+ {
+ SdrObject* pObj = GetMarkedObjectByIndex(i);
+
+ if (pObj && pObj->ISA(E3dObject))
+ {
+ if(!(((E3dObject*)pObj)->IsBreakObjPossible()))
+ return sal_False;
+ }
+ else
+ {
+ return sal_False;
+ }
+
+ i++;
+ }
+ }
+ else
+ {
+ return sal_False;
+ }
+
+ return sal_True;
+}
+
+/*************************************************************************
+|*
+|* Selektierte Lathe-Objekte aufbrechen
+|*
+\************************************************************************/
+
+void E3dView::Break3DObj()
+{
+ if(IsBreak3DObjPossible())
+ {
+ // ALLE selektierten Objekte werden gewandelt
+ sal_uInt32 nCount = GetMarkedObjectCount();
+
+ BegUndo(String(SVX_RESSTR(RID_SVX_3D_UNDO_BREAK_LATHE)));
+ for(sal_uInt32 a=0;a<nCount;a++)
+ {
+ E3dObject* pObj = (E3dObject*)GetMarkedObjectByIndex(a);
+ BreakSingle3DObj(pObj);
+ }
+ DeleteMarked();
+ EndUndo();
+ }
+}
+
+void E3dView::BreakSingle3DObj(E3dObject* pObj)
+{
+ if(pObj->ISA(E3dScene))
+ {
+ SdrObjList* pSubList = pObj->GetSubList();
+ SdrObjListIter aIter(*pSubList, IM_FLAT);
+
+ while(aIter.IsMore())
+ {
+ E3dObject* pSubObj = (E3dObject*)aIter.Next();
+ BreakSingle3DObj(pSubObj);
+ }
+ }
+ else
+ {
+ SdrAttrObj* pNewObj = pObj->GetBreakObj();
+ if(pNewObj)
+ {
+ InsertObjectAtView(pNewObj, *GetSdrPageView(), SDRINSERT_DONTMARK);
+ pNewObj->SetChanged();
+ pNewObj->BroadcastObjectChange();
+ }
+ }
+}
+
+/*************************************************************************
+|*
+|* Szenen mischen
+|*
+\************************************************************************/
+
+void E3dView::MergeScenes ()
+{
+ sal_uIntPtr nCount = GetMarkedObjectCount();
+
+ if (nCount > 0)
+ {
+ sal_uIntPtr nObj = 0;
+ SdrObject *pObj = GetMarkedObjectByIndex(nObj);
+ E3dScene *pScene = new E3dPolyScene(Get3DDefaultAttributes());
+ basegfx::B3DRange aBoundVol;
+ Rectangle aAllBoundRect (GetMarkedObjBoundRect ());
+ Point aCenter (aAllBoundRect.Center());
+
+ while (pObj)
+ {
+ if (pObj->ISA(E3dScene))
+ {
+ /**********************************************************
+ * Es ist eine 3D-Scene oder 3D-PolyScene
+ **********************************************************/
+ SdrObjList* pSubList = ((E3dObject*) pObj)->GetSubList();
+
+ SdrObjListIter aIter(*pSubList, IM_FLAT);
+
+ while (aIter.IsMore())
+ {
+ /******************************************************
+ * LatheObjekte suchen
+ ******************************************************/
+ SdrObject* pSubObj = aIter.Next();
+
+ E3dObject *pNewObj = 0;
+
+ switch (pSubObj->GetObjIdentifier())
+ {
+ case E3D_CUBEOBJ_ID :
+ pNewObj = new E3dCubeObj;
+ *(E3dCubeObj*)pNewObj = *(E3dCubeObj*)pSubObj;
+ break;
+
+ case E3D_SPHEREOBJ_ID:
+ pNewObj = new E3dSphereObj;
+ *(E3dSphereObj*)pNewObj = *(E3dSphereObj*)pSubObj;
+ break;
+
+ case E3D_EXTRUDEOBJ_ID:
+ pNewObj = new E3dExtrudeObj;
+ *(E3dExtrudeObj*)pNewObj = *(E3dExtrudeObj*)pSubObj;
+ break;
+
+ case E3D_LATHEOBJ_ID:
+ pNewObj = new E3dLatheObj;
+ *(E3dLatheObj*)pNewObj = *(E3dLatheObj*)pSubObj;
+ break;
+
+ case E3D_COMPOUNDOBJ_ID:
+ pNewObj = new E3dCompoundObject;
+ *(E3dCompoundObject*)pNewObj = *(E3dCompoundObject*)pSubObj;
+ break;
+ }
+
+ Rectangle aBoundRect = pSubObj->GetCurrentBoundRect();
+
+ basegfx::B3DHomMatrix aMatrix;
+ aMatrix.translate(aBoundRect.Left() - aCenter.getX(), aCenter.getY(), 0.0);
+ pNewObj->SetTransform(aMatrix * pNewObj->GetTransform()); // #112587#
+
+ if (pNewObj) aBoundVol.expand(pNewObj->GetBoundVolume());
+ pScene->Insert3DObj (pNewObj);
+ }
+ }
+
+ nObj++;
+
+ if (nObj < nCount)
+ {
+ pObj = GetMarkedObjectByIndex(nObj);
+ }
+ else
+ {
+ pObj = NULL;
+ }
+ }
+
+ double fW = aAllBoundRect.GetWidth();
+ double fH = aAllBoundRect.GetHeight();
+ Rectangle aRect(0,0, (long) fW, (long) fH);
+
+ InitScene(pScene, fW, fH, aBoundVol.getMaxZ() + + ((fW + fH) / 4.0));
+ pScene->NbcSetSnapRect(aRect);
+
+ Camera3D &aCamera = (Camera3D&) pScene->GetCamera ();
+ basegfx::B3DPoint aMinVec(aBoundVol.getMinimum());
+ basegfx::B3DPoint aMaxVec(aBoundVol.getMaximum());
+ double fDeepth(fabs(aMaxVec.getZ() - aMinVec.getZ()));
+
+ aCamera.SetPRP(basegfx::B3DPoint(0.0, 0.0, 1000.0));
+ double fDefaultCamPosZ(GetDefaultCamPosZ());
+ aCamera.SetPosition(basegfx::B3DPoint(0.0, 0.0, fDefaultCamPosZ + fDeepth / 2.0));
+ aCamera.SetFocalLength(GetDefaultCamFocal());
+ pScene->SetCamera (aCamera);
+
+ // SnapRects der Objekte ungueltig
+ pScene->SetRectsDirty();
+
+ InsertObjectAtView(pScene, *(GetSdrPageViewOfMarkedByIndex(0)));
+
+ // SnapRects der Objekte ungueltig
+ pScene->SetRectsDirty();
+ }
+}
+
+/*************************************************************************
+|*
+|* Possibilities, hauptsaechlich gruppieren/ungruppieren
+|*
+\************************************************************************/
+void E3dView::CheckPossibilities()
+{
+ // call parent
+ SdrView::CheckPossibilities();
+
+ // Weitere Flags bewerten
+ if(bGroupPossible || bUnGroupPossible || bGrpEnterPossible)
+ {
+ sal_Int32 nMarkCnt = GetMarkedObjectCount();
+ sal_Bool bCoumpound = sal_False;
+ sal_Bool b3DObject = sal_False;
+ for(sal_Int32 nObjs = 0L; (nObjs < nMarkCnt) && !bCoumpound; nObjs++)
+ {
+ SdrObject *pObj = GetMarkedObjectByIndex(nObjs);
+ if(pObj && pObj->ISA(E3dCompoundObject))
+ bCoumpound = sal_True;
+ if(pObj && pObj->ISA(E3dObject))
+ b3DObject = sal_True;
+ }
+
+ // Bisher: Es sind ZWEI oder mehr beliebiger Objekte selektiert.
+ // Nachsehen, ob CompoundObjects beteiligt sind. Falls ja,
+ // das Gruppieren verbieten.
+ if(bGroupPossible && bCoumpound)
+ bGroupPossible = sal_False;
+
+ if(bUnGroupPossible && b3DObject)
+ bUnGroupPossible = sal_False;
+
+ if(bGrpEnterPossible && bCoumpound)
+ bGrpEnterPossible = sal_False;
+ }
+}
+
+// eof
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */