Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 : #include <DocumentLayoutManager.hxx>
20 : #include <doc.hxx>
21 : #include <IDocumentState.hxx>
22 : #include <IDocumentUndoRedo.hxx>
23 : #include <DocumentContentOperationsManager.hxx>
24 : #include <IDocumentStylePoolAccess.hxx>
25 : #include <undobj.hxx>
26 : #include <viewsh.hxx>
27 : #include <layouter.hxx>
28 : #include <poolfmt.hxx>
29 : #include <frmfmt.hxx>
30 : #include <fmtcntnt.hxx>
31 : #include <fmtcnct.hxx>
32 : #include <ndole.hxx>
33 : #include <com/sun/star/embed/EmbedStates.hpp>
34 : #include <fmtanchr.hxx>
35 : #include <txtflcnt.hxx>
36 : #include <fmtflcnt.hxx>
37 : #include <ndtxt.hxx>
38 : #include <dcontact.hxx>
39 : #include <unoframe.hxx>
40 : #include <docary.hxx>
41 : #include <textboxhelper.hxx>
42 :
43 : using namespace ::com::sun::star;
44 :
45 : namespace sw
46 : {
47 :
48 2958 : DocumentLayoutManager::DocumentLayoutManager( SwDoc& i_rSwdoc ) :
49 : m_rDoc( i_rSwdoc ),
50 : mpCurrentView( 0 ),
51 2958 : mpLayouter( 0 )
52 : {
53 2958 : }
54 :
55 464404 : const SwViewShell *DocumentLayoutManager::GetCurrentViewShell() const
56 : {
57 464404 : return mpCurrentView;
58 : }
59 :
60 108795176 : SwViewShell *DocumentLayoutManager::GetCurrentViewShell()
61 : {
62 108795176 : return mpCurrentView;
63 : }
64 :
65 8546 : void DocumentLayoutManager::SetCurrentViewShell( SwViewShell* pNew )
66 : {
67 8546 : mpCurrentView = pNew;
68 8546 : }
69 :
70 : // It must be able to communicate to a SwViewShell. This is going to be removed later.
71 195785 : const SwRootFrm *DocumentLayoutManager::GetCurrentLayout() const
72 : {
73 195785 : if(GetCurrentViewShell())
74 187991 : return GetCurrentViewShell()->GetLayout();
75 7794 : return 0;
76 : }
77 :
78 27674251 : SwRootFrm *DocumentLayoutManager::GetCurrentLayout()
79 : {
80 27674251 : if(GetCurrentViewShell())
81 27041156 : return GetCurrentViewShell()->GetLayout();
82 633095 : return 0;
83 : }
84 :
85 7387 : bool DocumentLayoutManager::HasLayout() const
86 : {
87 : // if there is a view, there is always a layout
88 7387 : return (mpCurrentView != 0);
89 : }
90 :
91 110936 : SwLayouter* DocumentLayoutManager::GetLayouter()
92 : {
93 110936 : return mpLayouter;
94 : }
95 :
96 3250529 : const SwLayouter* DocumentLayoutManager::GetLayouter() const
97 : {
98 3250529 : return mpLayouter;
99 : }
100 :
101 2764 : void DocumentLayoutManager::SetLayouter( SwLayouter* pNew )
102 : {
103 2764 : mpLayouter = pNew;
104 2764 : }
105 :
106 : /** Create a new format whose settings fit to the Request by default.
107 :
108 : The format is put into the respective format array.
109 : If there already is a fitting format, it is returned instead. */
110 1426 : SwFrameFormat *DocumentLayoutManager::MakeLayoutFormat( RndStdIds eRequest, const SfxItemSet* pSet )
111 : {
112 1426 : SwFrameFormat *pFormat = 0;
113 1426 : const bool bMod = m_rDoc.getIDocumentState().IsModified();
114 1426 : bool bHeader = false;
115 :
116 1426 : switch ( eRequest )
117 : {
118 : case RND_STD_HEADER:
119 : case RND_STD_HEADERL:
120 : case RND_STD_HEADERR:
121 : {
122 712 : bHeader = true;
123 : // no break, we continue further down
124 : }
125 : case RND_STD_FOOTER:
126 : case RND_STD_FOOTERL:
127 : case RND_STD_FOOTERR:
128 : {
129 1426 : pFormat = new SwFrameFormat( m_rDoc.GetAttrPool(),
130 : (bHeader ? "Right header" : "Right footer"),
131 1426 : m_rDoc.GetDfltFrameFormat() );
132 :
133 1426 : SwNodeIndex aTmpIdx( m_rDoc.GetNodes().GetEndOfAutotext() );
134 : SwStartNode* pSttNd =
135 1426 : m_rDoc.GetNodes().MakeTextSection
136 : ( aTmpIdx,
137 : bHeader ? SwHeaderStartNode : SwFooterStartNode,
138 1426 : m_rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(static_cast<sal_uInt16>( bHeader
139 : ? ( eRequest == RND_STD_HEADERL
140 : ? RES_POOLCOLL_HEADERL
141 : : eRequest == RND_STD_HEADERR
142 : ? RES_POOLCOLL_HEADERR
143 : : RES_POOLCOLL_HEADER )
144 : : ( eRequest == RND_STD_FOOTERL
145 : ? RES_POOLCOLL_FOOTERL
146 : : eRequest == RND_STD_FOOTERR
147 : ? RES_POOLCOLL_FOOTERR
148 : : RES_POOLCOLL_FOOTER )
149 2852 : ) ) );
150 1426 : pFormat->SetFormatAttr( SwFormatContent( pSttNd ));
151 :
152 1426 : if( pSet ) // Set a few more attributes
153 0 : pFormat->SetFormatAttr( *pSet );
154 :
155 : // Why set it back? Doc has changed, or not?
156 : // In any case, wrong for the FlyFrames!
157 1426 : if ( !bMod )
158 0 : m_rDoc.getIDocumentState().ResetModified();
159 : }
160 1426 : break;
161 :
162 : case RND_DRAW_OBJECT:
163 : {
164 0 : pFormat = m_rDoc.MakeDrawFrameFormat( OUString(), m_rDoc.GetDfltFrameFormat() );
165 0 : if( pSet ) // Set a few more attributes
166 0 : pFormat->SetFormatAttr( *pSet );
167 :
168 0 : if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
169 : {
170 0 : m_rDoc.GetIDocumentUndoRedo().AppendUndo(
171 0 : new SwUndoInsLayFormat(pFormat, 0, 0));
172 : }
173 : }
174 0 : break;
175 :
176 : #if OSL_DEBUG_LEVEL > 0
177 : case FLY_AT_PAGE:
178 : case FLY_AT_CHAR:
179 : case FLY_AT_FLY:
180 : case FLY_AT_PARA:
181 : case FLY_AS_CHAR:
182 : OSL_FAIL( "use new interface instead: SwDoc::MakeFlySection!" );
183 : break;
184 : #endif
185 :
186 : default:
187 : OSL_ENSURE( false,
188 : "LayoutFormat was requested with an invalid Request." );
189 :
190 : }
191 1426 : return pFormat;
192 : }
193 :
194 : /// Deletes the denoted format and its content.
195 2417 : void DocumentLayoutManager::DelLayoutFormat( SwFrameFormat *pFormat )
196 : {
197 : // A chain of frames needs to be merged, if necessary,
198 : // so that the Frame's contents are adjusted accordingly before we destroy the Frames.
199 2417 : const SwFormatChain &rChain = pFormat->GetChain();
200 2417 : if ( rChain.GetPrev() )
201 : {
202 0 : SwFormatChain aChain( rChain.GetPrev()->GetChain() );
203 0 : aChain.SetNext( rChain.GetNext() );
204 0 : m_rDoc.SetAttr( aChain, *rChain.GetPrev() );
205 : }
206 2417 : if ( rChain.GetNext() )
207 : {
208 0 : SwFormatChain aChain( rChain.GetNext()->GetChain() );
209 0 : aChain.SetPrev( rChain.GetPrev() );
210 0 : m_rDoc.SetAttr( aChain, *rChain.GetNext() );
211 : }
212 :
213 2417 : const SwNodeIndex* pCntIdx = 0;
214 : // The draw format doesn't own its content, it just has a pointer to it.
215 2417 : if (pFormat->Which() != RES_DRAWFRMFMT)
216 1154 : pCntIdx = pFormat->GetContent().GetContentIdx();
217 2417 : if (pCntIdx && !m_rDoc.GetIDocumentUndoRedo().DoesUndo())
218 : {
219 : // Disconnect if it's an OLE object
220 441 : SwOLENode* pOLENd = m_rDoc.GetNodes()[ pCntIdx->GetIndex()+1 ]->GetOLENode();
221 441 : if( pOLENd && pOLENd->GetOLEObj().IsOleRef() )
222 : {
223 :
224 : // TODO: the old object closed the object and cleared all references to it, but didn't remove it from the container.
225 : // I have no idea, why, nobody could explain it - so I do my very best to mimic this behavior
226 : //uno::Reference < util::XCloseable > xClose( pOLENd->GetOLEObj().GetOleRef(), uno::UNO_QUERY );
227 : //if ( xClose.is() )
228 : {
229 : try
230 : {
231 29 : pOLENd->GetOLEObj().GetOleRef()->changeState( embed::EmbedStates::LOADED );
232 : }
233 0 : catch ( uno::Exception& )
234 : {
235 : }
236 : }
237 :
238 : }
239 : }
240 :
241 : // Destroy Frames
242 2417 : pFormat->DelFrms();
243 :
244 : // Only FlyFrames are undoable at first
245 2417 : const sal_uInt16 nWh = pFormat->Which();
246 2420 : if (m_rDoc.GetIDocumentUndoRedo().DoesUndo() &&
247 1 : (RES_FLYFRMFMT == nWh || RES_DRAWFRMFMT == nWh))
248 : {
249 3 : m_rDoc.GetIDocumentUndoRedo().AppendUndo( new SwUndoDelLayFormat( pFormat ));
250 : }
251 : else
252 : {
253 : // #i32089# - delete at-frame anchored objects
254 2414 : if ( nWh == RES_FLYFRMFMT )
255 : {
256 : // determine frame formats of at-frame anchored objects
257 1152 : const SwNodeIndex* pContentIdx = 0;
258 1152 : if (pFormat->Which() != RES_DRAWFRMFMT)
259 1152 : pContentIdx = pFormat->GetContent().GetContentIdx();
260 1152 : if (pContentIdx)
261 : {
262 441 : const SwFrameFormats* pTable = pFormat->GetDoc()->GetSpzFrameFormats();
263 441 : if ( pTable )
264 : {
265 441 : std::vector<SwFrameFormat*> aToDeleteFrameFormats;
266 441 : const sal_uLong nNodeIdxOfFlyFormat( pContentIdx->GetIndex() );
267 :
268 5228 : for ( size_t i = 0; i < pTable->size(); ++i )
269 : {
270 4787 : SwFrameFormat* pTmpFormat = (*pTable)[i];
271 4787 : const SwFormatAnchor &rAnch = pTmpFormat->GetAnchor();
272 4787 : if ( rAnch.GetAnchorId() == FLY_AT_FLY &&
273 0 : rAnch.GetContentAnchor()->nNode.GetIndex() == nNodeIdxOfFlyFormat )
274 : {
275 0 : aToDeleteFrameFormats.push_back( pTmpFormat );
276 : }
277 : }
278 :
279 : // delete found frame formats
280 882 : while ( !aToDeleteFrameFormats.empty() )
281 : {
282 0 : SwFrameFormat* pTmpFormat = aToDeleteFrameFormats.back();
283 0 : pFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat( pTmpFormat );
284 :
285 0 : aToDeleteFrameFormats.pop_back();
286 441 : }
287 : }
288 : }
289 : }
290 :
291 : // Delete content
292 2414 : if( pCntIdx )
293 : {
294 441 : SwNode *pNode = &pCntIdx->GetNode();
295 441 : const_cast<SwFormatContent&>(static_cast<const SwFormatContent&>(pFormat->GetFormatAttr( RES_CNTNT ))).SetNewContentIdx( 0 );
296 441 : m_rDoc.getIDocumentContentOperations().DeleteSection( pNode );
297 : }
298 :
299 : // Delete the character for FlyFrames anchored as char (if necessary)
300 2414 : const SwFormatAnchor& rAnchor = pFormat->GetAnchor();
301 2414 : if ((FLY_AS_CHAR == rAnchor.GetAnchorId()) && rAnchor.GetContentAnchor())
302 : {
303 296 : const SwPosition* pPos = rAnchor.GetContentAnchor();
304 296 : SwTextNode *pTextNd = pPos->nNode.GetNode().GetTextNode();
305 :
306 : // attribute is still in text node, delete it
307 296 : if ( pTextNd )
308 : {
309 : SwTextFlyCnt* const pAttr = static_cast<SwTextFlyCnt*>(
310 : pTextNd->GetTextAttrForCharAt( pPos->nContent.GetIndex(),
311 296 : RES_TXTATR_FLYCNT ));
312 296 : if ( pAttr && (pAttr->GetFlyCnt().GetFrameFormat() == pFormat) )
313 : {
314 : // dont delete, set pointer to 0
315 0 : const_cast<SwFormatFlyCnt&>(pAttr->GetFlyCnt()).SetFlyFormat();
316 0 : SwIndex aIdx( pPos->nContent );
317 0 : pTextNd->EraseText( aIdx, 1 );
318 : }
319 : }
320 : }
321 :
322 2414 : m_rDoc.DelFrameFormat( pFormat );
323 : }
324 2417 : m_rDoc.getIDocumentState().SetModified();
325 2417 : }
326 :
327 : /** Copies the stated format (pSrc) to pDest and returns pDest.
328 :
329 : If there's no pDest, it is created.
330 : If the source format is located in another document, also copy correctly
331 : in this case.
332 : The Anchor attribute's position is always set to 0! */
333 739 : SwFrameFormat *DocumentLayoutManager::CopyLayoutFormat(
334 : const SwFrameFormat& rSource,
335 : const SwFormatAnchor& rNewAnchor,
336 : bool bSetTextFlyAtt,
337 : bool bMakeFrms )
338 : {
339 739 : const bool bFly = RES_FLYFRMFMT == rSource.Which();
340 739 : const bool bDraw = RES_DRAWFRMFMT == rSource.Which();
341 : OSL_ENSURE( bFly || bDraw, "this method only works for fly or draw" );
342 :
343 739 : SwDoc* pSrcDoc = const_cast<SwDoc*>(rSource.GetDoc());
344 :
345 : // May we copy this object?
346 : // We may, unless it's 1) it's a control (and therfore a draw)
347 : // 2) anchored in a header/footer
348 : // 3) anchored (to paragraph?)
349 739 : bool bMayNotCopy = false;
350 739 : if( bDraw )
351 : {
352 : const SwDrawContact* pDrawContact =
353 285 : static_cast<const SwDrawContact*>( rSource.FindContactObj() );
354 :
355 : bMayNotCopy =
356 405 : ((FLY_AT_PARA == rNewAnchor.GetAnchorId()) ||
357 240 : (FLY_AT_FLY == rNewAnchor.GetAnchorId()) ||
358 367 : (FLY_AT_CHAR == rNewAnchor.GetAnchorId())) &&
359 494 : rNewAnchor.GetContentAnchor() &&
360 483 : m_rDoc.IsInHeaderFooter( rNewAnchor.GetContentAnchor()->nNode ) &&
361 236 : pDrawContact != NULL &&
362 757 : pDrawContact->GetMaster() != NULL &&
363 521 : CheckControlLayer( pDrawContact->GetMaster() );
364 : }
365 :
366 : // just return if we can't copy this
367 739 : if( bMayNotCopy )
368 0 : return NULL;
369 :
370 739 : SwFrameFormat* pDest = m_rDoc.GetDfltFrameFormat();
371 739 : if( rSource.GetRegisteredIn() != pSrcDoc->GetDfltFrameFormat() )
372 454 : pDest = m_rDoc.CopyFrameFormat( *static_cast<const SwFrameFormat*>(rSource.GetRegisteredIn()) );
373 739 : if( bFly )
374 : {
375 : // #i11176#
376 : // To do a correct cloning concerning the ZOrder for all objects
377 : // it is necessary to actually create a draw object for fly frames, too.
378 : // These are then added to the DrawingLayer (which needs to exist).
379 : // Together with correct sorting of all drawinglayer based objects
380 : // before cloning ZOrder transfer works correctly then.
381 454 : SwFlyFrameFormat *pFormat = m_rDoc.MakeFlyFrameFormat( rSource.GetName(), pDest );
382 454 : pDest = pFormat;
383 :
384 454 : SwXFrame::GetOrCreateSdrObject(*pFormat);
385 : }
386 : else
387 285 : pDest = m_rDoc.MakeDrawFrameFormat( OUString(), pDest );
388 :
389 : // Copy all other or new attributes
390 739 : pDest->CopyAttrs( rSource );
391 :
392 : // Do not copy chains
393 739 : pDest->ResetFormatAttr( RES_CHAIN );
394 :
395 739 : if( bFly )
396 : {
397 : // Duplicate the content.
398 454 : const SwNode& rCSttNd = rSource.GetContent().GetContentIdx()->GetNode();
399 454 : SwNodeRange aRg( rCSttNd, 1, *rCSttNd.EndOfSectionNode() );
400 :
401 908 : SwNodeIndex aIdx( m_rDoc.GetNodes().GetEndOfAutotext() );
402 454 : SwStartNode* pSttNd = SwNodes::MakeEmptySection( aIdx, SwFlyStartNode );
403 :
404 : // Set the Anchor/ContentIndex first.
405 : // Within the copying part, we can access the values (DrawFormat in Headers and Footers)
406 454 : aIdx = *pSttNd;
407 908 : SwFormatContent aAttr( rSource.GetContent() );
408 454 : aAttr.SetNewContentIdx( &aIdx );
409 454 : pDest->SetFormatAttr( aAttr );
410 454 : pDest->SetFormatAttr( rNewAnchor );
411 :
412 454 : if( !m_rDoc.IsCopyIsMove() || &m_rDoc != pSrcDoc )
413 : {
414 429 : if( m_rDoc.IsInReading() || m_rDoc.IsInMailMerge() )
415 415 : pDest->SetName( OUString() );
416 : else
417 : {
418 : // Test first if the name is already taken, if so generate a new one.
419 14 : sal_Int8 nNdTyp = aRg.aStart.GetNode().GetNodeType();
420 :
421 14 : OUString sOld( pDest->GetName() );
422 14 : pDest->SetName( OUString() );
423 14 : if( m_rDoc.FindFlyByName( sOld, nNdTyp ) ) // found one
424 1 : switch( nNdTyp )
425 : {
426 0 : case ND_GRFNODE: sOld = m_rDoc.GetUniqueGrfName(); break;
427 0 : case ND_OLENODE: sOld = m_rDoc.GetUniqueOLEName(); break;
428 1 : default: sOld = m_rDoc.GetUniqueFrameName(); break;
429 : }
430 :
431 14 : pDest->SetName( sOld );
432 : }
433 : }
434 :
435 454 : if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
436 : {
437 1 : m_rDoc.GetIDocumentUndoRedo().AppendUndo(new SwUndoInsLayFormat(pDest,0,0));
438 : }
439 :
440 : // Make sure that FlyFrames in FlyFrames are copied
441 454 : aIdx = *pSttNd->EndOfSectionNode();
442 :
443 : //fdo#36631 disable (scoped) any undo operations associated with the
444 : //contact object itself. They should be managed by SwUndoInsLayFormat.
445 454 : const ::sw::DrawUndoGuard drawUndoGuard(m_rDoc.GetIDocumentUndoRedo());
446 :
447 908 : pSrcDoc->GetDocumentContentOperationsManager().CopyWithFlyInFly( aRg, 0, aIdx, NULL, false, true, true );
448 : }
449 : else
450 : {
451 : OSL_ENSURE( RES_DRAWFRMFMT == rSource.Which(), "Neither Fly nor Draw." );
452 : // #i52780# - Note: moving object to visible layer not needed.
453 285 : const SwDrawContact* pSourceContact = static_cast<const SwDrawContact *>(rSource.FindContactObj());
454 :
455 : SwDrawContact* pContact = new SwDrawContact( static_cast<SwDrawFrameFormat*>(pDest),
456 285 : m_rDoc.CloneSdrObj( *pSourceContact->GetMaster(),
457 570 : m_rDoc.IsCopyIsMove() && &m_rDoc == pSrcDoc ) );
458 : // #i49730# - notify draw frame format that position attributes are
459 : // already set, if the position attributes are already set at the
460 : // source draw frame format.
461 855 : if ( pDest->ISA(SwDrawFrameFormat) &&
462 570 : rSource.ISA(SwDrawFrameFormat) &&
463 285 : static_cast<const SwDrawFrameFormat&>(rSource).IsPosAttrSet() )
464 : {
465 21 : static_cast<SwDrawFrameFormat*>(pDest)->PosAttrSet();
466 : }
467 :
468 285 : if( pDest->GetAnchor() == rNewAnchor )
469 : {
470 : // Do *not* connect to layout, if a <MakeFrms> will not be called.
471 32 : if ( bMakeFrms )
472 : {
473 10 : pContact->ConnectToLayout( &rNewAnchor );
474 : }
475 : }
476 : else
477 253 : pDest->SetFormatAttr( rNewAnchor );
478 :
479 285 : if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
480 : {
481 9 : m_rDoc.GetIDocumentUndoRedo().AppendUndo(new SwUndoInsLayFormat(pDest,0,0));
482 : }
483 : }
484 :
485 739 : if (bSetTextFlyAtt && (FLY_AS_CHAR == rNewAnchor.GetAnchorId()))
486 : {
487 0 : const SwPosition* pPos = rNewAnchor.GetContentAnchor();
488 0 : SwFormatFlyCnt aFormat( pDest );
489 0 : pPos->nNode.GetNode().GetTextNode()->InsertItem(
490 0 : aFormat, pPos->nContent.GetIndex(), 0 );
491 : }
492 :
493 739 : if( bMakeFrms )
494 563 : pDest->MakeFrms();
495 :
496 : // If the draw format has a TextBox, then copy its fly format as well.
497 739 : if (SwFrameFormat* pSourceTextBox = SwTextBoxHelper::findTextBox(&rSource))
498 : {
499 2 : SwFrameFormat* pDestTextBox = CopyLayoutFormat(*pSourceTextBox, rNewAnchor, bSetTextFlyAtt, bMakeFrms);
500 2 : SwAttrSet aSet(pDest->GetAttrSet());
501 4 : SwFormatContent aContent(pDestTextBox->GetContent().GetContentIdx()->GetNode().GetStartNode());
502 2 : aSet.Put(aContent);
503 4 : pDest->SetFormatAttr(aSet);
504 : }
505 :
506 739 : return pDest;
507 : }
508 :
509 : //Load document from fdo#42534 under valgrind, drag the scrollbar down so full
510 : //document layout is triggered. Close document before layout has completed, and
511 : //SwAnchoredObject objects deleted by the deletion of layout remain referenced
512 : //by the SwLayouter
513 1004792 : void DocumentLayoutManager::ClearSwLayouterEntries()
514 : {
515 1004792 : SwLayouter::ClearMovedFwdFrms( m_rDoc );
516 1004792 : SwLayouter::ClearObjsTmpConsiderWrapInfluence( m_rDoc );
517 : // #i65250#
518 1004792 : SwLayouter::ClearMoveBwdLayoutInfo( m_rDoc );
519 1004792 : }
520 :
521 8847 : DocumentLayoutManager::~DocumentLayoutManager()
522 : {
523 2949 : delete mpLayouter;
524 2949 : mpLayouter = 0L;
525 5898 : }
526 :
527 177 : }
528 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
529 :
|