xref: /trunk/main/svx/source/svdraw/svdocirc.cxx (revision 3a7cf181c55416e69e525ddc0b38c22235ec1569)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_svx.hxx"
30 #include <svl/style.hxx>
31 #include <tools/bigint.hxx>
32 #include <svx/xlnwtit.hxx>
33 #include <svx/xlnedwit.hxx>
34 #include <svx/xlnstwit.hxx>
35 #include <svx/xlnstit.hxx>
36 #include <svx/xlnedit.hxx>
37 #include <svx/svdocirc.hxx>
38 #include <math.h>
39 #include <svx/xpool.hxx>
40 #include <svx/svdattr.hxx>
41 #include <svx/svdpool.hxx>
42 #include <svx/svdattrx.hxx>
43 #include <svx/svdtrans.hxx>
44 #include <svx/svdetc.hxx>
45 #include <svx/svddrag.hxx>
46 #include <svx/svdmodel.hxx>
47 #include <svx/svdpage.hxx>
48 #include <svx/svdopath.hxx> // fuer die Objektkonvertierung
49 #include <svx/svdview.hxx>  // Zum Draggen (Ortho)
50 #include "svx/svdglob.hxx"   // StringCache
51 #include "svx/svdstr.hrc"    // Objektname
52 #include <editeng/eeitem.hxx>
53 #include "svdoimp.hxx"
54 #include <svx/sdr/properties/circleproperties.hxx>
55 #include <svx/sdr/contact/viewcontactofsdrcircobj.hxx>
56 #include <basegfx/point/b2dpoint.hxx>
57 #include <basegfx/polygon/b2dpolygon.hxx>
58 #include <basegfx/polygon/b2dpolygontools.hxx>
59 #include <basegfx/matrix/b2dhommatrix.hxx>
60 #include <basegfx/polygon/b2dpolygontools.hxx>
61 #include <basegfx/matrix/b2dhommatrixtools.hxx>
62 
63 //////////////////////////////////////////////////////////////////////////////
64 
65 Point GetWinkPnt(const Rectangle& rR, long nWink)
66 {
67     Point aCenter(rR.Center());
68     long nWdt=rR.Right()-rR.Left();
69     long nHgt=rR.Bottom()-rR.Top();
70     long nMaxRad=((nWdt>nHgt ? nWdt : nHgt)+1) /2;
71     double a;
72     a=nWink*nPi180;
73     Point aRetval(Round(cos(a)*nMaxRad),-Round(sin(a)*nMaxRad));
74     if (nWdt==0) aRetval.X()=0;
75     if (nHgt==0) aRetval.Y()=0;
76     if (nWdt!=nHgt) {
77         if (nWdt>nHgt) {
78             if (nWdt!=0) {
79                 // eventuelle Ueberlaeufe bei sehr grossen Objekten abfangen (Bug 23384)
80                 if (Abs(nHgt)>32767 || Abs(aRetval.Y())>32767) {
81                     aRetval.Y()=BigMulDiv(aRetval.Y(),nHgt,nWdt);
82                 } else {
83                     aRetval.Y()=aRetval.Y()*nHgt/nWdt;
84                 }
85             }
86         } else {
87             if (nHgt!=0) {
88                 // eventuelle Ueberlaeufe bei sehr grossen Objekten abfangen (Bug 23384)
89                 if (Abs(nWdt)>32767 || Abs(aRetval.X())>32767) {
90                     aRetval.X()=BigMulDiv(aRetval.X(),nWdt,nHgt);
91                 } else {
92                     aRetval.X()=aRetval.X()*nWdt/nHgt;
93                 }
94             }
95         }
96     }
97     aRetval+=aCenter;
98     return aRetval;
99 }
100 
101 //////////////////////////////////////////////////////////////////////////////
102 // BaseProperties section
103 
104 sdr::properties::BaseProperties* SdrCircObj::CreateObjectSpecificProperties()
105 {
106     return new sdr::properties::CircleProperties(*this);
107 }
108 
109 //////////////////////////////////////////////////////////////////////////////
110 // DrawContact section
111 
112 sdr::contact::ViewContact* SdrCircObj::CreateObjectSpecificViewContact()
113 {
114     return new sdr::contact::ViewContactOfSdrCircObj(*this);
115 }
116 
117 //////////////////////////////////////////////////////////////////////////////
118 
119 TYPEINIT1(SdrCircObj,SdrRectObj);
120 
121 SdrCircObj::SdrCircObj(SdrObjKind eNewKind)
122 {
123     nStartWink=0;
124     nEndWink=36000;
125     meCircleKind=eNewKind;
126     bClosedObj=eNewKind!=OBJ_CARC;
127 }
128 
129 SdrCircObj::SdrCircObj(SdrObjKind eNewKind, const Rectangle& rRect):
130     SdrRectObj(rRect)
131 {
132     nStartWink=0;
133     nEndWink=36000;
134     meCircleKind=eNewKind;
135     bClosedObj=eNewKind!=OBJ_CARC;
136 }
137 
138 SdrCircObj::SdrCircObj(SdrObjKind eNewKind, const Rectangle& rRect, long nNewStartWink, long nNewEndWink):
139     SdrRectObj(rRect)
140 {
141     long nWinkDif=nNewEndWink-nNewStartWink;
142     nStartWink=NormAngle360(nNewStartWink);
143     nEndWink=NormAngle360(nNewEndWink);
144     if (nWinkDif==36000) nEndWink+=nWinkDif; // Vollkreis
145     meCircleKind=eNewKind;
146     bClosedObj=eNewKind!=OBJ_CARC;
147 }
148 
149 SdrCircObj::~SdrCircObj()
150 {
151 }
152 
153 void SdrCircObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
154 {
155     FASTBOOL bCanConv=!HasText() || ImpCanConvTextToCurve();
156     rInfo.bEdgeRadiusAllowed    = sal_False;
157     rInfo.bCanConvToPath=bCanConv;
158     rInfo.bCanConvToPoly=bCanConv;
159     rInfo.bCanConvToContour = !IsFontwork() && (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary());
160 }
161 
162 sal_uInt16 SdrCircObj::GetObjIdentifier() const
163 {
164     return sal_uInt16(meCircleKind);
165 }
166 
167 FASTBOOL SdrCircObj::PaintNeedsXPolyCirc() const
168 {
169     // XPoly ist notwendig fuer alle gedrehten Ellipsenobjekte,
170     // fuer alle Kreis- und Ellipsenabschnitte
171     // und wenn nicht WIN dann (erstmal) auch fuer Kreis-/Ellipsenausschnitte
172     // und Kreis-/Ellipsenboegen (wg. Genauigkeit)
173     FASTBOOL bNeed=aGeo.nDrehWink!=0 || aGeo.nShearWink!=0 || meCircleKind==OBJ_CCUT;
174     // Wenn nicht Win, dann fuer alle ausser Vollkreis (erstmal!!!)
175     if (meCircleKind!=OBJ_CIRC) bNeed=sal_True;
176 
177     const SfxItemSet& rSet = GetObjectItemSet();
178     if(!bNeed)
179     {
180         // XPoly ist notwendig fuer alles was nicht LineSolid oder LineNone ist
181         XLineStyle eLine = ((XLineStyleItem&)(rSet.Get(XATTR_LINESTYLE))).GetValue();
182         bNeed = eLine != XLINE_NONE && eLine != XLINE_SOLID;
183 
184         // XPoly ist notwendig fuer dicke Linien
185         if(!bNeed && eLine != XLINE_NONE)
186             bNeed = ((XLineWidthItem&)(rSet.Get(XATTR_LINEWIDTH))).GetValue() != 0;
187 
188         // XPoly ist notwendig fuer Kreisboegen mit Linienenden
189         if(!bNeed && meCircleKind == OBJ_CARC)
190         {
191             // Linienanfang ist da, wenn StartPolygon und StartWidth!=0
192             bNeed=((XLineStartItem&)(rSet.Get(XATTR_LINESTART))).GetLineStartValue().count() != 0L &&
193                   ((XLineStartWidthItem&)(rSet.Get(XATTR_LINESTARTWIDTH))).GetValue() != 0;
194 
195             if(!bNeed)
196             {
197                 // Linienende ist da, wenn EndPolygon und EndWidth!=0
198                 bNeed = ((XLineEndItem&)(rSet.Get(XATTR_LINEEND))).GetLineEndValue().count() != 0L &&
199                         ((XLineEndWidthItem&)(rSet.Get(XATTR_LINEENDWIDTH))).GetValue() != 0;
200             }
201         }
202     }
203 
204     // XPoly ist notwendig, wenn Fill !=None und !=Solid
205     if(!bNeed && meCircleKind != OBJ_CARC)
206     {
207         XFillStyle eFill=((XFillStyleItem&)(rSet.Get(XATTR_FILLSTYLE))).GetValue();
208         bNeed = eFill != XFILL_NONE && eFill != XFILL_SOLID;
209     }
210 
211     if(!bNeed && meCircleKind != OBJ_CIRC && nStartWink == nEndWink)
212         bNeed=sal_True; // Weil sonst Vollkreis gemalt wird
213 
214     return bNeed;
215 }
216 
217 basegfx::B2DPolygon SdrCircObj::ImpCalcXPolyCirc(const SdrObjKind eCicrleKind, const Rectangle& rRect1, long nStart, long nEnd) const
218 {
219     const basegfx::B2DRange aRange(rRect1.Left(), rRect1.Top(), rRect1.Right(), rRect1.Bottom());
220     basegfx::B2DPolygon aCircPolygon;
221 
222     if(OBJ_CIRC == eCicrleKind)
223     {
224         // create full circle. Do not use createPolygonFromEllipse; it's necessary
225         // to get the start point to the bottom of the circle to keep compatible to
226         // old geometry creation
227         aCircPolygon = basegfx::tools::createPolygonFromUnitCircle(1);
228 
229         // needs own scaling and translation from unit circle to target size (same as
230         // would be in createPolygonFromEllipse)
231         const basegfx::B2DPoint aCenter(aRange.getCenter());
232         const basegfx::B2DHomMatrix aMatrix(basegfx::tools::createScaleTranslateB2DHomMatrix(
233             aRange.getWidth() / 2.0, aRange.getHeight() / 2.0,
234             aCenter.getX(), aCenter.getY()));
235         aCircPolygon.transform(aMatrix);
236     }
237     else
238     {
239         // mirror start, end for geometry creation since model coordinate system is mirrored in Y
240         // #i111715# increase numerical correctness by first dividing and not using F_PI1800
241         const double fStart((((36000 - nEnd) % 36000) / 18000.0) * F_PI);
242         const double fEnd((((36000 - nStart) % 36000) / 18000.0) * F_PI);
243 
244         // create circle segment. This is not closed by default
245         aCircPolygon = basegfx::tools::createPolygonFromEllipseSegment(
246             aRange.getCenter(), aRange.getWidth() / 2.0, aRange.getHeight() / 2.0,
247             fStart, fEnd);
248 
249         // check closing states
250         const bool bCloseSegment(OBJ_CARC != eCicrleKind);
251         const bool bCloseUsingCenter(OBJ_SECT == eCicrleKind);
252 
253         if(bCloseSegment)
254         {
255             if(bCloseUsingCenter)
256             {
257                 // add center point at start (for historical reasons)
258                 basegfx::B2DPolygon aSector;
259                 aSector.append(aRange.getCenter());
260                 aSector.append(aCircPolygon);
261                 aCircPolygon = aSector;
262             }
263 
264             // close
265             aCircPolygon.setClosed(true);
266         }
267     }
268 
269     // #i76950#
270     if(aGeo.nShearWink || aGeo.nDrehWink)
271     {
272         // translate top left to (0,0)
273         const basegfx::B2DPoint aTopLeft(aRange.getMinimum());
274         basegfx::B2DHomMatrix aMatrix(basegfx::tools::createTranslateB2DHomMatrix(
275             -aTopLeft.getX(), -aTopLeft.getY()));
276 
277         // shear, rotate and back to top left (if needed)
278         aMatrix = basegfx::tools::createShearXRotateTranslateB2DHomMatrix(
279             aGeo.nShearWink ? tan((36000 - aGeo.nShearWink) * F_PI18000) : 0.0,
280             aGeo.nDrehWink ? (36000 - aGeo.nDrehWink) * F_PI18000 : 0.0,
281             aTopLeft) * aMatrix;
282 
283         // apply transformation
284         aCircPolygon.transform(aMatrix);
285     }
286 
287     return aCircPolygon;
288 }
289 
290 void SdrCircObj::RecalcXPoly()
291 {
292     const basegfx::B2DPolygon aPolyCirc(ImpCalcXPolyCirc(meCircleKind, aRect, nStartWink, nEndWink));
293     mpXPoly = new XPolygon(aPolyCirc);
294 }
295 
296 void SdrCircObj::TakeObjNameSingul(XubString& rName) const
297 {
298     sal_uInt16 nID=STR_ObjNameSingulCIRC;
299     if (aRect.GetWidth()==aRect.GetHeight() && aGeo.nShearWink==0) {
300         switch (meCircleKind) {
301             case OBJ_CIRC: nID=STR_ObjNameSingulCIRC; break;
302             case OBJ_SECT: nID=STR_ObjNameSingulSECT; break;
303             case OBJ_CARC: nID=STR_ObjNameSingulCARC; break;
304             case OBJ_CCUT: nID=STR_ObjNameSingulCCUT; break;
305             default: break;
306         }
307     } else {
308         switch (meCircleKind) {
309             case OBJ_CIRC: nID=STR_ObjNameSingulCIRCE; break;
310             case OBJ_SECT: nID=STR_ObjNameSingulSECTE; break;
311             case OBJ_CARC: nID=STR_ObjNameSingulCARCE; break;
312             case OBJ_CCUT: nID=STR_ObjNameSingulCCUTE; break;
313             default: break;
314         }
315     }
316     rName=ImpGetResStr(nID);
317 
318     String aName( GetName() );
319     if(aName.Len())
320     {
321         rName += sal_Unicode(' ');
322         rName += sal_Unicode('\'');
323         rName += aName;
324         rName += sal_Unicode('\'');
325     }
326 }
327 
328 void SdrCircObj::TakeObjNamePlural(XubString& rName) const
329 {
330     sal_uInt16 nID=STR_ObjNamePluralCIRC;
331     if (aRect.GetWidth()==aRect.GetHeight() && aGeo.nShearWink==0) {
332         switch (meCircleKind) {
333             case OBJ_CIRC: nID=STR_ObjNamePluralCIRC; break;
334             case OBJ_SECT: nID=STR_ObjNamePluralSECT; break;
335             case OBJ_CARC: nID=STR_ObjNamePluralCARC; break;
336             case OBJ_CCUT: nID=STR_ObjNamePluralCCUT; break;
337             default: break;
338         }
339     } else {
340         switch (meCircleKind) {
341             case OBJ_CIRC: nID=STR_ObjNamePluralCIRCE; break;
342             case OBJ_SECT: nID=STR_ObjNamePluralSECTE; break;
343             case OBJ_CARC: nID=STR_ObjNamePluralCARCE; break;
344             case OBJ_CCUT: nID=STR_ObjNamePluralCCUTE; break;
345             default: break;
346         }
347     }
348     rName=ImpGetResStr(nID);
349 }
350 
351 void SdrCircObj::operator=(const SdrObject& rObj)
352 {
353     SdrRectObj::operator=(rObj);
354 
355     nStartWink = ((SdrCircObj&)rObj).nStartWink;
356     nEndWink = ((SdrCircObj&)rObj).nEndWink;
357 }
358 
359 basegfx::B2DPolyPolygon SdrCircObj::TakeXorPoly() const
360 {
361     const basegfx::B2DPolygon aCircPolygon(ImpCalcXPolyCirc(meCircleKind, aRect, nStartWink, nEndWink));
362     return basegfx::B2DPolyPolygon(aCircPolygon);
363 }
364 
365 struct ImpCircUser : public SdrDragStatUserData
366 {
367     Rectangle                   aR;
368     Point                       aCenter;
369     Point                       aRadius;
370     Point                       aP1;
371     Point                       aP2;
372     long                        nMaxRad;
373     long                        nHgt;
374     long                        nWdt;
375     long                        nStart;
376     long                        nEnd;
377     long                        nWink;
378     FASTBOOL                    bRight; // noch nicht implementiert
379 
380 public:
381     ImpCircUser()
382     :   nMaxRad(0),
383         nHgt(0),
384         nWdt(0),
385         nStart(0),
386         nEnd(0),
387         bRight(sal_False)
388     {}
389     void SetCreateParams(SdrDragStat& rStat);
390 };
391 
392 sal_uInt32 SdrCircObj::GetHdlCount() const
393 {
394     if(OBJ_CIRC != meCircleKind)
395     {
396         return 10L;
397     }
398     else
399     {
400         return 8L;
401     }
402 }
403 
404 SdrHdl* SdrCircObj::GetHdl(sal_uInt32 nHdlNum) const
405 {
406     if (meCircleKind==OBJ_CIRC)
407     {
408         nHdlNum += 2L;
409     }
410 
411     SdrHdl* pH = NULL;
412     Point aPnt;
413     SdrHdlKind eLocalKind(HDL_MOVE);
414     sal_uInt32 nPNum(0);
415 
416     switch (nHdlNum)
417     {
418         case 0:
419             aPnt = GetWinkPnt(aRect,nStartWink);
420             eLocalKind = HDL_CIRC;
421             nPNum = 1;
422             break;
423         case 1:
424             aPnt = GetWinkPnt(aRect,nEndWink);
425             eLocalKind = HDL_CIRC;
426             nPNum = 2L;
427             break;
428         case 2:
429             aPnt = aRect.TopLeft();
430             eLocalKind = HDL_UPLFT;
431             break;
432         case 3:
433             aPnt = aRect.TopCenter();
434             eLocalKind = HDL_UPPER;
435             break;
436         case 4:
437             aPnt = aRect.TopRight();
438             eLocalKind = HDL_UPRGT;
439             break;
440         case 5:
441             aPnt = aRect.LeftCenter();
442             eLocalKind = HDL_LEFT;
443             break;
444         case 6:
445             aPnt = aRect.RightCenter();
446             eLocalKind = HDL_RIGHT;
447             break;
448         case 7:
449             aPnt = aRect.BottomLeft();
450             eLocalKind = HDL_LWLFT;
451             break;
452         case 8:
453             aPnt = aRect.BottomCenter();
454             eLocalKind = HDL_LOWER;
455             break;
456         case 9:
457             aPnt = aRect.BottomRight();
458             eLocalKind = HDL_LWRGT;
459             break;
460     }
461 
462     if (aGeo.nShearWink)
463     {
464         ShearPoint(aPnt,aRect.TopLeft(),aGeo.nTan);
465     }
466 
467     if (aGeo.nDrehWink)
468     {
469         RotatePoint(aPnt,aRect.TopLeft(),aGeo.nSin,aGeo.nCos);
470     }
471 
472     if (eLocalKind != HDL_MOVE)
473     {
474         pH = new SdrHdl(aPnt,eLocalKind);
475         pH->SetPointNum(nPNum);
476         pH->SetObj((SdrObject*)this);
477         pH->SetDrehWink(aGeo.nDrehWink);
478     }
479 
480     return pH;
481 }
482 
483 ////////////////////////////////////////////////////////////////////////////////////////////////////
484 
485 bool SdrCircObj::hasSpecialDrag() const
486 {
487     return true;
488 }
489 
490 bool SdrCircObj::beginSpecialDrag(SdrDragStat& rDrag) const
491 {
492     const bool bWink(rDrag.GetHdl() && HDL_CIRC == rDrag.GetHdl()->GetKind());
493 
494     if(bWink)
495     {
496         if(1 == rDrag.GetHdl()->GetPointNum() || 2 == rDrag.GetHdl()->GetPointNum())
497         {
498             rDrag.SetNoSnap(true);
499         }
500 
501         return true;
502     }
503 
504     return SdrTextObj::beginSpecialDrag(rDrag);
505 }
506 
507 bool SdrCircObj::applySpecialDrag(SdrDragStat& rDrag)
508 {
509     const bool bWink(rDrag.GetHdl() && HDL_CIRC == rDrag.GetHdl()->GetKind());
510 
511     if(bWink)
512     {
513         Point aPt(rDrag.GetNow());
514 
515         if (aGeo.nDrehWink!=0)
516             RotatePoint(aPt,aRect.TopLeft(),-aGeo.nSin,aGeo.nCos);
517 
518         if (aGeo.nShearWink!=0)
519             ShearPoint(aPt,aRect.TopLeft(),-aGeo.nTan);
520 
521         aPt-=aRect.Center();
522 
523         long nWdt=aRect.Right()-aRect.Left();
524         long nHgt=aRect.Bottom()-aRect.Top();
525 
526         if(nWdt>=nHgt)
527         {
528             aPt.Y()=BigMulDiv(aPt.Y(),nWdt,nHgt);
529         }
530         else
531         {
532             aPt.X()=BigMulDiv(aPt.X(),nHgt,nWdt);
533         }
534 
535         long nWink=NormAngle360(GetAngle(aPt));
536 
537         if (rDrag.GetView() && rDrag.GetView()->IsAngleSnapEnabled())
538         {
539             long nSA=rDrag.GetView()->GetSnapAngle();
540 
541             if (nSA!=0)
542             {
543                 nWink+=nSA/2;
544                 nWink/=nSA;
545                 nWink*=nSA;
546                 nWink=NormAngle360(nWink);
547             }
548         }
549 
550         if(1 == rDrag.GetHdl()->GetPointNum())
551         {
552             nStartWink = nWink;
553         }
554         else if(2 == rDrag.GetHdl()->GetPointNum())
555         {
556             nEndWink = nWink;
557         }
558 
559         SetRectsDirty();
560         SetXPolyDirty();
561         ImpSetCircInfoToAttr();
562         SetChanged();
563 
564         return true;
565     }
566     else
567     {
568         return SdrTextObj::applySpecialDrag(rDrag);
569     }
570 }
571 
572 String SdrCircObj::getSpecialDragComment(const SdrDragStat& rDrag) const
573 {
574     const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj());
575 
576     if(bCreateComment)
577     {
578         XubString aStr;
579         ImpTakeDescriptionStr(STR_ViewCreateObj, aStr);
580         const sal_uInt32 nPntAnz(rDrag.GetPointAnz());
581 
582         if(OBJ_CIRC != meCircleKind && nPntAnz > 2)
583         {
584             ImpCircUser* pU = (ImpCircUser*)rDrag.GetUser();
585             sal_Int32 nWink;
586 
587             aStr.AppendAscii(" (");
588 
589             if(3 == nPntAnz)
590             {
591                 nWink = pU->nStart;
592             }
593             else
594             {
595                 nWink = pU->nEnd;
596             }
597 
598             aStr += GetWinkStr(nWink,sal_False);
599             aStr += sal_Unicode(')');
600         }
601 
602         return aStr;
603     }
604     else
605     {
606         const bool bWink(rDrag.GetHdl() && HDL_CIRC == rDrag.GetHdl()->GetKind());
607 
608         if(bWink)
609         {
610             XubString aStr;
611             const sal_Int32 nWink(1 == rDrag.GetHdl()->GetPointNum() ? nStartWink : nEndWink);
612 
613             ImpTakeDescriptionStr(STR_DragCircAngle, aStr);
614             aStr.AppendAscii(" (");
615             aStr += GetWinkStr(nWink,sal_False);
616             aStr += sal_Unicode(')');
617 
618             return aStr;
619         }
620         else
621         {
622             return SdrTextObj::getSpecialDragComment(rDrag);
623         }
624     }
625 }
626 
627 ////////////////////////////////////////////////////////////////////////////////////////////////////
628 
629 void ImpCircUser::SetCreateParams(SdrDragStat& rStat)
630 {
631     rStat.TakeCreateRect(aR);
632     aR.Justify();
633     aCenter=aR.Center();
634     nWdt=aR.Right()-aR.Left();
635     nHgt=aR.Bottom()-aR.Top();
636     nMaxRad=((nWdt>nHgt ? nWdt : nHgt)+1) /2;
637     nStart=0;
638     nEnd=36000;
639     if (rStat.GetPointAnz()>2) {
640         Point aP(rStat.GetPoint(2)-aCenter);
641         if (nWdt==0) aP.X()=0;
642         if (nHgt==0) aP.Y()=0;
643         if (nWdt>=nHgt) {
644             if (nHgt!=0) aP.Y()=aP.Y()*nWdt/nHgt;
645         } else {
646             if (nWdt!=0) aP.X()=aP.X()*nHgt/nWdt;
647         }
648         nStart=NormAngle360(GetAngle(aP));
649         if (rStat.GetView()!=NULL && rStat.GetView()->IsAngleSnapEnabled()) {
650             long nSA=rStat.GetView()->GetSnapAngle();
651             if (nSA!=0) { // Winkelfang
652                 nStart+=nSA/2;
653                 nStart/=nSA;
654                 nStart*=nSA;
655                 nStart=NormAngle360(nStart);
656             }
657         }
658         aP1 = GetWinkPnt(aR,nStart);
659         nEnd=nStart;
660         aP2=aP1;
661     } else aP1=aCenter;
662     if (rStat.GetPointAnz()>3) {
663         Point aP(rStat.GetPoint(3)-aCenter);
664         if (nWdt>=nHgt) {
665             aP.Y()=BigMulDiv(aP.Y(),nWdt,nHgt);
666         } else {
667             aP.X()=BigMulDiv(aP.X(),nHgt,nWdt);
668         }
669         nEnd=NormAngle360(GetAngle(aP));
670         if (rStat.GetView()!=NULL && rStat.GetView()->IsAngleSnapEnabled()) {
671             long nSA=rStat.GetView()->GetSnapAngle();
672             if (nSA!=0) { // Winkelfang
673                 nEnd+=nSA/2;
674                 nEnd/=nSA;
675                 nEnd*=nSA;
676                 nEnd=NormAngle360(nEnd);
677             }
678         }
679         aP2 = GetWinkPnt(aR,nEnd);
680     } else aP2=aCenter;
681 }
682 
683 void SdrCircObj::ImpSetCreateParams(SdrDragStat& rStat) const
684 {
685     ImpCircUser* pU=(ImpCircUser*)rStat.GetUser();
686     if (pU==NULL) {
687         pU=new ImpCircUser;
688         rStat.SetUser(pU);
689     }
690     pU->SetCreateParams(rStat);
691 }
692 
693 FASTBOOL SdrCircObj::BegCreate(SdrDragStat& rStat)
694 {
695     rStat.SetOrtho4Possible();
696     Rectangle aRect1(rStat.GetStart(), rStat.GetNow());
697     aRect1.Justify();
698     rStat.SetActionRect(aRect1);
699     aRect = aRect1;
700     ImpSetCreateParams(rStat);
701     return sal_True;
702 }
703 
704 FASTBOOL SdrCircObj::MovCreate(SdrDragStat& rStat)
705 {
706     ImpSetCreateParams(rStat);
707     ImpCircUser* pU=(ImpCircUser*)rStat.GetUser();
708     rStat.SetActionRect(pU->aR);
709     aRect=pU->aR; // fuer ObjName
710     ImpJustifyRect(aRect);
711     nStartWink=pU->nStart;
712     nEndWink=pU->nEnd;
713     SetBoundRectDirty();
714     bSnapRectDirty=sal_True;
715     SetXPolyDirty();
716 
717     // #i103058# push current angle settings to ItemSet to
718     // allow FullDrag visualisation
719     if(rStat.GetPointAnz() >= 4)
720     {
721         ImpSetCircInfoToAttr();
722     }
723 
724     return sal_True;
725 }
726 
727 FASTBOOL SdrCircObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
728 {
729     ImpSetCreateParams(rStat);
730     ImpCircUser* pU=(ImpCircUser*)rStat.GetUser();
731     FASTBOOL bRet=sal_False;
732     if (eCmd==SDRCREATE_FORCEEND && rStat.GetPointAnz()<4) meCircleKind=OBJ_CIRC;
733     if (meCircleKind==OBJ_CIRC) {
734         bRet=rStat.GetPointAnz()>=2;
735         if (bRet) {
736             aRect=pU->aR;
737             ImpJustifyRect(aRect);
738         }
739     } else {
740         rStat.SetNoSnap(rStat.GetPointAnz()>=2);
741         rStat.SetOrtho4Possible(rStat.GetPointAnz()<2);
742         bRet=rStat.GetPointAnz()>=4;
743         if (bRet) {
744             aRect=pU->aR;
745             ImpJustifyRect(aRect);
746             nStartWink=pU->nStart;
747             nEndWink=pU->nEnd;
748         }
749     }
750     bClosedObj=meCircleKind!=OBJ_CARC;
751     SetRectsDirty();
752     SetXPolyDirty();
753     ImpSetCircInfoToAttr();
754     if (bRet) {
755         delete pU;
756         rStat.SetUser(NULL);
757     }
758     return bRet;
759 }
760 
761 void SdrCircObj::BrkCreate(SdrDragStat& rStat)
762 {
763     ImpCircUser* pU=(ImpCircUser*)rStat.GetUser();
764     delete pU;
765     rStat.SetUser(NULL);
766 }
767 
768 FASTBOOL SdrCircObj::BckCreate(SdrDragStat& rStat)
769 {
770     rStat.SetNoSnap(rStat.GetPointAnz()>=3);
771     rStat.SetOrtho4Possible(rStat.GetPointAnz()<3);
772     return meCircleKind!=OBJ_CIRC;
773 }
774 
775 basegfx::B2DPolyPolygon SdrCircObj::TakeCreatePoly(const SdrDragStat& rDrag) const
776 {
777     ImpCircUser* pU = (ImpCircUser*)rDrag.GetUser();
778 
779     if(rDrag.GetPointAnz() < 4L)
780     {
781         // force to OBJ_CIRC to get full visualisation
782         basegfx::B2DPolyPolygon aRetval(ImpCalcXPolyCirc(OBJ_CIRC, pU->aR, pU->nStart, pU->nEnd));
783 
784         if(3L == rDrag.GetPointAnz())
785         {
786             // add edge to first point on ellipse
787             basegfx::B2DPolygon aNew;
788 
789             aNew.append(basegfx::B2DPoint(pU->aCenter.X(), pU->aCenter.Y()));
790             aNew.append(basegfx::B2DPoint(pU->aP1.X(), pU->aP1.Y()));
791             aRetval.append(aNew);
792         }
793 
794         return aRetval;
795     }
796     else
797     {
798         return basegfx::B2DPolyPolygon(ImpCalcXPolyCirc(meCircleKind, pU->aR, pU->nStart, pU->nEnd));
799     }
800 }
801 
802 Pointer SdrCircObj::GetCreatePointer() const
803 {
804     switch (meCircleKind) {
805         case OBJ_CIRC: return Pointer(POINTER_DRAW_ELLIPSE);
806         case OBJ_SECT: return Pointer(POINTER_DRAW_PIE);
807         case OBJ_CARC: return Pointer(POINTER_DRAW_ARC);
808         case OBJ_CCUT: return Pointer(POINTER_DRAW_CIRCLECUT);
809         default: break;
810     } // switch
811     return Pointer(POINTER_CROSS);
812 }
813 
814 void SdrCircObj::NbcMove(const Size& aSiz)
815 {
816     MoveRect(aRect,aSiz);
817     MoveRect(aOutRect,aSiz);
818     MoveRect(maSnapRect,aSiz);
819     SetXPolyDirty();
820     SetRectsDirty(sal_True);
821 }
822 
823 void SdrCircObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
824 {
825     long nWink0=aGeo.nDrehWink;
826     FASTBOOL bNoShearRota=(aGeo.nDrehWink==0 && aGeo.nShearWink==0);
827     SdrTextObj::NbcResize(rRef,xFact,yFact);
828     bNoShearRota|=(aGeo.nDrehWink==0 && aGeo.nShearWink==0);
829     if (meCircleKind!=OBJ_CIRC) {
830         FASTBOOL bXMirr=(xFact.GetNumerator()<0) != (xFact.GetDenominator()<0);
831         FASTBOOL bYMirr=(yFact.GetNumerator()<0) != (yFact.GetDenominator()<0);
832         if (bXMirr || bYMirr) {
833             // bei bXMirr!=bYMirr muessten eigentlich noch die beiden
834             // Linienende vertauscht werden. Das ist jedoch mal wieder
835             // schlecht (wg. zwangslaeufiger harter Formatierung).
836             // Alternativ koennte ein bMirrored-Flag eingefuehrt werden
837             // (Vielleicht ja mal grundsaetzlich, auch fuer gepiegelten Text, ...).
838             long nS0=nStartWink;
839             long nE0=nEndWink;
840             if (bNoShearRota) {
841                 // Das RectObj spiegelt bei VMirror bereits durch durch 180deg Drehung.
842                 if (! (bXMirr && bYMirr)) {
843                     long nTmp=nS0;
844                     nS0=18000-nE0;
845                     nE0=18000-nTmp;
846                 }
847             } else { // Spiegeln fuer verzerrte Ellipsen
848                 if (bXMirr!=bYMirr) {
849                     nS0+=nWink0;
850                     nE0+=nWink0;
851                     if (bXMirr) {
852                         long nTmp=nS0;
853                         nS0=18000-nE0;
854                         nE0=18000-nTmp;
855                     }
856                     if (bYMirr) {
857                         long nTmp=nS0;
858                         nS0=-nE0;
859                         nE0=-nTmp;
860                     }
861                     nS0-=aGeo.nDrehWink;
862                     nE0-=aGeo.nDrehWink;
863                 }
864             }
865             long nWinkDif=nE0-nS0;
866             nStartWink=NormAngle360(nS0);
867             nEndWink  =NormAngle360(nE0);
868             if (nWinkDif==36000) nEndWink+=nWinkDif; // Vollkreis
869         }
870     }
871     SetXPolyDirty();
872     ImpSetCircInfoToAttr();
873 }
874 
875 void SdrCircObj::NbcShear(const Point& rRef, long nWink, double tn, FASTBOOL bVShear)
876 {
877     SdrTextObj::NbcShear(rRef,nWink,tn,bVShear);
878     SetXPolyDirty();
879     ImpSetCircInfoToAttr();
880 }
881 
882 void SdrCircObj::NbcMirror(const Point& rRef1, const Point& rRef2)
883 {
884     //long nWink0=aGeo.nDrehWink;
885     FASTBOOL bFreeMirr=meCircleKind!=OBJ_CIRC;
886     Point aTmpPt1;
887     Point aTmpPt2;
888     if (bFreeMirr) { // bei freier Spiegelachse einige Vorbereitungen Treffen
889         Point aCenter(aRect.Center());
890         long nWdt=aRect.GetWidth()-1;
891         long nHgt=aRect.GetHeight()-1;
892         long nMaxRad=((nWdt>nHgt ? nWdt : nHgt)+1) /2;
893         double a;
894         // Startpunkt
895         a=nStartWink*nPi180;
896         aTmpPt1=Point(Round(cos(a)*nMaxRad),-Round(sin(a)*nMaxRad));
897         if (nWdt==0) aTmpPt1.X()=0;
898         if (nHgt==0) aTmpPt1.Y()=0;
899         aTmpPt1+=aCenter;
900         // Endpunkt
901         a=nEndWink*nPi180;
902         aTmpPt2=Point(Round(cos(a)*nMaxRad),-Round(sin(a)*nMaxRad));
903         if (nWdt==0) aTmpPt2.X()=0;
904         if (nHgt==0) aTmpPt2.Y()=0;
905         aTmpPt2+=aCenter;
906         if (aGeo.nDrehWink!=0) {
907             RotatePoint(aTmpPt1,aRect.TopLeft(),aGeo.nSin,aGeo.nCos);
908             RotatePoint(aTmpPt2,aRect.TopLeft(),aGeo.nSin,aGeo.nCos);
909         }
910         if (aGeo.nShearWink!=0) {
911             ShearPoint(aTmpPt1,aRect.TopLeft(),aGeo.nTan);
912             ShearPoint(aTmpPt2,aRect.TopLeft(),aGeo.nTan);
913         }
914     }
915     SdrTextObj::NbcMirror(rRef1,rRef2);
916     if (meCircleKind!=OBJ_CIRC) { // Anpassung von Start- und Endwinkel
917         MirrorPoint(aTmpPt1,rRef1,rRef2);
918         MirrorPoint(aTmpPt2,rRef1,rRef2);
919         // Unrotate:
920         if (aGeo.nDrehWink!=0) {
921             RotatePoint(aTmpPt1,aRect.TopLeft(),-aGeo.nSin,aGeo.nCos); // -sin fuer Umkehrung
922             RotatePoint(aTmpPt2,aRect.TopLeft(),-aGeo.nSin,aGeo.nCos); // -sin fuer Umkehrung
923         }
924         // Unshear:
925         if (aGeo.nShearWink!=0) {
926             ShearPoint(aTmpPt1,aRect.TopLeft(),-aGeo.nTan); // -tan fuer Umkehrung
927             ShearPoint(aTmpPt2,aRect.TopLeft(),-aGeo.nTan); // -tan fuer Umkehrung
928         }
929         Point aCenter(aRect.Center());
930         aTmpPt1-=aCenter;
931         aTmpPt2-=aCenter;
932         // Weil gespiegelt sind die Winkel nun auch noch vertauscht
933         nStartWink=GetAngle(aTmpPt2);
934         nEndWink  =GetAngle(aTmpPt1);
935         long nWinkDif=nEndWink-nStartWink;
936         nStartWink=NormAngle360(nStartWink);
937         nEndWink  =NormAngle360(nEndWink);
938         if (nWinkDif==36000) nEndWink+=nWinkDif; // Vollkreis
939     }
940     SetXPolyDirty();
941     ImpSetCircInfoToAttr();
942 }
943 
944 SdrObjGeoData* SdrCircObj::NewGeoData() const
945 {
946     return new SdrCircObjGeoData;
947 }
948 
949 void SdrCircObj::SaveGeoData(SdrObjGeoData& rGeo) const
950 {
951     SdrRectObj::SaveGeoData(rGeo);
952     SdrCircObjGeoData& rCGeo=(SdrCircObjGeoData&)rGeo;
953     rCGeo.nStartWink=nStartWink;
954     rCGeo.nEndWink  =nEndWink;
955 }
956 
957 void SdrCircObj::RestGeoData(const SdrObjGeoData& rGeo)
958 {
959     SdrRectObj::RestGeoData(rGeo);
960     SdrCircObjGeoData& rCGeo=(SdrCircObjGeoData&)rGeo;
961     nStartWink=rCGeo.nStartWink;
962     nEndWink  =rCGeo.nEndWink;
963     SetXPolyDirty();
964     ImpSetCircInfoToAttr();
965 }
966 
967 void Union(Rectangle& rR, const Point& rP)
968 {
969     if (rP.X()<rR.Left  ()) rR.Left  ()=rP.X();
970     if (rP.X()>rR.Right ()) rR.Right ()=rP.X();
971     if (rP.Y()<rR.Top   ()) rR.Top   ()=rP.Y();
972     if (rP.Y()>rR.Bottom()) rR.Bottom()=rP.Y();
973 }
974 
975 void SdrCircObj::TakeUnrotatedSnapRect(Rectangle& rRect) const
976 {
977     rRect=aRect;
978     if (meCircleKind!=OBJ_CIRC) {
979         const Point aPntStart(GetWinkPnt(aRect,nStartWink));
980         const Point aPntEnd(GetWinkPnt(aRect,nEndWink));
981         long a=nStartWink;
982         long e=nEndWink;
983         rRect.Left  ()=aRect.Right();
984         rRect.Right ()=aRect.Left();
985         rRect.Top   ()=aRect.Bottom();
986         rRect.Bottom()=aRect.Top();
987         Union(rRect,aPntStart);
988         Union(rRect,aPntEnd);
989         if ((a<=18000 && e>=18000) || (a>e && (a<=18000 || e>=18000))) {
990             Union(rRect,aRect.LeftCenter());
991         }
992         if ((a<=27000 && e>=27000) || (a>e && (a<=27000 || e>=27000))) {
993             Union(rRect,aRect.BottomCenter());
994         }
995         if (a>e) {
996             Union(rRect,aRect.RightCenter());
997         }
998         if ((a<=9000 && e>=9000) || (a>e && (a<=9000 || e>=9000))) {
999             Union(rRect,aRect.TopCenter());
1000         }
1001         if (meCircleKind==OBJ_SECT) {
1002             Union(rRect,aRect.Center());
1003         }
1004         if (aGeo.nDrehWink!=0) {
1005             Point aDst(rRect.TopLeft());
1006             aDst-=aRect.TopLeft();
1007             Point aDst0(aDst);
1008             RotatePoint(aDst,Point(),aGeo.nSin,aGeo.nCos);
1009             aDst-=aDst0;
1010             rRect.Move(aDst.X(),aDst.Y());
1011         }
1012     }
1013     if (aGeo.nShearWink!=0) {
1014         long nDst=Round((rRect.Bottom()-rRect.Top())*aGeo.nTan);
1015         if (aGeo.nShearWink>0) {
1016             Point aRef(rRect.TopLeft());
1017             rRect.Left()-=nDst;
1018             Point aTmpPt(rRect.TopLeft());
1019             RotatePoint(aTmpPt,aRef,aGeo.nSin,aGeo.nCos);
1020             aTmpPt-=rRect.TopLeft();
1021             rRect.Move(aTmpPt.X(),aTmpPt.Y());
1022         } else {
1023             rRect.Right()-=nDst;
1024         }
1025     }
1026 }
1027 
1028 void SdrCircObj::RecalcSnapRect()
1029 {
1030     if (PaintNeedsXPolyCirc()) {
1031         maSnapRect=GetXPoly().GetBoundRect();
1032     } else {
1033         TakeUnrotatedSnapRect(maSnapRect);
1034     }
1035 }
1036 
1037 void SdrCircObj::NbcSetSnapRect(const Rectangle& rRect)
1038 {
1039     if (aGeo.nDrehWink!=0 || aGeo.nShearWink!=0 || meCircleKind!=OBJ_CIRC) {
1040         Rectangle aSR0(GetSnapRect());
1041         long nWdt0=aSR0.Right()-aSR0.Left();
1042         long nHgt0=aSR0.Bottom()-aSR0.Top();
1043         long nWdt1=rRect.Right()-rRect.Left();
1044         long nHgt1=rRect.Bottom()-rRect.Top();
1045         NbcResize(maSnapRect.TopLeft(),Fraction(nWdt1,nWdt0),Fraction(nHgt1,nHgt0));
1046         NbcMove(Size(rRect.Left()-aSR0.Left(),rRect.Top()-aSR0.Top()));
1047     } else {
1048         aRect=rRect;
1049         ImpJustifyRect(aRect);
1050     }
1051     SetRectsDirty();
1052     SetXPolyDirty();
1053     ImpSetCircInfoToAttr();
1054 }
1055 
1056 sal_uInt32 SdrCircObj::GetSnapPointCount() const
1057 {
1058     if (meCircleKind==OBJ_CIRC) {
1059         return 1L;
1060     } else {
1061         return 3L;
1062     }
1063 }
1064 
1065 Point SdrCircObj::GetSnapPoint(sal_uInt32 i) const
1066 {
1067     switch (i) {
1068         case 1 : return GetWinkPnt(aRect,nStartWink);
1069         case 2 : return GetWinkPnt(aRect,nEndWink);
1070         default: return aRect.Center();
1071     }
1072 }
1073 
1074 void __EXPORT SdrCircObj::Notify(SfxBroadcaster& rBC, const SfxHint& rHint)
1075 {
1076     SetXPolyDirty();
1077     SdrRectObj::Notify(rBC,rHint);
1078     ImpSetAttrToCircInfo();
1079 }
1080 
1081 ////////////////////////////////////////////////////////////////////////////////////////////////////
1082 
1083 void SdrCircObj::ImpSetAttrToCircInfo()
1084 {
1085     const SfxItemSet& rSet = GetObjectItemSet();
1086     SdrCircKind eNewKindA = ((SdrCircKindItem&)rSet.Get(SDRATTR_CIRCKIND)).GetValue();
1087     SdrObjKind eNewKind = meCircleKind;
1088 
1089     if(eNewKindA == SDRCIRC_FULL)
1090         eNewKind = OBJ_CIRC;
1091     else if(eNewKindA == SDRCIRC_SECT)
1092         eNewKind = OBJ_SECT;
1093     else if(eNewKindA == SDRCIRC_ARC)
1094         eNewKind = OBJ_CARC;
1095     else if(eNewKindA == SDRCIRC_CUT)
1096         eNewKind = OBJ_CCUT;
1097 
1098     sal_Int32 nNewStart = ((SdrCircStartAngleItem&)rSet.Get(SDRATTR_CIRCSTARTANGLE)).GetValue();
1099     sal_Int32 nNewEnd = ((SdrCircEndAngleItem&)rSet.Get(SDRATTR_CIRCENDANGLE)).GetValue();
1100 
1101     sal_Bool bKindChg = meCircleKind != eNewKind;
1102     sal_Bool bWinkChg = nNewStart != nStartWink || nNewEnd != nEndWink;
1103 
1104     if(bKindChg || bWinkChg)
1105     {
1106         meCircleKind = eNewKind;
1107         nStartWink = nNewStart;
1108         nEndWink = nNewEnd;
1109 
1110         if(bKindChg || (meCircleKind != OBJ_CIRC && bWinkChg))
1111         {
1112             SetXPolyDirty();
1113             SetRectsDirty();
1114         }
1115     }
1116 }
1117 
1118 void SdrCircObj::ImpSetCircInfoToAttr()
1119 {
1120     SdrCircKind eNewKindA = SDRCIRC_FULL;
1121     const SfxItemSet& rSet = GetObjectItemSet();
1122 
1123     if(meCircleKind == OBJ_SECT)
1124         eNewKindA = SDRCIRC_SECT;
1125     else if(meCircleKind == OBJ_CARC)
1126         eNewKindA = SDRCIRC_ARC;
1127     else if(meCircleKind == OBJ_CCUT)
1128         eNewKindA = SDRCIRC_CUT;
1129 
1130     SdrCircKind eOldKindA = ((SdrCircKindItem&)rSet.Get(SDRATTR_CIRCKIND)).GetValue();
1131     sal_Int32 nOldStartWink = ((SdrCircStartAngleItem&)rSet.Get(SDRATTR_CIRCSTARTANGLE)).GetValue();
1132     sal_Int32 nOldEndWink = ((SdrCircEndAngleItem&)rSet.Get(SDRATTR_CIRCENDANGLE)).GetValue();
1133 
1134     if(eNewKindA != eOldKindA || nStartWink != nOldStartWink || nEndWink != nOldEndWink)
1135     {
1136         // #81921# since SetItem() implicitly calls ImpSetAttrToCircInfo()
1137         // setting the item directly is necessary here.
1138         if(eNewKindA != eOldKindA)
1139         {
1140             GetProperties().SetObjectItemDirect(SdrCircKindItem(eNewKindA));
1141         }
1142 
1143         if(nStartWink != nOldStartWink)
1144         {
1145             GetProperties().SetObjectItemDirect(SdrCircStartAngleItem(nStartWink));
1146         }
1147 
1148         if(nEndWink != nOldEndWink)
1149         {
1150             GetProperties().SetObjectItemDirect(SdrCircEndAngleItem(nEndWink));
1151         }
1152 
1153         SetXPolyDirty();
1154         ImpSetAttrToCircInfo();
1155     }
1156 }
1157 
1158 SdrObject* SdrCircObj::DoConvertToPolyObj(sal_Bool bBezier, bool bAddText) const
1159 {
1160     const sal_Bool bFill(OBJ_CARC == meCircleKind ? sal_False : sal_True);
1161     const basegfx::B2DPolygon aCircPolygon(ImpCalcXPolyCirc(meCircleKind, aRect, nStartWink, nEndWink));
1162     SdrObject* pRet = ImpConvertMakeObj(basegfx::B2DPolyPolygon(aCircPolygon), bFill, bBezier);
1163 
1164     if(bAddText)
1165     {
1166         pRet = ImpConvertAddText(pRet, bBezier);
1167     }
1168 
1169     return pRet;
1170 }
1171 
1172 //////////////////////////////////////////////////////////////////////////////
1173 // eof
1174