xref: /AOO41X/main/svx/source/svdraw/svdovirt.cxx (revision 707fc0d4d52eb4f69d89a98ffec6918ca5de6326)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_svx.hxx"
26 
27 #include <svx/svdovirt.hxx>
28 #include <svx/xpool.hxx>
29 #include <svx/svdtrans.hxx>
30 #include <svx/svdetc.hxx>
31 #include <svx/svdhdl.hxx>
32 #include <svx/sdr/contact/viewcontactofvirtobj.hxx>
33 #include <basegfx/matrix/b2dhommatrix.hxx>
34 #include <svx/svdograf.hxx>
35 #include <svx/svddrgv.hxx>
36 #include <basegfx/matrix/b2dhommatrixtools.hxx>
37 
38 ////////////////////////////////////////////////////////////////////////////////////////////////////
39 
40 sdr::properties::BaseProperties& SdrVirtObj::GetProperties() const
41 {
42     return rRefObj.GetProperties();
43 }
44 
45 ////////////////////////////////////////////////////////////////////////////////////////////////////
46 // AW, OD 2004-05-03 #i27224#
47 sdr::contact::ViewContact* SdrVirtObj::CreateObjectSpecificViewContact()
48 {
49     return new sdr::contact::ViewContactOfVirtObj(*this);
50 }
51 
52 ////////////////////////////////////////////////////////////////////////////////////////////////////
53 
54 TYPEINIT1(SdrVirtObj,SdrObject);
55 
56 SdrVirtObj::SdrVirtObj(SdrObject& rNewObj):
57     rRefObj(rNewObj)
58 {
59     bVirtObj=sal_True; // Ja, ich bin ein virtuelles Objekt
60     rRefObj.AddReference(*this);
61     bClosedObj=rRefObj.IsClosedObj();
62 }
63 
64 SdrVirtObj::SdrVirtObj(SdrObject& rNewObj, const Point& rAnchorPos):
65     rRefObj(rNewObj)
66 {
67     aAnchor=rAnchorPos;
68     bVirtObj=sal_True; // Ja, ich bin ein virtuelles Objekt
69     rRefObj.AddReference(*this);
70     bClosedObj=rRefObj.IsClosedObj();
71 }
72 
73 SdrVirtObj::~SdrVirtObj()
74 {
75     rRefObj.DelReference(*this);
76 }
77 
78 ////////////////////////////////////////////////////////////////////////////////////////////////////
79 
80 const SdrObject& SdrVirtObj::GetReferencedObj() const
81 {
82     return rRefObj;
83 }
84 
85 SdrObject& SdrVirtObj::ReferencedObj()
86 {
87     return rRefObj;
88 }
89 
90 void __EXPORT SdrVirtObj::Notify(SfxBroadcaster& /*rBC*/, const SfxHint& /*rHint*/)
91 {
92     bClosedObj=rRefObj.IsClosedObj();
93     SetRectsDirty(); // hier noch Optimieren.
94 
95     // Only a repaint here, rRefObj may have changed and broadcasts
96     ActionChanged();
97     // BroadcastObjectChange();
98 }
99 
100 void SdrVirtObj::NbcSetAnchorPos(const Point& rAnchorPos)
101 {
102     aAnchor=rAnchorPos;
103 }
104 
105 ////////////////////////////////////////////////////////////////////////////////////////////////////
106 
107 void SdrVirtObj::SetModel(SdrModel* pNewModel)
108 {
109     SdrObject::SetModel(pNewModel);
110     rRefObj.SetModel(pNewModel);
111 }
112 
113 void SdrVirtObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
114 {
115     rRefObj.TakeObjInfo(rInfo);
116 }
117 
118 sal_uInt32 SdrVirtObj::GetObjInventor() const
119 {
120     return rRefObj.GetObjInventor();
121 }
122 
123 sal_uInt16 SdrVirtObj::GetObjIdentifier() const
124 {
125     return rRefObj.GetObjIdentifier();
126 }
127 
128 SdrObjList* SdrVirtObj::GetSubList() const
129 {
130     return rRefObj.GetSubList();
131 }
132 
133 const Rectangle& SdrVirtObj::GetCurrentBoundRect() const
134 {
135     ((SdrVirtObj*)this)->aOutRect=rRefObj.GetCurrentBoundRect(); // Hier noch optimieren
136     ((SdrVirtObj*)this)->aOutRect+=aAnchor;
137     return aOutRect;
138 }
139 
140 const Rectangle& SdrVirtObj::GetLastBoundRect() const
141 {
142     ((SdrVirtObj*)this)->aOutRect=rRefObj.GetLastBoundRect(); // Hier noch optimieren
143     ((SdrVirtObj*)this)->aOutRect+=aAnchor;
144     return aOutRect;
145 }
146 
147 void SdrVirtObj::RecalcBoundRect()
148 {
149     aOutRect=rRefObj.GetCurrentBoundRect();
150     aOutRect+=aAnchor;
151 }
152 
153 void SdrVirtObj::SetChanged()
154 {
155     SdrObject::SetChanged();
156 }
157 
158 SdrObject* SdrVirtObj::Clone() const
159 {
160     SdrObject* pObj=new SdrVirtObj(((SdrVirtObj*)this)->rRefObj); // Nur eine weitere Referenz
161     return pObj;
162 }
163 
164 void SdrVirtObj::operator=(const SdrObject& rObj)
165 {   // ???anderes Objekt referenzieren???
166     SdrObject::operator=(rObj);
167     aAnchor=((SdrVirtObj&)rObj).aAnchor;
168 }
169 
170 void SdrVirtObj::TakeObjNameSingul(XubString& rName) const
171 {
172     rRefObj.TakeObjNameSingul(rName);
173     rName.Insert(sal_Unicode('['), 0);
174     rName += sal_Unicode(']');
175 
176     String aName( GetName() );
177     if(aName.Len())
178     {
179         rName += sal_Unicode(' ');
180         rName += sal_Unicode('\'');
181         rName += aName;
182         rName += sal_Unicode('\'');
183     }
184 }
185 
186 void SdrVirtObj::TakeObjNamePlural(XubString& rName) const
187 {
188     rRefObj.TakeObjNamePlural(rName);
189     rName.Insert(sal_Unicode('['), 0);
190     rName += sal_Unicode(']');
191 }
192 
193 void operator +=(PolyPolygon& rPoly, const Point& rOfs)
194 {
195     if (rOfs.X()!=0 || rOfs.Y()!=0) {
196         sal_uInt16 i,j;
197         for (j=0; j<rPoly.Count(); j++) {
198             Polygon aP1(rPoly.GetObject(j));
199             for (i=0; i<aP1.GetSize(); i++) {
200                  aP1[i]+=rOfs;
201             }
202             rPoly.Replace(aP1,j);
203         }
204     }
205 }
206 
207 basegfx::B2DPolyPolygon SdrVirtObj::TakeXorPoly() const
208 {
209     basegfx::B2DPolyPolygon aPolyPolygon(rRefObj.TakeXorPoly());
210 
211     if(aAnchor.X() || aAnchor.Y())
212     {
213         aPolyPolygon.transform(basegfx::tools::createTranslateB2DHomMatrix(aAnchor.X(), aAnchor.Y()));
214     }
215 
216     return aPolyPolygon;
217 }
218 
219 ////////////////////////////////////////////////////////////////////////////////////////////////////
220 
221 sal_uInt32 SdrVirtObj::GetHdlCount() const
222 {
223     return rRefObj.GetHdlCount();
224 }
225 
226 SdrHdl* SdrVirtObj::GetHdl(sal_uInt32 nHdlNum) const
227 {
228     SdrHdl* pHdl=rRefObj.GetHdl(nHdlNum);
229 
230     // #i73248#
231     // GetHdl() at SdrObject is not guaranteed to return an object
232     if(pHdl)
233     {
234         Point aP(pHdl->GetPos()+aAnchor);
235         pHdl->SetPos(aP);
236     }
237 
238     return pHdl;
239 }
240 
241 sal_uInt32 SdrVirtObj::GetPlusHdlCount(const SdrHdl& rHdl) const
242 {
243     return rRefObj.GetPlusHdlCount(rHdl);
244 }
245 
246 SdrHdl* SdrVirtObj::GetPlusHdl(const SdrHdl& rHdl, sal_uInt32 nPlNum) const
247 {
248     SdrHdl* pHdl=rRefObj.GetPlusHdl(rHdl,nPlNum);
249     pHdl->SetPos(pHdl->GetPos() + aAnchor);
250     return pHdl;
251 }
252 
253 void SdrVirtObj::AddToHdlList(SdrHdlList& rHdlList) const
254 {
255     // #i73248#
256     // SdrObject::AddToHdlList(rHdlList) is not a good thing to call
257     // since at SdrPathObj, only AddToHdlList may be used and the call
258     // will instead use the standard implementation which uses GetHdlCount()
259     // and GetHdl instead. This is not wrong, but may be much less effective
260     // and may not be prepared to GetHdl returning NULL
261 
262     // get handles using AddToHdlList from ref object
263     SdrHdlList aLocalList(0);
264     rRefObj.AddToHdlList(aLocalList);
265     const sal_uInt32 nHdlCount(aLocalList.GetHdlCount());
266 
267     if(nHdlCount)
268     {
269         // translate handles and add them to dest list. They are temporarily part of
270         // two lists then
271         const Point aOffset(GetOffset());
272 
273         for(sal_uInt32 a(0L); a < nHdlCount; a++)
274         {
275             SdrHdl* pCandidate = aLocalList.GetHdl(a);
276             pCandidate->SetPos(pCandidate->GetPos() + aOffset);
277             rHdlList.AddHdl(pCandidate);
278         }
279 
280         // remove them from source list, else they will be deleted when
281         // source list is deleted
282         while(aLocalList.GetHdlCount())
283         {
284             aLocalList.RemoveHdl(aLocalList.GetHdlCount() - 1L);
285         }
286     }
287 }
288 
289 ////////////////////////////////////////////////////////////////////////////////////////////////////
290 
291 bool SdrVirtObj::hasSpecialDrag() const
292 {
293     return rRefObj.hasSpecialDrag();
294 }
295 
296 bool SdrVirtObj::supportsFullDrag() const
297 {
298     return false;
299 }
300 
301 SdrObject* SdrVirtObj::getFullDragClone() const
302 {
303     static bool bSpecialHandling(false);
304     SdrObject* pRetval = 0;
305 
306     if(bSpecialHandling)
307     {
308         // special handling for VirtObj. Do not create another
309         // reference to rRefObj, this would allow to change that
310         // one on drag. Instead, create a SdrGrafObj for drag containing
311         // the graphical representation
312         pRetval = new SdrGrafObj(SdrDragView::GetObjGraphic(GetModel(), this), GetLogicRect());
313     }
314     else
315     {
316         SdrObject& rReferencedObject = ((SdrVirtObj*)this)->ReferencedObj();
317         pRetval = new SdrGrafObj(SdrDragView::GetObjGraphic(GetModel(), &rReferencedObject), GetLogicRect());
318     }
319 
320     return pRetval;
321 }
322 
323 bool SdrVirtObj::beginSpecialDrag(SdrDragStat& rDrag) const
324 {
325     return rRefObj.beginSpecialDrag(rDrag);
326 }
327 
328 bool SdrVirtObj::applySpecialDrag(SdrDragStat& rDrag)
329 {
330     return rRefObj.applySpecialDrag(rDrag);
331 }
332 
333 basegfx::B2DPolyPolygon SdrVirtObj::getSpecialDragPoly(const SdrDragStat& rDrag) const
334 {
335     return rRefObj.getSpecialDragPoly(rDrag);
336     // Offset handlen !!!!!! fehlt noch !!!!!!!
337 }
338 
339 String SdrVirtObj::getSpecialDragComment(const SdrDragStat& rDrag) const
340 {
341     return rRefObj.getSpecialDragComment(rDrag);
342 }
343 
344 ////////////////////////////////////////////////////////////////////////////////////////////////////
345 
346 FASTBOOL SdrVirtObj::BegCreate(SdrDragStat& rStat)
347 {
348     return rRefObj.BegCreate(rStat);
349 }
350 
351 FASTBOOL SdrVirtObj::MovCreate(SdrDragStat& rStat)
352 {
353     return rRefObj.MovCreate(rStat);
354 }
355 
356 FASTBOOL SdrVirtObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
357 {
358     return rRefObj.EndCreate(rStat,eCmd);
359 }
360 
361 FASTBOOL SdrVirtObj::BckCreate(SdrDragStat& rStat)
362 {
363     return rRefObj.BckCreate(rStat);
364 }
365 
366 void SdrVirtObj::BrkCreate(SdrDragStat& rStat)
367 {
368     rRefObj.BrkCreate(rStat);
369 }
370 
371 basegfx::B2DPolyPolygon SdrVirtObj::TakeCreatePoly(const SdrDragStat& rDrag) const
372 {
373     return rRefObj.TakeCreatePoly(rDrag);
374     // Offset handlen !!!!!! fehlt noch !!!!!!!
375 }
376 
377 ////////////////////////////////////////////////////////////////////////////////////////////////////
378 
379 void SdrVirtObj::NbcMove(const Size& rSiz)
380 {
381     MovePoint(aAnchor,rSiz);
382     SetRectsDirty();
383 }
384 
385 void SdrVirtObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
386 {
387     rRefObj.NbcResize(rRef-aAnchor,xFact,yFact);
388     SetRectsDirty();
389 }
390 
391 void SdrVirtObj::NbcRotate(const Point& rRef, long nWink, double sn, double cs)
392 {
393     rRefObj.NbcRotate(rRef-aAnchor,nWink,sn,cs);
394     SetRectsDirty();
395 }
396 
397 void SdrVirtObj::NbcMirror(const Point& rRef1, const Point& rRef2)
398 {
399     rRefObj.NbcMirror(rRef1-aAnchor,rRef2-aAnchor);
400     SetRectsDirty();
401 }
402 
403 void SdrVirtObj::NbcShear(const Point& rRef, long nWink, double tn, FASTBOOL bVShear)
404 {
405     rRefObj.NbcShear(rRef-aAnchor,nWink,tn,bVShear);
406     SetRectsDirty();
407 }
408 
409 ////////////////////////////////////////////////////////////////////////////////////////////////////
410 
411 void SdrVirtObj::Move(const Size& rSiz)
412 {
413     if (rSiz.Width()!=0 || rSiz.Height()!=0) {
414         Rectangle aBoundRect0; if (pUserCall!=NULL) aBoundRect0=GetLastBoundRect();
415         // #110094#-14 SendRepaintBroadcast();
416         NbcMove(rSiz);
417         SetChanged();
418         BroadcastObjectChange();
419         SendUserCall(SDRUSERCALL_MOVEONLY,aBoundRect0);
420     }
421 }
422 
423 void SdrVirtObj::Resize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
424 {
425     if (xFact.GetNumerator()!=xFact.GetDenominator() || yFact.GetNumerator()!=yFact.GetDenominator()) {
426         Rectangle aBoundRect0; if (pUserCall!=NULL) aBoundRect0=GetLastBoundRect();
427         rRefObj.Resize(rRef-aAnchor,xFact,yFact);
428         SetRectsDirty();
429         SendUserCall(SDRUSERCALL_RESIZE,aBoundRect0);
430     }
431 }
432 
433 void SdrVirtObj::Rotate(const Point& rRef, long nWink, double sn, double cs)
434 {
435     if (nWink!=0) {
436         Rectangle aBoundRect0; if (pUserCall!=NULL) aBoundRect0=GetLastBoundRect();
437         rRefObj.Rotate(rRef-aAnchor,nWink,sn,cs);
438         SetRectsDirty();
439         SendUserCall(SDRUSERCALL_RESIZE,aBoundRect0);
440     }
441 }
442 
443 void SdrVirtObj::Mirror(const Point& rRef1, const Point& rRef2)
444 {
445     Rectangle aBoundRect0; if (pUserCall!=NULL) aBoundRect0=GetLastBoundRect();
446     rRefObj.Mirror(rRef1-aAnchor,rRef2-aAnchor);
447     SetRectsDirty();
448     SendUserCall(SDRUSERCALL_RESIZE,aBoundRect0);
449 }
450 
451 void SdrVirtObj::Shear(const Point& rRef, long nWink, double tn, FASTBOOL bVShear)
452 {
453     if (nWink!=0) {
454         Rectangle aBoundRect0; if (pUserCall!=NULL) aBoundRect0=GetLastBoundRect();
455         rRefObj.Shear(rRef-aAnchor,nWink,tn,bVShear);
456         SetRectsDirty();
457         SendUserCall(SDRUSERCALL_RESIZE,aBoundRect0);
458     }
459 }
460 
461 ////////////////////////////////////////////////////////////////////////////////////////////////////
462 
463 void SdrVirtObj::RecalcSnapRect()
464 {
465     aSnapRect=rRefObj.GetSnapRect();
466     aSnapRect+=aAnchor;
467 }
468 
469 const Rectangle& SdrVirtObj::GetSnapRect() const
470 {
471     ((SdrVirtObj*)this)->aSnapRect=rRefObj.GetSnapRect();
472     ((SdrVirtObj*)this)->aSnapRect+=aAnchor;
473     return aSnapRect;
474 }
475 
476 void SdrVirtObj::SetSnapRect(const Rectangle& rRect)
477 {
478     {
479         Rectangle aBoundRect0; if (pUserCall!=NULL) aBoundRect0=GetLastBoundRect();
480         Rectangle aR(rRect);
481         aR-=aAnchor;
482         rRefObj.SetSnapRect(aR);
483         SetRectsDirty();
484         SendUserCall(SDRUSERCALL_RESIZE,aBoundRect0);
485     }
486 }
487 
488 void SdrVirtObj::NbcSetSnapRect(const Rectangle& rRect)
489 {
490     Rectangle aR(rRect);
491     aR-=aAnchor;
492     SetRectsDirty();
493     rRefObj.NbcSetSnapRect(aR);
494 }
495 
496 ////////////////////////////////////////////////////////////////////////////////////////////////////
497 
498 const Rectangle& SdrVirtObj::GetLogicRect() const
499 {
500     ((SdrVirtObj*)this)->aSnapRect=rRefObj.GetLogicRect();  // !!! Missbrauch von aSnapRect !!!
501     ((SdrVirtObj*)this)->aSnapRect+=aAnchor;                // Wenns mal Aerger gibt, muss ein weiteres Rectangle Member her (oder Heap)
502     return aSnapRect;
503 }
504 
505 void SdrVirtObj::SetLogicRect(const Rectangle& rRect)
506 {
507     Rectangle aBoundRect0; if (pUserCall!=NULL) aBoundRect0=GetLastBoundRect();
508     Rectangle aR(rRect);
509     aR-=aAnchor;
510     rRefObj.SetLogicRect(aR);
511     SetRectsDirty();
512     SendUserCall(SDRUSERCALL_RESIZE,aBoundRect0);
513 }
514 
515 void SdrVirtObj::NbcSetLogicRect(const Rectangle& rRect)
516 {
517     Rectangle aR(rRect);
518     aR-=aAnchor;
519     SetRectsDirty();
520     rRefObj.NbcSetLogicRect(aR);
521 }
522 
523 ////////////////////////////////////////////////////////////////////////////////////////////////////
524 
525 long SdrVirtObj::GetRotateAngle() const
526 {
527     return rRefObj.GetRotateAngle();
528 }
529 
530 long SdrVirtObj::GetShearAngle(FASTBOOL bVertical) const
531 {
532     return rRefObj.GetShearAngle(bVertical);
533 }
534 
535 ////////////////////////////////////////////////////////////////////////////////////////////////////
536 
537 sal_uInt32 SdrVirtObj::GetSnapPointCount() const
538 {
539     return rRefObj.GetSnapPointCount();
540 }
541 
542 Point SdrVirtObj::GetSnapPoint(sal_uInt32 i) const
543 {
544     Point aP(rRefObj.GetSnapPoint(i));
545     aP+=aAnchor;
546     return aP;
547 }
548 
549 sal_Bool SdrVirtObj::IsPolyObj() const
550 {
551     return rRefObj.IsPolyObj();
552 }
553 
554 sal_uInt32 SdrVirtObj::GetPointCount() const
555 {
556     return rRefObj.GetPointCount();
557 }
558 
559 Point SdrVirtObj::GetPoint(sal_uInt32 i) const
560 {
561     return Point(rRefObj.GetPoint(i) + aAnchor);
562 }
563 
564 void SdrVirtObj::NbcSetPoint(const Point& rPnt, sal_uInt32 i)
565 {
566     Point aP(rPnt);
567     aP-=aAnchor;
568     rRefObj.SetPoint(aP,i);
569     SetRectsDirty();
570 }
571 
572 ////////////////////////////////////////////////////////////////////////////////////////////////////
573 
574 SdrObjGeoData* SdrVirtObj::NewGeoData() const
575 {
576     return rRefObj.NewGeoData();
577 }
578 
579 void SdrVirtObj::SaveGeoData(SdrObjGeoData& rGeo) const
580 {
581     rRefObj.SaveGeoData(rGeo);
582 }
583 
584 void SdrVirtObj::RestGeoData(const SdrObjGeoData& rGeo)
585 {
586     rRefObj.RestGeoData(rGeo);
587     SetRectsDirty();
588 }
589 
590 ////////////////////////////////////////////////////////////////////////////////////////////////////
591 
592 SdrObjGeoData* SdrVirtObj::GetGeoData() const
593 {
594     return rRefObj.GetGeoData();
595 }
596 
597 void SdrVirtObj::SetGeoData(const SdrObjGeoData& rGeo)
598 {
599     Rectangle aBoundRect0; if (pUserCall!=NULL) aBoundRect0=GetLastBoundRect();
600     rRefObj.SetGeoData(rGeo);
601     SetRectsDirty();
602     SendUserCall(SDRUSERCALL_RESIZE,aBoundRect0);
603 }
604 
605 ////////////////////////////////////////////////////////////////////////////////////////////////////
606 
607 void SdrVirtObj::NbcReformatText()
608 {
609     rRefObj.NbcReformatText();
610 }
611 
612 void SdrVirtObj::ReformatText()
613 {
614     rRefObj.ReformatText();
615 }
616 
617 ////////////////////////////////////////////////////////////////////////////////////////////////////
618 
619 FASTBOOL SdrVirtObj::HasMacro() const
620 {
621     return rRefObj.HasMacro();
622 }
623 
624 SdrObject* SdrVirtObj::CheckMacroHit(const SdrObjMacroHitRec& rRec) const
625 {
626     return rRefObj.CheckMacroHit(rRec); // Todo: Positionsversatz
627 }
628 
629 Pointer SdrVirtObj::GetMacroPointer(const SdrObjMacroHitRec& rRec) const
630 {
631     return rRefObj.GetMacroPointer(rRec); // Todo: Positionsversatz
632 }
633 
634 void SdrVirtObj::PaintMacro(OutputDevice& rOut, const Rectangle& rDirtyRect, const SdrObjMacroHitRec& rRec) const
635 {
636     rRefObj.PaintMacro(rOut,rDirtyRect,rRec); // Todo: Positionsversatz
637 }
638 
639 FASTBOOL SdrVirtObj::DoMacro(const SdrObjMacroHitRec& rRec)
640 {
641     return rRefObj.DoMacro(rRec); // Todo: Positionsversatz
642 }
643 
644 XubString SdrVirtObj::GetMacroPopupComment(const SdrObjMacroHitRec& rRec) const
645 {
646     return rRefObj.GetMacroPopupComment(rRec); // Todo: Positionsversatz
647 }
648 
649 const Point SdrVirtObj::GetOffset() const
650 {
651     // #i73248# default offset of SdrVirtObj is aAnchor
652     return aAnchor;
653 }
654 
655 // eof
656