Fix rudimentary Emscripten Qt6 copy -> paste support
[LibreOffice.git] / sc / source / core / tool / chartpos.cxx
blobc3bb069be92c5b944bf5d80c5262d0856fb38b96
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <chartpos.hxx>
21 #include <document.hxx>
22 #include <osl/diagnose.h>
23 #include <svl/numformat.hxx>
25 #include <memory>
26 #include <utility>
28 namespace
30 bool lcl_hasValueDataButNoDates( const ScDocument& rDocument, SCCOL nCol, SCROW nRow, SCTAB nTab )
32 bool bReturn = false;
33 if (rDocument.HasValueData( nCol, nRow, nTab ))
35 //treat dates like text #i25706#
36 sal_uInt32 nNumberFormat = rDocument.GetNumberFormat( ScRange(ScAddress( nCol, nRow, nTab )) );
37 SvNumFormatType nType = rDocument.GetFormatTable()->GetType(nNumberFormat);
38 bool bIsDate(nType & SvNumFormatType::DATE);
39 bReturn = !bIsDate;
41 return bReturn;
45 ScChartPositioner::ScChartPositioner( ScDocument& rDoc, SCTAB nTab,
46 SCCOL nStartColP, SCROW nStartRowP, SCCOL nEndColP, SCROW nEndRowP) :
47 rDocument( rDoc ),
48 eGlue( ScChartGlue::NA ),
49 nStartCol(0),
50 nStartRow(0),
51 bColHeaders( false ),
52 bRowHeaders( false ),
53 bDummyUpperLeft( false )
55 SetRangeList( ScRange( nStartColP, nStartRowP, nTab, nEndColP, nEndRowP, nTab ) );
56 CheckColRowHeaders();
59 ScChartPositioner::ScChartPositioner( ScDocument& rDoc, ScRangeListRef xRangeList ) :
60 aRangeListRef(std::move( xRangeList )),
61 rDocument( rDoc ),
62 eGlue( ScChartGlue::NA ),
63 nStartCol(0),
64 nStartRow(0),
65 bColHeaders( false ),
66 bRowHeaders( false ),
67 bDummyUpperLeft( false )
69 if ( aRangeListRef.is() )
70 CheckColRowHeaders();
73 ScChartPositioner::ScChartPositioner( const ScChartPositioner& rPositioner ) :
74 aRangeListRef( rPositioner.aRangeListRef ),
75 rDocument(rPositioner.rDocument),
76 eGlue(rPositioner.eGlue),
77 nStartCol(rPositioner.nStartCol),
78 nStartRow(rPositioner.nStartRow),
79 bColHeaders(rPositioner.bColHeaders),
80 bRowHeaders(rPositioner.bRowHeaders),
81 bDummyUpperLeft( rPositioner.bDummyUpperLeft )
85 ScChartPositioner::~ScChartPositioner()
89 void ScChartPositioner::SetRangeList( const ScRange& rRange )
91 aRangeListRef = new ScRangeList( rRange );
92 InvalidateGlue();
95 void ScChartPositioner::GlueState()
97 if ( eGlue != ScChartGlue::NA )
98 return;
99 bDummyUpperLeft = false;
100 ScRange* pR;
101 if ( aRangeListRef->size() <= 1 )
103 if ( !aRangeListRef->empty() )
105 pR = &aRangeListRef->front();
106 if ( pR->aStart.Tab() == pR->aEnd.Tab() )
107 eGlue = ScChartGlue::NONE;
108 else
109 eGlue = ScChartGlue::Cols; // several tables column by column
110 nStartCol = pR->aStart.Col();
111 nStartRow = pR->aStart.Row();
113 else
115 InvalidateGlue();
116 nStartCol = 0;
117 nStartRow = 0;
119 return;
122 pR = &aRangeListRef->front();
123 nStartCol = pR->aStart.Col();
124 nStartRow = pR->aStart.Row();
125 SCCOL nMaxCols, nEndCol;
126 SCROW nMaxRows, nEndRow;
127 nMaxCols = nEndCol = 0;
128 nMaxRows = nEndRow = 0;
130 // <= so 1 extra pass after last item
131 for ( size_t i = 1, nRanges = aRangeListRef->size(); i <= nRanges; ++i )
132 { // detect spanning/surrounding area etc.
133 SCCOLROW nTmp, n1, n2;
134 if ( (n1 = pR->aStart.Col()) < nStartCol ) nStartCol = static_cast<SCCOL>(n1 );
135 if ( (n2 = pR->aEnd.Col() ) > nEndCol ) nEndCol = static_cast<SCCOL>(n2 );
136 if ( (nTmp = n2 - n1 + 1 ) > nMaxCols ) nMaxCols = static_cast<SCCOL>(nTmp);
137 if ( (n1 = pR->aStart.Row()) < nStartRow ) nStartRow = static_cast<SCROW>(n1 );
138 if ( (n2 = pR->aEnd.Row() ) > nEndRow ) nEndRow = static_cast<SCROW>(n2 );
139 if ( (nTmp = n2 - n1 + 1 ) > nMaxRows ) nMaxRows = static_cast<SCROW>(nTmp);
141 // in last pass; i = nRanges so don't use at()
142 if ( i < nRanges )
143 pR = &(*aRangeListRef)[i];
145 SCCOL nC = nEndCol - nStartCol + 1;
146 assert(nC > 0 && "coverity 2023.12.2");
147 if ( nC == 1 )
149 eGlue = ScChartGlue::Rows;
150 return;
152 SCROW nR = nEndRow - nStartRow + 1;
153 if ( nR == 1 )
155 eGlue = ScChartGlue::Cols;
156 return;
158 sal_uLong nCR = static_cast<sal_uLong>(nC) * nR;
161 TODO:
162 First do it simple without bit masking. A maximum of 8MB could be allocated
163 this way (256 Cols x 32000 Rows). That could be reduced to 2MB by
164 using 2 Bits per entry, but it is faster this way.
165 Another optimization would be to store only used rows/columns in the array, but
166 would mean another iteration of the RangeList indirect access to the array. */
168 enum class CellState : sal_uInt8 { Hole, Occupied, Free, Glue };
169 CellState* p;
170 std::unique_ptr<CellState[]> pA(new CellState[ nCR ]);
171 memset( pA.get(), 0, nCR * sizeof(CellState) );
173 SCCOL nCol, nCol1, nCol2;
174 SCROW nRow, nRow1, nRow2;
175 for ( size_t i = 0, nRanges = aRangeListRef->size(); i < nRanges; ++i )
176 { // mark selections as used in 2D
177 pR = &(*aRangeListRef)[i];
178 nCol1 = pR->aStart.Col() - nStartCol;
179 nCol2 = pR->aEnd.Col() - nStartCol;
180 nRow1 = pR->aStart.Row() - nStartRow;
181 nRow2 = pR->aEnd.Row() - nStartRow;
182 for ( nCol = nCol1; nCol <= nCol2; nCol++ )
184 p = pA.get() + static_cast<sal_uLong>(nCol) * nR + nRow1;
185 for ( nRow = nRow1; nRow <= nRow2; nRow++, p++ )
186 *p = CellState::Occupied;
189 bool bGlue = true;
191 bool bGlueCols = false;
192 for ( nCol = 0; bGlue && nCol < nC; nCol++ )
193 { // iterate columns and try to mark as unused
194 p = pA.get() + static_cast<sal_uLong>(nCol) * nR;
195 for ( nRow = 0; bGlue && nRow < nR; nRow++, p++ )
197 if ( *p == CellState::Occupied )
198 { // If there's one right in the middle, we can't combine.
199 // If it were at the edge, we could combine, if in this Column
200 // in every set line, one is set.
201 if ( nRow > 0 && nCol > 0 )
202 bGlue = false; // nCol==0 can be DummyUpperLeft
203 else
204 nRow = nR;
206 else
207 *p = CellState::Free;
209 if ( bGlue )
211 p = pA.get() + (((static_cast<sal_uLong>(nCol)+1) * nR) - 1);
212 if (*p == CellState::Free)
213 { // mark column as totally unused
214 *p = CellState::Glue;
215 bGlueCols = true; // one unused column at least
220 bool bGlueRows = false;
221 for ( nRow = 0; bGlue && nRow < nR; nRow++ )
222 { // iterate rows and try to mark as unused
223 p = pA.get() + nRow;
224 for ( nCol = 0; bGlue && nCol < nC; nCol++, p+=nR )
226 if ( *p == CellState::Occupied )
228 if ( nCol > 0 && nRow > 0 )
229 bGlue = false; // nRow==0 can be DummyUpperLeft
230 else
231 nCol = nC;
233 else
234 *p = CellState::Free;
236 if ( bGlue )
238 p = pA.get() + (((static_cast<sal_uLong>(nC)-1) * nR) + nRow);
239 if (*p == CellState::Free )
240 { // mark row as totally unused
241 *p = CellState::Glue;
242 bGlueRows = true; // one unused row at least
247 // If n=1: The upper left corner could be automagically pulled in for labeling
248 p = pA.get() + 1;
249 for ( sal_uLong n = 1; bGlue && n < nCR; n++, p++ )
250 { // An untouched field means we could neither reach it through rows nor columns,
251 // thus we can't combine anything
252 if ( *p == CellState::Hole )
253 bGlue = false;
255 if ( bGlue )
257 if ( bGlueCols && bGlueRows )
258 eGlue = ScChartGlue::Both;
259 else if ( bGlueRows )
260 eGlue = ScChartGlue::Rows;
261 else
262 eGlue = ScChartGlue::Cols;
263 if ( pA[0] != CellState::Occupied )
264 bDummyUpperLeft = true;
266 else
268 eGlue = ScChartGlue::NONE;
272 void ScChartPositioner::CheckColRowHeaders()
274 SCCOL nCol1, nCol2, iCol;
275 SCROW nRow1, nRow2, iRow;
276 SCTAB nTab1, nTab2;
278 bool bColStrings = true;
279 bool bRowStrings = true;
280 GlueState();
281 if ( aRangeListRef->size() == 1 )
283 aRangeListRef->front().GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
284 if ( nCol1 > nCol2 || nRow1 > nRow2 )
285 bColStrings = bRowStrings = false;
286 else
288 for (iCol=nCol1; iCol<=nCol2 && bColStrings; iCol++)
290 if (lcl_hasValueDataButNoDates( rDocument, iCol, nRow1, nTab1 ))
291 bColStrings = false;
293 for (iRow=nRow1; iRow<=nRow2 && bRowStrings; iRow++)
295 if (lcl_hasValueDataButNoDates( rDocument, nCol1, iRow, nTab1 ))
296 bRowStrings = false;
300 else
302 bool bVert = (eGlue == ScChartGlue::NONE || eGlue == ScChartGlue::Rows);
303 for ( size_t i = 0, nRanges = aRangeListRef->size();
304 (i < nRanges) && (bColStrings || bRowStrings);
308 const ScRange & rR = (*aRangeListRef)[i];
309 rR.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
310 bool bTopRow = (nRow1 == nStartRow);
311 if ( bRowStrings && (bVert || nCol1 == nStartCol) )
312 { // NONE or ROWS: RowStrings in every selection possible
313 // COLS or BOTH: only from first column
314 if ( nCol1 <= nCol2 )
315 for (iRow=nRow1; iRow<=nRow2 && bRowStrings; iRow++)
317 if (lcl_hasValueDataButNoDates( rDocument, nCol1, iRow, nTab1 ))
318 bRowStrings = false;
321 if ( bColStrings && bTopRow )
322 { // ColStrings only from first row
323 if ( nRow1 <= nRow2 )
324 for (iCol=nCol1; iCol<=nCol2 && bColStrings; iCol++)
326 if (lcl_hasValueDataButNoDates( rDocument, iCol, nRow1, nTab1 ))
327 bColStrings = false;
332 bColHeaders = bColStrings;
333 bRowHeaders = bRowStrings;
336 const ScChartPositionMap* ScChartPositioner::GetPositionMap()
338 CreatePositionMap();
339 return pPositionMap.get();
342 void ScChartPositioner::CreatePositionMap()
344 if ( eGlue == ScChartGlue::NA && pPositionMap )
346 pPositionMap.reset();
349 if ( pPositionMap )
350 return ;
352 SCSIZE nColAdd = bRowHeaders ? 1 : 0;
353 SCSIZE nRowAdd = bColHeaders ? 1 : 0;
355 SCCOL nCol, nCol1, nCol2;
356 SCROW nRow, nRow1, nRow2;
357 SCTAB nTab, nTab1, nTab2;
359 // real size (without hidden rows/columns)
361 SCSIZE nColCount = 0;
362 SCSIZE nRowCount = 0;
364 GlueState();
366 const bool bNoGlue = (eGlue == ScChartGlue::NONE);
367 ColumnMap aColMap;
368 SCROW nNoGlueRow = 0;
369 for ( size_t i = 0, nRanges = aRangeListRef->size(); i < nRanges; ++i )
371 const ScRange & rR = (*aRangeListRef)[i];
372 rR.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
373 for ( nTab = nTab1; nTab <= nTab2; nTab++ )
375 // nTab in ColKey to allow to have the same col/row in another table
376 sal_uLong nInsCol = (static_cast<sal_uLong>(nTab) << 16) | (bNoGlue ? 0 :
377 static_cast<sal_uLong>(nCol1));
378 for ( nCol = nCol1; nCol <= nCol2; ++nCol, ++nInsCol )
380 RowMap* pCol = &aColMap[nInsCol];
382 // in other table a new ColKey already was created,
383 // the rows must be equal to be filled with Dummy
384 sal_uLong nInsRow = (bNoGlue ? nNoGlueRow : nRow1);
385 for ( nRow = nRow1; nRow <= nRow2; nRow++, nInsRow++ )
387 if ( pCol->find( nInsRow ) == pCol->end() )
389 pCol->emplace( nInsRow, std::make_unique<ScAddress>( nCol, nRow, nTab ) );
394 // For NoGlue: associated tables will be rendered as ColGlue
395 nNoGlueRow += nRow2 - nRow1 + 1;
398 // count of data
399 nColCount = static_cast< SCSIZE >( aColMap.size());
400 if ( !aColMap.empty() )
402 RowMap& rCol = aColMap.begin()->second;
403 if ( bDummyUpperLeft )
404 rCol[ 0 ] = nullptr; // Dummy for labeling
405 nRowCount = static_cast< SCSIZE >( rCol.size());
407 else
408 nRowCount = 0;
409 if ( nColCount > 0 )
410 nColCount -= nColAdd;
411 if ( nRowCount > 0 )
412 nRowCount -= nRowAdd;
414 if ( nColCount==0 || nRowCount==0 )
415 { // create an entry without data
416 RowMap& rCol = aColMap[0];
417 nColCount = 1;
418 rCol[ 0 ] = nullptr;
419 nRowCount = 1;
420 nColAdd = 0;
421 nRowAdd = 0;
423 else
425 if ( bNoGlue )
426 { // fill gaps with Dummies, first column is master
427 RowMap& rFirstCol = aColMap.begin()->second;
429 for ( const auto& it1 : rFirstCol )
431 sal_uLong nKey = it1.first;
432 for (ColumnMap::iterator it2 = ++aColMap.begin(); it2 != aColMap.end(); ++it2 )
433 it2->second.emplace( nKey, nullptr ); // no data
438 pPositionMap.reset( new ScChartPositionMap( static_cast<SCCOL>(nColCount), static_cast<SCROW>(nRowCount),
439 static_cast<SCCOL>(nColAdd), static_cast<SCROW>(nRowAdd), aColMap ) );
442 void ScChartPositioner::InvalidateGlue()
444 eGlue = ScChartGlue::NA;
445 pPositionMap.reset();
448 ScChartPositionMap::ScChartPositionMap( SCCOL nChartCols, SCROW nChartRows,
449 SCCOL nColAdd, SCROW nRowAdd, ColumnMap& rCols ) :
450 ppData( new std::unique_ptr<ScAddress> [ nChartCols * nChartRows ] ),
451 ppColHeader( new std::unique_ptr<ScAddress> [ nChartCols ] ),
452 ppRowHeader( new std::unique_ptr<ScAddress> [ nChartRows ] ),
453 nCount( static_cast<sal_uLong>(nChartCols) * nChartRows ),
454 nColCount( nChartCols ),
455 nRowCount( nChartRows )
457 OSL_ENSURE( nColCount && nRowCount, "ScChartPositionMap without dimension" );
459 ColumnMap::iterator pColIter = rCols.begin();
460 RowMap& rCol1 = pColIter->second;
462 // row header
463 auto pPos1Iter = rCol1.begin();
464 if ( nRowAdd )
465 ++pPos1Iter;
466 if ( nColAdd )
467 { // independent
468 SCROW nRow = 0;
469 for ( ; nRow < nRowCount && pPos1Iter != rCol1.end(); nRow++ )
471 ppRowHeader[ nRow ] = std::move(pPos1Iter->second);
472 ++pPos1Iter;
475 else
476 { // copy
477 SCROW nRow = 0;
478 for ( ; nRow < nRowCount && pPos1Iter != rCol1.end(); nRow++ )
480 if (pPos1Iter->second)
481 ppRowHeader[ nRow ].reset(new ScAddress( *pPos1Iter->second ));
482 ++pPos1Iter;
485 if ( nColAdd )
487 ++pColIter;
490 // data column by column and column-header
491 sal_uLong nIndex = 0;
492 for ( SCCOL nCol = 0; nCol < nColCount; nCol++ )
494 if ( pColIter != rCols.end() )
496 RowMap& rCol2 = pColIter->second;
497 RowMap::iterator pPosIter = rCol2.begin();
498 if ( pPosIter != rCol2.end() )
500 if ( nRowAdd )
502 ppColHeader[ nCol ] = std::move(pPosIter->second); // independent
503 ++pPosIter;
505 else if ( pPosIter->second )
506 ppColHeader[ nCol ].reset( new ScAddress( *pPosIter->second ) );
509 SCROW nRow = 0;
510 for ( ; nRow < nRowCount && pPosIter != rCol2.end(); nRow++, nIndex++ )
512 ppData[ nIndex ] = std::move(pPosIter->second);
513 ++pPosIter;
516 ++pColIter;
521 ScChartPositionMap::~ScChartPositionMap()
525 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */