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 <hintids.hxx>
21 #include <osl/diagnose.h>
22 #include <unotools/collatorwrapper.hxx>
23 #include <unotools/localedatawrapper.hxx>
24 #include <comphelper/processfactory.hxx>
25 #include <docary.hxx>
26 #include <fmtanchr.hxx>
27 #include <frmfmt.hxx>
28 #include <doc.hxx>
29 #include <IDocumentUndoRedo.hxx>
30 #include <IDocumentFieldsAccess.hxx>
31 #include <IDocumentRedlineAccess.hxx>
32 #include <IDocumentState.hxx>
33 #include <node.hxx>
34 #include <pam.hxx>
35 #include <ndtxt.hxx>
36 #include <swtable.hxx>
37 #include <swundo.hxx>
38 #include <sortopt.hxx>
39 #include <docsort.hxx>
40 #include <UndoSort.hxx>
41 #include <UndoRedline.hxx>
42 #include <hints.hxx>
43 #include <tblsel.hxx>
44 #include <cellatr.hxx>
45 #include <redline.hxx>
46 #include <node2lay.hxx>
47 #include <frameformats.hxx>
48 
49 #include <set>
50 #include <utility>
51 
52 using namespace ::com::sun::star::lang;
53 using namespace ::com::sun::star;
54 
55 SwSortOptions*      SwSortElement::pOptions = nullptr;
56 SwDoc*              SwSortElement::pDoc = nullptr;
57 const FlatFndBox*   SwSortElement::pBox = nullptr;
58 CollatorWrapper*    SwSortElement::pSortCollator = nullptr;
59 lang::Locale*       SwSortElement::pLocale = nullptr;
60 std::optional<OUString> SwSortElement::xLastAlgorithm;
61 LocaleDataWrapper*  SwSortElement::pLclData = nullptr;
62 
63 // List of all sorted elements
64 
65 /// Construct a SortElement for the Sort
Init(SwDoc * pD,const SwSortOptions & rOpt,FlatFndBox const * pFltBx)66 void SwSortElement::Init( SwDoc* pD, const SwSortOptions& rOpt,
67                             FlatFndBox const * pFltBx )
68 {
69     OSL_ENSURE( !pDoc && !pOptions && !pBox, "Who forgot to call Finit?" );
70     pDoc = pD;
71     pOptions = new SwSortOptions( rOpt );
72     pBox = pFltBx;
73 
74     LanguageType nLang = rOpt.nLanguage;
75     if ( nLang.anyOf(
76         LANGUAGE_NONE,
77         LANGUAGE_DONTKNOW))
78         nLang = GetAppLanguage();
79     pLocale = new lang::Locale( LanguageTag::convertToLocale( nLang ) );
80 
81     pSortCollator = new CollatorWrapper( ::comphelper::getProcessComponentContext() );
82 }
83 
Finit()84 void SwSortElement::Finit()
85 {
86     delete pOptions;
87     pOptions = nullptr;
88     delete pLocale;
89     pLocale = nullptr;
90     xLastAlgorithm.reset();
91     delete pSortCollator;
92     pSortCollator = nullptr;
93     delete pLclData;
94     pLclData = nullptr;
95     pDoc = nullptr;
96     pBox = nullptr;
97 }
98 
~SwSortElement()99 SwSortElement::~SwSortElement()
100 {
101 }
102 
StrToDouble(const OUString & rStr)103 double SwSortElement::StrToDouble( const OUString& rStr )
104 {
105     if( !pLclData )
106         pLclData = new LocaleDataWrapper( LanguageTag( *pLocale ));
107 
108     rtl_math_ConversionStatus eStatus;
109     sal_Int32 nEnd;
110     double nRet = pLclData->stringToDouble( rStr, true, &eStatus, &nEnd );
111 
112     if( rtl_math_ConversionStatus_Ok != eStatus || nEnd == 0 )
113         nRet = 0.0;
114     return nRet;
115 }
116 
keycompare(const SwSortElement & rCmp,sal_uInt16 nKey) const117 int SwSortElement::keycompare(const SwSortElement& rCmp, sal_uInt16 nKey) const
118 {
119     int nCmp = 0;
120     // The actual comparison
121     const SwSortElement *pOrig, *pCmp;
122 
123     const SwSortKey& rSrtKey = pOptions->aKeys[ nKey ];
124     if( rSrtKey.eSortOrder == SwSortOrder::Ascending )
125     {
126         pOrig = this;
127         pCmp = &rCmp;
128     }
129     else
130     {
131         pOrig = &rCmp;
132         pCmp = this;
133     }
134 
135     if( rSrtKey.bIsNumeric )
136     {
137         double n1 = pOrig->GetValue( nKey );
138         double n2 = pCmp->GetValue( nKey );
139 
140         nCmp = n1 < n2 ? -1 : n1 == n2 ? 0 : 1;
141     }
142     else
143     {
144         if( !xLastAlgorithm || *xLastAlgorithm != rSrtKey.sSortType )
145         {
146             xLastAlgorithm = rSrtKey.sSortType;
147             pSortCollator->loadCollatorAlgorithm( *xLastAlgorithm,
148                     *pLocale,
149                     pOptions->bIgnoreCase ? SW_COLLATOR_IGNORES : 0 );
150         }
151 
152         nCmp = pSortCollator->compareString(
153                     pOrig->GetKey( nKey ), pCmp->GetKey( nKey ));
154     }
155     return nCmp;
156 }
157 
operator <(const SwSortElement & rCmp) const158 bool SwSortElement::operator<(const SwSortElement& rCmp) const
159 {
160     // The actual comparison
161     for(size_t nKey = 0; nKey < pOptions->aKeys.size(); ++nKey)
162     {
163         int nCmp = keycompare(rCmp, nKey);
164 
165         if (nCmp == 0)
166             continue;
167 
168         return nCmp < 0;
169     }
170 
171     return false;
172 }
173 
GetValue(sal_uInt16 nKey) const174 double SwSortElement::GetValue( sal_uInt16 nKey ) const
175 {
176     return StrToDouble( GetKey( nKey ));
177 }
178 
179 /// SortingElement for Text
SwSortTextElement(const SwNodeIndex & rPos)180 SwSortTextElement::SwSortTextElement(const SwNodeIndex& rPos)
181     : nOrg(rPos.GetIndex()), aPos(rPos)
182 {
183 }
184 
GetKey(sal_uInt16 nId) const185 OUString SwSortTextElement::GetKey(sal_uInt16 nId) const
186 {
187     SwTextNode* pTextNd = aPos.GetNode().GetTextNode();
188     if( !pTextNd )
189         return OUString();
190 
191     // for TextNodes
192     const OUString& rStr = pTextNd->GetText();
193 
194     sal_Unicode nDeli = pOptions->cDeli;
195     sal_uInt16 nDCount = pOptions->aKeys[nId].nColumnId, i = 1;
196     sal_Int32 nStart = 0;
197 
198     // Find the delimiter
199     while( nStart != -1 && i < nDCount)
200     {
201         nStart = rStr.indexOf( nDeli, nStart );
202         if( -1 != nStart )
203         {
204             nStart++;
205             i++;
206         }
207     }
208 
209     // Found next delimiter or end of String
210     // and copy
211     sal_Int32 nEnd = rStr.indexOf( nDeli, nStart+1 );
212     if (nEnd == -1)
213         return rStr.copy( nStart );
214     return rStr.copy( nStart, nEnd-nStart );
215 }
216 
217 /// SortingElement for Tables
SwSortBoxElement(sal_uInt16 nRC)218 SwSortBoxElement::SwSortBoxElement( sal_uInt16 nRC )
219     : nRow( nRC )
220 {
221 }
222 
223 /// Get Key for a cell
GetKey(sal_uInt16 nKey) const224 OUString SwSortBoxElement::GetKey(sal_uInt16 nKey) const
225 {
226     const FndBox_* pFndBox;
227     sal_uInt16 nCol = pOptions->aKeys[nKey].nColumnId-1;
228 
229     if( SwSortDirection::Rows == pOptions->eDirection )
230         pFndBox = pBox->GetBox(nCol, nRow);         // Sort rows
231     else
232         pFndBox = pBox->GetBox(nRow, nCol);         // Sort columns
233 
234     // Extract the Text
235     OUStringBuffer aRetStr;
236     if( pFndBox )
237     {   // Get StartNode and skip it
238         const SwTableBox* pMyBox = pFndBox->GetBox();
239         OSL_ENSURE(pMyBox, "No atomic Box");
240 
241         if( pMyBox->GetSttNd() )
242         {
243             // Iterate over all the Box's TextNodes
244             const SwNode *pNd = nullptr, *pEndNd = pMyBox->GetSttNd()->EndOfSectionNode();
245             for( sal_uLong nIdx = pMyBox->GetSttIdx() + 1; pNd != pEndNd; ++nIdx )
246             {
247                 pNd = pDoc->GetNodes()[ nIdx ];
248                 if( pNd->IsTextNode() )
249                     aRetStr.append(pNd->GetTextNode()->GetText());
250             }
251         }
252     }
253     return aRetStr.makeStringAndClear();
254 }
255 
GetValue(sal_uInt16 nKey) const256 double SwSortBoxElement::GetValue( sal_uInt16 nKey ) const
257 {
258     const FndBox_* pFndBox;
259     sal_uInt16 nCol = pOptions->aKeys[nKey].nColumnId-1;
260 
261     if( SwSortDirection::Rows == pOptions->eDirection )
262         pFndBox = pBox->GetBox(nCol, nRow);         // Sort rows
263     else
264         pFndBox = pBox->GetBox(nRow, nCol);         // Sort columns
265 
266     double nVal;
267     if( pFndBox )
268     {
269         const SwFormat *pFormat = pFndBox->GetBox()->GetFrameFormat();
270         if (pDoc->GetNumberFormatter()->IsTextFormat( pFormat->GetTableBoxNumFormat().GetValue()))
271             nVal = SwSortElement::GetValue( nKey );
272         else
273             nVal = pFormat->GetTableBoxValue().GetValue();
274     }
275     else
276         nVal = 0;
277 
278     return nVal;
279 }
280 
281 /// Sort Text in the Document
SortText(const SwPaM & rPaM,const SwSortOptions & rOpt)282 bool SwDoc::SortText(const SwPaM& rPaM, const SwSortOptions& rOpt)
283 {
284     // Check if Frame is in the Text
285     const SwPosition *pStart = rPaM.Start(), *pEnd = rPaM.End();
286 
287     // Set index to the Selection's start
288     for ( const auto *pFormat : *GetSpzFrameFormats() )
289     {
290         SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor();
291         SwPosition const*const pAPos = pAnchor->GetContentAnchor();
292 
293         if (pAPos && (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId()) &&
294             pStart->nNode <= pAPos->nNode && pAPos->nNode <= pEnd->nNode )
295             return false;
296     }
297 
298     // Check if only TextNodes are within the Selection
299     {
300         sal_uLong nStart = pStart->nNode.GetIndex(),
301                         nEnd = pEnd->nNode.GetIndex();
302         while( nStart <= nEnd )
303             // Iterate over a selected range
304             if( !GetNodes()[ nStart++ ]->IsTextNode() )
305                 return false;
306     }
307 
308     bool const bUndo = GetIDocumentUndoRedo().DoesUndo();
309     if( bUndo )
310     {
311         GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
312     }
313 
314     SwPaM* pRedlPam = nullptr;
315     SwUndoRedlineSort* pRedlUndo = nullptr;
316     SwUndoSort* pUndoSort = nullptr;
317 
318     // To-Do - add 'SwExtraRedlineTable' also ?
319     if( getIDocumentRedlineAccess().IsRedlineOn() || (!getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() ))
320     {
321         pRedlPam = new SwPaM( pStart->nNode, pEnd->nNode, -1, 1 );
322         SwContentNode* pCNd = pRedlPam->GetContentNode( false );
323         if( pCNd )
324             pRedlPam->GetMark()->nContent = pCNd->Len();
325 
326         if( getIDocumentRedlineAccess().IsRedlineOn() && !IDocumentRedlineAccess::IsShowOriginal( getIDocumentRedlineAccess().GetRedlineFlags() ) )
327         {
328             if( bUndo )
329             {
330                 pRedlUndo = new SwUndoRedlineSort( *pRedlPam,rOpt );
331                 GetIDocumentUndoRedo().DoUndo(false);
332             }
333             // First copy the range
334             SwNodeIndex aEndIdx( pEnd->nNode, 1 );
335             SwNodeRange aRg( pStart->nNode, aEndIdx );
336             GetNodes().Copy_( aRg, aEndIdx );
337 
338             // range is new from pEnd->nNode+1 to aEndIdx
339             getIDocumentRedlineAccess().DeleteRedline( *pRedlPam, true, RedlineType::Any );
340 
341             pRedlPam->GetMark()->nNode.Assign( pEnd->nNode.GetNode(), 1 );
342             pCNd = pRedlPam->GetContentNode( false );
343             pRedlPam->GetMark()->nContent.Assign( pCNd, 0 );
344 
345             pRedlPam->GetPoint()->nNode.Assign( aEndIdx.GetNode() );
346             pCNd = pRedlPam->GetContentNode();
347             sal_Int32 nCLen = 0;
348             if( !pCNd )
349             {
350                 pCNd = GetNodes()[ aEndIdx.GetIndex()-1 ]->GetContentNode();
351                 if( pCNd )
352                 {
353                     nCLen = pCNd->Len();
354                     pRedlPam->GetPoint()->nNode.Assign( *pCNd );
355                 }
356             }
357             pRedlPam->GetPoint()->nContent.Assign( pCNd, nCLen );
358 
359             if( pRedlUndo )
360                 pRedlUndo->SetValues( rPaM );
361         }
362         else
363         {
364             getIDocumentRedlineAccess().DeleteRedline( *pRedlPam, true, RedlineType::Any );
365             delete pRedlPam;
366             pRedlPam = nullptr;
367         }
368     }
369 
370     SwNodeIndex aStart(pStart->nNode);
371     SwSortElement::Init( this, rOpt );
372     std::multiset<SwSortTextElement> aSortSet;
373     while( aStart <= pEnd->nNode )
374     {
375         // Iterate over a selected range
376         aSortSet.insert(SwSortTextElement(aStart));
377         ++aStart;
378     }
379 
380     // Now comes the tricky part: Move Nodes (and always keep Undo in mind)
381     sal_uLong nBeg = pStart->nNode.GetIndex();
382     SwNodeRange aRg( aStart, aStart );
383 
384     if( bUndo && !pRedlUndo )
385     {
386         pUndoSort = new SwUndoSort(rPaM, rOpt);
387         GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoSort));
388     }
389 
390     GetIDocumentUndoRedo().DoUndo(false);
391 
392     size_t n = 0;
393     for (const auto& rElem : aSortSet)
394     {
395         aStart      = nBeg + n;
396         aRg.aStart  = rElem.aPos.GetIndex();
397         aRg.aEnd    = aRg.aStart.GetIndex() + 1;
398 
399         // Move Nodes
400         getIDocumentContentOperations().MoveNodeRange( aRg, aStart,
401             SwMoveFlags::DEFAULT );
402 
403         // Insert Move in Undo
404         if(pUndoSort)
405         {
406             pUndoSort->Insert(rElem.nOrg, nBeg + n);
407         }
408         ++n;
409     }
410     // Delete all elements from the SortArray
411     aSortSet.clear();
412     SwSortElement::Finit();
413 
414     if( pRedlPam )
415     {
416         if( pRedlUndo )
417         {
418             pRedlUndo->SetSaveRange( *pRedlPam );
419             // UGLY: temp. enable Undo
420             GetIDocumentUndoRedo().DoUndo(true);
421             GetIDocumentUndoRedo().AppendUndo( std::unique_ptr<SwUndo>(pRedlUndo) );
422             GetIDocumentUndoRedo().DoUndo(false);
423         }
424 
425         // nBeg is start of sorted range
426         SwNodeIndex aSttIdx( GetNodes(), nBeg );
427 
428         // the copied range is deleted
429         SwRangeRedline *const pDeleteRedline(
430             new SwRangeRedline( RedlineType::Delete, *pRedlPam ));
431 
432         // pRedlPam points to nodes that may be deleted (hidden) by
433         // AppendRedline, so adjust it beforehand to prevent ASSERT
434         pRedlPam->GetPoint()->nNode = aSttIdx;
435         SwContentNode* pCNd = aSttIdx.GetNode().GetContentNode();
436         pRedlPam->GetPoint()->nContent.Assign( pCNd, 0 );
437 
438         getIDocumentRedlineAccess().AppendRedline(pDeleteRedline, true);
439 
440         // the sorted range is inserted
441         getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, *pRedlPam ), true);
442 
443         if( pRedlUndo )
444         {
445             SwNodeIndex aInsEndIdx( pRedlPam->GetMark()->nNode, -1 );
446             pRedlPam->GetMark()->nNode = aInsEndIdx;
447             SwContentNode *const pPrevNode =
448                 pRedlPam->GetMark()->nNode.GetNode().GetContentNode();
449             pRedlPam->GetMark()->nContent.Assign( pPrevNode, pPrevNode->Len() );
450 
451             pRedlUndo->SetValues( *pRedlPam );
452         }
453 
454         delete pRedlPam;
455         pRedlPam = nullptr;
456     }
457     GetIDocumentUndoRedo().DoUndo( bUndo );
458     if( bUndo )
459     {
460         GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
461     }
462 
463     return true;
464 }
465 
466 /// Sort Table in the Document
SortTable(const SwSelBoxes & rBoxes,const SwSortOptions & rOpt)467 bool SwDoc::SortTable(const SwSelBoxes& rBoxes, const SwSortOptions& rOpt)
468 {
469     // Via SwDoc for Undo!
470     OSL_ENSURE( !rBoxes.empty(), "no valid Box list" );
471     SwTableNode* pTableNd = const_cast<SwTableNode*>(rBoxes[0]->GetSttNd()->FindTableNode());
472     if( !pTableNd )
473         return false;
474 
475     // We begin sorting
476     // Find all Boxes/Lines
477     FndBox_ aFndBox( nullptr, nullptr );
478     {
479         FndPara aPara( rBoxes, &aFndBox );
480         ForEach_FndLineCopyCol( pTableNd->GetTable().GetTabLines(), &aPara );
481     }
482 
483     if(aFndBox.GetLines().empty())
484         return false;
485 
486     if( !getIDocumentRedlineAccess().IsIgnoreRedline() && !getIDocumentRedlineAccess().GetRedlineTable().empty() )
487         getIDocumentRedlineAccess().DeleteRedline( *pTableNd, true, RedlineType::Any );
488 
489     FndLines_t::size_type nStart = 0;
490     if( pTableNd->GetTable().GetRowsToRepeat() > 0 && rOpt.eDirection == SwSortDirection::Rows )
491     {
492         // Uppermost selected Cell
493         FndLines_t& rLines = aFndBox.GetLines();
494 
495         while( nStart < rLines.size() )
496         {
497             // Respect Split Merge nesting,
498             // extract the upper most
499             SwTableLine* pLine = rLines[nStart]->GetLine();
500             while ( pLine->GetUpper() )
501                 pLine = pLine->GetUpper()->GetUpper();
502 
503             if( pTableNd->GetTable().IsHeadline( *pLine ) )
504                 nStart++;
505             else
506                 break;
507         }
508         // Are all selected in the HeaderLine?  -> no Offset
509         if( nStart == rLines.size() )
510             nStart = 0;
511     }
512 
513     // Switch to relative Formulas
514     SwTableFormulaUpdate aMsgHint( &pTableNd->GetTable() );
515     aMsgHint.m_eFlags = TBL_RELBOXNAME;
516     getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint );
517 
518     // Table as a flat array structure
519     FlatFndBox aFlatBox(this, aFndBox);
520 
521     if(!aFlatBox.IsSymmetric())
522         return false;
523 
524     // Delete HTML layout
525     pTableNd->GetTable().SetHTMLTableLayout(std::shared_ptr<SwHTMLTableLayout>());
526 
527     // #i37739# A simple 'MakeFrames' after the node sorting
528     // does not work if the table is inside a frame and has no prev/next.
529     SwNode2LayoutSaveUpperFrames aNode2Layout(*pTableNd);
530 
531     // Delete the Table's Frames
532     pTableNd->DelFrames();
533     // ? TL_CHART2: ?
534 
535     SwUndoSort* pUndoSort = nullptr;
536     if (GetIDocumentUndoRedo().DoesUndo())
537     {
538         pUndoSort = new SwUndoSort( rBoxes[0]->GetSttIdx(),
539                                     rBoxes.back()->GetSttIdx(),
540                                    *pTableNd, rOpt, aFlatBox.HasItemSets() );
541         GetIDocumentUndoRedo().AppendUndo(std::unique_ptr<SwUndo>(pUndoSort));
542     }
543     ::sw::UndoGuard const undoGuard(GetIDocumentUndoRedo());
544 
545     // Insert KeyElements
546     sal_uInt16 nCount = (rOpt.eDirection == SwSortDirection::Rows) ?
547                     aFlatBox.GetRows() : aFlatBox.GetCols();
548 
549     // Sort SortList by Key
550     SwSortElement::Init( this, rOpt, &aFlatBox );
551     std::multiset<SwSortBoxElement> aSortList;
552 
553     // When sorting, do not include the first row if the HeaderLine is repeated
554     for( sal_uInt16 i = o3tl::narrowing<sal_uInt16>(nStart); i < nCount; ++i)
555     {
556         aSortList.insert(SwSortBoxElement(i));
557     }
558 
559     // Move after Sorting
560     SwMovedBoxes aMovedList;
561     sal_uInt16 i = 0;
562     for (const auto& rElem : aSortList)
563     {
564         if(rOpt.eDirection == SwSortDirection::Rows)
565         {
566             MoveRow(this, aFlatBox, rElem.nRow, i+nStart, aMovedList, pUndoSort);
567         }
568         else
569         {
570             MoveCol(this, aFlatBox, rElem.nRow, i+nStart, aMovedList, pUndoSort);
571         }
572         ++i;
573     }
574 
575     // Restore table frames:
576     // #i37739# A simple 'MakeFrames' after the node sorting
577     // does not work if the table is inside a frame and has no prev/next.
578     const sal_uLong nIdx = pTableNd->GetIndex();
579     aNode2Layout.RestoreUpperFrames( GetNodes(), nIdx, nIdx + 1 );
580 
581     // TL_CHART2: need to inform chart of probably changed cell names
582     UpdateCharts( pTableNd->GetTable().GetFrameFormat()->GetName() );
583 
584     // Delete all Elements in the SortArray
585     aSortList.clear();
586     SwSortElement::Finit();
587 
588     getIDocumentState().SetModified();
589     return true;
590 }
591 
592 /// Move a row
MoveRow(SwDoc * pDoc,const FlatFndBox & rBox,sal_uInt16 nS,sal_uInt16 nT,SwMovedBoxes & rMovedList,SwUndoSort * pUD)593 void MoveRow(SwDoc* pDoc, const FlatFndBox& rBox, sal_uInt16 nS, sal_uInt16 nT,
594              SwMovedBoxes& rMovedList, SwUndoSort* pUD)
595 {
596     for( sal_uInt16 i=0; i < rBox.GetCols(); ++i )
597     {   // Get old cell position and remember it
598         const FndBox_* pSource = rBox.GetBox(i, nS);
599 
600         // new cell position
601         const FndBox_* pTarget = rBox.GetBox(i, nT);
602 
603         const SwTableBox* pT = pTarget->GetBox();
604         const SwTableBox* pS = pSource->GetBox();
605 
606         bool bMoved = rMovedList.GetPos(pT) != USHRT_MAX;
607 
608         // and move it
609         MoveCell(pDoc, pS, pT, bMoved, pUD);
610 
611         rMovedList.push_back(pS);
612 
613         if( pS != pT )
614         {
615             SwFrameFormat* pTFormat = pT->GetFrameFormat();
616             const SfxItemSet* pSSet = rBox.GetItemSet( i, nS );
617 
618             if( pSSet ||
619                 SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMAT ) ||
620                 SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMULA ) ||
621                 SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_VALUE ) )
622             {
623                 pTFormat = const_cast<SwTableBox*>(pT)->ClaimFrameFormat();
624                 pTFormat->LockModify();
625                 if( pTFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ) )
626                     pTFormat->ResetFormatAttr( RES_VERT_ORIENT );
627 
628                 if( pSSet )
629                     pTFormat->SetFormatAttr( *pSSet );
630                 pTFormat->UnlockModify();
631             }
632         }
633     }
634 }
635 
636 /// Move a column
MoveCol(SwDoc * pDoc,const FlatFndBox & rBox,sal_uInt16 nS,sal_uInt16 nT,SwMovedBoxes & rMovedList,SwUndoSort * pUD)637 void MoveCol(SwDoc* pDoc, const FlatFndBox& rBox, sal_uInt16 nS, sal_uInt16 nT,
638              SwMovedBoxes& rMovedList, SwUndoSort* pUD)
639 {
640     for(sal_uInt16 i=0; i < rBox.GetRows(); ++i)
641     {   // Get old cell position and remember it
642         const FndBox_* pSource = rBox.GetBox(nS, i);
643 
644         // new cell position
645         const FndBox_* pTarget = rBox.GetBox(nT, i);
646 
647         // and move it
648         const SwTableBox* pT = pTarget->GetBox();
649         const SwTableBox* pS = pSource->GetBox();
650 
651         // and move it
652         bool bMoved = rMovedList.GetPos(pT) != USHRT_MAX;
653         MoveCell(pDoc, pS, pT, bMoved, pUD);
654 
655         rMovedList.push_back(pS);
656 
657         if( pS != pT )
658         {
659             SwFrameFormat* pTFormat = pT->GetFrameFormat();
660             const SfxItemSet* pSSet = rBox.GetItemSet( nS, i );
661 
662             if( pSSet ||
663                 SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMAT ) ||
664                 SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_FORMULA ) ||
665                 SfxItemState::SET == pTFormat->GetItemState( RES_BOXATR_VALUE ) )
666             {
667                 pTFormat = const_cast<SwTableBox*>(pT)->ClaimFrameFormat();
668                 pTFormat->LockModify();
669                 if( pTFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ) )
670                     pTFormat->ResetFormatAttr( RES_VERT_ORIENT );
671 
672                 if( pSSet )
673                     pTFormat->SetFormatAttr( *pSSet );
674                 pTFormat->UnlockModify();
675             }
676         }
677     }
678 }
679 
680 /// Move a single Cell
MoveCell(SwDoc * pDoc,const SwTableBox * pSource,const SwTableBox * pTar,bool bMovedBefore,SwUndoSort * pUD)681 void MoveCell(SwDoc* pDoc, const SwTableBox* pSource, const SwTableBox* pTar,
682               bool bMovedBefore, SwUndoSort* pUD)
683 {
684     OSL_ENSURE(pSource && pTar,"Source or target missing");
685 
686     if(pSource == pTar)
687         return;
688 
689     if(pUD)
690         pUD->Insert( pSource->GetName(), pTar->GetName() );
691 
692     // Set Pam source to the first ContentNode
693     SwNodeRange aRg( *pSource->GetSttNd(), 0, *pSource->GetSttNd() );
694     SwNode* pNd = pDoc->GetNodes().GoNext( &aRg.aStart );
695 
696     // If the Cell (Source) wasn't moved
697     // -> insert an empty Node and move the rest or the Mark
698     // points to the first ContentNode
699     if( pNd->StartOfSectionNode() == pSource->GetSttNd() )
700         pNd = pDoc->GetNodes().MakeTextNode( aRg.aStart,
701                 pDoc->GetDfltTextFormatColl() );
702     aRg.aEnd = *pNd->EndOfSectionNode();
703 
704     // If the Target is empty (there is one empty Node)
705     // -> move and delete it
706     SwNodeIndex aTar( *pTar->GetSttNd() );
707     pNd = pDoc->GetNodes().GoNext( &aTar );     // next ContentNode
708     sal_uLong nCount = pNd->EndOfSectionIndex() - pNd->StartOfSectionIndex();
709 
710     bool bDelFirst = false;
711     if( nCount == 2 )
712     {
713         OSL_ENSURE( pNd->GetContentNode(), "No ContentNode");
714         bDelFirst = !pNd->GetContentNode()->Len() && bMovedBefore;
715     }
716 
717     if(!bDelFirst)
718     {   // We already have Content -> old Content Section Down
719         SwNodeRange aRgTar( aTar.GetNode(), 0, *pNd->EndOfSectionNode() );
720         pDoc->GetNodes().SectionDown( &aRgTar );
721     }
722 
723     // Insert the Source
724     SwNodeIndex aIns( *pTar->GetSttNd()->EndOfSectionNode() );
725     pDoc->getIDocumentContentOperations().MoveNodeRange( aRg, aIns,
726         SwMoveFlags::DEFAULT );
727 
728     // If first Node is empty -> delete it
729     if(bDelFirst)
730         pDoc->GetNodes().Delete( aTar );
731 }
732 
733 /// Generate two-dimensional array of FndBoxes
FlatFndBox(SwDoc * pDocPtr,const FndBox_ & rBoxRef)734 FlatFndBox::FlatFndBox(SwDoc* pDocPtr, const FndBox_& rBoxRef) :
735     m_pDoc(pDocPtr),
736     m_nRow(0),
737     m_nCol(0)
738 { // If the array is symmetric
739     m_bSym = CheckLineSymmetry(rBoxRef);
740     if( !m_bSym )
741         return;
742 
743     // Determine column/row count
744     m_nCols = GetColCount(rBoxRef);
745     m_nRows = GetRowCount(rBoxRef);
746 
747     // Create linear array
748     size_t nCount = static_cast<size_t>(m_nRows) * m_nCols;
749     m_pArr = std::make_unique<FndBox_ const *[]>(nCount);
750     memset(m_pArr.get(), 0, sizeof(const FndBox_*) * nCount);
751 
752     FillFlat( rBoxRef );
753 }
754 
~FlatFndBox()755 FlatFndBox::~FlatFndBox()
756 {
757 }
758 
759 /// All Lines of a Box need to have same number of Boxes
CheckLineSymmetry(const FndBox_ & rBox)760 bool FlatFndBox::CheckLineSymmetry(const FndBox_& rBox)
761 {
762     const FndLines_t &rLines = rBox.GetLines();
763     FndBoxes_t::size_type nBoxes {0};
764 
765     for (FndLines_t::size_type i=0; i < rLines.size(); ++i)
766     {
767         const FndLine_* pLn = rLines[i].get();
768         const FndBoxes_t& rBoxes = pLn->GetBoxes();
769 
770         // Number of Boxes of all Lines is unequal -> no symmetry
771         if( i  && nBoxes != rBoxes.size())
772             return false;
773 
774         nBoxes = rBoxes.size();
775         if( !CheckBoxSymmetry( *pLn ) )
776             return false;
777     }
778     return true;
779 }
780 
781 /// Check Box for symmetry (All Boxes of a Line need to have same number of Lines)
CheckBoxSymmetry(const FndLine_ & rLn)782 bool FlatFndBox::CheckBoxSymmetry(const FndLine_& rLn)
783 {
784     const FndBoxes_t &rBoxes = rLn.GetBoxes();
785     FndLines_t::size_type nLines {0};
786 
787     for (FndBoxes_t::size_type i = 0; i < rBoxes.size(); ++i)
788     {
789         FndBox_ const*const pBox = rBoxes[i].get();
790         const FndLines_t& rLines = pBox->GetLines();
791 
792         // Number of Lines of all Boxes is unequal -> no symmetry
793         if( i && nLines != rLines.size() )
794             return false;
795 
796         nLines = rLines.size();
797         if( nLines && !CheckLineSymmetry( *pBox ) )
798             return false;
799     }
800     return true;
801 }
802 
803 /// Maximum count of Columns (Boxes)
GetColCount(const FndBox_ & rBox)804 sal_uInt16 FlatFndBox::GetColCount(const FndBox_& rBox)
805 {
806     const FndLines_t& rLines = rBox.GetLines();
807     // Iterate over Lines
808     if( rLines.empty() )
809         return 1;
810 
811     sal_uInt16 nSum = 0;
812     for (const auto & pLine : rLines)
813     {
814         // The Boxes of a Line
815         sal_uInt16 nCount = 0;
816         const FndBoxes_t& rBoxes = pLine->GetBoxes();
817         for (const auto &rpB : rBoxes)
818         {   // Iterate recursively over the Lines
819             nCount += rpB->GetLines().empty() ? 1 : GetColCount(*rpB);
820         }
821 
822         if( nSum < nCount )
823             nSum = nCount;
824     }
825     return nSum;
826 }
827 
828 /// Maximum count of Rows (Lines)
GetRowCount(const FndBox_ & rBox)829 sal_uInt16 FlatFndBox::GetRowCount(const FndBox_& rBox)
830 {
831     const FndLines_t& rLines = rBox.GetLines();
832     if( rLines.empty() )
833         return 1;
834 
835     sal_uInt16 nLines = 0;
836     for (const auto & pLine : rLines)
837     {   // The Boxes of a Line
838         const FndBoxes_t& rBoxes = pLine->GetBoxes();
839         sal_uInt16 nLn = 1;
840         for (const auto &rpB : rBoxes)
841         {
842             if (!rpB->GetLines().empty())
843             {   // Iterate recursively over the Lines
844                 nLn = std::max(GetRowCount(*rpB), nLn);
845             }
846         }
847 
848         nLines = nLines + nLn;
849     }
850     return nLines;
851 }
852 
853 /// Create a linear array of atomic FndBoxes
FillFlat(const FndBox_ & rBox,bool bLastBox)854 void FlatFndBox::FillFlat(const FndBox_& rBox, bool bLastBox)
855 {
856     bool bModRow = false;
857     const FndLines_t& rLines = rBox.GetLines();
858 
859     // Iterate over Lines
860     sal_uInt16 nOldRow = m_nRow;
861     for (const auto & pLine : rLines)
862     {
863         // The Boxes of a Line
864         const FndBoxes_t& rBoxes = pLine->GetBoxes();
865         sal_uInt16 nOldCol = m_nCol;
866         for( FndBoxes_t::size_type j = 0; j < rBoxes.size(); ++j )
867         {
868             // Check the Box if it's an atomic one
869             const FndBox_ *const pBox = rBoxes[j].get();
870 
871             if( pBox->GetLines().empty() )
872             {
873                 // save it
874                 sal_uInt16 nOff = m_nRow * m_nCols + m_nCol;
875                 m_pArr[nOff] = pBox;
876 
877                 // Save the Formula/Format/Value values
878                 const SwFrameFormat* pFormat = pBox->GetBox()->GetFrameFormat();
879                 if( SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMAT ) ||
880                     SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_FORMULA ) ||
881                     SfxItemState::SET == pFormat->GetItemState( RES_BOXATR_VALUE ) )
882                 {
883                     auto pSet = std::make_unique<SfxItemSet>(
884                         m_pDoc->GetAttrPool(),
885                         svl::Items<
886                             RES_VERT_ORIENT, RES_VERT_ORIENT,
887                             RES_BOXATR_FORMAT, RES_BOXATR_VALUE>{});
888                     pSet->Put( pFormat->GetAttrSet() );
889                     if( m_ppItemSets.empty() )
890                     {
891                         size_t nCount = static_cast<size_t>(m_nRows) * m_nCols;
892                         m_ppItemSets.resize(nCount);
893                     }
894                     m_ppItemSets[nOff] = std::move(pSet);
895                 }
896 
897                 bModRow = true;
898             }
899             else
900             {
901                 // Iterate recursively over the Lines of a Box
902                 FillFlat( *pBox, ( j+1 == rBoxes.size() ) );
903             }
904             m_nCol++;
905         }
906         if(bModRow)
907             m_nRow++;
908         m_nCol = nOldCol;
909     }
910     if(!bLastBox)
911         m_nRow = nOldRow;
912 }
913 
914 /// Access a specific Cell
GetBox(sal_uInt16 n_Col,sal_uInt16 n_Row) const915 const FndBox_* FlatFndBox::GetBox(sal_uInt16 n_Col, sal_uInt16 n_Row) const
916 {
917     sal_uInt16 nOff = n_Row * m_nCols + n_Col;
918     const FndBox_* pTmp = m_pArr[nOff];
919 
920     OSL_ENSURE(n_Col < m_nCols && n_Row < m_nRows && pTmp, "invalid array access");
921     return pTmp;
922 }
923 
GetItemSet(sal_uInt16 n_Col,sal_uInt16 n_Row) const924 const SfxItemSet* FlatFndBox::GetItemSet(sal_uInt16 n_Col, sal_uInt16 n_Row) const
925 {
926     OSL_ENSURE( m_ppItemSets.empty() || ( n_Col < m_nCols && n_Row < m_nRows), "invalid array access");
927 
928     return !m_ppItemSets.empty() ? m_ppItemSets[unsigned(n_Row * m_nCols) + n_Col].get() : nullptr;
929 }
930 
GetPos(const SwTableBox * pTableBox) const931 sal_uInt16 SwMovedBoxes::GetPos(const SwTableBox* pTableBox) const
932 {
933     std::vector<const SwTableBox*>::const_iterator it = std::find(mBoxes.begin(), mBoxes.end(), pTableBox);
934     return it == mBoxes.end() ? USHRT_MAX : it - mBoxes.begin();
935 }
936 
937 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
938