xref: /trunk/main/sw/source/core/doc/docfly.cxx (revision 56b35d86153c02ff07ff2435fcc383d035fd8df0)
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_sw.hxx"
26 
27 #include <hintids.hxx>
28 #include <svl/itemiter.hxx>
29 #include <svx/svdobj.hxx>
30 #include <svx/svdpage.hxx>
31 #include <svx/svdmodel.hxx>
32 #include <svx/svdocapt.hxx>
33 #include <svx/svdmark.hxx>
34 #include <svx/xlndsit.hxx>
35 #include <svx/xlnstit.hxx>
36 #include <svx/xlnedit.hxx>
37 #include <svx/xflhtit.hxx>
38 #include <fmtfsize.hxx>
39 #include <fmtornt.hxx>
40 #include <fmtsrnd.hxx>
41 #include <dcontact.hxx>
42 
43 #include <ndgrf.hxx>
44 #include <doc.hxx>
45 #include <IDocumentUndoRedo.hxx>
46 #include <ndindex.hxx>
47 #include <docary.hxx>
48 #include <fmtcntnt.hxx>
49 #include <fmtanchr.hxx>
50 #include <txtflcnt.hxx>
51 #include <fmtflcnt.hxx>
52 #include <txtfrm.hxx>
53 #include <pagefrm.hxx>
54 #include <rootfrm.hxx>
55 #include <flyfrms.hxx>
56 #include <frmtool.hxx>
57 #include <frmfmt.hxx>
58 #include <ndtxt.hxx>
59 #include <pam.hxx>
60 #include <tblsel.hxx>
61 #include <swundo.hxx>
62 #include <swtable.hxx>
63 #include <crstate.hxx>
64 #include <UndoCore.hxx>
65 #include <UndoAttribute.hxx>
66 #include <fmtcnct.hxx>
67 #include <dflyobj.hxx>
68 #include <undoflystrattr.hxx>
69 #include <switerator.hxx>
70 
71 //UUUU
72 #include <svx/xbtmpit.hxx>
73 #include <svx/xflftrit.hxx>
74 
75 extern sal_uInt16 GetHtmlMode( const SwDocShell* );
76 
77 
78 using namespace ::com::sun::star;
79 
80 sal_uInt16 SwDoc::GetFlyCount( FlyCntType eType ) const
81 {
82     const SwSpzFrmFmts& rFmts = *GetSpzFrmFmts();
83     sal_uInt16 nSize = rFmts.Count();
84     sal_uInt16 nCount = 0;
85     const SwNodeIndex* pIdx;
86     for ( sal_uInt16 i = 0; i < nSize; i++)
87     {
88         const SwFrmFmt* pFlyFmt = rFmts[ i ];
89         if( RES_FLYFRMFMT == pFlyFmt->Which()
90             && 0 != ( pIdx = pFlyFmt->GetCntnt().GetCntntIdx() )
91             && pIdx->GetNodes().IsDocNodes()
92             )
93         {
94             const SwNode* pNd = GetNodes()[ pIdx->GetIndex() + 1 ];
95 
96             switch( eType )
97             {
98             case FLYCNTTYPE_FRM:
99                 if(!pNd->IsNoTxtNode())
100                     nCount++;
101                 break;
102 
103             case FLYCNTTYPE_GRF:
104                 if( pNd->IsGrfNode() )
105                     nCount++;
106                 break;
107 
108             case FLYCNTTYPE_OLE:
109                 if(pNd->IsOLENode())
110                     nCount++;
111                 break;
112 
113             default:
114                 nCount++;
115             }
116         }
117     }
118     return nCount;
119 }
120 
121 // If you change this, also update SwXFrameEnumeration in unocoll.
122 SwFrmFmt* SwDoc::GetFlyNum( sal_uInt16 nIdx, FlyCntType eType )
123 {
124     SwSpzFrmFmts& rFmts = *GetSpzFrmFmts();
125     SwFrmFmt* pRetFmt = 0;
126     sal_uInt16 nSize = rFmts.Count();
127     const SwNodeIndex* pIdx;
128     sal_uInt16 nCount = 0;
129     for( sal_uInt16 i = 0; !pRetFmt && i < nSize; ++i )
130     {
131         SwFrmFmt* pFlyFmt = rFmts[ i ];
132         if( RES_FLYFRMFMT == pFlyFmt->Which()
133             && 0 != ( pIdx = pFlyFmt->GetCntnt().GetCntntIdx() )
134             && pIdx->GetNodes().IsDocNodes()
135             )
136         {
137             const SwNode* pNd = GetNodes()[ pIdx->GetIndex() + 1 ];
138             switch( eType )
139             {
140             case FLYCNTTYPE_FRM:
141                 if( !pNd->IsNoTxtNode() && nIdx == nCount++)
142                     pRetFmt = pFlyFmt;
143                 break;
144             case FLYCNTTYPE_GRF:
145                 if(pNd->IsGrfNode() && nIdx == nCount++ )
146                     pRetFmt = pFlyFmt;
147                 break;
148             case FLYCNTTYPE_OLE:
149                 if(pNd->IsOLENode() && nIdx == nCount++)
150                     pRetFmt = pFlyFmt;
151                 break;
152             default:
153                 if(nIdx == nCount++)
154                     pRetFmt = pFlyFmt;
155             }
156         }
157     }
158     return pRetFmt;
159 }
160 
161 Point lcl_FindAnchorLayPos( SwDoc& rDoc, const SwFmtAnchor& rAnch,
162                             const SwFrmFmt* pFlyFmt )
163 {
164     Point aRet;
165     if( rDoc.GetCurrentViewShell() )    //swmod 071107//swmod 071225
166         switch( rAnch.GetAnchorId() )
167         {
168         case FLY_AS_CHAR:
169             if( pFlyFmt && rAnch.GetCntntAnchor() )
170             {
171                 const SwFrm* pOld = ((SwFlyFrmFmt*)pFlyFmt)->GetFrm( &aRet, sal_False );
172                 if( pOld )
173                     aRet = pOld->Frm().Pos();
174             }
175             break;
176 
177         case FLY_AT_PARA:
178         case FLY_AT_CHAR: // LAYER_IMPL
179             if( rAnch.GetCntntAnchor() )
180             {
181                 const SwPosition *pPos = rAnch.GetCntntAnchor();
182                 const SwCntntNode* pNd = pPos->nNode.GetNode().GetCntntNode();
183                 const SwFrm* pOld = pNd ? pNd->getLayoutFrm( rDoc.GetCurrentLayout(), &aRet, 0, sal_False ) : 0;
184                 if( pOld )
185                     aRet = pOld->Frm().Pos();
186             }
187             break;
188 
189         case FLY_AT_FLY: // LAYER_IMPL
190             if( rAnch.GetCntntAnchor() )
191             {
192                 const SwFlyFrmFmt* pFmt = (SwFlyFrmFmt*)rAnch.GetCntntAnchor()->
193                                                 nNode.GetNode().GetFlyFmt();
194                 const SwFrm* pOld = pFmt ? pFmt->GetFrm( &aRet, sal_False ) : 0;
195                 if( pOld )
196                     aRet = pOld->Frm().Pos();
197             }
198             break;
199 
200         case FLY_AT_PAGE:
201             {
202                 sal_uInt16 nPgNum = rAnch.GetPageNum();
203                 const SwPageFrm *pPage = (SwPageFrm*)rDoc.GetCurrentLayout()->Lower();
204                 for( sal_uInt16 i = 1; (i <= nPgNum) && pPage; ++i,
205                                     pPage = (const SwPageFrm*)pPage->GetNext() )
206                     if( i == nPgNum )
207                     {
208                         aRet = pPage->Frm().Pos();
209                         break;
210                     }
211             }
212             break;
213         default:
214             break;
215         }
216     return aRet;
217 }
218 
219 #define MAKEFRMS 0
220 #define IGNOREANCHOR 1
221 #define DONTMAKEFRMS 2
222 
223 sal_Int8 SwDoc::SetFlyFrmAnchor( SwFrmFmt& rFmt, SfxItemSet& rSet, sal_Bool bNewFrms )
224 {
225     //Ankerwechsel sind fast immer in alle 'Richtungen' erlaubt.
226     //Ausnahme: Absatz- bzw. Zeichengebundene Rahmen duerfen wenn sie in
227     //Kopf-/Fusszeilen stehen nicht Seitengebunden werden.
228     const SwFmtAnchor &rOldAnch = rFmt.GetAnchor();
229     const RndStdIds nOld = rOldAnch.GetAnchorId();
230 
231     SwFmtAnchor aNewAnch( (SwFmtAnchor&)rSet.Get( RES_ANCHOR ) );
232     RndStdIds nNew = aNewAnch.GetAnchorId();
233 
234     // ist der neue ein gueltiger Anker?
235     if( !aNewAnch.GetCntntAnchor() && (FLY_AT_FLY == nNew ||
236         (FLY_AT_PARA == nNew) || (FLY_AS_CHAR == nNew) ||
237         (FLY_AT_CHAR == nNew) ))
238     {
239         return IGNOREANCHOR;
240     }
241 
242     if( nOld == nNew )
243         return DONTMAKEFRMS;
244 
245 
246     Point aOldAnchorPos( ::lcl_FindAnchorLayPos( *this, rOldAnch, &rFmt ));
247     Point aNewAnchorPos( ::lcl_FindAnchorLayPos( *this, aNewAnch, 0 ));
248 
249     //Die alten Frms vernichten. Dabei werden die Views implizit gehidet und
250     //doppeltes hiden waere so eine art Show!
251     rFmt.DelFrms();
252 
253     if ( FLY_AS_CHAR == nOld )
254     {
255         //Bei InCntnt's wird es spannend: Das TxtAttribut muss vernichtet
256         //werden. Leider reisst dies neben den Frms auch noch das Format mit
257         //in sein Grab. Um dass zu unterbinden loesen wir vorher die
258         //Verbindung zwischen Attribut und Format.
259         const SwPosition *pPos = rOldAnch.GetCntntAnchor();
260         SwTxtNode *pTxtNode = pPos->nNode.GetNode().GetTxtNode();
261         ASSERT( pTxtNode->HasHints(), "Missing FlyInCnt-Hint." );
262         const xub_StrLen nIdx = pPos->nContent.GetIndex();
263         SwTxtAttr * const  pHnt =
264             pTxtNode->GetTxtAttrForCharAt( nIdx, RES_TXTATR_FLYCNT );
265         ASSERT( pHnt && pHnt->Which() == RES_TXTATR_FLYCNT,
266                     "Missing FlyInCnt-Hint." );
267         ASSERT( pHnt && pHnt->GetFlyCnt().GetFrmFmt() == &rFmt,
268                     "Wrong TxtFlyCnt-Hint." );
269         const_cast<SwFmtFlyCnt&>(pHnt->GetFlyCnt()).SetFlyFmt();
270 
271         //Die Verbindung ist geloest, jetzt muss noch das Attribut vernichtet
272         //werden.
273         pTxtNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIdx, nIdx );
274     }
275 
276     //Endlich kann das Attribut gesetzt werden. Es muss das erste Attribut
277     //sein; Undo depends on it!
278     rFmt.SetFmtAttr( aNewAnch );
279 
280     //Positionskorrekturen
281     const SfxPoolItem* pItem;
282     switch( nNew )
283     {
284     case FLY_AS_CHAR:
285             //Wenn keine Positionsattribute hereinkommen, dann muss dafuer
286             //gesorgt werden, das keine unerlaubte automatische Ausrichtung
287             //bleibt.
288         {
289             const SwPosition *pPos = aNewAnch.GetCntntAnchor();
290             SwTxtNode *pNd = pPos->nNode.GetNode().GetTxtNode();
291             ASSERT( pNd, "Crsr steht nicht auf TxtNode." );
292 
293             SwFmtFlyCnt aFmt( static_cast<SwFlyFrmFmt*>(&rFmt) );
294             pNd->InsertItem( aFmt, pPos->nContent.GetIndex(), 0 );
295         }
296 
297         if( SFX_ITEM_SET != rSet.GetItemState( RES_VERT_ORIENT, sal_False, &pItem ))
298         {
299             SwFmtVertOrient aOldV( rFmt.GetVertOrient() );
300             sal_Bool bSet = sal_True;
301             switch( aOldV.GetVertOrient() )
302             {
303             case text::VertOrientation::LINE_TOP:     aOldV.SetVertOrient( text::VertOrientation::TOP );   break;
304             case text::VertOrientation::LINE_CENTER:  aOldV.SetVertOrient( text::VertOrientation::CENTER); break;
305             case text::VertOrientation::LINE_BOTTOM:  aOldV.SetVertOrient( text::VertOrientation::BOTTOM); break;
306             case text::VertOrientation::NONE:         aOldV.SetVertOrient( text::VertOrientation::CENTER); break;
307             default:
308                 bSet = sal_False;
309             }
310             if( bSet )
311                 rSet.Put( aOldV );
312         }
313         break;
314 
315     case FLY_AT_PARA:
316     case FLY_AT_CHAR: // LAYER_IMPL
317     case FLY_AT_FLY: // LAYER_IMPL
318     case FLY_AT_PAGE:
319         {
320             //Wenn keine Positionsattribute hereinschneien korrigieren wir
321             //die Position so, dass die Dokumentkoordinaten des Flys erhalten
322             //bleiben.
323             //Chg: Wenn sich in den Positionsattributen lediglich die
324             //Ausrichtung veraendert (text::RelOrientation::FRAME vs. text::RelOrientation::PRTAREA), dann wird die
325             //Position ebenfalls korrigiert.
326             if( SFX_ITEM_SET != rSet.GetItemState( RES_HORI_ORIENT, sal_False, &pItem ))
327                 pItem = 0;
328 
329             SwFmtHoriOrient aOldH( rFmt.GetHoriOrient() );
330 
331             if( text::HoriOrientation::NONE == aOldH.GetHoriOrient() && ( !pItem ||
332                 aOldH.GetPos() == ((SwFmtHoriOrient*)pItem)->GetPos() ))
333             {
334                 SwTwips nPos = (FLY_AS_CHAR == nOld) ? 0 : aOldH.GetPos();
335                 nPos += aOldAnchorPos.X() - aNewAnchorPos.X();
336 
337                 if( pItem )
338                 {
339                     SwFmtHoriOrient* pH = (SwFmtHoriOrient*)pItem;
340                     aOldH.SetHoriOrient( pH->GetHoriOrient() );
341                     aOldH.SetRelationOrient( pH->GetRelationOrient() );
342                 }
343                 aOldH.SetPos( nPos );
344                 rSet.Put( aOldH );
345             }
346 
347             if( SFX_ITEM_SET != rSet.GetItemState( RES_VERT_ORIENT, sal_False, &pItem ))
348                 pItem = 0;
349             SwFmtVertOrient aOldV( rFmt.GetVertOrient() );
350 
351             // OD 2004-05-14 #i28922# - correction: compare <aOldV.GetVertOrient()
352             // with <text::VertOrientation::NONE>
353             if( text::VertOrientation::NONE == aOldV.GetVertOrient() && (!pItem ||
354                 aOldV.GetPos() == ((SwFmtVertOrient*)pItem)->GetPos() ) )
355             {
356                 SwTwips nPos = (FLY_AS_CHAR == nOld) ? 0 : aOldV.GetPos();
357                 nPos += aOldAnchorPos.Y() - aNewAnchorPos.Y();
358                 if( pItem )
359                 {
360                     SwFmtVertOrient* pV = (SwFmtVertOrient*)pItem;
361                     aOldV.SetVertOrient( pV->GetVertOrient() );
362                     aOldV.SetRelationOrient( pV->GetRelationOrient() );
363                 }
364                 aOldV.SetPos( nPos );
365                 rSet.Put( aOldV );
366             }
367         }
368         break;
369     default:
370         break;
371     }
372 
373     if( bNewFrms )
374         rFmt.MakeFrms();
375 
376     return MAKEFRMS;
377 }
378 
379 static bool
380 lcl_SetFlyFrmAttr(SwDoc & rDoc,
381         sal_Int8 (SwDoc::*pSetFlyFrmAnchor)(SwFrmFmt &, SfxItemSet &, sal_Bool),
382         SwFrmFmt & rFlyFmt, SfxItemSet & rSet)
383 {
384     // #i32968# Inserting columns in the frame causes MakeFrmFmt to put two
385     // objects of type SwUndoFrmFmt on the undo stack. We don't want them.
386     ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
387 
388     //Ist das Ankerattribut dabei? Falls ja ueberlassen wir die Verarbeitung
389     //desselben einer Spezialmethode. Sie Returnt sal_True wenn der Fly neu
390     //erzeugt werden muss (z.B. weil ein Wechsel des FlyTyps vorliegt).
391     sal_Int8 const nMakeFrms =
392         (SFX_ITEM_SET == rSet.GetItemState( RES_ANCHOR, sal_False ))
393              ?  (rDoc.*pSetFlyFrmAnchor)( rFlyFmt, rSet, sal_False )
394              :  DONTMAKEFRMS;
395 
396     const SfxPoolItem* pItem;
397     SfxItemIter aIter( rSet );
398     SfxItemSet aTmpSet( rDoc.GetAttrPool(), aFrmFmtSetRange );
399     sal_uInt16 nWhich = aIter.GetCurItem()->Which();
400     do {
401         switch( nWhich )
402         {
403         case RES_FILL_ORDER:
404         case RES_BREAK:
405         case RES_PAGEDESC:
406         case RES_CNTNT:
407         case RES_FOOTER:
408             OSL_ENSURE(false, ":-) unknown Attribute for Fly.");
409             // kein break;
410         case RES_CHAIN:
411             rSet.ClearItem( nWhich );
412             break;
413         case RES_ANCHOR:
414             if( DONTMAKEFRMS != nMakeFrms )
415                 break;
416 
417         default:
418             if( !IsInvalidItem( aIter.GetCurItem() ) && ( SFX_ITEM_SET !=
419                 rFlyFmt.GetAttrSet().GetItemState( nWhich, sal_True, &pItem ) ||
420                 *pItem != *aIter.GetCurItem() ))
421                 aTmpSet.Put( *aIter.GetCurItem() );
422             break;
423         }
424 
425         if( aIter.IsAtEnd() )
426             break;
427 
428     } while( 0 != ( nWhich = aIter.NextItem()->Which() ) );
429 
430     if( aTmpSet.Count() )
431         rFlyFmt.SetFmtAttr( aTmpSet );
432 
433     if( MAKEFRMS == nMakeFrms )
434         rFlyFmt.MakeFrms();
435 
436     return aTmpSet.Count() || MAKEFRMS == nMakeFrms;
437 }
438 
439 void SwDoc::CheckForUniqueItemForLineFillNameOrIndex(SfxItemSet& rSet)
440 {
441     SdrModel* pDrawModel = GetOrCreateDrawModel();
442     SfxItemIter aIter(rSet);
443 
444     for(const SfxPoolItem* pItem = aIter.FirstItem(); pItem; pItem = aIter.NextItem())
445     {
446         const SfxPoolItem* pResult = pItem;
447 
448         switch(pItem->Which())
449         {
450             case XATTR_FILLBITMAP:
451             {
452                 pResult = static_cast< const XFillBitmapItem* >(pItem)->checkForUniqueItem(pDrawModel);
453                 break;
454             }
455             case XATTR_LINEDASH:
456             {
457                 pResult = static_cast< const XLineDashItem* >(pItem)->checkForUniqueItem(pDrawModel);
458                 break;
459             }
460             case XATTR_LINESTART:
461             {
462                 pResult = static_cast< const XLineStartItem* >(pItem)->checkForUniqueItem(pDrawModel);
463                 break;
464             }
465             case XATTR_LINEEND:
466             {
467                 pResult = static_cast< const XLineEndItem* >(pItem)->checkForUniqueItem(pDrawModel);
468                 break;
469             }
470             case XATTR_FILLGRADIENT:
471             {
472                 pResult = static_cast< const XFillGradientItem* >(pItem)->checkForUniqueItem(pDrawModel);
473                 break;
474             }
475             case XATTR_FILLFLOATTRANSPARENCE:
476             {
477                 pResult = static_cast< const XFillFloatTransparenceItem* >(pItem)->checkForUniqueItem(pDrawModel);
478                 break;
479             }
480             case XATTR_FILLHATCH:
481             {
482                 pResult = static_cast< const XFillHatchItem* >(pItem)->checkForUniqueItem(pDrawModel);
483                 break;
484             }
485         }
486 
487         if(pResult != pItem)
488         {
489             rSet.Put(*pResult);
490             delete pResult;
491         }
492     }
493 }
494 
495 sal_Bool SwDoc::SetFlyFrmAttr( SwFrmFmt& rFlyFmt, SfxItemSet& rSet )
496 {
497     if( !rSet.Count() )
498         return sal_False;
499 
500     ::std::auto_ptr<SwUndoFmtAttrHelper> pSaveUndo;
501 
502     if (GetIDocumentUndoRedo().DoesUndo())
503     {
504         GetIDocumentUndoRedo().ClearRedo(); // AppendUndo far below, so leave it
505         pSaveUndo.reset( new SwUndoFmtAttrHelper( rFlyFmt ) );
506     }
507 
508     bool const bRet = lcl_SetFlyFrmAttr(*this, &SwDoc::SetFlyFrmAnchor, rFlyFmt, rSet);
509 
510     if ( pSaveUndo.get() )
511     {
512         if ( pSaveUndo->GetUndo() )
513         {
514             GetIDocumentUndoRedo().AppendUndo( pSaveUndo->ReleaseUndo() );
515         }
516     }
517 
518     SetModified();
519 
520     return bRet;
521 }
522 
523 // --> OD 2009-07-20 #i73249#
524 void SwDoc::SetFlyFrmTitle( SwFlyFrmFmt& rFlyFrmFmt,
525                             const String& sNewTitle )
526 {
527     if ( rFlyFrmFmt.GetObjTitle() == sNewTitle )
528     {
529         return;
530     }
531 
532     ::sw::DrawUndoGuard const drawUndoGuard(GetIDocumentUndoRedo());
533 
534     if (GetIDocumentUndoRedo().DoesUndo())
535     {
536         GetIDocumentUndoRedo().AppendUndo( new SwUndoFlyStrAttr( rFlyFrmFmt,
537                                           UNDO_FLYFRMFMT_TITLE,
538                                           rFlyFrmFmt.GetObjTitle(),
539                                           sNewTitle ) );
540     }
541 
542     rFlyFrmFmt.SetObjTitle( sNewTitle, true );
543 
544     SetModified();
545 }
546 
547 void SwDoc::SetFlyFrmDescription( SwFlyFrmFmt& rFlyFrmFmt,
548                                   const String& sNewDescription )
549 {
550     if ( rFlyFrmFmt.GetObjDescription() == sNewDescription )
551     {
552         return;
553     }
554 
555     ::sw::DrawUndoGuard const drawUndoGuard(GetIDocumentUndoRedo());
556 
557     if (GetIDocumentUndoRedo().DoesUndo())
558     {
559         GetIDocumentUndoRedo().AppendUndo( new SwUndoFlyStrAttr( rFlyFrmFmt,
560                                           UNDO_FLYFRMFMT_DESCRIPTION,
561                                           rFlyFrmFmt.GetObjDescription(),
562                                           sNewDescription ) );
563     }
564 
565     rFlyFrmFmt.SetObjDescription( sNewDescription, true );
566 
567     SetModified();
568 }
569 // <--
570 
571 sal_Bool SwDoc::SetFrmFmtToFly( SwFrmFmt& rFmt, SwFrmFmt& rNewFmt,
572                             SfxItemSet* pSet, sal_Bool bKeepOrient )
573 {
574     sal_Bool bChgAnchor = sal_False, bFrmSz = sal_False;
575 
576     const SwFmtFrmSize aFrmSz( rFmt.GetFrmSize() );
577     const SwFmtVertOrient aVert( rFmt.GetVertOrient() );
578     const SwFmtHoriOrient aHori( rFmt.GetHoriOrient() );
579 
580     SwUndoSetFlyFmt* pUndo = 0;
581     bool const bUndo = GetIDocumentUndoRedo().DoesUndo();
582     if (bUndo)
583     {
584         pUndo = new SwUndoSetFlyFmt( rFmt, rNewFmt );
585         GetIDocumentUndoRedo().AppendUndo(pUndo);
586     }
587 
588     // #i32968# Inserting columns in the section causes MakeFrmFmt to put
589     // 2 objects of type SwUndoFrmFmt on the undo stack. We don't want them.
590     ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo());
591 
592     //Erstmal die Spalten setzen, sonst gibts nix als Aerger mit dem
593     //Set/Reset/Abgleich usw.
594     const SfxPoolItem* pItem;
595     if( SFX_ITEM_SET != rNewFmt.GetAttrSet().GetItemState( RES_COL ))
596         rFmt.ResetFmtAttr( RES_COL );
597 
598     if( rFmt.DerivedFrom() != &rNewFmt )
599     {
600         rFmt.SetDerivedFrom( &rNewFmt );
601 
602         // 1. wenn nicht automatisch -> ignorieren, sonst -> wech
603         // 2. wech damit, MB!
604         if( SFX_ITEM_SET == rNewFmt.GetAttrSet().GetItemState( RES_FRM_SIZE, sal_False ))
605         {
606             rFmt.ResetFmtAttr( RES_FRM_SIZE );
607             bFrmSz = sal_True;
608         }
609 
610         const SfxItemSet* pAsk = pSet;
611         if( !pAsk ) pAsk = &rNewFmt.GetAttrSet();
612         if( SFX_ITEM_SET == pAsk->GetItemState( RES_ANCHOR, sal_False, &pItem )
613             && ((SwFmtAnchor*)pItem)->GetAnchorId() !=
614                 rFmt.GetAnchor().GetAnchorId() )
615         {
616             if( pSet )
617                 bChgAnchor = MAKEFRMS == SetFlyFrmAnchor( rFmt, *pSet, sal_False );
618             else
619             {
620                 //JP 23.04.98: muss den FlyFmt-Range haben, denn im SetFlyFrmAnchor
621                 //              werden Attribute in diesen gesetzt!
622                 SfxItemSet aFlySet( *rNewFmt.GetAttrSet().GetPool(),
623                                     rNewFmt.GetAttrSet().GetRanges() );
624                 aFlySet.Put( *pItem );
625                 bChgAnchor = MAKEFRMS == SetFlyFrmAnchor( rFmt, aFlySet, sal_False);
626             }
627         }
628     }
629 
630     //Hori und Vert nur dann resetten, wenn in der Vorlage eine
631     //automatische Ausrichtung eingestellt ist, anderfalls den alten Wert
632     //wieder hineinstopfen.
633     //JP 09.06.98: beim Update der RahmenVorlage sollte der Fly NICHT
634     //              seine Orientierng verlieren (diese wird nicht geupdatet!)
635     //OS: #96584# text::HoriOrientation::NONE and text::VertOrientation::NONE are allowed now
636     if (!bKeepOrient)
637     {
638         rFmt.ResetFmtAttr(RES_VERT_ORIENT);
639         rFmt.ResetFmtAttr(RES_HORI_ORIENT);
640     }
641 
642     rFmt.ResetFmtAttr( RES_PRINT, RES_SURROUND );
643     rFmt.ResetFmtAttr( RES_LR_SPACE, RES_UL_SPACE );
644     rFmt.ResetFmtAttr( RES_BACKGROUND, RES_COL );
645     rFmt.ResetFmtAttr( RES_URL, RES_EDIT_IN_READONLY );
646 
647     if( !bFrmSz )
648         rFmt.SetFmtAttr( aFrmSz );
649 
650     if( bChgAnchor )
651         rFmt.MakeFrms();
652 
653     if( pUndo )
654         pUndo->DeRegisterFromFormat( rFmt );
655 
656     SetModified();
657 
658     return bChgAnchor;
659 }
660 
661 void SwDoc::GetGrfNms( const SwFlyFrmFmt& rFmt, String* pGrfName,
662                         String* pFltName ) const
663 {
664     SwNodeIndex aIdx( *rFmt.GetCntnt().GetCntntIdx(), 1 );
665     const SwGrfNode* pGrfNd = aIdx.GetNode().GetGrfNode();
666     if( pGrfNd && pGrfNd->IsLinkedFile() )
667         pGrfNd->GetFileFilterNms( pGrfName, pFltName );
668 }
669 
670 sal_Bool SwDoc::ChgAnchor( const SdrMarkList& _rMrkList,
671                            RndStdIds _eAnchorType,
672                            const sal_Bool _bSameOnly,
673                            const sal_Bool _bPosCorr )
674 {
675     ASSERT( GetCurrentLayout(), "Ohne Layout geht gar nichts" );    //swmod 080218
676 
677     if ( !_rMrkList.GetMarkCount() ||
678          _rMrkList.GetMark( 0 )->GetMarkedSdrObj()->GetUpGroup() )
679     {
680         return false;
681     }
682 
683     GetIDocumentUndoRedo().StartUndo( UNDO_INSATTR, NULL );
684 
685     sal_Bool bUnmark = sal_False;
686     for ( sal_uInt16 i = 0; i < _rMrkList.GetMarkCount(); ++i )
687     {
688         SdrObject* pObj = _rMrkList.GetMark( i )->GetMarkedSdrObj();
689         if ( !pObj->ISA(SwVirtFlyDrawObj) )
690         {
691             SwDrawContact* pContact = static_cast<SwDrawContact*>(GetUserCall(pObj));
692 
693             // OD 27.06.2003 #108784# - consider, that drawing object has
694             // no user call. E.g.: a 'virtual' drawing object is disconnected by
695             // the anchor type change of the 'master' drawing object.
696             // Continue with next selected object and assert, if this isn't excepted.
697             if ( !pContact )
698             {
699 #ifdef DBG_UTIL
700                 bool bNoUserCallExcepted =
701                         pObj->ISA(SwDrawVirtObj) &&
702                         !static_cast<SwDrawVirtObj*>(pObj)->IsConnected();
703                 ASSERT( bNoUserCallExcepted, "SwDoc::ChgAnchor(..) - no contact at selected drawing object" );
704 #endif
705                 continue;
706             }
707 
708             // OD 2004-03-29 #i26791#
709             const SwFrm* pOldAnchorFrm = pContact->GetAnchorFrm( pObj );
710             const SwFrm* pNewAnchorFrm = pOldAnchorFrm;
711 
712             // --> OD 2006-03-01 #i54336#
713             // Instead of only keeping the index position for an as-character
714             // anchored object the complete <SwPosition> is kept, because the
715             // anchor index position could be moved, if the object again is
716             // anchored as character.
717 //            xub_StrLen nIndx = STRING_NOTFOUND;
718             const SwPosition* pOldAsCharAnchorPos( 0L );
719             const RndStdIds eOldAnchorType = pContact->GetAnchorId();
720             if ( !_bSameOnly && eOldAnchorType == FLY_AS_CHAR )
721             {
722                 pOldAsCharAnchorPos = new SwPosition( pContact->GetCntntAnchor() );
723             }
724             // <--
725 
726             if ( _bSameOnly )
727                 _eAnchorType = eOldAnchorType;
728 
729             SwFmtAnchor aNewAnch( _eAnchorType );
730             Rectangle aObjRect( pContact->GetAnchoredObj( pObj )->GetObjRect().SVRect() );
731             const Point aPt( aObjRect.TopLeft() );
732 
733             switch ( _eAnchorType )
734             {
735             case FLY_AT_PARA:
736             case FLY_AT_CHAR:
737                 {
738                     const Point aNewPoint = pOldAnchorFrm &&
739                                             ( pOldAnchorFrm->IsVertical() ||
740                                               pOldAnchorFrm->IsRightToLeft() )
741                                             ? aObjRect.TopRight()
742                                             : aPt;
743 
744                     // OD 18.06.2003 #108784# - allow drawing objects in header/footer
745                     pNewAnchorFrm = ::FindAnchor( pOldAnchorFrm, aNewPoint, false );
746                     if ( pNewAnchorFrm->IsTxtFrm() && ((SwTxtFrm*)pNewAnchorFrm)->IsFollow() )
747                     {
748                         pNewAnchorFrm = ((SwTxtFrm*)pNewAnchorFrm)->FindMaster();
749                     }
750                     if ( pNewAnchorFrm->IsProtected() )
751                     {
752                         pNewAnchorFrm = 0;
753                     }
754                     else
755                     {
756                         SwPosition aPos( *((SwCntntFrm*)pNewAnchorFrm)->GetNode() );
757                         aNewAnch.SetType( _eAnchorType );
758                         aNewAnch.SetAnchor( &aPos );
759                     }
760                 }
761                 break;
762 
763             case FLY_AT_FLY: // LAYER_IMPL
764                 {
765                     //Ausgehend von der linken oberen Ecke des Fly den
766                     //dichtesten SwFlyFrm suchen.
767                     SwFrm *pTxtFrm;
768                     {
769                         SwCrsrMoveState aState( MV_SETONLYTEXT );
770                         SwPosition aPos( GetNodes() );
771                         Point aPoint( aPt );
772                         aPoint.X() -= 1;
773                         GetCurrentLayout()->GetCrsrOfst( &aPos, aPoint, &aState );
774                         // OD 20.06.2003 #108784# - consider that drawing objects
775                         // can be in header/footer. Thus, <GetFrm()> by left-top-corner
776                         pTxtFrm = aPos.nNode.GetNode().
777                                         GetCntntNode()->getLayoutFrm( GetCurrentLayout(), &aPt, 0, sal_False );
778                     }
779                     const SwFrm *pTmp = ::FindAnchor( pTxtFrm, aPt );
780                     pNewAnchorFrm = pTmp->FindFlyFrm();
781                     if( pNewAnchorFrm && !pNewAnchorFrm->IsProtected() )
782                     {
783                         const SwFrmFmt *pTmpFmt = ((SwFlyFrm*)pNewAnchorFrm)->GetFmt();
784                         const SwFmtCntnt& rCntnt = pTmpFmt->GetCntnt();
785                         SwPosition aPos( *rCntnt.GetCntntIdx() );
786                         aNewAnch.SetAnchor( &aPos );
787                         break;
788                     }
789 
790                     aNewAnch.SetType( FLY_AT_PAGE );
791                     // no break
792                 }
793             case FLY_AT_PAGE:
794                 {
795                     pNewAnchorFrm = GetCurrentLayout()->Lower();
796                     while ( pNewAnchorFrm && !pNewAnchorFrm->Frm().IsInside( aPt ) )
797                         pNewAnchorFrm = pNewAnchorFrm->GetNext();
798                     if ( !pNewAnchorFrm )
799                         continue;
800 
801                     aNewAnch.SetPageNum( ((SwPageFrm*)pNewAnchorFrm)->GetPhyPageNum());
802                 }
803                 break;
804             case FLY_AS_CHAR:
805                 if( _bSameOnly )    // Positions/Groessenaenderung
806                 {
807                     if( !pOldAnchorFrm )
808                     {
809                         pContact->ConnectToLayout();
810                         pOldAnchorFrm = pContact->GetAnchorFrm();
811                     }
812                     ((SwTxtFrm*)pOldAnchorFrm)->Prepare();
813                 }
814                 else            // Ankerwechsel
815                 {
816                     // OD 18.06.2003 #108784# - allow drawing objects in header/footer
817                     pNewAnchorFrm = ::FindAnchor( pOldAnchorFrm, aPt, false );
818                     if( pNewAnchorFrm->IsProtected() )
819                     {
820                         pNewAnchorFrm = 0;
821                         break;
822                     }
823 
824                     bUnmark = ( 0 != i );
825                     Point aPoint( aPt );
826                     aPoint.X() -= 1;    // nicht im DrawObj landen!!
827                     aNewAnch.SetType( FLY_AS_CHAR );
828                     SwPosition aPos( *((SwCntntFrm*)pNewAnchorFrm)->GetNode() );
829                     if ( pNewAnchorFrm->Frm().IsInside( aPoint ) )
830                     {
831                     // es muss ein TextNode gefunden werden, denn nur dort
832                     // ist ein inhaltsgebundenes DrawObjekt zu verankern
833                         SwCrsrMoveState aState( MV_SETONLYTEXT );
834                         GetCurrentLayout()->GetCrsrOfst( &aPos, aPoint, &aState );  //swmod 080218
835                     }
836                     else
837                     {
838                         SwCntntNode &rCNd = (SwCntntNode&)
839                             *((SwCntntFrm*)pNewAnchorFrm)->GetNode();
840                         if ( pNewAnchorFrm->Frm().Bottom() < aPt.Y() )
841                             rCNd.MakeStartIndex( &aPos.nContent );
842                         else
843                             rCNd.MakeEndIndex( &aPos.nContent );
844                     }
845                     aNewAnch.SetAnchor( &aPos );
846                     SetAttr( aNewAnch, *pContact->GetFmt() );
847                     // OD 2004-04-13 #i26791# - adjust vertical positioning to
848                     // 'center to baseline'
849                     SetAttr( SwFmtVertOrient( 0, text::VertOrientation::CENTER, text::RelOrientation::FRAME ), *pContact->GetFmt() );
850                     SwTxtNode *pNd = aPos.nNode.GetNode().GetTxtNode();
851                     ASSERT( pNd, "Cursor not positioned at TxtNode." );
852 
853                     SwFmtFlyCnt aFmt( pContact->GetFmt() );
854                     pNd->InsertItem( aFmt, aPos.nContent.GetIndex(), 0 );
855                 }
856                 break;
857             default:
858                 ASSERT( !this, "unexpected AnchorId." );
859             }
860 
861             if ( (FLY_AS_CHAR != _eAnchorType) &&
862                  pNewAnchorFrm &&
863                  ( !_bSameOnly || pNewAnchorFrm != pOldAnchorFrm ) )
864             {
865                 // OD 2004-04-06 #i26791# - Direct object positioning no longer
866                 // needed. Apply of attributes (method call <SetAttr(..)>) takes
867                 // care of the invalidation of the object position.
868                 SetAttr( aNewAnch, *pContact->GetFmt() );
869                 if ( _bPosCorr )
870                 {
871                     // --> OD 2004-08-24 #i33313# - consider not connected
872                     // 'virtual' drawing objects
873                     if ( pObj->ISA(SwDrawVirtObj) &&
874                          !static_cast<SwDrawVirtObj*>(pObj)->IsConnected() )
875                     {
876                         SwRect aNewObjRect( aObjRect );
877                         static_cast<SwAnchoredDrawObject*>(pContact->GetAnchoredObj( 0L ))
878                                         ->AdjustPositioningAttr( pNewAnchorFrm,
879                                                                  &aNewObjRect );
880 
881                     }
882                     else
883                     {
884                         static_cast<SwAnchoredDrawObject*>(pContact->GetAnchoredObj( pObj ))
885                                     ->AdjustPositioningAttr( pNewAnchorFrm );
886                     }
887                 }
888             }
889 
890             // --> OD 2006-03-01 #i54336#
891             if ( pNewAnchorFrm && pOldAsCharAnchorPos )
892             {
893                 //Bei InCntnt's wird es spannend: Das TxtAttribut muss vernichtet
894                 //werden. Leider reisst dies neben den Frms auch noch das Format mit
895                 //in sein Grab. Um dass zu unterbinden loesen wir vorher die
896                 //Verbindung zwischen Attribut und Format.
897                 const xub_StrLen nIndx( pOldAsCharAnchorPos->nContent.GetIndex() );
898                 SwTxtNode* pTxtNode( pOldAsCharAnchorPos->nNode.GetNode().GetTxtNode() );
899                 ASSERT( pTxtNode, "<SwDoc::ChgAnchor(..)> - missing previous anchor text node for as-character anchored object" );
900                 ASSERT( pTxtNode->HasHints(), "Missing FlyInCnt-Hint." );
901                 SwTxtAttr * const pHnt =
902                     pTxtNode->GetTxtAttrForCharAt( nIndx, RES_TXTATR_FLYCNT );
903                 const_cast<SwFmtFlyCnt&>(pHnt->GetFlyCnt()).SetFlyFmt();
904 
905                 //Die Verbindung ist geloest, jetzt muss noch das Attribut vernichtet
906                 //werden.
907                 pTxtNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIndx, nIndx );
908                 delete pOldAsCharAnchorPos;
909             }
910             // <--
911         }
912     }
913 
914     GetIDocumentUndoRedo().EndUndo( UNDO_END, NULL );
915     SetModified();
916 
917     return bUnmark;
918 }
919 
920 
921 int SwDoc::Chainable( const SwFrmFmt &rSource, const SwFrmFmt &rDest )
922 {
923     //Die Source darf noch keinen Follow haben.
924     const SwFmtChain &rOldChain = rSource.GetChain();
925     if ( rOldChain.GetNext() )
926         return SW_CHAIN_SOURCE_CHAINED;
927 
928     //Ziel darf natuerlich nicht gleich Source sein und es
929     //darf keine geschlossene Kette entstehen.
930     const SwFrmFmt *pFmt = &rDest;
931     do {
932         if( pFmt == &rSource )
933             return SW_CHAIN_SELF;
934         pFmt = pFmt->GetChain().GetNext();
935     } while ( pFmt );
936 
937     //Auch eine Verkettung von Innen nach aussen oder von aussen
938     //nach innen ist nicht zulaessig.
939     if( rDest.IsLowerOf( rSource ) || rSource .IsLowerOf( rDest ) )
940         return SW_CHAIN_SELF;
941 
942     //Das Ziel darf noch keinen Master haben.
943     const SwFmtChain &rChain = rDest.GetChain();
944     if( rChain.GetPrev() )
945         return SW_CHAIN_IS_IN_CHAIN;
946 
947     //Das Ziel muss leer sein.
948     const SwNodeIndex* pCntIdx = rDest.GetCntnt().GetCntntIdx();
949     if( !pCntIdx )
950         return SW_CHAIN_NOT_FOUND;
951 
952     SwNodeIndex aNxtIdx( *pCntIdx, 1 );
953     const SwTxtNode* pTxtNd = aNxtIdx.GetNode().GetTxtNode();
954     if( !pTxtNd )
955         return SW_CHAIN_NOT_FOUND;
956 
957     const sal_uLong nFlySttNd = pCntIdx->GetIndex();
958     if( 2 != ( pCntIdx->GetNode().EndOfSectionIndex() - nFlySttNd ) ||
959         pTxtNd->GetTxt().Len() )
960         return SW_CHAIN_NOT_EMPTY;
961 
962     sal_uInt16 nArrLen = GetSpzFrmFmts()->Count();
963     for( sal_uInt16 n = 0; n < nArrLen; ++n )
964     {
965         const SwFmtAnchor& rAnchor = (*GetSpzFrmFmts())[ n ]->GetAnchor();
966         sal_uLong nTstSttNd;
967         // OD 11.12.2003 #i20622# - to-frame anchored objects are allowed.
968         if ( ((rAnchor.GetAnchorId() == FLY_AT_PARA) ||
969               (rAnchor.GetAnchorId() == FLY_AT_CHAR)) &&
970              0 != rAnchor.GetCntntAnchor() &&
971              nFlySttNd <= ( nTstSttNd =
972                         rAnchor.GetCntntAnchor()->nNode.GetIndex() ) &&
973              nTstSttNd < nFlySttNd + 2 )
974         {
975             return SW_CHAIN_NOT_EMPTY;
976         }
977     }
978 
979     //Auf die richtige Area muessen wir auch noch einen Blick werfen.
980     //Beide Flys muessen im selben Bereich (Body, Head/Foot, Fly) sitzen
981     //Wenn die Source nicht der selektierte Rahmen ist, so reicht es
982     //Wenn ein passender gefunden wird (Der Wunsch kann z.B. von der API
983     //kommen).
984 
985     // both in the same fly, header, footer or on the page?
986     const SwFmtAnchor &rSrcAnchor = rSource.GetAnchor(),
987                       &rDstAnchor = rDest.GetAnchor();
988     sal_uLong nEndOfExtras = GetNodes().GetEndOfExtras().GetIndex();
989     sal_Bool bAllowed = sal_False;
990     if ( FLY_AT_PAGE == rSrcAnchor.GetAnchorId() )
991     {
992         if ( (FLY_AT_PAGE == rDstAnchor.GetAnchorId()) ||
993             ( rDstAnchor.GetCntntAnchor() &&
994               rDstAnchor.GetCntntAnchor()->nNode.GetIndex() > nEndOfExtras ))
995             bAllowed = sal_True;
996     }
997     else if( rSrcAnchor.GetCntntAnchor() && rDstAnchor.GetCntntAnchor() )
998     {
999         const SwNodeIndex &rSrcIdx = rSrcAnchor.GetCntntAnchor()->nNode,
1000                             &rDstIdx = rDstAnchor.GetCntntAnchor()->nNode;
1001         const SwStartNode* pSttNd = 0;
1002         if( rSrcIdx == rDstIdx ||
1003             ( !pSttNd &&
1004                 0 != ( pSttNd = rSrcIdx.GetNode().FindFlyStartNode() ) &&
1005                 pSttNd == rDstIdx.GetNode().FindFlyStartNode() ) ||
1006             ( !pSttNd &&
1007                 0 != ( pSttNd = rSrcIdx.GetNode().FindFooterStartNode() ) &&
1008                 pSttNd == rDstIdx.GetNode().FindFooterStartNode() ) ||
1009             ( !pSttNd &&
1010                 0 != ( pSttNd = rSrcIdx.GetNode().FindHeaderStartNode() ) &&
1011                 pSttNd == rDstIdx.GetNode().FindHeaderStartNode() ) ||
1012             ( !pSttNd && rDstIdx.GetIndex() > nEndOfExtras &&
1013                             rSrcIdx.GetIndex() > nEndOfExtras ))
1014             bAllowed = sal_True;
1015     }
1016 
1017     return bAllowed ? SW_CHAIN_OK : SW_CHAIN_WRONG_AREA;
1018 }
1019 
1020 int SwDoc::Chain( SwFrmFmt &rSource, const SwFrmFmt &rDest )
1021 {
1022     int nErr = Chainable( rSource, rDest );
1023     if ( !nErr )
1024     {
1025         GetIDocumentUndoRedo().StartUndo( UNDO_CHAINE, NULL );
1026 
1027         SwFlyFrmFmt& rDestFmt = (SwFlyFrmFmt&)rDest;
1028 
1029         //Follow an den Master haengen.
1030         SwFmtChain aChain = rDestFmt.GetChain();
1031         aChain.SetPrev( &(SwFlyFrmFmt&)rSource );
1032         SetAttr( aChain, rDestFmt );
1033 
1034         SfxItemSet aSet( GetAttrPool(), RES_FRM_SIZE, RES_FRM_SIZE,
1035                                         RES_CHAIN,  RES_CHAIN, 0 );
1036 
1037         //Follow an den Master haengen.
1038         aChain.SetPrev( &(SwFlyFrmFmt&)rSource );
1039         SetAttr( aChain, rDestFmt );
1040 
1041         //Master an den Follow haengen und dafuer sorgen, dass der Master
1042         //eine fixierte Hoehe hat.
1043         aChain = rSource.GetChain();
1044         aChain.SetNext( &rDestFmt );
1045         aSet.Put( aChain );
1046 
1047         SwFmtFrmSize aSize( rSource.GetFrmSize() );
1048         if ( aSize.GetHeightSizeType() != ATT_FIX_SIZE )
1049         {
1050             SwFlyFrm *pFly = SwIterator<SwFlyFrm,SwFmt>::FirstElement( rSource );
1051             if ( pFly )
1052                 aSize.SetHeight( pFly->Frm().Height() );
1053             aSize.SetHeightSizeType( ATT_FIX_SIZE );
1054             aSet.Put( aSize );
1055         }
1056         SetAttr( aSet, rSource );
1057 
1058         GetIDocumentUndoRedo().EndUndo( UNDO_CHAINE, NULL );
1059     }
1060     return nErr;
1061 }
1062 
1063 void SwDoc::Unchain( SwFrmFmt &rFmt )
1064 {
1065     SwFmtChain aChain( rFmt.GetChain() );
1066     if ( aChain.GetNext() )
1067     {
1068         GetIDocumentUndoRedo().StartUndo( UNDO_UNCHAIN, NULL );
1069         SwFrmFmt *pFollow = aChain.GetNext();
1070         aChain.SetNext( 0 );
1071         SetAttr( aChain, rFmt );
1072         aChain = pFollow->GetChain();
1073         aChain.SetPrev( 0 );
1074         SetAttr( aChain, *pFollow );
1075         GetIDocumentUndoRedo().EndUndo( UNDO_UNCHAIN, NULL );
1076     }
1077 }
1078 
1079 
1080 
1081