/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * 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 .
*/
sal_Unicode cNext = p[nNum]; // 0 if at the end
sal_Unicode cLast = p[rValue.getLength()-1];
// #i5550# If there are numbers at the beginning and the end, // prefer the one at the beginning only if it's followed by a space. // Otherwise, use the number at the end, to enable things like IP addresses. if ( nNum > nSign && ( cNext == 0 || cNext == ' ' || !CharClass::isAsciiNumeric(std::u16string_view(&cLast, 1)) ) )
{ // number at the beginning
nVal = o3tl::toInt32(rValue.subView( 0, nNum )); // any number with a leading zero sets the minimum number of digits if ( p[nSign] == '0' && pMinDigits && ( nNum - nSign > *pMinDigits ) )
*pMinDigits = nNum - nSign;
rValue = rValue.copy(nNum); return -1;
} else
{
nSign = 0;
sal_Int32 nEnd = nNum = rValue.getLength() - 1; while ( nNum && CharClass::isAsciiNumeric( std::u16string_view(&p[nNum], 1) ) )
nNum--; if ( p[nNum] == '-' || p[nNum] == '+' )
{
nNum--;
nSign = 1;
} if ( nNum < nEnd - nSign )
{ // number at the end
nVal = o3tl::toInt32(rValue.subView( nNum + 1 )); // any number with a leading zero sets the minimum number of digits if ( p[nNum+1+nSign] == '0' && pMinDigits && ( nEnd - nNum - nSign > *pMinDigits ) )
*pMinDigits = nEnd - nNum - nSign;
rValue = rValue.copy(0, nNum + 1); if (nSign) // use the return value = 2 to put back the '+' return 2; else return 1;
}
}
nVal = 0; return 0;
}
// Text object instance will be owned by the cell.
rColumn.SetEditText(nRow, aEngine.CreateTextObject());
}
}
namespace { /* TODO: move this to rtl::math::approxDiff() ? Though the name is funny, the
* approx is expected to be more correct than the raw diff. */ /** Calculate a-b trying to diminish precision errors such as for 0.11-0.12 not return -0.009999999999999995 but -0.01 instead.
*/ double approxDiff( double a, double b )
{ if (a == b) return 0.0; if (a == 0.0) return -b; if (b == 0.0) return a; constdouble c = a - b; constdouble aa = fabs(a); constdouble ab = fabs(b); if (aa < 1e-16 || aa > 1e+16 || ab < 1e-16 || ab > 1e+16) // This is going nowhere, live with the result. return c;
constdouble q = aa < ab ? b / a : a / b; constdouble d = (a * q - b * q) / q; if (d == c) // No differing error, live with the result. return c;
// We now have two subtractions with a similar but not equal error. Obtain // the exponent of the error magnitude and round accordingly. constdouble e = fabs(d - c); constint nExp = static_cast<int>(floor(log10(e))) + 1; // tdf#129606: Limit precision to the 16th significant digit of the least precise argument. // Cf. mnMaxGeneralPrecision in sc/source/core/data/column3.cxx. constint nExpArg = static_cast<int>(floor(log10(std::max(aa, ab)))) - 15; return rtl::math::round(c, -std::max(nExp, nExpArg));
}
double approxTypedDiff( double a, double b, bool bTime, tools::Duration& rDuration )
{ if (bTime)
{
rDuration = tools::Duration(a - b); return rDuration.GetInDays();
} return approxDiff( a, b);
}
}
// Try to analyse the merged cells only if there are no filtered rows in the destination area // Else fallback to the old way to avoid regression. // Filling merged cells into an area with filtered (hidden) rows, is a very complex task // that is not implemented, but not even decided how to do, even excel can't handle that well if (!bHasFiltered)
{ bool bHasOverlappedCells = false; bool bSkipOverlappedCells = true;
SCCOL nColCurr = nCol1;
SCROW nRowCurr = nRow1;
// collect cells that are not empty or not overlapped
rNonOverlappedCellIdx.resize(nCount);
SCSIZE nValueCount = 0; for (SCSIZE i = 0; i < nCount; ++i)
{ const ScPatternAttr* pPattern = GetPattern(nColCurr, nRowCurr); const ScMergeFlagAttr* pMergeFlagItem = nullptr; bool bOverlapped
= pPattern->GetItemSet().GetItemState(ATTR_MERGE_FLAG, false, &pMergeFlagItem) == SfxItemState::SET
&& pMergeFlagItem->IsOverlapped();
if (bOverlapped)
bHasOverlappedCells = true;
if (!bOverlapped || GetCellValue(nColCurr, nRowCurr).getType() != CELLTYPE_NONE)
{
rNonOverlappedCellIdx[nValueCount++] = i; // if there is at least 1 non empty overlapped cell, then no cell should be skipped if (bOverlapped)
bSkipOverlappedCells = false;
}
sal_uInt64 nActFormCnt = 0; for (rOuter = nOStart; rOuter <= nOEnd; rOuter++)
{
sal_uInt64 nMaxFormCnt = 0; // for formulas
// transfer attributes
const ScPatternAttr* pSrcPattern = nullptr; const ScStyleSheet* pStyleSheet = nullptr;
SCCOLROW nAtSrc = nISrcStart;
std::unique_ptr<ScPatternAttr> pNewPattern; bool bGetPattern = true;
rInner = nIStart; while (true) // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
{ if (!ColHidden(nCol) && !RowHidden(nRow))
{ if ( bGetPattern )
{ if (bVertical) // rInner&:=nRow, rOuter&:=nCol
pSrcPattern = GetColumnData(nCol).GetPattern(static_cast<SCROW>(nAtSrc)); else// rInner&:=nCol, rOuter&:=nRow
pSrcPattern = GetColumnData(nAtSrc).GetPattern(static_cast<SCROW>(nRow));
bGetPattern = false;
pStyleSheet = pSrcPattern->GetStyleSheet(); // do transfer ATTR_MERGE / ATTR_MERGE_FLAG // // Note: ATTR_MERGE is an attribute of the top left cell of a merged area // containing the size of the area. ATTR_MERGE_FLAGs are attributes of the // other cells of a merged area, containing the information about also // overlapping, i.e. visibility of their content. // // TODO: extend the similar incomplete selections to a bounding rectangle to // avoid incomplete fill, where not all AUTO_MERGE_FLAGs are synchronized with // the copied ATTR_MERGE, resulting broken grid and visibility during run-time. // // +--+ +--+--+ // | | | | | // +--+--+ +--+--+ // | | -> | | // +--+--+ +--+--+ // | | | | | // +--+ +--+--+ // // TODO: protect incompatible merged cells of the destination area, for example // by skipping the fill operation. // // TODO: by dragging the fill handle select only the multiples of the height // of the originally selected area which is merged vertically to avoid of // incomplete fill. // // +--+ +--+ // |XX| |XX| // +XX+ +XX+ // |XX| -> |XX| // +--+ +--+ // | | | | // +--+ +--+ // | | // +--+ // // Other things stored in ATTR_MERGE_FLAG, like autofilter button, will be // deleted now, but may need to be repaired later, like at ScDocument::Fill. const SfxItemSet& rSet = pSrcPattern->GetItemSet(); if ( rSet.GetItemState(ATTR_MERGE_FLAG, false) == SfxItemState::SET )
{
ScMF nOldValue = pSrcPattern->GetItem(ATTR_MERGE_FLAG).GetValue();
ScMF nOldValueMerge = nOldValue & (ScMF::Hor | ScMF::Ver); // keep only the merge flags if ( nOldValue != nOldValueMerge )
{
pNewPattern.reset(new ScPatternAttr(*pSrcPattern));
SfxItemSet& rNewSet = pNewPattern->GetItemSet(); if ( nOldValueMerge == ScMF::NONE )
rNewSet.ClearItem(ATTR_MERGE_FLAG); else
rNewSet.Put(ScMergeFlagAttr(nOldValueMerge));
} else
pNewPattern.reset();
} else
pNewPattern.reset();
}
if ( bHasFiltered )
DeleteArea(static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow), static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow), InsertDeleteFlags::AUTOFILL);
if ( !ScPatternAttr::areSame(pSrcPattern, aCol[nCol].GetPattern( static_cast<SCROW>(nRow) ) ) )
{ // Transfer template too //TODO: Merge ApplyPattern to AttrArray ?? if ( pStyleSheet )
aCol[nCol].ApplyStyle( static_cast<SCROW>(nRow), pStyleSheet );
// Use ApplyPattern instead of SetPattern to keep old MergeFlags if ( pNewPattern )
aCol[nCol].ApplyPattern( static_cast<SCROW>(nRow), *pNewPattern ); else
aCol[nCol].ApplyPattern( static_cast<SCROW>(nRow), *pSrcPattern );
// Todo: update this function to calculate with merged cell fills, // after FillAnalyse / FillSeries fully handle them. // Now FillAnalyse called as if there are filtered rows, so it will work in the old way.
FillAnalyse(nCol1, nRow1, nCol2, nRow2, eFillCmd, eDateCmd,
nInc, aDurationInc, nMinDigits, pListData, nListIndex, true, bSkipOverlappedCells, aNonOverlappedCellIdx);
if ( pListData ) // user defined list
{
sal_uInt16 nListCount = pListData->GetSubCount(); if ( nListCount )
{
sal_uInt64 nSub = nSrcCount - 1; // nListIndex is from last source entry while ( nIndex < sal::static_int_cast<tools::Long>(nSub) )
nIndex += nListCount;
sal_uInt64 nPos = ( nListIndex + nIndex - nSub ) % nListCount;
aValue = pListData->GetSubStr(sal::static_int_cast<sal_uInt16>(nPos));
}
} elseif ( eFillCmd == FILL_SIMPLE ) // fill with pattern/sample
{
tools::Long nPosIndex = nIndex; while ( nPosIndex < 0 )
nPosIndex += nSrcCount;
sal_uInt64 nPos = nPosIndex % nSrcCount;
SCCOL nSrcX = nCol1;
SCROW nSrcY = nRow1; if ( eFillDir == FILL_TO_TOP || eFillDir == FILL_TO_BOTTOM )
nSrcY = sal::static_int_cast<SCROW>( nSrcY + static_cast<SCROW>(nPos) ); else
nSrcX = sal::static_int_cast<SCCOL>( nSrcX + static_cast<SCCOL>(nPos) );
void ScTable::FillFormulaVertical( const ScFormulaCell& rSrcCell,
SCCOLROW& rInner, SCCOL nCol, SCROW nRow1, SCROW nRow2,
ScProgress* pProgress, sal_uInt64& rProgress )
{ // rInner is the row position when filling vertically. Also, when filling // across hidden regions, it may create multiple dis-jointed spans of // formula cells.
} if (pProgress)
pProgress->SetStateOnPercent( rProgress );
}
namespace
{ // Target value exceeded? inlinebool isOverflow( constdouble& rVal, constdouble& rMax, constdouble& rStep, constdouble& rStartVal, FillCmd eFillCmd )
{ switch (eFillCmd)
{ case FILL_LINEAR: case FILL_DATE: if (rStep >= 0.0) return rVal > rMax; else return rVal < rMax; case FILL_GROWTH: if (rStep > 0.0)
{ if (rStep >= 1.0)
{ // Growing away from zero, including zero growth (1.0). if (rVal >= 0.0) return rVal > rMax; else return rVal < rMax;
} else
{ // Shrinking towards zero. if (rVal >= 0.0) return rVal < rMax; else return rVal > rMax;
}
} elseif (rStep < 0.0)
{ // Alternating positive and negative values. if (rStep <= -1.0)
{ // Growing away from zero, including zero growth (-1.0). if (rVal >= 0.0)
{ if (rMax >= 0.0) return rVal > rMax; else // Regard negative rMax as lower limit, which will // be reached only by a negative rVal. returnfalse;
} else
{ if (rMax <= 0.0) return rVal < rMax; else // Regard positive rMax as upper limit, which will // be reached only by a positive rVal. returnfalse;
}
} else
{ // Shrinking towards zero. if (rVal >= 0.0) return rVal < rMax; else return rVal > rMax;
}
} else// if (rStep == 0.0)
{ // All values become zero. // Corresponds with bEntireArea in FillSeries(). if (rMax > 0.0) return rMax < rStartVal; elseif (rMax < 0.0) return rStartVal < rMax;
} break; default:
assert(!"eFillCmd");
} returnfalse;
}
}
void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd, double nStepValue, const tools::Duration& rDurationStep, double nMaxValue, sal_uInt16 nArgMinDigits, bool bAttribs, ScProgress* pProgress, bool bSkipOverlappedCells, std::vector<sal_Int32>* pNonOverlappedCellIdx )
{ // The term 'inner' here refers to the loop in the filling direction i.e. // when filling vertically, the inner position is the row position whereas // when filling horizontally the column position becomes the inner // position. The term 'outer' refers to the column position when filling // vertically, or the row position when filling horizontally. The fill is // performed once in each 'outer' position e.g. when filling vertically, // we perform the fill once in each column.
constbool bIsFiltered = IsDataFiltered(aFillRange); bool bEntireArea = (!bIsFiltered && eFillCmd == FILL_SIMPLE); if (!bIsFiltered && !bEntireArea && (eFillCmd == FILL_LINEAR || eFillCmd == FILL_GROWTH)
&& (nOEnd - nOStart == 0))
{ // For the usual case of one col/row determine if a numeric series is // at least as long as the area to be filled and does not end earlier, // so we can treat it as entire area for performance reasons at least // in the vertical case. // This is not exact in case of merged cell fills with skipping overlapped parts, but // it is still a good upper estimation.
ScCellValue aSrcCell; if (bVertical)
aSrcCell = GetCellValue(static_cast<SCCOL>(nOStart), static_cast<SCROW>(nISource)); else
aSrcCell = GetCellValue(static_cast<SCCOL>(nISource), static_cast<SCROW>(nOStart)); // Same logic as for the actual series. if (!aSrcCell.isEmpty() && (aSrcCell.getType() == CELLTYPE_VALUE || aSrcCell.getType() == CELLTYPE_FORMULA))
{ double nStartVal; if (aSrcCell.getType() == CELLTYPE_VALUE)
nStartVal = aSrcCell.getDouble(); else
nStartVal = aSrcCell.getFormula()->GetValue(); if (eFillCmd == FILL_LINEAR)
{ if (nStepValue == 0.0)
bEntireArea = (nStartVal <= nMaxValue); // fill with same value elseif (((nMaxValue - nStartVal) / nStepValue) >= nFillCount)
bEntireArea = true;
} elseif (eFillCmd == FILL_GROWTH)
{ if (nStepValue == 1.0)
bEntireArea = (nStartVal <= nMaxValue); // fill with same value elseif (nStepValue == -1.0)
bEntireArea = (fabs(nStartVal) <= fabs(nMaxValue)); // fill with alternating value elseif (nStepValue == 0.0)
bEntireArea = (nStartVal == 0.0
|| (nStartVal < 0.0 && nMaxValue >= 0.0)
|| (nStartVal > 0.0 && nMaxValue <= 0.0)); // fill with 0.0
}
}
} if (bEntireArea)
{
InsertDeleteFlags nDel = (bAttribs ? InsertDeleteFlags::AUTOFILL :
(InsertDeleteFlags::AUTOFILL & InsertDeleteFlags::CONTENTS)); if (bVertical)
DeleteArea(nCol1, static_cast<SCROW>(nIMin), nCol2, static_cast<SCROW>(nIMax), nDel); else
DeleteArea(static_cast<SCCOL>(nIMin), nRow1, static_cast<SCCOL>(nIMax), nRow2, nDel);
}
sal_uInt64 nProgress = 0; if (pProgress)
nProgress = pProgress->GetState();
// Perform the fill once per each 'outer' position i.e. one per column // when filling vertically.
// Source cell value. We need to clone the value since it may be inserted repeatedly.
ScCellValue aSrcCell = GetCellValue(nCol, static_cast<SCROW>(nRow));
// Maybe another source cell need to be searched, if the fill is going through merged cells, // where overlapped parts does not contain any information, so they can be skipped. if (bSkipOverlappedCells)
{ // create a vector to make it easier to decide if a cell need to be filled, or skipped.
aIsNonEmptyCell.resize(nFillerCount, false);
SCCOLROW nFirstValueIdx; if (bPositive)
{
nFirstValueIdx = nISource + (*pNonOverlappedCellIdx)[0]; for (auto i : (*pNonOverlappedCellIdx))
aIsNonEmptyCell[i] = true;
} else
{
nFirstValueIdx = nISource - (nFillerCount - 1 - (*pNonOverlappedCellIdx).back()); for (auto i : (*pNonOverlappedCellIdx))
aIsNonEmptyCell[nFillerCount - 1 - i] = true;
}
//Set the real source cell if (bVertical)
aSrcCell = GetCellValue(nOStart, static_cast<SCROW>(nFirstValueIdx)); else
aSrcCell = GetCellValue(nFirstValueIdx, static_cast<SCROW>(nOStart));
}
if (bAttribs)
{ if (bVertical)
{ // If entire area (not filtered and simple fill) use the faster // method, else hidden cols/rows should be skipped and series // fill needs to determine the end row dynamically. if (bEntireArea)
{
SetPatternAreaCondFormat( nCol, static_cast<SCROW>(nIMin), static_cast<SCROW>(nIMax), *pSrcPattern, rCondFormatIndex);
} elseif (eFillCmd == FILL_SIMPLE)
{
assert(bIsFiltered); for(SCROW nAtRow = static_cast<SCROW>(nIMin); nAtRow <= static_cast<SCROW>(nIMax); ++nAtRow)
{ if(!RowHidden(nAtRow))
{
SetPatternAreaCondFormat( nCol, nAtRow, nAtRow, *pSrcPattern, rCondFormatIndex);
}
}
std::unique_ptr<ScPatternAttr> pPatternAttrs[16]; for (sal_uInt8 i = 0; i < 16; ++i)
{
pPatternAttrs[i].reset(new ScPatternAttr(rDocument.getCellAttributeHelper()));
pData->FillToItemSet(i, pPatternAttrs[i]->GetItemSet(), rDocument);
}
// Important special case: when the whole rows are selected. Then applying autoformat to right // column individually would create all columns. In this case, assume that right column isn't // needed, to allow "to the end of row" format optimization (which doesn't create columns). // Keep left column in this case, because it may be pre-formatted for categories. To enable the // optimization, apply uniform format row by row where possible, not column by column.
//////////////////////////////////////////////////////////////////////////////////////////////// // Top row data indexes: // 0 - left corner style // 1 - odd columns style // 2 - even column style // 3 - right corner style ////////////////////////////////////////////////////////////////////////////////////////////////
SCCOL startOffset = 1; // Left top corner if (pData->HasSameData(0, 1) && pData->HasSameData(0, 2))
startOffset = 0; // Left corner is same as the rest of the row else
AutoFormatArea(nStartCol, nStartRow, nStartCol, nStartRow, *pPatternAttrs[0], nFormatNo);
SCCOL endOffset = 1; // Right top corner: ignore when whole rows selected if (isWholeRows || (pData->HasSameData(3, 1) && pData->HasSameData(3, 2)))
endOffset = 0; // Right corner is same as the rest of the row (most important case) else
AutoFormatArea(nEndCol, nStartRow, nEndCol, nStartRow, *pPatternAttrs[3], nFormatNo);
//////////////////////////////////////////////////////////////////////////////////////////////// // Body data indexes: // 4 - left column odd row style // 8 - left column even row style // 7 - right column odd row style // 11 - right column even row style // 5 - body odd column odd row style // 6 - body even column odd row style // 9 - body odd column even row style // 10 - body even column even row style ////////////////////////////////////////////////////////////////////////////////////////////////
startOffset = 1; // Left column if (pData->HasSameData(4, 5) && pData->HasSameData(4, 6) && pData->HasSameData(8, 9) && pData->HasSameData(8, 10))
startOffset = 0; // Left column is same as the lines of the body else
{ if (pData->HasSameData(4, 8)) // even and odd rows are same
AutoFormatArea(nStartCol, nStartRow + 1, nStartCol, nEndRow - 1, *pPatternAttrs[4], nFormatNo); else
{
sal_uInt16 nIndex = 4; for (SCROW nRow = nStartRow + 1; nRow < nEndRow; nRow++)
{
AutoFormatArea(nStartCol, nRow, nStartCol, nRow, *pPatternAttrs[nIndex], nFormatNo); if (nIndex == 4)
nIndex = 8; else
nIndex = 4;
}
}
}
endOffset = 1; // Right column: ignore when whole rows selected if (isWholeRows || (pData->HasSameData(7, 5) && pData->HasSameData(7, 6) && pData->HasSameData(11, 9) && pData->HasSameData(11, 10)))
endOffset = 0; // Right column is same as the lines of the body (most important case) else
{ if (pData->HasSameData(7, 11)) // even and odd rows are same
AutoFormatArea(nEndCol, nStartRow + 1, nEndCol, nEndRow - 1, *pPatternAttrs[7], nFormatNo); else
{
sal_uInt16 nIndex = 7; for (SCROW nRow = nStartRow + 1; nRow < nEndRow; nRow++)
{
AutoFormatArea(nEndCol, nRow, nEndCol, nRow, *pPatternAttrs[nIndex], nFormatNo); if (nIndex == 7)
nIndex = 11; else
nIndex = 7;
}
}
}
// Body if (pData->HasSameData(5, 6) && pData->HasSameData(9, 10)) // Odd and even columns are same (most important case)
{ if (pData->HasSameData(5, 9)) // Everything is the same
AutoFormatArea(nStartCol + startOffset, nStartRow + 1, nEndCol - endOffset, nEndRow - 1, *pPatternAttrs[5], nFormatNo); else// Odd and even rows differ
{ if (pProgress)
pProgress->SetState(1, nEndRow - nStartRow + 3); // account for elements outside the "Body" block
sal_uInt16 nIndex = 5; for (SCROW nRow = nStartRow + 1; nRow < nEndRow; nRow++)
{
AutoFormatArea(nStartCol + startOffset, nRow, nEndCol - endOffset, nRow, *pPatternAttrs[nIndex], nFormatNo); if (nIndex == 5)
nIndex = 9; else
nIndex = 5; if (pProgress)
pProgress->SetStateOnPercent(nRow - nStartRow + 1);
}
}
} elseif (pData->HasSameData(5, 9) && pData->HasSameData(6, 10)) // odd and even rows are same
{ if (pProgress)
pProgress->SetState(1, nEndCol - nStartCol + 3); // account for elements outside the "Body" block
sal_uInt16 nIndex = 5; for (SCCOL nCol = nStartCol + startOffset; nCol <= nEndCol - endOffset; nCol++)
{
AutoFormatArea(nCol, nStartRow + 1, nCol, nEndRow - 1, *pPatternAttrs[nIndex], nFormatNo); if (nIndex == 5)
nIndex = 6; else
nIndex = 5; if (pProgress)
pProgress->SetStateOnPercent(nCol - nStartCol + 1);
}
} else// Everything is different
{ if (pProgress)
pProgress->SetState(1, nEndCol - nStartCol + 3); // account for elements outside the "Body" block
sal_uInt16 nIndex = 5; for (SCCOL nCol = nStartCol + startOffset; nCol <= nEndCol - endOffset; nCol++)
{ for (SCROW nRow = nStartRow + 1; nRow < nEndRow; nRow++)
{
AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo); if ((nIndex == 5) || (nIndex == 9))
{ if (nIndex == 5)
nIndex = 9; else
nIndex = 5;
} else
{ if (nIndex == 6)
nIndex = 10; else
nIndex = 6;
}
} // for nRow if ((nIndex == 5) || (nIndex == 9))
nIndex = 6; else
nIndex = 5; if (pProgress)
pProgress->SetStateOnPercent(nCol - nStartCol + 1);
} // for nCol
} // if not all equal
//////////////////////////////////////////////////////////////////////////////////////////////// // Bottom row data indexes: // 12 - left corner style // 13 - odd columns style // 14 - even column style // 15 - right corner style ////////////////////////////////////////////////////////////////////////////////////////////////
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.