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 :
20 : #include <fmtftn.hxx>
21 :
22 : #include <doc.hxx>
23 : #include <DocumentContentOperationsManager.hxx>
24 : #include <IDocumentStylePoolAccess.hxx>
25 : #include <cntfrm.hxx>
26 : #include <pagefrm.hxx>
27 : #include <txtftn.hxx>
28 : #include <ftnidx.hxx>
29 : #include <ftninfo.hxx>
30 : #include <swfont.hxx>
31 : #include <ndtxt.hxx>
32 : #include <poolfmt.hxx>
33 : #include <ftnfrm.hxx>
34 : #include <ndindex.hxx>
35 : #include <fmtftntx.hxx>
36 : #include <section.hxx>
37 : #include <calbck.hxx>
38 :
39 : namespace {
40 : /// Get a sorted list of the used footnote reference numbers.
41 : /// @param[in] rDoc The active document.
42 : /// @param[in] pExclude A footnote whose reference number should be excluded from the set.
43 : /// @param[out] rUsedRef The set of used reference numbers.
44 : /// @param[out] rInvalid A returned list of all items that had an invalid reference number.
45 2082 : static void lcl_FillUsedFootnoteRefNumbers(SwDoc &rDoc,
46 : SwTextFootnote *pExclude,
47 : std::set<sal_uInt16> &rUsedRef,
48 : std::vector<SwTextFootnote*> &rInvalid)
49 : {
50 2082 : SwFootnoteIdxs& ftnIdxs = rDoc.GetFootnoteIdxs();
51 :
52 2082 : rInvalid.clear();
53 :
54 2342 : for( size_t n = 0; n < ftnIdxs.size(); ++n )
55 : {
56 260 : SwTextFootnote* pTextFootnote = ftnIdxs[ n ];
57 260 : if ( pTextFootnote != pExclude )
58 : {
59 145 : if ( USHRT_MAX == pTextFootnote->GetSeqRefNo() )
60 : {
61 0 : rInvalid.push_back(pTextFootnote);
62 : }
63 : else
64 : {
65 145 : rUsedRef.insert( pTextFootnote->GetSeqRefNo() );
66 : }
67 : }
68 : }
69 2082 : }
70 :
71 : /// Check whether a requested reference number is available.
72 : /// @param[in] rUsedNums Set of used reference numbers.
73 : /// @param[in] requested The requested reference number.
74 : /// @returns true if the number is available, false if not.
75 115 : static bool lcl_IsRefNumAvailable(std::set<sal_uInt16> &rUsedNums,
76 : sal_uInt16 requested)
77 : {
78 115 : if ( USHRT_MAX == requested )
79 73 : return false; // Invalid sequence number.
80 42 : if ( rUsedNums.count(requested) )
81 0 : return false; // Number already used.
82 42 : return true;
83 : }
84 :
85 : /// Get the first few unused sequential reference numbers.
86 : /// @param[out] rLowestUnusedNums The lowest unused sequential reference numbers.
87 : /// @param[in] rUsedNums The set of used sequential reference numbers.
88 : /// @param[in] numRequired The number of reference number required.
89 2040 : static void lcl_FillUnusedSeqRefNums(std::vector<sal_uInt16> &rLowestUnusedNums,
90 : const std::set<sal_uInt16> &rUsedNums,
91 : size_t numRequired)
92 : {
93 2040 : if (!numRequired)
94 3936 : return;
95 :
96 73 : rLowestUnusedNums.reserve(numRequired);
97 73 : sal_uInt16 newNum = 0;
98 73 : std::set<sal_uInt16>::iterator it;
99 : //Start by using numbers from gaps in rUsedNums
100 119 : for( it = rUsedNums.begin(); it != rUsedNums.end(); ++it )
101 : {
102 96 : while ( newNum < *it )
103 : {
104 2 : rLowestUnusedNums.push_back( newNum++ );
105 2 : if ( --numRequired == 0)
106 2 : return;
107 : }
108 46 : newNum++;
109 : }
110 : //Filled in all gaps. Fill the rest of the list with new numbers.
111 71 : do
112 : {
113 71 : rLowestUnusedNums.push_back( newNum++ );
114 : }
115 : while ( --numRequired > 0 );
116 : }
117 :
118 : }
119 :
120 591 : SwFormatFootnote::SwFormatFootnote( bool bEndNote )
121 : : SfxPoolItem( RES_TXTATR_FTN )
122 : , SwModify(0)
123 : , m_pTextAttr(0)
124 : , m_nNumber(0)
125 591 : , m_bEndNote(bEndNote)
126 : {
127 591 : }
128 :
129 0 : bool SwFormatFootnote::operator==( const SfxPoolItem& rAttr ) const
130 : {
131 : assert(SfxPoolItem::operator==(rAttr));
132 0 : return m_nNumber == static_cast<const SwFormatFootnote&>(rAttr).m_nNumber &&
133 0 : m_aNumber == static_cast<const SwFormatFootnote&>(rAttr).m_aNumber &&
134 0 : m_bEndNote == static_cast<const SwFormatFootnote&>(rAttr).m_bEndNote;
135 : }
136 :
137 410 : SfxPoolItem* SwFormatFootnote::Clone( SfxItemPool* ) const
138 : {
139 410 : SwFormatFootnote* pNew = new SwFormatFootnote;
140 410 : pNew->m_aNumber = m_aNumber;
141 410 : pNew->m_nNumber = m_nNumber;
142 410 : pNew->m_bEndNote = m_bEndNote;
143 410 : return pNew;
144 : }
145 :
146 0 : void SwFormatFootnote::Modify(SfxPoolItem const* pOld, SfxPoolItem const* pNew)
147 : {
148 0 : NotifyClients(pOld, pNew);
149 0 : if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which()))
150 : { // invalidate cached UNO object
151 0 : SetXFootnote(css::uno::Reference<css::text::XFootnote>(0));
152 : }
153 0 : }
154 :
155 138 : void SwFormatFootnote::InvalidateFootnote()
156 : {
157 : SwPtrMsgPoolItem const item(RES_REMOVE_UNO_OBJECT,
158 138 : &static_cast<SwModify&>(*this)); // cast to base class (void*)
159 138 : NotifyClients(&item, &item);
160 138 : }
161 :
162 0 : void SwFormatFootnote::SetEndNote( bool b )
163 : {
164 0 : if ( b != m_bEndNote )
165 : {
166 0 : if ( GetTextFootnote() )
167 : {
168 0 : GetTextFootnote()->DelFrms(0);
169 : }
170 0 : m_bEndNote = b;
171 : }
172 0 : }
173 :
174 1060 : SwFormatFootnote::~SwFormatFootnote()
175 : {
176 1060 : }
177 :
178 0 : void SwFormatFootnote::GetFootnoteText( OUString& rStr ) const
179 : {
180 0 : if( m_pTextAttr->GetStartNode() )
181 : {
182 0 : SwNodeIndex aIdx( *m_pTextAttr->GetStartNode(), 1 );
183 0 : SwContentNode* pCNd = aIdx.GetNode().GetTextNode();
184 0 : if( !pCNd )
185 0 : pCNd = aIdx.GetNodes().GoNext( &aIdx );
186 :
187 0 : if( pCNd->IsTextNode() ) {
188 0 : rStr = static_cast<SwTextNode*>(pCNd)->GetExpandText();
189 :
190 0 : ++aIdx;
191 0 : while ( !aIdx.GetNode().IsEndNode() ) {
192 0 : if ( aIdx.GetNode().IsTextNode() )
193 0 : rStr += " " + static_cast<SwTextNode*>((aIdx.GetNode().GetTextNode()))->GetExpandText();
194 0 : ++aIdx;
195 : }
196 0 : }
197 : }
198 0 : }
199 :
200 : // returnt den anzuzeigenden String der Fuss-/Endnote
201 396 : OUString SwFormatFootnote::GetViewNumStr( const SwDoc& rDoc, bool bInclStrings ) const
202 : {
203 396 : OUString sRet( GetNumStr() );
204 396 : if( sRet.isEmpty() )
205 : {
206 : // dann ist die Nummer von Interesse, also ueber die Info diese
207 : // besorgen.
208 255 : bool bMakeNum = true;
209 : const SwSectionNode* pSectNd = m_pTextAttr
210 255 : ? SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *m_pTextAttr )
211 510 : : 0;
212 :
213 255 : if( pSectNd )
214 : {
215 : const SwFormatFootnoteEndAtTextEnd& rFootnoteEnd = static_cast<const SwFormatFootnoteEndAtTextEnd&>(
216 15 : pSectNd->GetSection().GetFormat()->GetFormatAttr(
217 15 : IsEndNote() ?
218 : static_cast<sal_uInt16>(RES_END_AT_TXTEND) :
219 30 : static_cast<sal_uInt16>(RES_FTN_AT_TXTEND) ) );
220 :
221 15 : if( FTNEND_ATTXTEND_OWNNUMANDFMT == rFootnoteEnd.GetValue() )
222 : {
223 0 : bMakeNum = false;
224 0 : sRet = rFootnoteEnd.GetSwNumType().GetNumStr( GetNumber() );
225 0 : if( bInclStrings )
226 : {
227 0 : sRet = rFootnoteEnd.GetPrefix() + sRet + rFootnoteEnd.GetSuffix();
228 : }
229 : }
230 : }
231 :
232 255 : if( bMakeNum )
233 : {
234 : const SwEndNoteInfo* pInfo;
235 255 : if( IsEndNote() )
236 71 : pInfo = &rDoc.GetEndNoteInfo();
237 : else
238 184 : pInfo = &rDoc.GetFootnoteInfo();
239 255 : sRet = pInfo->aFormat.GetNumStr( GetNumber() );
240 255 : if( bInclStrings )
241 : {
242 127 : sRet = pInfo->GetPrefix() + sRet + pInfo->GetSuffix();
243 : }
244 : }
245 : }
246 396 : return sRet;
247 : }
248 :
249 138 : SwTextFootnote::SwTextFootnote( SwFormatFootnote& rAttr, sal_Int32 nStartPos )
250 : : SwTextAttr( rAttr, nStartPos )
251 : , m_pStartNode( 0 )
252 : , m_pTextNode( 0 )
253 138 : , m_nSeqNo( USHRT_MAX )
254 : {
255 138 : rAttr.m_pTextAttr = this;
256 138 : SetHasDummyChar(true);
257 138 : }
258 :
259 414 : SwTextFootnote::~SwTextFootnote()
260 : {
261 138 : SetStartNode( 0 );
262 276 : }
263 :
264 289 : void SwTextFootnote::SetStartNode( const SwNodeIndex *pNewNode, bool bDelNode )
265 : {
266 289 : if( pNewNode )
267 : {
268 0 : if ( !m_pStartNode )
269 : {
270 0 : m_pStartNode = new SwNodeIndex( *pNewNode );
271 : }
272 : else
273 : {
274 0 : *m_pStartNode = *pNewNode;
275 : }
276 : }
277 289 : else if ( m_pStartNode )
278 : {
279 : // Zwei Dinge muessen erledigt werden:
280 : // 1) Die Fussnoten muessen bei ihren Seiten abgemeldet werden
281 : // 2) Die Fussnoten-Sektion in den Inserts muss geloescht werden.
282 : SwDoc* pDoc;
283 138 : if ( m_pTextNode )
284 : {
285 138 : pDoc = m_pTextNode->GetDoc();
286 : }
287 : else
288 : {
289 : //JP 27.01.97: der sw3-Reader setzt einen StartNode aber das
290 : // Attribut ist noch nicht im TextNode verankert.
291 : // Wird es geloescht (z.B. bei Datei einfuegen mit
292 : // Footnote in einen Rahmen), muss auch der Inhalt
293 : // geloescht werden
294 0 : pDoc = m_pStartNode->GetNodes().GetDoc();
295 : }
296 :
297 : // Wir duerfen die Fussnotennodes nicht loeschen
298 : // und brauchen die Fussnotenframes nicht loeschen, wenn
299 : // wir im ~SwDoc() stehen.
300 138 : if( !pDoc->IsInDtor() )
301 : {
302 13 : if( bDelNode )
303 : {
304 : // 1) Die Section fuer die Fussnote wird beseitigt
305 : // Es kann sein, dass die Inserts schon geloescht wurden.
306 0 : pDoc->getIDocumentContentOperations().DeleteSection( &m_pStartNode->GetNode() );
307 : }
308 : else
309 : // Werden die Nodes nicht geloescht mussen sie bei den Seiten
310 : // abmeldet (Frms loeschen) werden, denn sonst bleiben sie
311 : // stehen (Undo loescht sie nicht!)
312 13 : DelFrms( 0 );
313 : }
314 138 : DELETEZ( m_pStartNode );
315 :
316 : // loesche die Fussnote noch aus dem Array am Dokument
317 141 : for( size_t n = 0; n < pDoc->GetFootnoteIdxs().size(); ++n )
318 16 : if( this == pDoc->GetFootnoteIdxs()[n] )
319 : {
320 13 : pDoc->GetFootnoteIdxs().erase( pDoc->GetFootnoteIdxs().begin() + n );
321 : // gibt noch weitere Fussnoten
322 13 : if( !pDoc->IsInDtor() && n < pDoc->GetFootnoteIdxs().size() )
323 : {
324 6 : SwNodeIndex aTmp( pDoc->GetFootnoteIdxs()[n]->GetTextNode() );
325 6 : pDoc->GetFootnoteIdxs().UpdateFootnote( aTmp );
326 : }
327 13 : break;
328 : }
329 : }
330 289 : }
331 :
332 340 : void SwTextFootnote::SetNumber( const sal_uInt16 nNewNum, const OUString &sNumStr )
333 : {
334 340 : SwFormatFootnote& rFootnote = (SwFormatFootnote&)GetFootnote();
335 :
336 340 : rFootnote.m_aNumber = sNumStr;
337 340 : if ( sNumStr.isEmpty() )
338 : {
339 327 : rFootnote.m_nNumber = nNewNum;
340 : }
341 :
342 : OSL_ENSURE( m_pTextNode, "SwTextFootnote: where is my TextNode?" );
343 340 : SwNodes &rNodes = m_pTextNode->GetDoc()->GetNodes();
344 340 : m_pTextNode->ModifyNotification( 0, &rFootnote );
345 340 : if ( m_pStartNode )
346 : {
347 : // must iterate over all TextNodes because of footnotes on other pages
348 340 : sal_uLong nSttIdx = m_pStartNode->GetIndex() + 1;
349 340 : sal_uLong nEndIdx = m_pStartNode->GetNode().EndOfSectionIndex();
350 975 : for( ; nSttIdx < nEndIdx; ++nSttIdx )
351 : {
352 : // Es koennen ja auch Grafiken in der Fussnote stehen ...
353 : SwNode* pNd;
354 635 : if( ( pNd = rNodes[ nSttIdx ] )->IsTextNode() )
355 591 : static_cast<SwTextNode*>(pNd)->ModifyNotification( 0, &rFootnote );
356 : }
357 : }
358 340 : }
359 :
360 : // Die Fussnoten duplizieren
361 0 : void SwTextFootnote::CopyFootnote(
362 : SwTextFootnote & rDest,
363 : SwTextNode & rDestNode ) const
364 : {
365 0 : if (m_pStartNode && !rDest.GetStartNode())
366 : {
367 : // dest missing node section? create it here!
368 : // (happens in SwTextNode::CopyText if pDest == this)
369 0 : rDest.MakeNewTextSection( rDestNode.GetNodes() );
370 : }
371 0 : if (m_pStartNode && rDest.GetStartNode())
372 : {
373 : // footnotes not necessarily in same document!
374 0 : SwDoc *const pDstDoc = rDestNode.GetDoc();
375 0 : SwNodes &rDstNodes = pDstDoc->GetNodes();
376 :
377 : // copy only the content of the section
378 : SwNodeRange aRg( *m_pStartNode, 1,
379 0 : *m_pStartNode->GetNode().EndOfSectionNode() );
380 :
381 : // insert at the end of rDest, i.e., the nodes are appended.
382 : // nDestLen contains number of ContentNodes in rDest _before_ copy.
383 0 : SwNodeIndex aStart( *(rDest.GetStartNode()) );
384 0 : SwNodeIndex aEnd( *aStart.GetNode().EndOfSectionNode() );
385 0 : sal_uLong nDestLen = aEnd.GetIndex() - aStart.GetIndex() - 1;
386 :
387 0 : m_pTextNode->GetDoc()->GetDocumentContentOperationsManager().CopyWithFlyInFly( aRg, 0, aEnd, NULL, true );
388 :
389 : // in case the destination section was not empty, delete the old nodes
390 : // before: Src: SxxxE, Dst: SnE
391 : // now: Src: SxxxE, Dst: SnxxxE
392 : // after: Src: SxxxE, Dst: SxxxE
393 0 : ++aStart;
394 0 : rDstNodes.Delete( aStart, nDestLen );
395 : }
396 :
397 : // also copy user defined number string
398 0 : if( !GetFootnote().m_aNumber.isEmpty() )
399 : {
400 0 : const_cast<SwFormatFootnote &>(rDest.GetFootnote()).m_aNumber = GetFootnote().m_aNumber;
401 : }
402 0 : }
403 :
404 : // lege eine neue leere TextSection fuer diese Fussnote an
405 138 : void SwTextFootnote::MakeNewTextSection( SwNodes& rNodes )
406 : {
407 138 : if ( m_pStartNode )
408 138 : return;
409 :
410 : // Nun verpassen wir dem TextNode noch die Fussnotenvorlage.
411 : SwTextFormatColl *pFormatColl;
412 : const SwEndNoteInfo* pInfo;
413 : sal_uInt16 nPoolId;
414 :
415 138 : if( GetFootnote().IsEndNote() )
416 : {
417 19 : pInfo = &rNodes.GetDoc()->GetEndNoteInfo();
418 19 : nPoolId = RES_POOLCOLL_ENDNOTE;
419 : }
420 : else
421 : {
422 119 : pInfo = &rNodes.GetDoc()->GetFootnoteInfo();
423 119 : nPoolId = RES_POOLCOLL_FOOTNOTE;
424 : }
425 :
426 138 : if( 0 == (pFormatColl = pInfo->GetFootnoteTextColl() ) )
427 136 : pFormatColl = rNodes.GetDoc()->getIDocumentStylePoolAccess().GetTextCollFromPool( nPoolId );
428 :
429 138 : SwStartNode* pSttNd = rNodes.MakeTextSection( SwNodeIndex( rNodes.GetEndOfInserts() ),
430 138 : SwFootnoteStartNode, pFormatColl );
431 138 : m_pStartNode = new SwNodeIndex( *pSttNd );
432 : }
433 :
434 108 : void SwTextFootnote::DelFrms( const SwFrm* pSib )
435 : {
436 : // delete the FootnoteFrames from the pages
437 : OSL_ENSURE( m_pTextNode, "SwTextFootnote: where is my TextNode?" );
438 108 : if ( !m_pTextNode )
439 108 : return;
440 :
441 108 : const SwRootFrm* pRoot = pSib ? pSib->getRootFrm() : 0;
442 108 : bool bFrmFnd = false;
443 : {
444 108 : SwIterator<SwContentFrm,SwTextNode> aIter( *m_pTextNode );
445 200 : for( SwContentFrm* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() )
446 : {
447 92 : if( pRoot != pFnd->getRootFrm() && pRoot )
448 0 : continue;
449 92 : SwPageFrm* pPage = pFnd->FindPageFrm();
450 92 : if( pPage )
451 : {
452 13 : pPage->RemoveFootnote( pFnd, this );
453 13 : bFrmFnd = true;
454 : }
455 108 : }
456 : }
457 : //JP 13.05.97: falls das Layout vorm loeschen der Fussnoten entfernt
458 : // wird, sollte man das ueber die Fussnote selbst tun
459 108 : if ( !bFrmFnd && m_pStartNode )
460 : {
461 95 : SwNodeIndex aIdx( *m_pStartNode );
462 95 : SwContentNode* pCNd = m_pTextNode->GetNodes().GoNext( &aIdx );
463 95 : if( pCNd )
464 : {
465 95 : SwIterator<SwContentFrm,SwContentNode> aIter( *pCNd );
466 95 : for( SwContentFrm* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() )
467 : {
468 0 : if( pRoot != pFnd->getRootFrm() && pRoot )
469 0 : continue;
470 0 : SwPageFrm* pPage = pFnd->FindPageFrm();
471 :
472 0 : SwFrm *pFrm = pFnd->GetUpper();
473 0 : while ( pFrm && !pFrm->IsFootnoteFrm() )
474 0 : pFrm = pFrm->GetUpper();
475 :
476 0 : SwFootnoteFrm *pFootnote = static_cast<SwFootnoteFrm*>(pFrm);
477 0 : while ( pFootnote && pFootnote->GetMaster() )
478 0 : pFootnote = pFootnote->GetMaster();
479 : OSL_ENSURE( pFootnote->GetAttr() == this, "Footnote mismatch error." );
480 :
481 0 : while ( pFootnote )
482 : {
483 0 : SwFootnoteFrm *pFoll = pFootnote->GetFollow();
484 0 : pFootnote->Cut();
485 0 : SwFrm::DestroyFrm(pFootnote);
486 0 : pFootnote = pFoll;
487 : }
488 :
489 : // #i20556# During hiding of a section, the connection
490 : // to the layout is already lost. pPage may be 0:
491 0 : if ( pPage )
492 0 : pPage->UpdateFootnoteNum();
493 95 : }
494 95 : }
495 : }
496 : }
497 :
498 : /// Set the sequence number for the current footnote.
499 : /// @returns The new sequence number or USHRT_MAX if invalid.
500 180 : sal_uInt16 SwTextFootnote::SetSeqRefNo()
501 : {
502 180 : if( !m_pTextNode )
503 0 : return USHRT_MAX;
504 :
505 180 : SwDoc* pDoc = m_pTextNode->GetDoc();
506 180 : if( pDoc->IsInReading() )
507 65 : return USHRT_MAX;
508 :
509 115 : std::set<sal_uInt16> aUsedNums;
510 230 : std::vector<SwTextFootnote*> badRefNums;
511 115 : ::lcl_FillUsedFootnoteRefNumbers(*pDoc, this, aUsedNums, badRefNums);
512 115 : if ( ::lcl_IsRefNumAvailable(aUsedNums, m_nSeqNo) )
513 42 : return m_nSeqNo;
514 146 : std::vector<sal_uInt16> unused;
515 73 : ::lcl_FillUnusedSeqRefNums(unused, aUsedNums, 1);
516 188 : return m_nSeqNo = unused[0];
517 : }
518 :
519 : /// Set a unique sequential reference number for every footnote in the document.
520 : /// @param[in] rDoc The document to be processed.
521 1967 : void SwTextFootnote::SetUniqueSeqRefNo( SwDoc& rDoc )
522 : {
523 1967 : std::set<sal_uInt16> aUsedNums;
524 3934 : std::vector<SwTextFootnote*> badRefNums;
525 1967 : ::lcl_FillUsedFootnoteRefNumbers(rDoc, NULL, aUsedNums, badRefNums);
526 3934 : std::vector<sal_uInt16> aUnused;
527 1967 : ::lcl_FillUnusedSeqRefNums(aUnused, aUsedNums, badRefNums.size());
528 :
529 1967 : for (size_t i = 0; i < badRefNums.size(); ++i)
530 : {
531 0 : badRefNums[i]->m_nSeqNo = aUnused[i];
532 1967 : }
533 1967 : }
534 :
535 0 : void SwTextFootnote::CheckCondColl()
536 : {
537 : //FEATURE::CONDCOLL
538 0 : if( GetStartNode() )
539 0 : static_cast<SwStartNode&>(GetStartNode()->GetNode()).CheckSectionCondColl();
540 : //FEATURE::CONDCOLL
541 177 : }
542 :
543 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|