1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*************************************************************************
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
6 * Copyright 2000, 2010 Oracle and/or its affiliates.
8 * OpenOffice.org - a multi-platform office productivity suite
10 * This file is part of OpenOffice.org.
12 * OpenOffice.org is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU Lesser General Public License version 3
14 * only, as published by the Free Software Foundation.
16 * OpenOffice.org is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License version 3 for more details
20 * (a copy is included in the LICENSE file that accompanied this code).
22 * You should have received a copy of the GNU Lesser General Public License
23 * version 3 along with OpenOffice.org. If not, see
24 * <http://www.openoffice.org/license.html>
25 * for a copy of the LGPLv3 License.
27 ************************************************************************/
30 #include <svx/svdedtv.hxx>
31 #include <editeng/outliner.hxx>
32 #include <svx/svdundo.hxx>
33 #include <svx/svdogrp.hxx> // for grouping objects
34 #include <svx/svdovirt.hxx> // for VirtualObject bundling (Writer)
35 #include <svx/svdopath.hxx> // for CombineObjects
36 #include <svx/svdpage.hxx>
37 #include <svx/svdpagv.hxx>
38 #include "svx/svditer.hxx"
39 #include <svx/svdograf.hxx> // for Possibilities
40 #include <svx/svdoole2.hxx> // and Mtf-Import
41 #include "svx/svdstr.hrc" // names taken from the resource
42 #include "svx/svdglob.hxx" // StringCache
43 #include "svdfmtf.hxx"
44 #include <svx/svdetc.hxx>
45 #include <sfx2/basedlgs.hxx>
46 #include <vcl/msgbox.hxx>
47 #include <editeng/outlobj.hxx>
48 #include <editeng/eeitem.hxx>
49 #include <basegfx/polygon/b2dpolypolygon.hxx>
50 #include <basegfx/polygon/b2dpolypolygontools.hxx>
52 #include <svx/svxdlg.hxx>
53 #include <svx/dialogs.hrc>
56 #include <svx/svdoashp.hxx>
57 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
62 SdrObject
* SdrEditView::GetMaxToTopObj(SdrObject
* /*pObj*/) const
67 SdrObject
* SdrEditView::GetMaxToBtmObj(SdrObject
* /*pObj*/) const
72 void SdrEditView::ObjOrderChanged(SdrObject
* /*pObj*/, sal_uIntPtr
/*nOldPos*/, sal_uIntPtr
/*nNewPos*/)
76 void SdrEditView::MovMarkedToTop()
78 sal_uIntPtr nAnz
=GetMarkedObjectCount();
81 const bool bUndo
= IsUndoEnabled();
84 BegUndo(ImpGetResStr(STR_EditMovToTop
),GetDescriptionOfMarkedObjects(),SDRREPFUNC_OBJ_MOVTOTOP
);
88 for (nm
=0; nm
<nAnz
; nm
++)
89 { // All Ordnums have to be correct!
90 GetMarkedObjectByIndex(nm
)->GetOrdNum();
92 sal_Bool bChg
=sal_False
;
93 SdrObjList
* pOL0
=NULL
;
94 sal_uIntPtr nNewPos
=0;
98 SdrMark
* pM
=GetSdrMarkByIndex(nm
);
99 SdrObject
* pObj
=pM
->GetMarkedSdrObj();
100 SdrObjList
* pOL
=pObj
->GetObjList();
103 nNewPos
=sal_uIntPtr(pOL
->GetObjCount()-1);
106 sal_uIntPtr nNowPos
=pObj
->GetOrdNumDirect();
107 const Rectangle
& rBR
=pObj
->GetCurrentBoundRect();
108 sal_uIntPtr nCmpPos
=nNowPos
+1;
109 SdrObject
* pMaxObj
=GetMaxToTopObj(pObj
);
112 sal_uIntPtr nMaxPos
=pMaxObj
->GetOrdNum();
116 nNewPos
=nMaxPos
; // neither go faster...
118 nNewPos
=nNowPos
; // nor go in the other direction
120 sal_Bool bEnd
=sal_False
;
121 while (nCmpPos
<nNewPos
&& !bEnd
)
123 SdrObject
* pCmpObj
=pOL
->GetObj(nCmpPos
);
126 OSL_FAIL("MovMarkedToTop(): Reference object not found.");
129 else if (pCmpObj
==pMaxObj
)
135 else if (rBR
.IsOver(pCmpObj
->GetCurrentBoundRect()))
145 if (nNowPos
!=nNewPos
)
148 pOL
->SetObjectOrdNum(nNowPos
,nNewPos
);
150 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj
,nNowPos
,nNewPos
));
151 ObjOrderChanged(pObj
,nNowPos
,nNewPos
);
160 MarkListHasChanged();
164 void SdrEditView::MovMarkedToBtm()
166 sal_uIntPtr nAnz
=GetMarkedObjectCount();
169 const bool bUndo
= IsUndoEnabled();
172 BegUndo(ImpGetResStr(STR_EditMovToBtm
),GetDescriptionOfMarkedObjects(),SDRREPFUNC_OBJ_MOVTOBTM
);
176 for (nm
=0; nm
<nAnz
; nm
++)
177 { // All Ordnums have to be correct!
178 GetMarkedObjectByIndex(nm
)->GetOrdNum();
181 sal_Bool bChg
=sal_False
;
182 SdrObjList
* pOL0
=NULL
;
183 sal_uIntPtr nNewPos
=0;
184 for (nm
=0; nm
<nAnz
; nm
++)
186 SdrMark
* pM
=GetSdrMarkByIndex(nm
);
187 SdrObject
* pObj
=pM
->GetMarkedSdrObj();
188 SdrObjList
* pOL
=pObj
->GetObjList();
194 sal_uIntPtr nNowPos
=pObj
->GetOrdNumDirect();
195 const Rectangle
& rBR
=pObj
->GetCurrentBoundRect();
196 sal_uIntPtr nCmpPos
=nNowPos
; if (nCmpPos
>0) nCmpPos
--;
197 SdrObject
* pMaxObj
=GetMaxToBtmObj(pObj
);
200 sal_uIntPtr nMinPos
=pMaxObj
->GetOrdNum()+1;
202 nNewPos
=nMinPos
; // neither go faster...
204 nNewPos
=nNowPos
; // nor go in the other direction
206 sal_Bool bEnd
=sal_False
;
207 // nNewPos in this case is the "maximum" position
208 // the object may reach without going faster than the object before
209 // it (multiple selection).
210 while (nCmpPos
>nNewPos
&& !bEnd
)
212 SdrObject
* pCmpObj
=pOL
->GetObj(nCmpPos
);
215 OSL_FAIL("MovMarkedToBtm(): Reference object not found.");
218 else if (pCmpObj
==pMaxObj
)
224 else if (rBR
.IsOver(pCmpObj
->GetCurrentBoundRect()))
234 if (nNowPos
!=nNewPos
)
237 pOL
->SetObjectOrdNum(nNowPos
,nNewPos
);
239 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj
,nNowPos
,nNewPos
));
240 ObjOrderChanged(pObj
,nNowPos
,nNewPos
);
249 MarkListHasChanged();
253 void SdrEditView::PutMarkedToTop()
255 PutMarkedInFrontOfObj(NULL
);
258 void SdrEditView::PutMarkedInFrontOfObj(const SdrObject
* pRefObj
)
260 sal_uIntPtr nAnz
=GetMarkedObjectCount();
263 const bool bUndo
= IsUndoEnabled();
265 BegUndo(ImpGetResStr(STR_EditPutToTop
),GetDescriptionOfMarkedObjects(),SDRREPFUNC_OBJ_PUTTOTOP
);
271 // Make "in front of the object" work, even if the
272 // selected objects are already in front of the other object
273 sal_uIntPtr nRefMark
=TryToFindMarkedObject(pRefObj
);
275 if (nRefMark
!=CONTAINER_ENTRY_NOTFOUND
)
277 aRefMark
=*GetSdrMarkByIndex(nRefMark
);
278 GetMarkedObjectListWriteAccess().DeleteMark(nRefMark
);
281 if (nRefMark
!=CONTAINER_ENTRY_NOTFOUND
)
283 GetMarkedObjectListWriteAccess().InsertEntry(aRefMark
);
288 for (nm
=0; nm
<nAnz
; nm
++)
289 { // All Ordnums have to be correct!
290 GetMarkedObjectByIndex(nm
)->GetOrdNum();
292 sal_Bool bChg
=sal_False
;
293 SdrObjList
* pOL0
=NULL
;
294 sal_uIntPtr nNewPos
=0;
298 SdrMark
* pM
=GetSdrMarkByIndex(nm
);
299 SdrObject
* pObj
=pM
->GetMarkedSdrObj();
302 SdrObjList
* pOL
=pObj
->GetObjList();
305 nNewPos
=sal_uIntPtr(pOL
->GetObjCount()-1);
308 sal_uIntPtr nNowPos
=pObj
->GetOrdNumDirect();
309 SdrObject
* pMaxObj
=GetMaxToTopObj(pObj
);
312 sal_uIntPtr nMaxOrd
=pMaxObj
->GetOrdNum(); // sadly doesn't work any other way
316 nNewPos
=nMaxOrd
; // neither go faster...
318 nNewPos
=nNowPos
; // nor go into the other direction
322 if (pRefObj
->GetObjList()==pObj
->GetObjList())
324 sal_uIntPtr nMaxOrd
=pRefObj
->GetOrdNum(); // sadly doesn't work any other way
326 nNewPos
=nMaxOrd
; // neither go faster...
328 nNewPos
=nNowPos
; // nor go into the other direction
332 nNewPos
=nNowPos
; // different PageView, so don't change
335 if (nNowPos
!=nNewPos
)
338 pOL
->SetObjectOrdNum(nNowPos
,nNewPos
);
340 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj
,nNowPos
,nNewPos
));
341 ObjOrderChanged(pObj
,nNowPos
,nNewPos
);
344 } // if (pObj!=pRefObj)
345 } // for loop over all selected objects
351 MarkListHasChanged();
355 void SdrEditView::PutMarkedToBtm()
357 PutMarkedBehindObj(NULL
);
360 void SdrEditView::PutMarkedBehindObj(const SdrObject
* pRefObj
)
362 sal_uIntPtr nAnz
=GetMarkedObjectCount();
365 const bool bUndo
= IsUndoEnabled();
368 BegUndo(ImpGetResStr(STR_EditPutToBtm
),GetDescriptionOfMarkedObjects(),SDRREPFUNC_OBJ_PUTTOBTM
);
373 // Make "behind the object" work, even if the
374 // selected objects are already behind the other object
375 sal_uIntPtr nRefMark
=TryToFindMarkedObject(pRefObj
);
377 if (nRefMark
!=CONTAINER_ENTRY_NOTFOUND
)
379 aRefMark
=*GetSdrMarkByIndex(nRefMark
);
380 GetMarkedObjectListWriteAccess().DeleteMark(nRefMark
);
383 if (nRefMark
!=CONTAINER_ENTRY_NOTFOUND
)
385 GetMarkedObjectListWriteAccess().InsertEntry(aRefMark
);
390 for (nm
=0; nm
<nAnz
; nm
++) { // All Ordnums have to be correct!
391 GetMarkedObjectByIndex(nm
)->GetOrdNum();
393 sal_Bool bChg
=sal_False
;
394 SdrObjList
* pOL0
=NULL
;
395 sal_uIntPtr nNewPos
=0;
396 for (nm
=0; nm
<nAnz
; nm
++) {
397 SdrMark
* pM
=GetSdrMarkByIndex(nm
);
398 SdrObject
* pObj
=pM
->GetMarkedSdrObj();
400 SdrObjList
* pOL
=pObj
->GetObjList();
405 sal_uIntPtr nNowPos
=pObj
->GetOrdNumDirect();
406 SdrObject
* pMinObj
=GetMaxToBtmObj(pObj
);
408 sal_uIntPtr nMinOrd
=pMinObj
->GetOrdNum()+1; // sadly doesn't work any differently
409 if (nNewPos
<nMinOrd
) nNewPos
=nMinOrd
; // neither go faster...
410 if (nNewPos
>nNowPos
) nNewPos
=nNowPos
; // nor go into the other direction
413 if (pRefObj
->GetObjList()==pObj
->GetObjList()) {
414 sal_uIntPtr nMinOrd
=pRefObj
->GetOrdNum(); // sadly doesn't work any differently
415 if (nNewPos
<nMinOrd
) nNewPos
=nMinOrd
; // neither go faster...
416 if (nNewPos
>nNowPos
) nNewPos
=nNowPos
; // nor go into the other direction
418 nNewPos
=nNowPos
; // different PageView, so don't change
421 if (nNowPos
!=nNewPos
) {
423 pOL
->SetObjectOrdNum(nNowPos
,nNewPos
);
425 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj
,nNowPos
,nNewPos
));
426 ObjOrderChanged(pObj
,nNowPos
,nNewPos
);
429 } // if (pObj!=pRefObj)
430 } // for loop over all selected objects
436 MarkListHasChanged();
440 void SdrEditView::ReverseOrderOfMarked()
443 sal_uIntPtr nMarkAnz
=GetMarkedObjectCount();
446 sal_Bool bChg
=sal_False
;
448 bool bUndo
= IsUndoEnabled();
450 BegUndo(ImpGetResStr(STR_EditRevOrder
),GetDescriptionOfMarkedObjects(),SDRREPFUNC_OBJ_REVORDER
);
454 // take into account selection across multiple PageViews
456 while (b
<nMarkAnz
&& GetSdrPageViewOfMarkedByIndex(b
) == GetSdrPageViewOfMarkedByIndex(a
)) b
++;
458 SdrObjList
* pOL
=GetSdrPageViewOfMarkedByIndex(a
)->GetObjList();
460 if (a
<c
) { // make sure OrdNums aren't dirty
461 GetMarkedObjectByIndex(a
)->GetOrdNum();
464 SdrObject
* pObj1
=GetMarkedObjectByIndex(a
);
465 SdrObject
* pObj2
=GetMarkedObjectByIndex(c
);
466 sal_uIntPtr nOrd1
=pObj1
->GetOrdNumDirect();
467 sal_uIntPtr nOrd2
=pObj2
->GetOrdNumDirect();
470 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj1
,nOrd1
,nOrd2
));
471 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoObjectOrdNum(*pObj2
,nOrd2
-1,nOrd1
));
473 pOL
->SetObjectOrdNum(nOrd1
,nOrd2
);
474 // Obj 2 has moved forward by one position, so now nOrd2-1
475 pOL
->SetObjectOrdNum(nOrd2
-1,nOrd1
);
476 // use Replace instead of SetOrdNum for performance reasons (recalculation of Ordnums)
481 } while (a
<nMarkAnz
);
487 MarkListHasChanged();
491 void SdrEditView::ImpCheckToTopBtmPossible()
493 sal_uIntPtr nAnz
=GetMarkedObjectCount();
497 { // special-casing for single selection
498 SdrObject
* pObj
=GetMarkedObjectByIndex(0);
499 SdrObjList
* pOL
=pObj
->GetObjList();
500 sal_uIntPtr nMax
=pOL
->GetObjCount();
502 sal_uIntPtr nObjNum
=pObj
->GetOrdNum();
503 SdrObject
* pRestrict
=GetMaxToTopObj(pObj
);
504 if (pRestrict
!=NULL
) {
505 sal_uIntPtr nRestrict
=pRestrict
->GetOrdNum();
506 if (nRestrict
<nMax
) nMax
=nRestrict
;
508 pRestrict
=GetMaxToBtmObj(pObj
);
509 if (pRestrict
!=NULL
) {
510 sal_uIntPtr nRestrict
=pRestrict
->GetOrdNum();
511 if (nRestrict
>nMin
) nMin
=nRestrict
;
513 bToTopPossible
=nObjNum
<sal_uIntPtr(nMax
-1);
514 bToBtmPossible
=nObjNum
>nMin
;
515 } else { // multiple selection
517 SdrObjList
* pOL0
=NULL
;
519 while (!bToBtmPossible
&& nm
<nAnz
) { // check 'send to background'
520 SdrObject
* pObj
=GetMarkedObjectByIndex(nm
);
521 SdrObjList
* pOL
=pObj
->GetObjList();
526 sal_uIntPtr nPos
=pObj
->GetOrdNum();
527 bToBtmPossible
=nPos
>sal_uIntPtr(nPos0
+1);
534 while (!bToTopPossible
&& nm
>0) { // check 'bring to front'
536 SdrObject
* pObj
=GetMarkedObjectByIndex(nm
);
537 SdrObjList
* pOL
=pObj
->GetObjList();
539 nPos0
=pOL
->GetObjCount();
542 sal_uIntPtr nPos
=pObj
->GetOrdNum();
543 bToTopPossible
=nPos
+1<sal_uIntPtr(nPos0
);
549 ////////////////////////////////////////////////////////////////////////////////////////////////////
551 ////////////////////////////////////////////////////////////////////////////////////////////////////
553 void SdrEditView::ImpCopyAttributes(const SdrObject
* pSource
, SdrObject
* pDest
) const
556 SdrObjList
* pOL
=pSource
->GetSubList();
557 if (pOL
!=NULL
&& !pSource
->Is3DObj()) { // get first non-group object from group
558 SdrObjListIter
aIter(*pOL
,IM_DEEPNOGROUPS
);
559 pSource
=aIter
.Next();
565 SfxItemSet
aSet(pMod
->GetItemPool(),
566 SDRATTR_START
, SDRATTR_NOTPERSIST_FIRST
-1,
567 SDRATTR_NOTPERSIST_LAST
+1, SDRATTR_END
,
568 EE_ITEMS_START
, EE_ITEMS_END
,
571 aSet
.Put(pSource
->GetMergedItemSet());
573 pDest
->ClearMergedItem();
574 pDest
->SetMergedItemSet(aSet
);
576 pDest
->NbcSetLayer(pSource
->GetLayer());
577 pDest
->NbcSetStyleSheet(pSource
->GetStyleSheet(), sal_True
);
581 sal_Bool
SdrEditView::ImpCanConvertForCombine1(const SdrObject
* pObj
) const
583 // new condition IsLine() to be able to combine simple Lines
584 sal_Bool
bIsLine(sal_False
);
586 const SdrPathObj
* pPath
= PTR_CAST(SdrPathObj
,pObj
);
590 bIsLine
= pPath
->IsLine();
593 SdrObjTransformInfoRec aInfo
;
594 pObj
->TakeObjInfo(aInfo
);
596 return (aInfo
.bCanConvToPath
|| aInfo
.bCanConvToPoly
|| bIsLine
);
599 sal_Bool
SdrEditView::ImpCanConvertForCombine(const SdrObject
* pObj
) const
601 SdrObjList
* pOL
= pObj
->GetSubList();
603 if(pOL
&& !pObj
->Is3DObj())
605 SdrObjListIter
aIter(*pOL
, IM_DEEPNOGROUPS
);
607 while(aIter
.IsMore())
609 SdrObject
* pObj1
= aIter
.Next();
611 // all members of a group have to be convertible
612 if(!ImpCanConvertForCombine1(pObj1
))
620 if(!ImpCanConvertForCombine1(pObj
))
629 basegfx::B2DPolyPolygon
SdrEditView::ImpGetPolyPolygon1(const SdrObject
* pObj
, sal_Bool bCombine
) const
631 basegfx::B2DPolyPolygon aRetval
;
632 SdrPathObj
* pPath
= PTR_CAST(SdrPathObj
, pObj
);
634 if(bCombine
&& pPath
&& !pObj
->GetOutlinerParaObject())
636 aRetval
= pPath
->GetPathPoly();
640 SdrObject
* pConvObj
= pObj
->ConvertToPolyObj(bCombine
, sal_False
);
644 SdrObjList
* pOL
= pConvObj
->GetSubList();
648 SdrObjListIter
aIter(*pOL
, IM_DEEPNOGROUPS
);
650 while(aIter
.IsMore())
652 SdrObject
* pObj1
= aIter
.Next();
653 pPath
= PTR_CAST(SdrPathObj
, pObj1
);
657 aRetval
.append(pPath
->GetPathPoly());
663 pPath
= PTR_CAST(SdrPathObj
, pConvObj
);
667 aRetval
= pPath
->GetPathPoly();
671 SdrObject::Free( pConvObj
);
678 basegfx::B2DPolyPolygon
SdrEditView::ImpGetPolyPolygon(const SdrObject
* pObj
, sal_Bool bCombine
) const
680 SdrObjList
* pOL
= pObj
->GetSubList();
682 if(pOL
&& !pObj
->Is3DObj())
684 basegfx::B2DPolyPolygon aRetval
;
685 SdrObjListIter
aIter(*pOL
, IM_DEEPNOGROUPS
);
687 while(aIter
.IsMore())
689 SdrObject
* pObj1
= aIter
.Next();
690 aRetval
.append(ImpGetPolyPolygon1(pObj1
, bCombine
));
697 return ImpGetPolyPolygon1(pObj
, bCombine
);
701 basegfx::B2DPolygon
SdrEditView::ImpCombineToSinglePolygon(const basegfx::B2DPolyPolygon
& rPolyPolygon
) const
703 const sal_uInt32
nPolyCount(rPolyPolygon
.count());
707 return basegfx::B2DPolygon();
709 else if(1L == nPolyCount
)
711 return rPolyPolygon
.getB2DPolygon(0L);
715 basegfx::B2DPolygon
aRetval(rPolyPolygon
.getB2DPolygon(0L));
717 for(sal_uInt32
a(1L); a
< nPolyCount
; a
++)
719 basegfx::B2DPolygon
aCandidate(rPolyPolygon
.getB2DPolygon(a
));
723 if(aCandidate
.count())
725 const basegfx::B2DPoint
aCA(aCandidate
.getB2DPoint(0L));
726 const basegfx::B2DPoint
aCB(aCandidate
.getB2DPoint(aCandidate
.count() - 1L));
727 const basegfx::B2DPoint
aRA(aRetval
.getB2DPoint(0L));
728 const basegfx::B2DPoint
aRB(aRetval
.getB2DPoint(aRetval
.count() - 1L));
730 const double fRACA(basegfx::B2DVector(aCA
- aRA
).getLength());
731 const double fRACB(basegfx::B2DVector(aCB
- aRA
).getLength());
732 const double fRBCA(basegfx::B2DVector(aCA
- aRB
).getLength());
733 const double fRBCB(basegfx::B2DVector(aCB
- aRB
).getLength());
735 const double fSmallestRA(fRACA
< fRACB
? fRACA
: fRACB
);
736 const double fSmallestRB(fRBCA
< fRBCB
? fRBCA
: fRBCB
);
738 if(fSmallestRA
< fSmallestRB
)
744 const double fSmallestCA(fRACA
< fRBCA
? fRACA
: fRBCA
);
745 const double fSmallestCB(fRACB
< fRBCB
? fRACB
: fRBCB
);
747 if(fSmallestCB
< fSmallestCA
)
753 // append candidate to retval
754 aRetval
.append(aCandidate
);
759 aRetval
= aCandidate
;
767 // for distribution dialog function
768 struct ImpDistributeEntry
775 typedef vector
< ImpDistributeEntry
*> ImpDistributeEntryList
;
777 void SdrEditView::DistributeMarkedObjects()
779 sal_uInt32
nMark(GetMarkedObjectCount());
783 SfxItemSet
aNewAttr(pMod
->GetItemPool());
785 SvxAbstractDialogFactory
* pFact
= SvxAbstractDialogFactory::Create();
788 AbstractSvxDistributeDialog
*pDlg
= pFact
->CreateSvxDistributeDialog(NULL
, aNewAttr
);
789 DBG_ASSERT(pDlg
, "Dialogdiet fail!");
791 sal_uInt16 nResult
= pDlg
->Execute();
793 if(nResult
== RET_OK
)
795 SvxDistributeHorizontal eHor
= pDlg
->GetDistributeHor();
796 SvxDistributeVertical eVer
= pDlg
->GetDistributeVer();
797 ImpDistributeEntryList aEntryList
;
798 ImpDistributeEntryList::iterator itEntryList
;
799 sal_uInt32 nFullLength
;
801 const bool bUndo
= IsUndoEnabled();
805 if(eHor
!= SvxDistributeHorizontalNone
)
807 // build sorted entry list
810 for( sal_uInt32 a
= 0; a
< nMark
; a
++ )
812 SdrMark
* pMark
= GetSdrMarkByIndex(a
);
813 ImpDistributeEntry
* pNew
= new ImpDistributeEntry
;
815 pNew
->mpObj
= pMark
->GetMarkedSdrObj();
819 case SvxDistributeHorizontalLeft
:
821 pNew
->mnPos
= pNew
->mpObj
->GetSnapRect().Left();
824 case SvxDistributeHorizontalCenter
:
826 pNew
->mnPos
= (pNew
->mpObj
->GetSnapRect().Right() + pNew
->mpObj
->GetSnapRect().Left()) / 2;
829 case SvxDistributeHorizontalDistance
:
831 pNew
->mnLength
= pNew
->mpObj
->GetSnapRect().GetWidth() + 1;
832 nFullLength
+= pNew
->mnLength
;
833 pNew
->mnPos
= (pNew
->mpObj
->GetSnapRect().Right() + pNew
->mpObj
->GetSnapRect().Left()) / 2;
836 case SvxDistributeHorizontalRight
:
838 pNew
->mnPos
= pNew
->mpObj
->GetSnapRect().Right();
844 for ( itEntryList
= aEntryList
.begin();
845 itEntryList
< aEntryList
.end() && (*itEntryList
)->mnPos
< pNew
->mnPos
;
848 if ( itEntryList
< aEntryList
.end() )
849 aEntryList
.insert( itEntryList
, pNew
);
851 aEntryList
.push_back( pNew
);
854 if(eHor
== SvxDistributeHorizontalDistance
)
856 // calculate room in-between
857 sal_Int32 nWidth
= GetAllMarkedBoundRect().GetWidth() + 1;
858 double fStepWidth
= ((double)nWidth
- (double)nFullLength
) / (double)(aEntryList
.size() - 1);
859 double fStepStart
= (double)aEntryList
[ 0 ]->mnPos
;
860 fStepStart
+= fStepWidth
+ (double)((aEntryList
[ 0 ]->mnLength
+ aEntryList
[ 1 ]->mnLength
) / 2);
862 // move entries 1..n-1
863 for( size_t i
= 1, n
= aEntryList
.size()-1; i
< n
; ++i
)
865 ImpDistributeEntry
* pCurr
= aEntryList
[ i
];
866 ImpDistributeEntry
* pNext
= aEntryList
[ i
+ 1];
867 sal_Int32 nDelta
= (sal_Int32
)(fStepStart
+ 0.5) - pCurr
->mnPos
;
869 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoGeoObject(*pCurr
->mpObj
));
870 pCurr
->mpObj
->Move(Size(nDelta
, 0));
871 fStepStart
+= fStepWidth
+ (double)((pCurr
->mnLength
+ pNext
->mnLength
) / 2);
876 // calculate distances
877 sal_Int32 nWidth
= aEntryList
[ aEntryList
.size() - 1 ]->mnPos
- aEntryList
[ 0 ]->mnPos
;
878 double fStepWidth
= (double)nWidth
/ (double)(aEntryList
.size() - 1);
879 double fStepStart
= (double)aEntryList
[ 0 ]->mnPos
;
880 fStepStart
+= fStepWidth
;
882 // move entries 1..n-1
883 for( size_t i
= 1 ; i
< aEntryList
.size()-1 ; ++i
)
885 ImpDistributeEntry
* pCurr
= aEntryList
[ i
];
886 sal_Int32 nDelta
= (sal_Int32
)(fStepStart
+ 0.5) - pCurr
->mnPos
;
888 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoGeoObject(*pCurr
->mpObj
));
889 pCurr
->mpObj
->Move(Size(nDelta
, 0));
890 fStepStart
+= fStepWidth
;
895 for ( size_t i
= 0, n
= aEntryList
.size(); i
< n
; ++i
)
896 delete aEntryList
[ i
];
900 if(eVer
!= SvxDistributeVerticalNone
)
902 // build sorted entry list
905 for( sal_uInt32 a
= 0; a
< nMark
; a
++ )
907 SdrMark
* pMark
= GetSdrMarkByIndex(a
);
908 ImpDistributeEntry
* pNew
= new ImpDistributeEntry
;
910 pNew
->mpObj
= pMark
->GetMarkedSdrObj();
914 case SvxDistributeVerticalTop
:
916 pNew
->mnPos
= pNew
->mpObj
->GetSnapRect().Top();
919 case SvxDistributeVerticalCenter
:
921 pNew
->mnPos
= (pNew
->mpObj
->GetSnapRect().Bottom() + pNew
->mpObj
->GetSnapRect().Top()) / 2;
924 case SvxDistributeVerticalDistance
:
926 pNew
->mnLength
= pNew
->mpObj
->GetSnapRect().GetHeight() + 1;
927 nFullLength
+= pNew
->mnLength
;
928 pNew
->mnPos
= (pNew
->mpObj
->GetSnapRect().Bottom() + pNew
->mpObj
->GetSnapRect().Top()) / 2;
931 case SvxDistributeVerticalBottom
:
933 pNew
->mnPos
= pNew
->mpObj
->GetSnapRect().Bottom();
939 for ( itEntryList
= aEntryList
.begin();
940 itEntryList
< aEntryList
.end() && (*itEntryList
)->mnPos
< pNew
->mnPos
;
943 if ( itEntryList
< aEntryList
.end() )
944 aEntryList
.insert( itEntryList
, pNew
);
946 aEntryList
.push_back( pNew
);
949 if(eVer
== SvxDistributeVerticalDistance
)
951 // calculate room in-between
952 sal_Int32 nHeight
= GetAllMarkedBoundRect().GetHeight() + 1;
953 double fStepWidth
= ((double)nHeight
- (double)nFullLength
) / (double)(aEntryList
.size() - 1);
954 double fStepStart
= (double)aEntryList
[ 0 ]->mnPos
;
955 fStepStart
+= fStepWidth
+ (double)((aEntryList
[ 0 ]->mnLength
+ aEntryList
[ 1 ]->mnLength
) / 2);
957 // move entries 1..n-1
958 for( size_t i
= 1, n
= aEntryList
.size()-1; i
< n
; ++i
)
960 ImpDistributeEntry
* pCurr
= aEntryList
[ i
];
961 ImpDistributeEntry
* pNext
= aEntryList
[ i
+ 1 ];
962 sal_Int32 nDelta
= (sal_Int32
)(fStepStart
+ 0.5) - pCurr
->mnPos
;
964 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoGeoObject(*pCurr
->mpObj
));
965 pCurr
->mpObj
->Move(Size(0, nDelta
));
966 fStepStart
+= fStepWidth
+ (double)((pCurr
->mnLength
+ pNext
->mnLength
) / 2);
971 // calculate distances
972 sal_Int32 nHeight
= aEntryList
[ aEntryList
.size() - 1 ]->mnPos
- aEntryList
[ 0 ]->mnPos
;
973 double fStepWidth
= (double)nHeight
/ (double)(aEntryList
.size() - 1);
974 double fStepStart
= (double)aEntryList
[ 0 ]->mnPos
;
975 fStepStart
+= fStepWidth
;
977 // move entries 1..n-1
978 for(size_t i
= 1, n
= aEntryList
.size()-1; i
< n
; ++i
)
980 ImpDistributeEntry
* pCurr
= aEntryList
[ i
];
981 sal_Int32 nDelta
= (sal_Int32
)(fStepStart
+ 0.5) - pCurr
->mnPos
;
983 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoGeoObject(*pCurr
->mpObj
));
984 pCurr
->mpObj
->Move(Size(0, nDelta
));
985 fStepStart
+= fStepWidth
;
990 for ( size_t i
= 0, n
= aEntryList
.size(); i
< n
; ++i
)
991 delete aEntryList
[ i
];
995 // UNDO-Comment and end of UNDO
996 SetUndoComment(ImpGetResStr(STR_DistributeMarkedObjects
));
1007 void SdrEditView::MergeMarkedObjects(SdrMergeMode eMode
)
1009 // #i73441# check content
1010 if(AreObjectsMarked())
1012 SdrMarkList aRemove
;
1013 SortMarkedObjects();
1015 const bool bUndo
= IsUndoEnabled();
1020 sal_uInt32 nInsPos
=0xFFFFFFFF;
1021 const SdrObject
* pAttrObj
= NULL
;
1022 basegfx::B2DPolyPolygon aMergePolyPolygonA
;
1023 basegfx::B2DPolyPolygon aMergePolyPolygonB
;
1025 SdrObjList
* pInsOL
= NULL
;
1026 SdrPageView
* pInsPV
= NULL
;
1027 sal_Bool
bFirstObjectComplete(sal_False
);
1029 // make sure selected objects are contour objects
1030 // since now basegfx::tools::adaptiveSubdivide() is used, it is no longer
1031 // necessary to use ConvertMarkedToPolyObj which will subdivide curves using the old
1032 // mechanisms. In a next step the polygon clipper will even be able to clip curves...
1033 // ConvertMarkedToPolyObj(sal_True);
1034 ConvertMarkedToPathObj(sal_True
);
1035 OSL_ENSURE(AreObjectsMarked(), "no more objects selected after preparations (!)");
1037 for(sal_uInt32 a
=0;a
<GetMarkedObjectCount();a
++)
1039 SdrMark
* pM
= GetSdrMarkByIndex(a
);
1040 SdrObject
* pObj
= pM
->GetMarkedSdrObj();
1042 if(ImpCanConvertForCombine(pObj
))
1047 nInsPos
= pObj
->GetOrdNum() + 1;
1048 pInsPV
= pM
->GetPageView();
1049 pInsOL
= pObj
->GetObjList();
1051 // #i76891# use single iteration from SJ here which works on SdrObjects and takes
1052 // groups into account by itself
1053 SdrObjListIter
aIter(*pObj
, IM_DEEPWITHGROUPS
);
1055 while(aIter
.IsMore())
1057 SdrObject
* pCandidate
= aIter
.Next();
1058 SdrPathObj
* pPathObj
= PTR_CAST(SdrPathObj
, pCandidate
);
1061 basegfx::B2DPolyPolygon
aTmpPoly(pPathObj
->GetPathPoly());
1063 // #i76891# unfortunately ConvertMarkedToPathObj has converted all
1064 // involved polygon data to curve segments, even if not necessary.
1065 // It is better to try to reduce to more simple polygons.
1066 aTmpPoly
= basegfx::tools::simplifyCurveSegments(aTmpPoly
);
1068 // for each part polygon as preparation, remove self-intersections
1069 // correct orientations and get rid of possible neutral polygons.
1070 aTmpPoly
= basegfx::tools::prepareForPolygonOperation(aTmpPoly
);
1072 if(!bFirstObjectComplete
)
1074 // #i111987# Also need to collect ORed source shape when more than
1075 // a single polygon is involved
1076 if(aMergePolyPolygonA
.count())
1078 aMergePolyPolygonA
= basegfx::tools::solvePolygonOperationOr(aMergePolyPolygonA
, aTmpPoly
);
1082 aMergePolyPolygonA
= aTmpPoly
;
1087 if(aMergePolyPolygonB
.count())
1089 // to topologically correctly collect the 2nd polygon
1090 // group it is necessary to OR the parts (each is seen as
1091 // XOR-FillRule polygon and they are drawn over each-other)
1092 aMergePolyPolygonB
= basegfx::tools::solvePolygonOperationOr(aMergePolyPolygonB
, aTmpPoly
);
1096 aMergePolyPolygonB
= aTmpPoly
;
1102 // was there something added to the first polygon?
1103 if(!bFirstObjectComplete
&& aMergePolyPolygonA
.count())
1105 bFirstObjectComplete
= sal_True
;
1108 // move object to temporary delete list
1109 aRemove
.InsertEntry(SdrMark(pObj
, pM
->GetPageView()));
1115 case SDR_MERGE_MERGE
:
1117 // merge all contained parts (OR)
1118 static bool bTestXOR(false);
1121 aMergePolyPolygonA
= basegfx::tools::solvePolygonOperationXor(aMergePolyPolygonA
, aMergePolyPolygonB
);
1125 aMergePolyPolygonA
= basegfx::tools::solvePolygonOperationOr(aMergePolyPolygonA
, aMergePolyPolygonB
);
1129 case SDR_MERGE_SUBSTRACT
:
1131 // Substract B from A
1132 aMergePolyPolygonA
= basegfx::tools::solvePolygonOperationDiff(aMergePolyPolygonA
, aMergePolyPolygonB
);
1135 case SDR_MERGE_INTERSECT
:
1138 aMergePolyPolygonA
= basegfx::tools::solvePolygonOperationAnd(aMergePolyPolygonA
, aMergePolyPolygonB
);
1143 // #i73441# check insert list before taking actions
1146 SdrPathObj
* pPath
= new SdrPathObj(OBJ_PATHFILL
, aMergePolyPolygonA
);
1147 ImpCopyAttributes(pAttrObj
, pPath
);
1148 SdrInsertReason
aReason(SDRREASON_VIEWCALL
, pAttrObj
);
1149 pInsOL
->InsertObject(pPath
, nInsPos
, &aReason
);
1151 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoNewObject(*pPath
));
1152 MarkObj(pPath
, pInsPV
, sal_False
, sal_True
);
1155 aRemove
.ForceSort();
1158 case SDR_MERGE_MERGE
:
1161 ImpGetResStr(STR_EditMergeMergePoly
),
1162 aRemove
.GetMarkDescription());
1165 case SDR_MERGE_SUBSTRACT
:
1168 ImpGetResStr(STR_EditMergeSubstractPoly
),
1169 aRemove
.GetMarkDescription());
1172 case SDR_MERGE_INTERSECT
:
1175 ImpGetResStr(STR_EditMergeIntersectPoly
),
1176 aRemove
.GetMarkDescription());
1180 DeleteMarkedList(aRemove
);
1187 void SdrEditView::CombineMarkedObjects(sal_Bool bNoPolyPoly
)
1189 // #105899# Start of Combine-Undo put to front, else ConvertMarkedToPolyObj would
1190 // create a 2nd Undo-action and Undo-Comment.
1192 bool bUndo
= IsUndoEnabled();
1194 // Undo-String will be set later
1196 BegUndo(String(), String(), bNoPolyPoly
? SDRREPFUNC_OBJ_COMBINE_ONEPOLY
: SDRREPFUNC_OBJ_COMBINE_POLYPOLY
);
1198 // #105899# First, guarantee that all objects are converted to polyobjects,
1199 // especially for SdrGrafObj with bitmap filling this is necessary to not
1200 // loose the bitmap filling.
1203 // ConvertMarkedToPolyObj was too strong here, it will loose quality and
1204 // information when curve objects are combined. This can be replaced by
1205 // using ConvertMarkedToPathObj without changing the previous fix.
1208 // Instead of simply passing sal_True as LineToArea, use bNoPolyPoly as info
1209 // if this command is a 'Combine' or a 'Connect' command. On Connect it's sal_True.
1210 // To not concert line segments with a set line width to polygons in that case,
1211 // use this info. Do not convert LineToArea on Connect commands.
1212 // ConvertMarkedToPathObj(!bNoPolyPoly);
1214 // This is used for Combine and Connect. In no case it is necessary to force
1215 // the content to curve, but it is also not good to force to polygons. Thus,
1216 // curve is the less information loosing one. Remember: This place is not
1218 // LineToArea is never necessary, both commands are able to take over the
1219 // set line style and to display it correctly. Thus, i will use a
1220 // ConvertMarkedToPathObj with a sal_False in any case. Only drawback is that
1221 // simple polygons will be changed to curves, but with no information loss.
1222 ConvertMarkedToPathObj(sal_False
/* bLineToArea */);
1224 // continue as before
1225 basegfx::B2DPolyPolygon aPolyPolygon
;
1226 SdrObjList
* pAktOL
= 0L;
1227 SdrMarkList aRemoveMerker
;
1229 SortMarkedObjects();
1230 sal_uInt32
nInsPos(0xFFFFFFFF);
1231 SdrObjList
* pInsOL
= 0L;
1232 SdrPageView
* pInsPV
= 0L;
1233 const sal_uInt32
nAnz(GetMarkedObjectCount());
1234 const SdrObject
* pAttrObj
= 0L;
1236 for(sal_uInt32
a(nAnz
); a
> 0L; )
1239 SdrMark
* pM
= GetSdrMarkByIndex(a
);
1240 SdrObject
* pObj
= pM
->GetMarkedSdrObj();
1241 SdrObjList
* pThisOL
= pObj
->GetObjList();
1243 if(pAktOL
!= pThisOL
)
1248 if(ImpCanConvertForCombine(pObj
))
1250 // remember objects to be able to copy attributes
1253 // unfortunately ConvertMarkedToPathObj has converted all
1254 // involved polygon data to curve segments, even if not necessary.
1255 // It is better to try to reduce to more simple polygons.
1256 basegfx::B2DPolyPolygon
aTmpPoly(basegfx::tools::simplifyCurveSegments(ImpGetPolyPolygon(pObj
, sal_True
)));
1257 aPolyPolygon
.insert(0L, aTmpPoly
);
1261 nInsPos
= pObj
->GetOrdNum() + 1L;
1262 pInsPV
= pM
->GetPageView();
1263 pInsOL
= pObj
->GetObjList();
1266 aRemoveMerker
.InsertEntry(SdrMark(pObj
, pM
->GetPageView()));
1272 basegfx::B2DPolygon
aCombinedPolygon(ImpCombineToSinglePolygon(aPolyPolygon
));
1273 aPolyPolygon
.clear();
1274 aPolyPolygon
.append(aCombinedPolygon
);
1277 const sal_uInt32
nPolyCount(aPolyPolygon
.count());
1281 SdrObjKind eKind
= OBJ_PATHFILL
;
1285 aPolyPolygon
.setClosed(true);
1289 // check for Polyline
1290 const basegfx::B2DPolygon
aPolygon(aPolyPolygon
.getB2DPolygon(0L));
1291 const sal_uInt32
nPointCount(aPolygon
.count());
1293 if(nPointCount
<= 2L)
1295 eKind
= OBJ_PATHLINE
;
1299 if(!aPolygon
.isClosed())
1301 const basegfx::B2DPoint
aPointA(aPolygon
.getB2DPoint(0L));
1302 const basegfx::B2DPoint
aPointB(aPolygon
.getB2DPoint(nPointCount
- 1L));
1303 const double fDistance(basegfx::B2DVector(aPointB
- aPointA
).getLength());
1304 const double fJoinTolerance(10.0);
1306 if(fDistance
< fJoinTolerance
)
1308 aPolyPolygon
.setClosed(true);
1312 eKind
= OBJ_PATHLINE
;
1318 SdrPathObj
* pPath
= new SdrPathObj(eKind
,aPolyPolygon
);
1320 // attributes of the lowest object
1321 ImpCopyAttributes(pAttrObj
, pPath
);
1323 // If LineStyle of pAttrObj is XLINE_NONE force to XLINE_SOLID to make visible.
1324 const XLineStyle eLineStyle
= ((const XLineStyleItem
&)pAttrObj
->GetMergedItem(XATTR_LINESTYLE
)).GetValue();
1325 const XFillStyle eFillStyle
= ((const XFillStyleItem
&)pAttrObj
->GetMergedItem(XATTR_FILLSTYLE
)).GetValue();
1327 // Take fill style/closed state of pAttrObj in account when deciding to change the line style
1328 sal_Bool
bIsClosedPathObj(pAttrObj
->ISA(SdrPathObj
) && ((SdrPathObj
*)pAttrObj
)->IsClosed());
1330 if(XLINE_NONE
== eLineStyle
&& (XFILL_NONE
== eFillStyle
|| !bIsClosedPathObj
))
1332 pPath
->SetMergedItem(XLineStyleItem(XLINE_SOLID
));
1335 SdrInsertReason
aReason(SDRREASON_VIEWCALL
,pAttrObj
);
1336 pInsOL
->InsertObject(pPath
,nInsPos
,&aReason
);
1338 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoNewObject(*pPath
));
1340 // Here was a severe error: Without UnmarkAllObj, the new object was marked
1341 // additionally to the two ones which are deleted below. As long as those are
1342 // in the UNDO there is no problem, but as soon as they get deleted, the
1343 // MarkList will contain deleted objects -> GPF.
1344 UnmarkAllObj(pInsPV
);
1345 MarkObj(pPath
, pInsPV
, sal_False
, sal_True
);
1348 // build an UndoComment from the objects actually used
1349 aRemoveMerker
.ForceSort(); // important for remove (see below)
1351 SetUndoComment(ImpGetResStr(bNoPolyPoly
?STR_EditCombine_OnePoly
:STR_EditCombine_PolyPoly
),aRemoveMerker
.GetMarkDescription());
1353 // remove objects actually used from the list
1354 DeleteMarkedList(aRemoveMerker
);
1359 ////////////////////////////////////////////////////////////////////////////////////////////////////
1361 ////////////////////////////////////////////////////////////////////////////////////////////////////
1363 sal_Bool
SdrEditView::ImpCanDismantle(const basegfx::B2DPolyPolygon
& rPpolyPolygon
, sal_Bool bMakeLines
) const
1365 sal_Bool
bCan(sal_False
);
1366 const sal_uInt32
nPolygonCount(rPpolyPolygon
.count());
1368 if(nPolygonCount
>= 2L)
1370 // #i69172# dismantle makes sense with 2 or more polygons in a polyPolygon
1373 else if(bMakeLines
&& 1L == nPolygonCount
)
1375 // #i69172# ..or with at least 2 edges (curves or lines)
1376 const basegfx::B2DPolygon
aPolygon(rPpolyPolygon
.getB2DPolygon(0L));
1377 const sal_uInt32
nPointCount(aPolygon
.count());
1379 if(nPointCount
> 2L)
1388 sal_Bool
SdrEditView::ImpCanDismantle(const SdrObject
* pObj
, sal_Bool bMakeLines
) const
1390 sal_Bool
bOtherObjs(sal_False
); // sal_True=objects other than PathObj's existent
1391 sal_Bool
bMin1PolyPoly(sal_False
); // sal_True=at least 1 PolyPolygon with more than one Polygon existent
1392 SdrObjList
* pOL
= pObj
->GetSubList();
1396 // group object -- check all members if they're PathObjs
1397 SdrObjListIter
aIter(*pOL
, IM_DEEPNOGROUPS
);
1399 while(aIter
.IsMore() && !bOtherObjs
)
1401 const SdrObject
* pObj1
= aIter
.Next();
1402 const SdrPathObj
* pPath
= PTR_CAST(SdrPathObj
, pObj1
);
1406 if(ImpCanDismantle(pPath
->GetPathPoly(), bMakeLines
))
1408 bMin1PolyPoly
= sal_True
;
1411 SdrObjTransformInfoRec aInfo
;
1412 pObj1
->TakeObjInfo(aInfo
);
1414 if(!aInfo
.bCanConvToPath
)
1416 // happens e. g. in the case of FontWork
1417 bOtherObjs
= sal_True
;
1422 bOtherObjs
= sal_True
;
1428 const SdrPathObj
* pPath
= PTR_CAST(SdrPathObj
, pObj
);
1429 const SdrObjCustomShape
* pCustomShape
= PTR_CAST(SdrObjCustomShape
, pObj
);
1434 if(ImpCanDismantle(pPath
->GetPathPoly(),bMakeLines
))
1436 bMin1PolyPoly
= sal_True
;
1439 SdrObjTransformInfoRec aInfo
;
1440 pObj
->TakeObjInfo(aInfo
);
1442 // new condition IsLine() to be able to break simple Lines
1443 if(!(aInfo
.bCanConvToPath
|| aInfo
.bCanConvToPoly
) && !pPath
->IsLine())
1445 // happens e. g. in the case of FontWork
1446 bOtherObjs
= sal_True
;
1449 else if(pCustomShape
)
1453 // allow break command
1454 bMin1PolyPoly
= sal_True
;
1459 bOtherObjs
= sal_True
;
1462 return bMin1PolyPoly
&& !bOtherObjs
;
1465 void SdrEditView::ImpDismantleOneObject(const SdrObject
* pObj
, SdrObjList
& rOL
, sal_uIntPtr
& rPos
, SdrPageView
* pPV
, sal_Bool bMakeLines
)
1467 const SdrPathObj
* pSrcPath
= PTR_CAST(SdrPathObj
, pObj
);
1468 const SdrObjCustomShape
* pCustomShape
= PTR_CAST(SdrObjCustomShape
, pObj
);
1470 const bool bUndo
= IsUndoEnabled();
1474 // #i74631# redesigned due to XpolyPolygon removal and explicit constructors
1475 SdrObject
* pLast
= 0; // to be able to apply OutlinerParaObject
1476 const basegfx::B2DPolyPolygon
& rPolyPolygon(pSrcPath
->GetPathPoly());
1477 const sal_uInt32
nPolyCount(rPolyPolygon
.count());
1479 for(sal_uInt32
a(0); a
< nPolyCount
; a
++)
1481 const basegfx::B2DPolygon
& rCandidate(rPolyPolygon
.getB2DPolygon(a
));
1482 const sal_uInt32
nPointCount(rCandidate
.count());
1484 if(!bMakeLines
|| nPointCount
< 2)
1486 SdrPathObj
* pPath
= new SdrPathObj((SdrObjKind
)pSrcPath
->GetObjIdentifier(), basegfx::B2DPolyPolygon(rCandidate
));
1487 ImpCopyAttributes(pSrcPath
, pPath
);
1489 SdrInsertReason
aReason(SDRREASON_VIEWCALL
, pSrcPath
);
1490 rOL
.InsertObject(pPath
, rPos
, &aReason
);
1492 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoNewObject(*pPath
, sal_True
));
1493 MarkObj(pPath
, pPV
, sal_False
, sal_True
);
1498 const sal_uInt32
nLoopCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
1500 for(sal_uInt32
b(0); b
< nLoopCount
; b
++)
1502 SdrObjKind
eKind(OBJ_PLIN
);
1503 basegfx::B2DPolygon aNewPolygon
;
1504 const sal_uInt32
nNextIndex((b
+ 1) % nPointCount
);
1506 aNewPolygon
.append(rCandidate
.getB2DPoint(b
));
1508 if(rCandidate
.areControlPointsUsed())
1510 aNewPolygon
.appendBezierSegment(
1511 rCandidate
.getNextControlPoint(b
),
1512 rCandidate
.getPrevControlPoint(nNextIndex
),
1513 rCandidate
.getB2DPoint(nNextIndex
));
1514 eKind
= OBJ_PATHLINE
;
1518 aNewPolygon
.append(rCandidate
.getB2DPoint(nNextIndex
));
1521 SdrPathObj
* pPath
= new SdrPathObj(eKind
, basegfx::B2DPolyPolygon(aNewPolygon
));
1522 ImpCopyAttributes(pSrcPath
, pPath
);
1524 SdrInsertReason
aReason(SDRREASON_VIEWCALL
, pSrcPath
);
1525 rOL
.InsertObject(pPath
, rPos
, &aReason
);
1527 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoNewObject(*pPath
, sal_True
));
1528 MarkObj(pPath
, pPV
, sal_False
, sal_True
);
1534 if(pLast
&& pSrcPath
->GetOutlinerParaObject())
1536 pLast
->SetOutlinerParaObject(new OutlinerParaObject(*pSrcPath
->GetOutlinerParaObject()));
1539 else if(pCustomShape
)
1543 // break up custom shape
1544 const SdrObject
* pReplacement
= pCustomShape
->GetSdrObjectFromCustomShape();
1548 SdrObject
* pCandidate
= pReplacement
->Clone();
1549 DBG_ASSERT(pCandidate
, "SdrEditView::ImpDismantleOneObject: Could not clone SdrObject (!)");
1550 pCandidate
->SetModel(pCustomShape
->GetModel());
1552 if(((SdrShadowItem
&)pCustomShape
->GetMergedItem(SDRATTR_SHADOW
)).GetValue())
1554 if(pReplacement
->ISA(SdrObjGroup
))
1556 pCandidate
->SetMergedItem(SdrShadowItem(sal_True
));
1560 SdrInsertReason
aReason(SDRREASON_VIEWCALL
, pCustomShape
);
1561 rOL
.InsertObject(pCandidate
, rPos
, &aReason
);
1563 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoNewObject(*pCandidate
, true));
1564 MarkObj(pCandidate
, pPV
, sal_False
, sal_True
);
1566 if(pCustomShape
->HasText() && !pCustomShape
->IsTextPath())
1568 // #i37011# also create a text object and add at rPos + 1
1569 SdrTextObj
* pTextObj
= (SdrTextObj
*)SdrObjFactory::MakeNewObject(
1570 pCustomShape
->GetObjInventor(), OBJ_TEXT
, 0L, pCustomShape
->GetModel());
1572 // Copy text content
1573 OutlinerParaObject
* pParaObj
= pCustomShape
->GetOutlinerParaObject();
1576 pTextObj
->NbcSetOutlinerParaObject(new OutlinerParaObject(*pParaObj
));
1579 // copy all attributes
1580 SfxItemSet
aTargetItemSet(pCustomShape
->GetMergedItemSet());
1582 // clear fill and line style
1583 aTargetItemSet
.Put(XLineStyleItem(XLINE_NONE
));
1584 aTargetItemSet
.Put(XFillStyleItem(XFILL_NONE
));
1586 // get the text bounds and set at text object
1587 Rectangle aTextBounds
= pCustomShape
->GetSnapRect();
1588 if(pCustomShape
->GetTextBounds(aTextBounds
))
1590 pTextObj
->SetSnapRect(aTextBounds
);
1593 // if rotated, copy GeoStat, too.
1594 const GeoStat
& rSourceGeo
= pCustomShape
->GetGeoStat();
1595 if(rSourceGeo
.nDrehWink
)
1597 pTextObj
->NbcRotate(
1598 pCustomShape
->GetSnapRect().Center(), rSourceGeo
.nDrehWink
,
1599 rSourceGeo
.nSin
, rSourceGeo
.nCos
);
1602 // set modified ItemSet at text object
1603 pTextObj
->SetMergedItemSet(aTargetItemSet
);
1606 rOL
.InsertObject(pTextObj
, rPos
+ 1, &aReason
);
1608 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoNewObject(*pTextObj
, true));
1609 MarkObj(pTextObj
, pPV
, sal_False
, sal_True
);
1616 void SdrEditView::DismantleMarkedObjects(sal_Bool bMakeLines
)
1618 // temporary MarkList
1619 SdrMarkList aRemoveMerker
;
1621 SortMarkedObjects();
1623 const bool bUndo
= IsUndoEnabled();
1627 // comment is constructed later
1628 BegUndo(String(), String(),
1629 bMakeLines
? SDRREPFUNC_OBJ_DISMANTLE_LINES
: SDRREPFUNC_OBJ_DISMANTLE_POLYS
);
1633 sal_uIntPtr nAnz
=GetMarkedObjectCount();
1634 SdrObjList
* pOL0
=NULL
;
1635 for (nm
=nAnz
; nm
>0;) {
1637 SdrMark
* pM
=GetSdrMarkByIndex(nm
);
1638 SdrObject
* pObj
=pM
->GetMarkedSdrObj();
1639 SdrPageView
* pPV
=pM
->GetPageView();
1640 SdrObjList
* pOL
=pObj
->GetObjList();
1641 if (pOL
!=pOL0
) { pOL0
=pOL
; pObj
->GetOrdNum(); } // make sure OrdNums are correct!
1642 if (ImpCanDismantle(pObj
,bMakeLines
)) {
1643 aRemoveMerker
.InsertEntry(SdrMark(pObj
,pM
->GetPageView()));
1644 sal_uIntPtr nPos0
=pObj
->GetOrdNumDirect();
1645 sal_uIntPtr nPos
=nPos0
+1;
1646 SdrObjList
* pSubList
=pObj
->GetSubList();
1647 if (pSubList
!=NULL
&& !pObj
->Is3DObj()) {
1648 SdrObjListIter
aIter(*pSubList
,IM_DEEPNOGROUPS
);
1649 while (aIter
.IsMore()) {
1650 const SdrObject
* pObj1
=aIter
.Next();
1651 ImpDismantleOneObject(pObj1
,*pOL
,nPos
,pPV
,bMakeLines
);
1654 ImpDismantleOneObject(pObj
,*pOL
,nPos
,pPV
,bMakeLines
);
1657 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoDeleteObject(*pObj
,sal_True
));
1658 pOL
->RemoveObject(nPos0
);
1661 SdrObject::Free(pObj
);
1667 // construct UndoComment from objects actually used
1668 SetUndoComment(ImpGetResStr(bMakeLines
?STR_EditDismantle_Lines
:STR_EditDismantle_Polys
),aRemoveMerker
.GetMarkDescription());
1669 // remove objects actually used from the list
1674 ////////////////////////////////////////////////////////////////////////////////////////////////////
1676 ////////////////////////////////////////////////////////////////////////////////////////////////////
1678 void SdrEditView::GroupMarked(const SdrObject
* pUserGrp
)
1680 if (AreObjectsMarked())
1682 SortMarkedObjects();
1684 const bool bUndo
= IsUndoEnabled();
1687 BegUndo(ImpGetResStr(STR_EditGroup
),GetDescriptionOfMarkedObjects(),SDRREPFUNC_OBJ_GROUP
);
1689 const sal_uIntPtr nAnz
= GetMarkedObjectCount();
1690 for(sal_uIntPtr nm
= nAnz
; nm
>0; )
1692 // add UndoActions for all affected objects
1694 SdrMark
* pM
=GetSdrMarkByIndex(nm
);
1695 SdrObject
* pObj
= pM
->GetMarkedSdrObj();
1696 std::vector
< SdrUndoAction
* > vConnectorUndoActions( CreateConnectorUndo( *pObj
) );
1697 AddUndoActions( vConnectorUndoActions
);
1698 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoRemoveObject( *pObj
));
1702 SdrMarkList aNewMark
;
1703 SdrPageView
* pPV
= GetSdrPageView();
1707 SdrObjList
* pAktLst
=pPV
->GetObjList();
1708 SdrObjList
* pSrcLst
=pAktLst
;
1709 SdrObjList
* pSrcLst0
=pSrcLst
;
1710 SdrPage
* pPage
=pPV
->GetPage();
1711 // make sure OrdNums are correct
1712 if (pSrcLst
->IsObjOrdNumsDirty())
1713 pSrcLst
->RecalcObjOrdNums();
1714 SdrObject
* pGrp
=NULL
;
1715 SdrObject
* pRefObj
=NULL
; // reference for InsertReason (-> anchors in Writer)
1716 SdrObject
* pRefObj1
=NULL
; // reference for InsertReason (-> anchors in Writer)
1717 SdrObjList
* pDstLst
=NULL
;
1718 // if all selected objects come from foreign object lists.
1719 // the group object is the last one in the list.
1720 sal_uIntPtr nInsPos
=pSrcLst
->GetObjCount();
1721 sal_Bool bNeedInsPos
=sal_True
;
1722 for (sal_uIntPtr nm
=GetMarkedObjectCount(); nm
>0;)
1725 SdrMark
* pM
=GetSdrMarkByIndex(nm
);
1726 if (pM
->GetPageView()==pPV
)
1731 pGrp
=pUserGrp
->Clone();
1733 pGrp
=new SdrObjGroup
;
1734 pDstLst
=pGrp
->GetSubList();
1735 DBG_ASSERT(pDstLst
!=NULL
,"Alleged group object doesn't return object list.");
1737 SdrObject
* pObj
=pM
->GetMarkedSdrObj();
1738 pSrcLst
=pObj
->GetObjList();
1739 if (pSrcLst
!=pSrcLst0
)
1741 if (pSrcLst
->IsObjOrdNumsDirty())
1742 pSrcLst
->RecalcObjOrdNums();
1744 sal_Bool bForeignList
=pSrcLst
!=pAktLst
;
1745 sal_Bool bGrouped
=pSrcLst
!=pPage
;
1746 if (!bForeignList
&& bNeedInsPos
)
1748 nInsPos
=pObj
->GetOrdNum(); // this way, all ObjOrdNum of the page are set
1750 bNeedInsPos
=sal_False
;
1752 pSrcLst
->RemoveObject(pObj
->GetOrdNumDirect());
1754 nInsPos
--; // correct InsertPos
1755 SdrInsertReason
aReason(SDRREASON_VIEWCALL
);
1756 pDstLst
->InsertObject(pObj
,0,&aReason
);
1757 GetMarkedObjectListWriteAccess().DeleteMark(nm
);
1759 pRefObj1
=pObj
; // the topmost visible object
1763 pRefObj
=pObj
; // the topmost visible non-group object
1772 aNewMark
.InsertEntry(SdrMark(pGrp
,pPV
));
1773 sal_uIntPtr nAnz
=pDstLst
->GetObjCount();
1774 SdrInsertReason
aReason(SDRREASON_VIEWCALL
,pRefObj
);
1775 pAktLst
->InsertObject(pGrp
,nInsPos
,&aReason
);
1778 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoNewObject(*pGrp
,true)); // no recalculation!
1779 for (sal_uIntPtr no
=0; no
<nAnz
; no
++)
1781 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoInsertObject(*pDstLst
->GetObj(no
)));
1786 GetMarkedObjectListWriteAccess().Merge(aNewMark
);
1787 MarkListHasChanged();
1794 ////////////////////////////////////////////////////////////////////////////////////////////////////
1796 ////////////////////////////////////////////////////////////////////////////////////////////////////
1798 void SdrEditView::UnGroupMarked()
1800 SdrMarkList aNewMark
;
1802 const bool bUndo
= IsUndoEnabled();
1804 BegUndo(String(), String(), SDRREPFUNC_OBJ_UNGROUP
);
1806 sal_uIntPtr nCount
=0;
1809 sal_Bool bNameOk
=sal_False
;
1810 for (sal_uIntPtr nm
=GetMarkedObjectCount(); nm
>0;) {
1812 SdrMark
* pM
=GetSdrMarkByIndex(nm
);
1813 SdrObject
* pGrp
=pM
->GetMarkedSdrObj();
1814 SdrObjList
* pSrcLst
=pGrp
->GetSubList();
1815 if (pSrcLst
!=NULL
) {
1818 pGrp
->TakeObjNameSingul(aName
); // retrieve name of group
1819 pGrp
->TakeObjNamePlural(aName1
); // retrieve name of group
1822 if (nCount
==2) aName
=aName1
; // set plural name
1825 pGrp
->TakeObjNamePlural(aStr
); // retrieve name of group
1827 if(!aStr
.Equals(aName
))
1828 bNameOk
= sal_False
;
1831 sal_uIntPtr nDstCnt
=pGrp
->GetOrdNum();
1832 SdrObjList
* pDstLst
=pM
->GetPageView()->GetObjList();
1834 // FIRST move contained objects to parent of group, so that
1835 // the contained objects are NOT migrated to the UNDO-ItemPool
1836 // when AddUndo(new SdrUndoDelObj(*pGrp)) is called.
1837 sal_uIntPtr nAnz
=pSrcLst
->GetObjCount();
1842 for (no
=nAnz
; no
>0;)
1845 SdrObject
* pObj
=pSrcLst
->GetObj(no
);
1846 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoRemoveObject(*pObj
));
1849 for (no
=0; no
<nAnz
; no
++)
1851 SdrObject
* pObj
=pSrcLst
->RemoveObject(0);
1852 SdrInsertReason
aReason(SDRREASON_VIEWCALL
,pGrp
);
1853 pDstLst
->InsertObject(pObj
,nDstCnt
,&aReason
);
1855 AddUndo( GetModel()->GetSdrUndoFactory().CreateUndoInsertObject(*pObj
,true));
1857 // No SortCheck when inserting into MarkList, because that would
1858 // provoke a RecalcOrdNums() each time because of pObj->GetOrdNum():
1859 aNewMark
.InsertEntry(SdrMark(pObj
,pM
->GetPageView()),sal_False
);
1864 // Now it is safe to add the delete-UNDO which triggers the
1865 // MigrateItemPool now only for itself, not for the sub-objects.
1866 // nDstCnt is right, because previous inserts move group
1867 // object deeper and increase nDstCnt.
1868 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoDeleteObject(*pGrp
));
1870 pDstLst
->RemoveObject(nDstCnt
);
1873 SdrObject::Free(pGrp
);
1875 GetMarkedObjectListWriteAccess().DeleteMark(nm
);
1881 aName
=ImpGetResStr(STR_ObjNamePluralGRUP
); // Use the term "Group Objects," if different objects are grouped.
1882 SetUndoComment(ImpGetResStr(STR_EditUngroup
),aName
);
1890 GetMarkedObjectListWriteAccess().Merge(aNewMark
,sal_True
); // Because of the sorting above, aNewMark is reversed
1891 MarkListHasChanged();
1895 ////////////////////////////////////////////////////////////////////////////////////////////////////
1897 ////////////////////////////////////////////////////////////////////////////////////////////////////
1899 SdrObject
* SdrEditView::ImpConvertOneObj(SdrObject
* pObj
, sal_Bool bPath
, sal_Bool bLineToArea
)
1901 SdrObject
* pNewObj
= pObj
->ConvertToPolyObj(bPath
, bLineToArea
);
1904 SdrObjList
* pOL
=pObj
->GetObjList();
1905 DBG_ASSERT(pOL
!=NULL
,"ConvertTo: Object doesn't return object list");
1908 const bool bUndo
= IsUndoEnabled();
1910 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoReplaceObject(*pObj
,*pNewObj
));
1912 pOL
->ReplaceObject(pNewObj
,pObj
->GetOrdNum());
1915 SdrObject::Free(pObj
);
1921 void SdrEditView::ImpConvertTo(sal_Bool bPath
, sal_Bool bLineToArea
)
1923 sal_Bool bMrkChg
=sal_False
;
1924 if (AreObjectsMarked()) {
1925 sal_uIntPtr nMarkAnz
=GetMarkedObjectCount();
1926 sal_uInt16 nDscrID
=0;
1930 nDscrID
= STR_EditConvToContour
;
1932 nDscrID
= STR_EditConvToContours
;
1934 BegUndo(ImpGetResStr(nDscrID
), GetDescriptionOfMarkedObjects());
1939 if (nMarkAnz
==1) nDscrID
=STR_EditConvToCurve
;
1940 else nDscrID
=STR_EditConvToCurves
;
1941 BegUndo(ImpGetResStr(nDscrID
),GetDescriptionOfMarkedObjects(),SDRREPFUNC_OBJ_CONVERTTOPATH
);
1943 if (nMarkAnz
==1) nDscrID
=STR_EditConvToPoly
;
1944 else nDscrID
=STR_EditConvToPolys
;
1945 BegUndo(ImpGetResStr(nDscrID
),GetDescriptionOfMarkedObjects(),SDRREPFUNC_OBJ_CONVERTTOPOLY
);
1948 for (sal_uIntPtr nm
=nMarkAnz
; nm
>0;) {
1950 SdrMark
* pM
=GetSdrMarkByIndex(nm
);
1951 SdrObject
* pObj
=pM
->GetMarkedSdrObj();
1952 SdrPageView
* pPV
=pM
->GetPageView();
1953 if (pObj
->IsGroupObject() && !pObj
->Is3DObj()) {
1954 SdrObject
* pGrp
=pObj
;
1955 SdrObjListIter
aIter(*pGrp
,IM_DEEPNOGROUPS
);
1956 while (aIter
.IsMore()) {
1958 ImpConvertOneObj(pObj
,bPath
,bLineToArea
);
1961 SdrObject
* pNewObj
=ImpConvertOneObj(pObj
,bPath
,bLineToArea
);
1962 if (pNewObj
!=NULL
) {
1964 GetMarkedObjectListWriteAccess().ReplaceMark(SdrMark(pNewObj
,pPV
),nm
);
1969 if (bMrkChg
) AdjustMarkHdl();
1970 if (bMrkChg
) MarkListHasChanged();
1974 void SdrEditView::ConvertMarkedToPathObj(sal_Bool bLineToArea
)
1976 ImpConvertTo(sal_True
, bLineToArea
);
1979 void SdrEditView::ConvertMarkedToPolyObj(sal_Bool bLineToArea
)
1981 ImpConvertTo(sal_False
, bLineToArea
);
1984 ////////////////////////////////////////////////////////////////////////////////////////////////////
1986 ////////////////////////////////////////////////////////////////////////////////////////////////////
1988 void SdrEditView::DoImportMarkedMtf(SvdProgressInfo
*pProgrInfo
)
1990 const bool bUndo
= IsUndoEnabled();
1993 BegUndo(String(), String(), SDRREPFUNC_OBJ_IMPORTMTF
);
1995 SortMarkedObjects();
1996 SdrMarkList aForTheDescription
;
1997 SdrMarkList aNewMarked
;
1998 sal_uIntPtr nAnz
=GetMarkedObjectCount();
2000 for (sal_uIntPtr nm
=nAnz
; nm
>0;)
2001 { // create Undo objects for all new objects
2002 // check for cancellation between the metafiles
2003 if( pProgrInfo
!= NULL
)
2005 pProgrInfo
->SetNextObject();
2006 if(!pProgrInfo
->ReportActions(0))
2011 SdrMark
* pM
=GetSdrMarkByIndex(nm
);
2012 SdrObject
* pObj
=pM
->GetMarkedSdrObj();
2013 SdrPageView
* pPV
=pM
->GetPageView();
2014 SdrObjList
* pOL
=pObj
->GetObjList();
2015 sal_uIntPtr nInsPos
=pObj
->GetOrdNum()+1;
2016 SdrGrafObj
* pGraf
=PTR_CAST(SdrGrafObj
,pObj
);
2017 SdrOle2Obj
* pOle2
=PTR_CAST(SdrOle2Obj
,pObj
);
2018 sal_uIntPtr nInsAnz
=0;
2019 if (pGraf
!=NULL
&& pGraf
->HasGDIMetaFile())
2021 ImpSdrGDIMetaFileImport
aFilter(*pMod
);
2022 aFilter
.SetScaleRect(pGraf
->GetSnapRect());
2023 aFilter
.SetLayer(pObj
->GetLayer());
2024 nInsAnz
=aFilter
.DoImport(pGraf
->GetTransformedGraphic().GetGDIMetaFile(),*pOL
,nInsPos
,pProgrInfo
);
2026 if ( pOle2
!=NULL
&& pOle2
->GetGraphic() )
2028 ImpSdrGDIMetaFileImport
aFilter(*pMod
);
2029 aFilter
.SetScaleRect(pOle2
->GetLogicRect());
2030 aFilter
.SetLayer(pObj
->GetLayer());
2031 nInsAnz
=aFilter
.DoImport(pOle2
->GetGraphic()->GetGDIMetaFile(),*pOL
,nInsPos
,pProgrInfo
);
2035 sal_uIntPtr nObj
=nInsPos
;
2036 for (sal_uIntPtr i
=0; i
<nInsAnz
; i
++)
2039 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoNewObject(*pOL
->GetObj(nObj
)));
2041 // update new MarkList
2042 SdrMark
aNewMark(pOL
->GetObj(nObj
), pPV
);
2043 aNewMarked
.InsertEntry(aNewMark
);
2047 aForTheDescription
.InsertEntry(*pM
);
2050 AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoDeleteObject(*pObj
));
2052 // remove object from selection and delete
2053 GetMarkedObjectListWriteAccess().DeleteMark(TryToFindMarkedObject(pObj
));
2054 pOL
->RemoveObject(nInsPos
-1);
2057 SdrObject::Free(pObj
);
2061 if(aNewMarked
.GetMarkCount())
2063 // create new selection
2064 for(sal_uIntPtr
a(0); a
< aNewMarked
.GetMarkCount(); a
++)
2066 GetMarkedObjectListWriteAccess().InsertEntry(*aNewMarked
.GetMark(a
));
2069 SortMarkedObjects();
2074 SetUndoComment(ImpGetResStr(STR_EditImportMtf
),aForTheDescription
.GetMarkDescription());
2079 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */