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