1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
30 bool lcl_hasValueDataButNoDates( const ScDocument
& rDocument
, SCCOL nCol
, SCROW nRow
, SCTAB nTab
)
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
);
45 ScChartPositioner::ScChartPositioner( ScDocument
& rDoc
, SCTAB nTab
,
46 SCCOL nStartColP
, SCROW nStartRowP
, SCCOL nEndColP
, SCROW nEndRowP
) :
48 eGlue( ScChartGlue::NA
),
53 bDummyUpperLeft( false )
55 SetRangeList( ScRange( nStartColP
, nStartRowP
, nTab
, nEndColP
, nEndRowP
, nTab
) );
59 ScChartPositioner::ScChartPositioner( ScDocument
& rDoc
, ScRangeListRef xRangeList
) :
60 aRangeListRef(std::move( xRangeList
)),
62 eGlue( ScChartGlue::NA
),
67 bDummyUpperLeft( false )
69 if ( aRangeListRef
.is() )
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
);
95 void ScChartPositioner::GlueState()
97 if ( eGlue
!= ScChartGlue::NA
)
99 bDummyUpperLeft
= false;
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
;
109 eGlue
= ScChartGlue::Cols
; // several tables column by column
110 nStartCol
= pR
->aStart
.Col();
111 nStartRow
= pR
->aStart
.Row();
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()
143 pR
= &(*aRangeListRef
)[i
];
145 SCCOL nC
= nEndCol
- nStartCol
+ 1;
146 assert(nC
> 0 && "coverity 2023.12.2");
149 eGlue
= ScChartGlue::Rows
;
152 SCROW nR
= nEndRow
- nStartRow
+ 1;
155 eGlue
= ScChartGlue::Cols
;
158 sal_uLong nCR
= static_cast<sal_uLong
>(nC
) * nR
;
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
};
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
;
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
207 *p
= CellState::Free
;
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
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
234 *p
= CellState::Free
;
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
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
)
257 if ( bGlueCols
&& bGlueRows
)
258 eGlue
= ScChartGlue::Both
;
259 else if ( bGlueRows
)
260 eGlue
= ScChartGlue::Rows
;
262 eGlue
= ScChartGlue::Cols
;
263 if ( pA
[0] != CellState::Occupied
)
264 bDummyUpperLeft
= true;
268 eGlue
= ScChartGlue::NONE
;
272 void ScChartPositioner::CheckColRowHeaders()
274 SCCOL nCol1
, nCol2
, iCol
;
275 SCROW nRow1
, nRow2
, iRow
;
278 bool bColStrings
= true;
279 bool bRowStrings
= true;
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;
288 for (iCol
=nCol1
; iCol
<=nCol2
&& bColStrings
; iCol
++)
290 if (lcl_hasValueDataButNoDates( rDocument
, iCol
, nRow1
, nTab1
))
293 for (iRow
=nRow1
; iRow
<=nRow2
&& bRowStrings
; iRow
++)
295 if (lcl_hasValueDataButNoDates( rDocument
, nCol1
, iRow
, nTab1
))
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
))
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
))
332 bColHeaders
= bColStrings
;
333 bRowHeaders
= bRowStrings
;
336 const ScChartPositionMap
* ScChartPositioner::GetPositionMap()
339 return pPositionMap
.get();
342 void ScChartPositioner::CreatePositionMap()
344 if ( eGlue
== ScChartGlue::NA
&& pPositionMap
)
346 pPositionMap
.reset();
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;
366 const bool bNoGlue
= (eGlue
== ScChartGlue::NONE
);
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;
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());
410 nColCount
-= nColAdd
;
412 nRowCount
-= nRowAdd
;
414 if ( nColCount
==0 || nRowCount
==0 )
415 { // create an entry without data
416 RowMap
& rCol
= aColMap
[0];
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
;
463 auto pPos1Iter
= rCol1
.begin();
469 for ( ; nRow
< nRowCount
&& pPos1Iter
!= rCol1
.end(); nRow
++ )
471 ppRowHeader
[ nRow
] = std::move(pPos1Iter
->second
);
478 for ( ; nRow
< nRowCount
&& pPos1Iter
!= rCol1
.end(); nRow
++ )
480 if (pPos1Iter
->second
)
481 ppRowHeader
[ nRow
].reset(new ScAddress( *pPos1Iter
->second
));
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() )
502 ppColHeader
[ nCol
] = std::move(pPosIter
->second
); // independent
505 else if ( pPosIter
->second
)
506 ppColHeader
[ nCol
].reset( new ScAddress( *pPosIter
->second
) );
510 for ( ; nRow
< nRowCount
&& pPosIter
!= rCol2
.end(); nRow
++, nIndex
++ )
512 ppData
[ nIndex
] = std::move(pPosIter
->second
);
521 ScChartPositionMap::~ScChartPositionMap()
525 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */