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 <sal/config.h>
21 
22 #include <com/sun/star/lang/NoSupportException.hpp>
23 #include <svx/svdotable.hxx>
24 #include "cellcursor.hxx"
25 #include "tablelayouter.hxx"
26 #include <cell.hxx>
27 #include <svx/svdmodel.hxx>
28 #include <svx/strings.hrc>
29 #include <svx/dialmgr.hxx>
30 #include <tools/debug.hxx>
31 
32 
33 using namespace ::com::sun::star::uno;
34 using namespace ::com::sun::star::lang;
35 using namespace ::com::sun::star::container;
36 using namespace ::com::sun::star::beans;
37 using namespace ::com::sun::star::table;
38 
39 
40 namespace sdr { namespace table {
41 
CellCursor(const TableModelRef & xTable,sal_Int32 nLeft,sal_Int32 nTop,sal_Int32 nRight,sal_Int32 nBottom)42 CellCursor::CellCursor( const TableModelRef & xTable, sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom )
43 : CellCursorBase( xTable, nLeft, nTop, nRight, nBottom )
44 {
45 }
46 
47 
~CellCursor()48 CellCursor::~CellCursor()
49 {
50 }
51 
52 
53 // XCellCursor
54 
55 
getCellByPosition(sal_Int32 nColumn,sal_Int32 nRow)56 Reference< XCell > SAL_CALL CellCursor::getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow )
57 {
58     return CellRange::getCellByPosition( nColumn, nRow );
59 }
60 
61 
getCellRangeByPosition(sal_Int32 nLeft,sal_Int32 nTop,sal_Int32 nRight,sal_Int32 nBottom)62 Reference< XCellRange > SAL_CALL CellCursor::getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom )
63 {
64     return CellRange::getCellRangeByPosition( nLeft, nTop, nRight, nBottom );
65 }
66 
67 
getCellRangeByName(const OUString & aRange)68 Reference< XCellRange > SAL_CALL CellCursor::getCellRangeByName( const OUString& aRange )
69 {
70     return CellRange::getCellRangeByName( aRange );
71 }
72 
73 
74 // XCellCursor
75 
76 
gotoStart()77 void SAL_CALL CellCursor::gotoStart(  )
78 {
79     mnRight = mnLeft;
80     mnBottom = mnTop;
81 }
82 
83 
gotoEnd()84 void SAL_CALL CellCursor::gotoEnd(  )
85 {
86     mnLeft = mnRight;
87     mnTop = mnBottom;
88 }
89 
90 
gotoNext()91 void SAL_CALL CellCursor::gotoNext(  )
92 {
93     if( mxTable.is() )
94     {
95         mnRight++;
96         if( mnRight >= mxTable->getColumnCount() )
97         {
98             // if we past the last column, try skip to the row line
99             mnTop++;
100             if( mnTop >= mxTable->getRowCount() )
101             {
102                 // if we past the last row, do not move cursor at all
103                 mnTop--;
104                 mnRight--;
105             }
106             else
107             {
108                 // restart at the first column on the next row
109                 mnRight = 0;
110             }
111         }
112     }
113 
114     mnLeft = mnRight;
115     mnTop = mnBottom;
116 }
117 
118 
gotoPrevious()119 void SAL_CALL CellCursor::gotoPrevious(  )
120 {
121     if( mxTable.is() )
122     {
123         if( mnLeft > 0 )
124         {
125             --mnLeft;
126         }
127         else if( mnTop > 0 )
128         {
129             --mnTop;
130             mnLeft = mxTable->getColumnCount() - 1;
131         }
132     }
133 
134     mnRight = mnLeft;
135     mnBottom = mnTop;
136 }
137 
138 
gotoOffset(::sal_Int32 nColumnOffset,::sal_Int32 nRowOffset)139 void SAL_CALL CellCursor::gotoOffset( ::sal_Int32 nColumnOffset, ::sal_Int32 nRowOffset )
140 {
141     if( mxTable.is() )
142     {
143         const sal_Int32 nLeft = mnLeft + nColumnOffset;
144         if( (nLeft >= 0) && (nLeft < mxTable->getColumnCount() ) )
145             mnRight = mnLeft = nLeft;
146 
147         const sal_Int32 nTop = mnTop + nRowOffset;
148         if( (nTop >= 0) && (nTop < mxTable->getRowCount()) )
149             mnTop = mnBottom = nTop;
150     }
151 }
152 
153 
154 // XMergeableCellCursor
155 
156 
157 /** returns true and the merged cell positions if a merge is valid or false if a merge is
158     not valid for that range */
GetMergedSelection(CellPos & rStart,CellPos & rEnd)159 bool CellCursor::GetMergedSelection( CellPos& rStart, CellPos& rEnd )
160 {
161     rStart.mnCol = mnLeft; rStart.mnRow = mnTop;
162     rEnd.mnCol = mnRight; rEnd.mnRow = mnBottom;
163 
164     // single cell merge is never valid
165     if( mxTable.is() && ((mnLeft != mnRight) || (mnTop != mnBottom)) ) try
166     {
167         CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( mnLeft, mnTop ).get() ) );
168 
169         // check if first cell is merged
170         if( xCell.is() && xCell->isMerged() )
171             findMergeOrigin( mxTable, mnLeft, mnTop, rStart.mnCol, rStart.mnRow );
172 
173         // check if last cell is merged
174         xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( mnRight, mnBottom ).get() ) );
175         if( xCell.is() )
176         {
177             if( xCell->isMerged() )
178             {
179                 findMergeOrigin( mxTable, mnRight, mnBottom, rEnd.mnCol, rEnd.mnRow );
180                 // merge not possible if selection is only one cell and all its merges
181                 if( rEnd == rStart )
182                     return false;
183                 xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( rEnd.mnCol, rEnd.mnRow ).get() ) );
184             }
185         }
186         if( xCell.is() )
187         {
188             rEnd.mnCol += xCell->getColumnSpan()-1;
189             rEnd.mnRow += xCell->getRowSpan()-1;
190         }
191 
192         // now check if everything is inside the given bounds
193         sal_Int32 nRow, nCol;
194         for( nRow = rStart.mnRow; nRow <= rEnd.mnRow; nRow++ )
195         {
196             for( nCol = rStart.mnCol; nCol <= rEnd.mnCol; nCol++ )
197             {
198                 xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) );
199                 if( !xCell.is() )
200                     continue;
201 
202                 if( xCell->isMerged() )
203                 {
204                     sal_Int32 nOriginCol, nOriginRow;
205                     if( findMergeOrigin( mxTable, nCol, nRow, nOriginCol, nOriginRow ) )
206                     {
207                         if( (nOriginCol < rStart.mnCol) || (nOriginRow < rStart.mnRow) )
208                             return false;
209 
210                         xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( nOriginCol, nOriginRow ).get() ) );
211                         if( xCell.is() )
212                         {
213                             nOriginCol += xCell->getColumnSpan()-1;
214                             nOriginRow += xCell->getRowSpan()-1;
215 
216                             if( (nOriginCol > rEnd.mnCol) || (nOriginRow > rEnd.mnRow) )
217                                 return false;
218                         }
219                     }
220                 }
221                 else if( ((nCol + xCell->getColumnSpan() - 1) > rEnd.mnCol) || ((nRow + xCell->getRowSpan() - 1 ) > rEnd.mnRow) )
222                 {
223                     return false;
224                 }
225             }
226         }
227         return true;
228     }
229     catch( Exception& )
230     {
231         OSL_FAIL("sdr::table::SvmxTableController::GetMergedSelection(), exception caught!");
232     }
233     return false;
234 }
235 
236 
merge()237 void SAL_CALL CellCursor::merge(  )
238 {
239     CellPos aStart, aEnd;
240     if( !GetMergedSelection( aStart, aEnd ) )
241         throw NoSupportException();
242 
243     if( !mxTable.is() || (mxTable->getSdrTableObj() == nullptr) )
244         throw DisposedException();
245 
246     SdrModel& rModel(mxTable->getSdrTableObj()->getSdrModelFromSdrObject());
247     const bool bUndo(mxTable->getSdrTableObj()->IsInserted() && rModel.IsUndoEnabled());
248 
249     if( bUndo )
250         rModel.BegUndo( SvxResId(STR_TABLE_MERGE) );
251 
252     try
253     {
254         mxTable->merge( aStart.mnCol, aStart.mnRow, aEnd.mnCol - aStart.mnCol + 1, aEnd.mnRow - aStart.mnRow + 1 );
255         mxTable->optimize();
256         mxTable->setModified(true);
257     }
258     catch( Exception& )
259     {
260         OSL_FAIL("sdr::table::CellCursor::merge(), exception caught!");
261     }
262 
263     if( bUndo )
264         rModel.EndUndo();
265 
266     rModel.SetChanged();
267 }
268 
269 
split_column(sal_Int32 nCol,sal_Int32 nColumns,std::vector<sal_Int32> & rLeftOvers)270 void CellCursor::split_column( sal_Int32 nCol, sal_Int32 nColumns, std::vector< sal_Int32 >& rLeftOvers )
271 {
272     const sal_Int32 nRowCount = mxTable->getRowCount();
273 
274     sal_Int32 nNewCols = 0, nRow;
275 
276     // first check how many columns we need to add
277     for( nRow = mnTop; nRow <= mnBottom; ++nRow )
278     {
279         CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) );
280         if( xCell.is() && !xCell->isMerged() )
281             nNewCols = std::max( nNewCols, nColumns - xCell->getColumnSpan() + 1 - rLeftOvers[nRow] );
282     }
283 
284     if( nNewCols > 0 )
285     {
286         const OUString sWidth("Width");
287         Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW );
288         Reference< XPropertySet > xRefColumn( xCols->getByIndex( nCol ), UNO_QUERY_THROW );
289         sal_Int32 nWidth = 0;
290         xRefColumn->getPropertyValue( sWidth ) >>= nWidth;
291         const sal_Int32 nNewWidth = nWidth / (nNewCols + 1);
292 
293         // reference column gets new width + rounding errors
294         xRefColumn->setPropertyValue( sWidth, Any( nWidth - (nNewWidth * nNewCols) ) );
295 
296         xCols->insertByIndex( nCol + 1, nNewCols );
297         mnRight += nNewCols;
298 
299         // distribute new width
300         for( sal_Int32 nNewCol = nCol + nNewCols; nNewCol > nCol; --nNewCol )
301         {
302             Reference< XPropertySet > xNewCol( xCols->getByIndex( nNewCol ), UNO_QUERY_THROW );
303             xNewCol->setPropertyValue( sWidth, Any( nNewWidth ) );
304         }
305     }
306 
307     for( nRow = 0; nRow < nRowCount; ++nRow )
308     {
309         CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) );
310         if( !xCell.is() || xCell->isMerged() )
311         {
312             if( nNewCols > 0 )
313             {
314                 // merged cells are ignored, but newly added columns will be added to leftovers
315                 xCell.set( dynamic_cast< Cell* >(mxTable->getCellByPosition( nCol+1, nRow ).get() ) );
316                 if( !xCell.is() || !xCell->isMerged() )
317                     rLeftOvers[nRow] += nNewCols;
318             }
319         }
320         else
321         {
322             sal_Int32 nRowSpan = xCell->getRowSpan() - 1;
323             sal_Int32 nColSpan = xCell->getColumnSpan() - 1;
324 
325             if( (nRow >= mnTop) && (nRow <= mnBottom) )
326             {
327                 sal_Int32 nCellsAvailable = 1 + nColSpan + rLeftOvers[nRow];
328                 if( nColSpan == 0 )
329                     nCellsAvailable += nNewCols;
330 
331                 DBG_ASSERT( nCellsAvailable > nColumns, "sdr::table::CellCursor::split_column(), somethings wrong" );
332 
333                 sal_Int32 nSplitSpan = (nCellsAvailable / (nColumns + 1)) - 1;
334 
335                 sal_Int32 nSplitCol = nCol;
336                 sal_Int32 nSplits = nColumns + 1;
337                 while( nSplits-- )
338                 {
339                     // last split eats rounding cells
340                     if( nSplits == 0 )
341                         nSplitSpan = nCellsAvailable - ((nSplitSpan+1) * nColumns) - 1;
342 
343                     mxTable->merge( nSplitCol, nRow, nSplitSpan + 1, nRowSpan + 1);
344                     if( nSplits > 0 )
345                         nSplitCol += nSplitSpan + 1;
346                 }
347 
348                 do
349                 {
350                     rLeftOvers[nRow++] = 0;
351                 }
352                 while( nRowSpan-- );
353                 --nRow;
354             }
355             else
356             {
357                 // cope with outside cells, merge if needed
358                 if( nColSpan < (rLeftOvers[nRow] + nNewCols) )
359                     mxTable->merge( nCol, nRow, (rLeftOvers[nRow] + nNewCols) + 1, nRowSpan + 1 );
360 
361                 do
362                 {
363                     rLeftOvers[nRow++] = 0; // consumed
364                 }
365                 while( nRowSpan-- );
366                 --nRow;
367             }
368         }
369     }
370 }
371 
372 
split_horizontal(sal_Int32 nColumns)373 void CellCursor::split_horizontal( sal_Int32 nColumns )
374 {
375     const sal_Int32 nRowCount = mxTable->getRowCount();
376 
377     std::vector< sal_Int32 > aLeftOvers( nRowCount );
378 
379     for( sal_Int32 nCol = mnRight; nCol >= mnLeft; --nCol )
380         split_column( nCol, nColumns, aLeftOvers );
381 }
382 
383 
split_row(sal_Int32 nRow,sal_Int32 nRows,std::vector<sal_Int32> & rLeftOvers)384 void CellCursor::split_row( sal_Int32 nRow, sal_Int32 nRows, std::vector< sal_Int32 >& rLeftOvers )
385 {
386     const sal_Int32 nColCount = mxTable->getColumnCount();
387 
388     sal_Int32 nNewRows = 0, nCol;
389 
390     // first check how many columns we need to add
391     for( nCol = mnLeft; nCol <= mnRight; ++nCol )
392     {
393         CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) );
394         if( xCell.is() && !xCell->isMerged() )
395             nNewRows = std::max( nNewRows, nRows - xCell->getRowSpan() + 1 - rLeftOvers[nCol] );
396     }
397 
398     if( nNewRows > 0 )
399     {
400         const OUString sHeight("Height");
401         Reference< XTableRows > xRows( mxTable->getRows(), UNO_SET_THROW );
402         Reference< XPropertySet > xRefRow( xRows->getByIndex( nRow ), UNO_QUERY_THROW );
403         sal_Int32 nHeight = 0;
404         xRefRow->getPropertyValue( sHeight ) >>= nHeight;
405         const sal_Int32 nNewHeight = nHeight / (nNewRows + 1);
406 
407         // reference row gets new height + rounding errors
408         xRefRow->setPropertyValue( sHeight, Any( nHeight - (nNewHeight * nNewRows) ) );
409 
410         xRows->insertByIndex( nRow + 1, nNewRows );
411         mnBottom += nNewRows;
412 
413         // distribute new width
414         for( sal_Int32 nNewRow = nRow + nNewRows; nNewRow > nRow; --nNewRow )
415         {
416             Reference< XPropertySet > xNewRow( xRows->getByIndex( nNewRow ), UNO_QUERY_THROW );
417             xNewRow->setPropertyValue( sHeight, Any( nNewHeight ) );
418         }
419     }
420 
421     for( nCol = 0; nCol < nColCount; ++nCol )
422     {
423         CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) );
424         if( !xCell.is() || xCell->isMerged() )
425         {
426             if( nNewRows )
427             {
428                 // merged cells are ignored, but newly added columns will be added to leftovers
429                 xCell.set( dynamic_cast< Cell* >(mxTable->getCellByPosition( nCol, nRow+1 ).get() ) );
430                 if( !xCell.is() || !xCell->isMerged() )
431                     rLeftOvers[nCol] += nNewRows;
432             }
433         }
434         else
435         {
436             sal_Int32 nRowSpan = xCell->getRowSpan() - 1;
437             sal_Int32 nColSpan = xCell->getColumnSpan() - 1;
438 
439             if( (nCol >= mnLeft) && (nCol <= mnRight) )
440             {
441                 sal_Int32 nCellsAvailable = 1 + nRowSpan + rLeftOvers[nCol];
442                 if( nRowSpan == 0 )
443                     nCellsAvailable += nNewRows;
444 
445                 DBG_ASSERT( nCellsAvailable > nRows, "sdr::table::CellCursor::split_row(), somethings wrong" );
446 
447                 sal_Int32 nSplitSpan = (nCellsAvailable / (nRows + 1)) - 1;
448 
449                 sal_Int32 nSplitRow = nRow;
450                 sal_Int32 nSplits = nRows + 1;
451                 while( nSplits-- )
452                 {
453                     // last split eats rounding cells
454                     if( nSplits == 0 )
455                         nSplitSpan = nCellsAvailable - ((nSplitSpan+1) * nRows) - 1;
456 
457                     mxTable->merge( nCol, nSplitRow, nColSpan + 1, nSplitSpan + 1 );
458                     if( nSplits > 0 )
459                         nSplitRow += nSplitSpan + 1;
460                 }
461 
462                 do
463                 {
464                     rLeftOvers[nCol++] = 0;
465                 }
466                 while( nColSpan-- );
467                 --nCol;
468             }
469             else
470             {
471                 // cope with outside cells, merge if needed
472                 if( nRowSpan < (rLeftOvers[nCol] + nNewRows) )
473                     mxTable->merge( nCol, nRow, nColSpan + 1, (rLeftOvers[nCol] + nNewRows) + 1 );
474 
475                 do
476                 {
477                     rLeftOvers[nCol++] = 0; // consumed
478                 }
479                 while( nColSpan-- );
480                 --nCol;
481             }
482         }
483     }
484 }
485 
486 
split_vertical(sal_Int32 nRows)487 void CellCursor::split_vertical( sal_Int32 nRows )
488 {
489     const sal_Int32 nColCount = mxTable->getColumnCount();
490 
491     std::vector< sal_Int32 > aLeftOvers( nColCount );
492 
493     for( sal_Int32 nRow = mnBottom; nRow >= mnTop; --nRow )
494         split_row( nRow, nRows, aLeftOvers );
495 }
496 
497 
split(sal_Int32 nColumns,sal_Int32 nRows)498 void SAL_CALL CellCursor::split( sal_Int32 nColumns, sal_Int32 nRows )
499 {
500     if( (nColumns < 0) || (nRows < 0) )
501         throw IllegalArgumentException();
502 
503     if( !mxTable.is() || (mxTable->getSdrTableObj() == nullptr) )
504         throw DisposedException();
505 
506     SdrModel& rModel(mxTable->getSdrTableObj()->getSdrModelFromSdrObject());
507     const bool bUndo(mxTable->getSdrTableObj()->IsInserted() && rModel.IsUndoEnabled());
508 
509     if( bUndo )
510         rModel.BegUndo( SvxResId(STR_TABLE_SPLIT) );
511 
512     try
513     {
514         if( nColumns > 0 )
515             split_horizontal( nColumns );
516 
517         if( nRows > 0 )
518             split_vertical( nRows );
519 
520         if( nColumns > 0 ||nRows > 0 )
521             mxTable->setModified(true);
522     }
523     catch( Exception& )
524     {
525         OSL_FAIL("sdr::table::CellCursor::split(), exception caught!");
526         throw NoSupportException();
527     }
528 
529     if( bUndo )
530         rModel.EndUndo();
531 
532     rModel.SetChanged();
533 }
534 
535 
isMergeable()536 sal_Bool SAL_CALL CellCursor::isMergeable(  )
537 {
538     CellPos aStart, aEnd;
539     return GetMergedSelection( aStart, aEnd );
540 }
541 
542 
543 } }
544 
545 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
546