blob: 316be56f50f044543221f06c8c16214c8f979493 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sc.hxx"
// INCLUDE ---------------------------------------------------------------
#include <tools/table.hxx>
#include "chartpos.hxx"
#include "document.hxx"
#include "rechead.hxx"
namespace
{
bool lcl_hasValueDataButNoDates( ScDocument* pDocument, SCCOL nCol, SCROW nRow, SCTAB nTab )
{
bool bReturn = false;
if (pDocument->HasValueData( nCol, nRow, nTab ))
{
//treat dates like text #i25706#
sal_uInt32 nNumberFormat = pDocument->GetNumberFormat( ScAddress( nCol, nRow, nTab ) );
short nType = pDocument->GetFormatTable()->GetType(nNumberFormat);
bool bIsDate = (nType & NUMBERFORMAT_DATE);
bReturn = !bIsDate;
}
return bReturn;
}
}
ScChartPositioner::ScChartPositioner( ScDocument* pDoc, SCTAB nTab,
SCCOL nStartColP, SCROW nStartRowP, SCCOL nEndColP, SCROW nEndRowP) :
pDocument( pDoc ),
pPositionMap( NULL ),
eGlue( SC_CHARTGLUE_NA ),
nStartCol(0),
nStartRow(0),
bColHeaders( sal_False ),
bRowHeaders( sal_False ),
bDummyUpperLeft( sal_False )
{
SetRangeList( ScRange( nStartColP, nStartRowP, nTab, nEndColP, nEndRowP, nTab ) );
CheckColRowHeaders();
}
ScChartPositioner::ScChartPositioner( ScDocument* pDoc, const ScRangeListRef& rRangeList ) :
aRangeListRef( rRangeList ),
pDocument( pDoc ),
pPositionMap( NULL ),
eGlue( SC_CHARTGLUE_NA ),
nStartCol(0),
nStartRow(0),
bColHeaders( sal_False ),
bRowHeaders( sal_False ),
bDummyUpperLeft( sal_False )
{
if ( aRangeListRef.Is() )
CheckColRowHeaders();
}
ScChartPositioner::ScChartPositioner( const ScChartPositioner& rPositioner ) :
aRangeListRef( rPositioner.aRangeListRef ),
pDocument(rPositioner.pDocument),
pPositionMap( NULL ),
eGlue(rPositioner.eGlue),
nStartCol(rPositioner.nStartCol),
nStartRow(rPositioner.nStartRow),
bColHeaders(rPositioner.bColHeaders),
bRowHeaders(rPositioner.bRowHeaders),
bDummyUpperLeft( rPositioner.bDummyUpperLeft )
{
}
ScChartPositioner::~ScChartPositioner()
{
delete pPositionMap;
}
sal_Bool ScChartPositioner::operator==(const ScChartPositioner& rCmp) const
{
return bColHeaders == rCmp.bColHeaders
&& bRowHeaders == rCmp.bRowHeaders
&& *aRangeListRef == *rCmp.aRangeListRef;
}
void ScChartPositioner::SetRangeList( const ScRange& rRange )
{
aRangeListRef = new ScRangeList;
aRangeListRef->Append( rRange );
InvalidateGlue();
}
void ScChartPositioner::GlueState()
{
if ( eGlue != SC_CHARTGLUE_NA )
return;
bDummyUpperLeft = sal_False;
ScRangePtr pR;
if ( aRangeListRef->Count() <= 1 )
{
if ( (pR = aRangeListRef->First())!=NULL )
{
if ( pR->aStart.Tab() == pR->aEnd.Tab() )
eGlue = SC_CHARTGLUE_NONE;
else
eGlue = SC_CHARTGLUE_COLS; // mehrere Tabellen spaltenweise
nStartCol = pR->aStart.Col();
nStartRow = pR->aStart.Row();
}
else
{
InvalidateGlue();
nStartCol = 0;
nStartRow = 0;
}
return;
}
// sal_uLong nOldPos = aRangeListRef->GetCurPos();
pR = aRangeListRef->First();
nStartCol = pR->aStart.Col();
nStartRow = pR->aStart.Row();
SCCOL nMaxCols, nEndCol;
SCROW nMaxRows, nEndRow;
nMaxCols = nEndCol = 0;
nMaxRows = nEndRow = 0;
do
{ // umspannenden Bereich etc. feststellen
SCCOLROW nTmp, n1, n2;
if ( (n1 = pR->aStart.Col()) < nStartCol )
nStartCol = static_cast<SCCOL>(n1);
if ( (n2 = pR->aEnd.Col()) > nEndCol )
nEndCol = static_cast<SCCOL>(n2);
if ( (nTmp = n2 - n1 + 1) > nMaxCols )
nMaxCols = static_cast<SCCOL>(nTmp);
if ( (n1 = pR->aStart.Row()) < nStartRow )
nStartRow = static_cast<SCROW>(n1);
if ( (n2 = pR->aEnd.Row()) > nEndRow )
nEndRow = static_cast<SCROW>(n2);
if ( (nTmp = n2 - n1 + 1) > nMaxRows )
nMaxRows = static_cast<SCROW>(nTmp);
} while ( (pR = aRangeListRef->Next())!=NULL );
SCCOL nC = nEndCol - nStartCol + 1;
if ( nC == 1 )
{
eGlue = SC_CHARTGLUE_ROWS;
return;
}
SCROW nR = nEndRow - nStartRow + 1;
if ( nR == 1 )
{
eGlue = SC_CHARTGLUE_COLS;
return;
}
sal_uLong nCR = (sal_uLong)nC * nR;
//2do:
/*
Erstmal simpel ohne Bitmaskiererei, maximal koennten so 8MB alloziert
werden (256 Cols mal 32000 Rows), das liesse sich mit 2 Bit je Eintrag
auf 2MB reduzieren, andererseits ist es so schneller.
Weitere Platz-Optimierung waere, in dem Array nur die wirklich benutzten
Zeilen/Spalten abzulegen, wuerde aber ein weiteres durchlaufen der
RangeList und indirekten Zugriff auf das Array bedeuten.
*/
const sal_uInt8 nHole = 0;
const sal_uInt8 nOccu = 1;
const sal_uInt8 nFree = 2;
const sal_uInt8 nGlue = 3;
sal_uInt8* p;
sal_uInt8* pA = new sal_uInt8[ nCR ];
memset( pA, 0, nCR * sizeof(sal_uInt8) );
SCCOL nCol, nCol1, nCol2;
SCROW nRow, nRow1, nRow2;
for ( pR = aRangeListRef->First(); pR; pR = aRangeListRef->Next() )
{ // Selektionen 2D als belegt markieren
nCol1 = pR->aStart.Col() - nStartCol;
nCol2 = pR->aEnd.Col() - nStartCol;
nRow1 = pR->aStart.Row() - nStartRow;
nRow2 = pR->aEnd.Row() - nStartRow;
for ( nCol = nCol1; nCol <= nCol2; nCol++ )
{
p = pA + (sal_uLong)nCol * nR + nRow1;
for ( nRow = nRow1; nRow <= nRow2; nRow++, p++ )
*p = nOccu;
}
}
sal_Bool bGlue = sal_True;
sal_Bool bGlueCols = sal_False;
for ( nCol = 0; bGlue && nCol < nC; nCol++ )
{ // Spalten probieren durchzugehen und als frei markieren
p = pA + (sal_uLong)nCol * nR;
for ( nRow = 0; bGlue && nRow < nR; nRow++, p++ )
{
if ( *p == nOccu )
{ // Wenn einer mittendrin liegt ist keine Zusammenfassung
// moeglich. Am Rand koennte ok sein, wenn in dieser Spalte
// in jeder belegten Zeile einer belegt ist.
if ( nRow > 0 && nCol > 0 )
bGlue = sal_False; // nCol==0 kann DummyUpperLeft sein
else
nRow = nR;
}
else
*p = nFree;
}
if ( bGlue && *(p = (pA + ((((sal_uLong)nCol+1) * nR) - 1))) == nFree )
{ // Spalte als komplett frei markieren
*p = nGlue;
bGlueCols = sal_True; // mindestens eine freie Spalte
}
}
sal_Bool bGlueRows = sal_False;
for ( nRow = 0; bGlue && nRow < nR; nRow++ )
{ // Zeilen probieren durchzugehen und als frei markieren
p = pA + nRow;
for ( nCol = 0; bGlue && nCol < nC; nCol++, p+=nR )
{
if ( *p == nOccu )
{
if ( nCol > 0 && nRow > 0 )
bGlue = sal_False; // nRow==0 kann DummyUpperLeft sein
else
nCol = nC;
}
else
*p = nFree;
}
if ( bGlue && *(p = (pA + ((((sal_uLong)nC-1) * nR) + nRow))) == nFree )
{ // Zeile als komplett frei markieren
*p = nGlue;
bGlueRows = sal_True; // mindestens eine freie Zeile
}
}
// n=1: die linke obere Ecke koennte bei Beschriftung automagisch
// hinzugezogen werden
p = pA + 1;
for ( sal_uLong n = 1; bGlue && n < nCR; n++, p++ )
{ // ein unberuehrtes Feld heisst, dass es weder spaltenweise noch
// zeilenweise zu erreichen war, also nichts zusamenzufassen
if ( *p == nHole )
bGlue = sal_False;
}
if ( bGlue )
{
if ( bGlueCols && bGlueRows )
eGlue = SC_CHARTGLUE_BOTH;
else if ( bGlueRows )
eGlue = SC_CHARTGLUE_ROWS;
else
eGlue = SC_CHARTGLUE_COLS;
if ( *pA != nOccu )
bDummyUpperLeft = sal_True;
}
else
{
eGlue = SC_CHARTGLUE_NONE;
}
delete [] pA;
}
void ScChartPositioner::CheckColRowHeaders()
{
SCCOL nCol1, nCol2, iCol;
SCROW nRow1, nRow2, iRow;
SCTAB nTab1, nTab2;
sal_Bool bColStrings = sal_True;
sal_Bool bRowStrings = sal_True;
GlueState();
if ( aRangeListRef->Count() == 1 )
{
aRangeListRef->First()->GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
if ( nCol1 > nCol2 || nRow1 > nRow2 )
bColStrings = bRowStrings = sal_False;
else
{
for (iCol=nCol1; iCol<=nCol2 && bColStrings; iCol++)
{
if (lcl_hasValueDataButNoDates( pDocument, iCol, nRow1, nTab1 ))
bColStrings = sal_False;
}
for (iRow=nRow1; iRow<=nRow2 && bRowStrings; iRow++)
{
if (lcl_hasValueDataButNoDates( pDocument, nCol1, iRow, nTab1 ))
bRowStrings = sal_False;
}
}
}
else
{
sal_Bool bVert = (eGlue == SC_CHARTGLUE_NONE || eGlue == SC_CHARTGLUE_ROWS);
for ( ScRangePtr pR = aRangeListRef->First();
pR && (bColStrings || bRowStrings);
pR = aRangeListRef->Next() )
{
pR->GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
sal_Bool bTopRow = (nRow1 == nStartRow);
if ( bRowStrings && (bVert || nCol1 == nStartCol) )
{ // NONE oder ROWS: RowStrings in jeder Selektion moeglich
// COLS oder BOTH: nur aus der ersten Spalte
if ( nCol1 <= nCol2 )
for (iRow=nRow1; iRow<=nRow2 && bRowStrings; iRow++)
{
if (lcl_hasValueDataButNoDates( pDocument, nCol1, iRow, nTab1 ))
bRowStrings = sal_False;
}
}
if ( bColStrings && bTopRow )
{ // ColStrings nur aus der ersten Zeile
if ( nRow1 <= nRow2 )
for (iCol=nCol1; iCol<=nCol2 && bColStrings; iCol++)
{
if (lcl_hasValueDataButNoDates( pDocument, iCol, nRow1, nTab1 ))
bColStrings = sal_False;
}
}
}
}
bColHeaders = bColStrings;
bRowHeaders = bRowStrings;
}
const ScChartPositionMap* ScChartPositioner::GetPositionMap()
{
CreatePositionMap();
return pPositionMap;
}
void ScChartPositioner::CreatePositionMap()
{
if ( eGlue == SC_CHARTGLUE_NA && pPositionMap )
{
delete pPositionMap;
pPositionMap = NULL;
}
if ( pPositionMap )
return ;
SCSIZE nColAdd = bRowHeaders ? 1 : 0;
SCSIZE nRowAdd = bColHeaders ? 1 : 0;
SCCOL nCol, nCol1, nCol2;
SCROW nRow, nRow1, nRow2;
SCTAB nTab, nTab1, nTab2;
//
// wirkliche Groesse (ohne versteckte Zeilen/Spalten)
//
SCSIZE nColCount = 0;
SCSIZE nRowCount = 0;
GlueState();
sal_Bool bNoGlue = (eGlue == SC_CHARTGLUE_NONE);
Table* pCols = new Table;
Table* pNewRowTable = new Table;
ScAddress* pNewAddress = new ScAddress;
ScRangePtr pR;
Table* pCol;
ScAddress* pPos;
SCROW nNoGlueRow = 0;
for ( pR = aRangeListRef->First(); pR; pR = aRangeListRef->Next() )
{
pR->GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
for ( nTab = nTab1; nTab <= nTab2; nTab++ )
{
// nTab im ColKey, um gleiche Col/Row in anderer Table haben zu koennen
sal_uLong nInsCol = (static_cast<sal_uLong>(nTab) << 16) | (bNoGlue ? 0 :
static_cast<sal_uLong>(nCol1));
for ( nCol = nCol1; nCol <= nCol2; ++nCol, ++nInsCol )
{
if ( bNoGlue || eGlue == SC_CHARTGLUE_ROWS )
{ // meistens gleiche Cols
if ( (pCol = (Table*) pCols->Get( nInsCol ))==NULL )
{
pCols->Insert( nInsCol, pNewRowTable );
pCol = pNewRowTable;
pNewRowTable = new Table;
}
}
else
{ // meistens neue Cols
if ( pCols->Insert( nInsCol, pNewRowTable ) )
{
pCol = pNewRowTable;
pNewRowTable = new Table;
}
else
pCol = (Table*) pCols->Get( nInsCol );
}
// bei anderer Tabelle wurde bereits neuer ColKey erzeugt,
// die Zeilen muessen fuer's Dummy fuellen gleich sein!
sal_uLong nInsRow = (bNoGlue ? nNoGlueRow : nRow1);
for ( nRow = nRow1; nRow <= nRow2; nRow++, nInsRow++ )
{
if ( pCol->Insert( nInsRow, pNewAddress ) )
{
pNewAddress->Set( nCol, nRow, nTab );
pNewAddress = new ScAddress;
}
}
}
}
// bei NoGlue werden zusammengehoerige Tabellen als ColGlue dargestellt
nNoGlueRow += nRow2 - nRow1 + 1;
}
delete pNewAddress;
delete pNewRowTable;
// Anzahl der Daten
nColCount = static_cast< SCSIZE >( pCols->Count());
if ( (pCol = (Table*) pCols->First())!=NULL )
{
if ( bDummyUpperLeft )
pCol->Insert( 0, (void*)0 ); // Dummy fuer Beschriftung
nRowCount = static_cast< SCSIZE >( pCol->Count());
}
else
nRowCount = 0;
if ( nColCount > 0 )
nColCount -= nColAdd;
if ( nRowCount > 0 )
nRowCount -= nRowAdd;
if ( nColCount==0 || nRowCount==0 )
{ // einen Eintrag ohne Daten erzeugen
pR = aRangeListRef->First();
if ( pCols->Count() > 0 )
pCol = (Table*) pCols->First();
else
{
pCol = new Table;
pCols->Insert( 0, pCol );
}
nColCount = 1;
if ( pCol->Count() > 0 )
{ // kann ja eigentlich nicht sein, wenn nColCount==0 || nRowCount==0
pPos = (ScAddress*) pCol->First();
if ( pPos )
{
delete pPos;
pCol->Replace( pCol->GetCurKey(), (void*)0 );
}
}
else
pCol->Insert( 0, (void*)0 );
nRowCount = 1;
nColAdd = 0;
nRowAdd = 0;
}
else
{
if ( bNoGlue )
{ // Luecken mit Dummies fuellen, erste Spalte ist Master
Table* pFirstCol = (Table*) pCols->First();
sal_uLong nCount = pFirstCol->Count();
pFirstCol->First();
for ( sal_uLong n = 0; n < nCount; n++, pFirstCol->Next() )
{
sal_uLong nKey = pFirstCol->GetCurKey();
pCols->First();
while ( (pCol = (Table*) pCols->Next())!=NULL )
pCol->Insert( nKey, (void*)0 ); // keine Daten
}
}
}
pPositionMap = new ScChartPositionMap( static_cast<SCCOL>(nColCount), static_cast<SCROW>(nRowCount),
static_cast<SCCOL>(nColAdd), static_cast<SCROW>(nRowAdd), *pCols );
// Aufraeumen
for ( pCol = (Table*) pCols->First(); pCol; pCol = (Table*) pCols->Next() )
{ //! nur Tables loeschen, nicht die ScAddress*
delete pCol;
}
delete pCols;
}
ScChartPositionMap::ScChartPositionMap( SCCOL nChartCols, SCROW nChartRows,
SCCOL nColAdd, SCROW nRowAdd, Table& rCols ) :
ppData( new ScAddress* [ nChartCols * nChartRows ] ),
ppColHeader( new ScAddress* [ nChartCols ] ),
ppRowHeader( new ScAddress* [ nChartRows ] ),
nCount( (sal_uLong) nChartCols * nChartRows ),
nColCount( nChartCols ),
nRowCount( nChartRows )
{
DBG_ASSERT( nColCount && nRowCount, "ScChartPositionMap without dimension" );
ScAddress* pPos;
SCCOL nCol;
SCROW nRow;
Table* pCol = (Table*) rCols.First();
// Zeilen-Header
pPos = (ScAddress*) pCol->First();
if ( nRowAdd )
pPos = (ScAddress*) pCol->Next();
if ( nColAdd )
{ // eigenstaendig
for ( nRow = 0; nRow < nRowCount; nRow++ )
{
ppRowHeader[ nRow ] = pPos;
pPos = (ScAddress*) pCol->Next();
}
}
else
{ // Kopie
for ( nRow = 0; nRow < nRowCount; nRow++ )
{
ppRowHeader[ nRow ] = ( pPos ? new ScAddress( *pPos ) : NULL );
pPos = (ScAddress*) pCol->Next();
}
}
if ( nColAdd )
pCol = (Table*) rCols.Next();
// Daten spaltenweise und Spalten-Header
sal_uLong nIndex = 0;
for ( nCol = 0; nCol < nColCount; nCol++ )
{
if ( pCol )
{
pPos = (ScAddress*) pCol->First();
if ( nRowAdd )
{
ppColHeader[ nCol ] = pPos; // eigenstaendig
pPos = (ScAddress*) pCol->Next();
}
else
ppColHeader[ nCol ] = ( pPos ? new ScAddress( *pPos ) : NULL );
for ( nRow = 0; nRow < nRowCount; nRow++, nIndex++ )
{
ppData[ nIndex ] = pPos;
pPos = (ScAddress*) pCol->Next();
}
}
else
{
ppColHeader[ nCol ] = NULL;
for ( nRow = 0; nRow < nRowCount; nRow++, nIndex++ )
{
ppData[ nIndex ] = NULL;
}
}
pCol = (Table*) rCols.Next();
}
}
ScChartPositionMap::~ScChartPositionMap()
{
for ( sal_uLong nIndex=0; nIndex < nCount; nIndex++ )
{
delete ppData[nIndex];
}
delete [] ppData;
SCCOL j;
for ( j=0; j < nColCount; j++ )
{
delete ppColHeader[j];
}
delete [] ppColHeader;
SCROW i;
for ( i=0; i < nRowCount; i++ )
{
delete ppRowHeader[i];
}
delete [] ppRowHeader;
}
//UNUSED2009-05 ScRangeListRef ScChartPositionMap::GetColRanges( SCCOL nChartCol ) const
//UNUSED2009-05 {
//UNUSED2009-05 ScRangeListRef xRangeList = new ScRangeList;
//UNUSED2009-05 if ( nChartCol < nColCount )
//UNUSED2009-05 {
//UNUSED2009-05 sal_uLong nStop = GetIndex( nChartCol, nRowCount );
//UNUSED2009-05 for ( sal_uLong nIndex = GetIndex( nChartCol, 0 ); nIndex < nStop; nIndex++ )
//UNUSED2009-05 {
//UNUSED2009-05 if ( ppData[ nIndex ] )
//UNUSED2009-05 xRangeList->Join( *ppData[ nIndex ] );
//UNUSED2009-05 }
//UNUSED2009-05 }
//UNUSED2009-05 return xRangeList;
//UNUSED2009-05 }
//UNUSED2009-05 ScRangeListRef ScChartPositionMap::GetRowRanges( SCROW nChartRow ) const
//UNUSED2009-05 {
//UNUSED2009-05 ScRangeListRef xRangeList = new ScRangeList;
//UNUSED2009-05 if ( nChartRow < nRowCount )
//UNUSED2009-05 {
//UNUSED2009-05 sal_uLong nStop = GetIndex( nColCount, nChartRow );
//UNUSED2009-05 for ( sal_uLong nIndex = GetIndex( 0, nChartRow ); nIndex < nStop;
//UNUSED2009-05 nIndex += nRowCount )
//UNUSED2009-05 {
//UNUSED2009-05 if ( ppData[ nIndex ] )
//UNUSED2009-05 xRangeList->Join( *ppData[ nIndex ] );
//UNUSED2009-05 }
//UNUSED2009-05 }
//UNUSED2009-05 return xRangeList;
//UNUSED2009-05 }