Files
libreoffice/sw/source/core/text/txttab.cxx
Cédric Bosdonnat fd886cdc6a n#693238: fixed docx import of tabs set after the end margin
Added a new compatibility option to keep the previous behavior, but
changed the default to avoid lines insertion for tabs when there are tab
stops set beyond the end margin
2011-11-02 15:10:24 +01:00

683 lines
25 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright 2000, 2010 Oracle and/or its affiliates.
*
* OpenOffice.org - a multi-platform office productivity suite
*
* This file is part of OpenOffice.org.
*
* OpenOffice.org is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* only, as published by the Free Software Foundation.
*
* OpenOffice.org is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License version 3 for more details
* (a copy is included in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with OpenOffice.org. If not, see
* <http://www.openoffice.org/license.html>
* for a copy of the LGPLv3 License.
*
************************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sw.hxx"
#include "hintids.hxx"
#include <editeng/lrspitem.hxx>
#include <editeng/tstpitem.hxx>
#include <IDocumentSettingAccess.hxx>
#include <frmatr.hxx>
#include <SwPortionHandler.hxx>
#include "viewopt.hxx" // SwViewOptions
#include "portab.hxx"
#include "inftxt.hxx"
#include "itrform2.hxx"
#include "txtfrm.hxx"
#include <numrule.hxx>
// #i89179#
#include <porfld.hxx>
/*************************************************************************
* SwLineInfo::GetTabStop()
*************************************************************************/
//#i24363# tab stops relative to indent
/* Return the first tab stop that is > nSearchPos.
* If the tab stop is outside the print area, we
* return 0 if it is not the first tab stop.*/
const SvxTabStop *SwLineInfo::GetTabStop( const SwTwips nSearchPos,
const SwTwips nRight ) const
{
for( MSHORT i = 0; i < pRuler->Count(); ++i )
{
const SvxTabStop &rTabStop = pRuler->operator[](i);
if( rTabStop.GetTabPos() > SwTwips(nRight) )
return i ? 0 : &rTabStop;
if( rTabStop.GetTabPos() > nSearchPos )
return &rTabStop;
}
return 0;
}
/*************************************************************************
* SwLineInfo::NumberOfTabStops()
*************************************************************************/
sal_uInt16 SwLineInfo::NumberOfTabStops() const
{
return pRuler->Count();
}
/*************************************************************************
* SwTxtFormatter::NewTabPortion()
*************************************************************************/
SwTabPortion *SwTxtFormatter::NewTabPortion( SwTxtFormatInfo &rInf, bool bAuto ) const
{
SwTabPortion *pTabPor = 0;
SwTabPortion *pLastTab = rInf.GetLastTab();
if( pLastTab && ( pLastTab->IsTabCntPortion() || pLastTab->IsTabDecimalPortion() ) )
if( pLastTab->PostFormat( rInf ) )
return 0;
xub_Unicode cFill = 0;
xub_Unicode cDec = 0;
SvxTabAdjust eAdj;
KSHORT nNewTabPos;
bool bAutoTabStop = true;
{
const bool bRTL = pFrm->IsRightToLeft();
// #i24363# tab stops relative to indent
// nTabLeft: The absolute value, the tab stops are relative to: Tabs origin.
//
// #i91133#
const bool bTabsRelativeToIndent =
pFrm->GetTxtNode()->getIDocumentSettingAccess()->get(IDocumentSettingAccess::TABS_RELATIVE_TO_INDENT);
const SwTwips nTabLeft = bRTL
? pFrm->Frm().Right() -
( bTabsRelativeToIndent ? GetTabLeft() : 0 )
: pFrm->Frm().Left() +
( bTabsRelativeToIndent ? GetTabLeft() : 0 );
//
// nLinePos: The absolute position, where we started the line formatting.
//
SwTwips nLinePos = GetLeftMargin();
if ( bRTL )
{
Point aPoint( nLinePos, 0 );
pFrm->SwitchLTRtoRTL( aPoint );
nLinePos = aPoint.X();
}
//
// nTabPos: The current position, relative to the line start.
//
SwTwips nTabPos = rInf.GetLastTab() ? rInf.GetLastTab()->GetTabPos() : 0;
if( nTabPos < rInf.X() )
{
nTabPos = rInf.X();
}
//
// nCurrentAbsPos: The current position in absolute coordinates.
//
const SwTwips nCurrentAbsPos = bRTL ?
nLinePos - nTabPos :
nLinePos + nTabPos;
//Badaa: 2008-04-18 * Support for Classical Mongolian Script (SCMS) joint with Jiayanmin
SwTwips nMyRight;
if ( pFrm->IsVertLR() )
nMyRight = Left();
else
nMyRight = Right();
if ( pFrm->IsVertical() )
{
Point aRightTop( nMyRight, pFrm->Frm().Top() );
pFrm->SwitchHorizontalToVertical( aRightTop );
nMyRight = aRightTop.Y();
}
SwTwips nNextPos;
// #i24363# tab stops relative to indent
// nSearchPos: The current position relative to the tabs origin.
//
const SwTwips nSearchPos = bRTL ?
nTabLeft - nCurrentAbsPos :
nCurrentAbsPos - nTabLeft;
//
// First, we examine the tab stops set at the paragraph style or
// any hard set tab stops:
// Note: If there are no user defined tab stops, there is always a
// default tab stop.
//
const SvxTabStop* pTabStop =
aLineInf.GetTabStop( nSearchPos, nMyRight );
if( pTabStop )
{
cFill = ' ' != pTabStop->GetFill() ? pTabStop->GetFill() : 0;
cDec = pTabStop->GetDecimal();
eAdj = pTabStop->GetAdjustment();
nNextPos = pTabStop->GetTabPos();
if(!bTabsRelativeToIndent && eAdj == SVX_TAB_ADJUST_DEFAULT && nSearchPos < 0)
{
//calculate default tab position of default tabs in negative indent
nNextPos = ( nSearchPos / nNextPos ) * nNextPos;
}
bAutoTabStop = false;
}
else
{
KSHORT nDefTabDist = aLineInf.GetDefTabStop();
if( KSHRT_MAX == nDefTabDist )
{
const SvxTabStopItem& rTab =
(const SvxTabStopItem &)pFrm->GetAttrSet()->
GetPool()->GetDefaultItem( RES_PARATR_TABSTOP );
if( rTab.Count() )
nDefTabDist = (KSHORT)rTab.GetStart()->GetTabPos();
else
nDefTabDist = SVX_TAB_DEFDIST;
aLineInf.SetDefTabStop( nDefTabDist );
}
SwTwips nCount = nSearchPos;
//Minimum tab stop width is 1
if (nDefTabDist <= 0)
nDefTabDist = 1;
nCount /= nDefTabDist;
nNextPos = nCount < 0 || (!nCount && nSearchPos <= 0)? nCount * nDefTabDist :( nCount + 1 ) * nDefTabDist ;
// --> FME 2004-09-21 #117919 Minimum tab stop width is 1 or 51 twips:
const SwTwips nMinimumTabWidth = pFrm->GetTxtNode()->getIDocumentSettingAccess()->get(IDocumentSettingAccess::TAB_COMPAT) ? 0 : 50;
if( ( bRTL && nTabLeft - nNextPos >= nCurrentAbsPos - nMinimumTabWidth ) ||
( !bRTL && nNextPos + nTabLeft <= nCurrentAbsPos + nMinimumTabWidth ) )
{
nNextPos += nDefTabDist;
}
cFill = 0;
eAdj = SVX_TAB_ADJUST_LEFT;
}
// #i115705# - correction and refactoring:
// overrule determined next tab stop position in order to apply
// a tab stop at the left margin under the following conditions:
// - the new tab portion is inside the hanging indent
// - a tab stop at the left margin is allowed
// - the determined next tab stop is a default tab stop position OR
// the determined next tab stop is beyond the left margin
{
long nLeftMarginTabPos = 0;
{
if ( !bTabsRelativeToIndent )
{
if ( bRTL )
{
Point aPoint( Left(), 0 );
pFrm->SwitchLTRtoRTL( aPoint );
nLeftMarginTabPos = pFrm->Frm().Right() - aPoint.X();
}
else
{
nLeftMarginTabPos = Left() - pFrm->Frm().Left();
}
}
if( pCurr->HasForcedLeftMargin() )
{
SwLinePortion* pPor = pCurr->GetPortion();
while( pPor && !pPor->IsFlyPortion() )
{
pPor = pPor->GetPortion();
}
if ( pPor )
{
nLeftMarginTabPos += pPor->Width();
}
}
}
const bool bNewTabPortionInsideHangingIndent =
bRTL ? nCurrentAbsPos > nTabLeft - nLeftMarginTabPos
: nCurrentAbsPos < nTabLeft + nLeftMarginTabPos;
if ( bNewTabPortionInsideHangingIndent )
{
// If the paragraph is not inside a list having a list tab stop following
// the list label or no further tab stop found in such a paragraph or
// the next tab stop position does not equal the list tab stop,
// a tab stop at the left margin can be applied. If this condition is
// not hold, it is overruled by compatibility option TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST.
const bool bTabAtLeftMarginAllowed =
( !aLineInf.IsListTabStopIncluded() ||
!pTabStop ||
nNextPos != aLineInf.GetListTabStopPosition() ) ||
// compatibility option TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST:
pFrm->GetTxtNode()->getIDocumentSettingAccess()->
get(IDocumentSettingAccess::TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST);
if ( bTabAtLeftMarginAllowed )
{
if ( !pTabStop || eAdj == SVX_TAB_ADJUST_DEFAULT ||
( nNextPos > nLeftMarginTabPos ) )
{
eAdj = SVX_TAB_ADJUST_DEFAULT;
cFill = 0;
nNextPos = nLeftMarginTabPos;
}
}
}
}
nNextPos += bRTL ? nLinePos - nTabLeft : nTabLeft - nLinePos;
OSL_ENSURE( nNextPos >= 0, "GetTabStop: Don't go back!" );
nNewTabPos = KSHORT(nNextPos);
}
if ( bAuto )
{
if ( SVX_TAB_ADJUST_DECIMAL == eAdj &&
// #127428#
1 == aLineInf.NumberOfTabStops() )
pTabPor = new SwAutoTabDecimalPortion( nNewTabPos, cDec, cFill );
}
else
{
switch( eAdj )
{
case SVX_TAB_ADJUST_RIGHT :
{
pTabPor = new SwTabRightPortion( nNewTabPos, cFill );
break;
}
case SVX_TAB_ADJUST_CENTER :
{
pTabPor = new SwTabCenterPortion( nNewTabPos, cFill );
break;
}
case SVX_TAB_ADJUST_DECIMAL :
{
pTabPor = new SwTabDecimalPortion( nNewTabPos, cDec, cFill );
break;
}
default:
{
OSL_ENSURE( SVX_TAB_ADJUST_LEFT == eAdj || SVX_TAB_ADJUST_DEFAULT == eAdj,
"+SwTxtFormatter::NewTabPortion: unknown adjustment" );
pTabPor = new SwTabLeftPortion( nNewTabPos, cFill, bAutoTabStop );
break;
}
}
}
// Vorhandensein von Tabulatoren anzeigen ... ist nicht mehr noetig
// pCurr->SetTabulation();
// Aus Sicherheitsgruenden lassen wir uns die Daten errechnen
// pTabPor->Height( pLast->Height() );
// pTabPor->SetAscent( pLast->GetAscent() );
return pTabPor;
}
/*************************************************************************
* SwTabPortion::SwTabPortion()
*************************************************************************/
// Die Basisklasse wird erstmal ohne alles initialisiert.
SwTabPortion::SwTabPortion( const KSHORT nTabPosition, const xub_Unicode cFillChar, const bool bAutoTab )
: SwFixPortion( 0, 0 ), nTabPos(nTabPosition), cFill(cFillChar), bAutoTabStop( bAutoTab )
{
nLineLength = 1;
#if OSL_DEBUG_LEVEL > 1
if( IsFilled() )
{
OSL_ENSURE( ' ' != cFill, "SwTabPortion::CTOR: blanks ?!" );
}
#endif
SetWhichPor( POR_TAB );
}
/*************************************************************************
* virtual SwTabPortion::Format()
*************************************************************************/
sal_Bool SwTabPortion::Format( SwTxtFormatInfo &rInf )
{
SwTabPortion *pLastTab = rInf.GetLastTab();
if( pLastTab == this )
return PostFormat( rInf );
if( pLastTab )
pLastTab->PostFormat( rInf );
return PreFormat( rInf );
}
/*************************************************************************
* virtual SwTabPortion::FormatEOL()
*************************************************************************/
void SwTabPortion::FormatEOL( SwTxtFormatInfo &rInf )
{
if( rInf.GetLastTab() == this && !IsTabLeftPortion() )
PostFormat( rInf );
}
/*************************************************************************
* SwTabPortion::PreFormat()
*************************************************************************/
sal_Bool SwTabPortion::PreFormat( SwTxtFormatInfo &rInf )
{
OSL_ENSURE( rInf.X() <= GetTabPos(), "SwTabPortion::PreFormat: rush hour" );
// Hier lassen wir uns nieder...
Fix( static_cast<sal_uInt16>(rInf.X()) );
const bool bTabCompat = rInf.GetTxtFrm()->GetTxtNode()->getIDocumentSettingAccess()->get(IDocumentSettingAccess::TAB_COMPAT);
const bool bTabOverflow = rInf.GetTxtFrm()->GetTxtNode()->getIDocumentSettingAccess()->get(IDocumentSettingAccess::TAB_OVERFLOW);
// Die Mindestbreite eines Tabs ist immer mindestens ein Blank
// #i37686# In compatibility mode, the minimum width
// should be 1, even for non-left tab stops.
sal_uInt16 nMinimumTabWidth = 1;
if ( !bTabCompat )
{
// #i89179#
// tab portion representing the list tab of a list label gets the
// same font as the corresponding number portion
SAL_WNODEPRECATED_DECLARATIONS_PUSH
std::auto_ptr< SwFontSave > pSave( 0 );
SAL_WNODEPRECATED_DECLARATIONS_POP
if ( GetLen() == 0 &&
rInf.GetLast() && rInf.GetLast()->InNumberGrp() &&
static_cast<SwNumberPortion*>(rInf.GetLast())->HasFont() )
{
const SwFont* pNumberPortionFont =
static_cast<SwNumberPortion*>(rInf.GetLast())->GetFont();
pSave.reset( new SwFontSave( rInf, const_cast<SwFont*>(pNumberPortionFont) ) );
}
XubString aTmp( ' ' );
SwTxtSizeInfo aInf( rInf, aTmp );
nMinimumTabWidth = aInf.GetTxtSize().Width();
}
PrtWidth( nMinimumTabWidth );
// Break tab stop to next line if:
// 1. Minmal width does not fit to line anymore.
// 2. An underflow event was called for the tab portion.
sal_Bool bFull = ( bTabCompat && rInf.IsUnderFlow() ) ||
( rInf.Width() <= rInf.X() + PrtWidth() && rInf.X() <= rInf.Width() ) ;
// #95477# Rotated tab stops get the width of one blank
const sal_uInt16 nDir = rInf.GetFont()->GetOrientation( rInf.GetTxtFrm()->IsVertical() );
if( ! bFull && 0 == nDir )
{
const MSHORT nWhich = GetWhichPor();
switch( nWhich )
{
case POR_TABRIGHT:
case POR_TABDECIMAL:
case POR_TABCENTER:
{
if( POR_TABDECIMAL == nWhich )
rInf.SetTabDecimal(
((SwTabDecimalPortion*)this)->GetTabDecimal());
rInf.SetLastTab( this );
break;
}
case POR_TABLEFT:
{
PrtWidth( static_cast<sal_uInt16>(GetTabPos() - rInf.X()) );
bFull = rInf.Width() <= rInf.X() + PrtWidth();
// In tabulator compatibility mode, we reset the bFull flag
// if the tabulator is at the end of the paragraph and the
// tab stop position is outside the frame:
bool bAtParaEnd = rInf.GetIdx() + GetLen() == rInf.GetTxt().Len();
if ( bFull && bTabCompat &&
( bTabOverflow && ( rInf.IsTabOverflow() || !IsAutoTabStop() ) || bAtParaEnd ) &&
GetTabPos() >= rInf.GetTxtFrm()->Frm().Width() )
{
bFull = sal_False;
if ( bTabOverflow && !IsAutoTabStop() )
rInf.SetTabOverflow( sal_True );
}
break;
}
default: OSL_ENSURE( !this, "SwTabPortion::PreFormat: unknown adjustment" );
}
}
if( bFull )
{
// Wir muessen aufpassen, dass wir nicht endlos schleifen,
// wenn die Breite kleiner ist, als ein Blank ...
if( rInf.GetIdx() == rInf.GetLineStart() &&
// #119175# TabStop should be forced to current
// line if there is a fly reducing the line width:
!rInf.GetFly() )
{
PrtWidth( static_cast<sal_uInt16>(rInf.Width() - rInf.X()) );
SetFixWidth( PrtWidth() );
}
else
{
Height( 0 );
Width( 0 );
SetLen( 0 );
SetAscent( 0 );
SetPortion( NULL ); //?????
}
return sal_True;
}
else
{
// Ein Kunstgriff mit Effekt: Die neuen Tabportions verhalten sich nun
// so, wie FlyFrms, die in der Zeile stehen - inklusive Adjustment !
SetFixWidth( PrtWidth() );
return sal_False;
}
}
/*************************************************************************
* SwTabPortion::PostFormat()
*************************************************************************/
sal_Bool SwTabPortion::PostFormat( SwTxtFormatInfo &rInf )
{
const KSHORT nRight = Min( GetTabPos(), rInf.Width() );
const SwLinePortion *pPor = GetPortion();
KSHORT nPorWidth = 0;
while( pPor )
{
nPorWidth = nPorWidth + pPor->Width();
pPor = pPor->GetPortion();
}
const MSHORT nWhich = GetWhichPor();
OSL_ENSURE( POR_TABLEFT != nWhich, "SwTabPortion::PostFormat: already formatted" );
const bool bTabCompat = rInf.GetTxtFrm()->GetTxtNode()->getIDocumentSettingAccess()->get(IDocumentSettingAccess::TAB_COMPAT);
// #127428# Abandon dec. tab position if line is full
if ( bTabCompat && POR_TABDECIMAL == nWhich )
{
KSHORT nPrePorWidth = static_cast<const SwTabDecimalPortion*>(this)->GetWidthOfPortionsUpToDecimalPosition();
// no value was set => no decimal character was found
if ( USHRT_MAX != nPrePorWidth )
{
if ( nPrePorWidth && nPorWidth - nPrePorWidth > rInf.Width() - nRight )
{
nPrePorWidth += nPorWidth - nPrePorWidth - ( rInf.Width() - nRight );
}
nPorWidth = nPrePorWidth - 1;
}
}
if( POR_TABCENTER == nWhich )
{
// zentrierte Tabs bereiten Probleme:
// Wir muessen den Anteil herausfinden, der noch auf die Zeile passt.
KSHORT nNewWidth = nPorWidth /2;
if( nNewWidth > rInf.Width() - nRight )
nNewWidth = nPorWidth - (rInf.Width() - nRight);
nPorWidth = nNewWidth;
}
const KSHORT nDiffWidth = nRight - Fix();
if( nDiffWidth > nPorWidth )
{
const KSHORT nOldWidth = GetFixWidth();
const KSHORT nAdjDiff = nDiffWidth - nPorWidth;
if( nAdjDiff > GetFixWidth() )
PrtWidth( nAdjDiff );
// Nicht erschrecken: wir muessen rInf weiterschieben.
// Immerhin waren wir als Rechtstab bislang nur ein Blank breit.
// Da wir uns jetzt aufgespannt haben, muss der Differenzbetrag
// auf rInf.X() addiert werden !
rInf.X( rInf.X() + PrtWidth() - nOldWidth );
}
SetFixWidth( PrtWidth() );
// letzte Werte zuruecksetzen
rInf.SetLastTab(0);
if( POR_TABDECIMAL == nWhich )
rInf.SetTabDecimal(0);
return rInf.Width() <= rInf.X();
}
/*************************************************************************
* virtual SwTabPortion::Paint()
*
* Ex: LineIter::DrawTab()
*************************************************************************/
void SwTabPortion::Paint( const SwTxtPaintInfo &rInf ) const
{
#if OSL_DEBUG_LEVEL > 1
// Wir wollen uns die Fixbreite anzeigen
if( rInf.OnWin() && OPTDBG( rInf ) &&
!rInf.GetOpt().IsPagePreview() && \
!rInf.GetOpt().IsReadonly() && \
SwViewOption::IsFieldShadings() )
{
const KSHORT nTmpWidth = PrtWidth();
((SwTabPortion*)this)->PrtWidth( GetFixWidth() );
rInf.DrawViewOpt( *this, POR_TAB );
((SwTabPortion*)this)->PrtWidth( nTmpWidth );
}
#endif
// #i89179#
// tab portion representing the list tab of a list label gets the
// same font as the corresponding number portion
SAL_WNODEPRECATED_DECLARATIONS_PUSH
std::auto_ptr< SwFontSave > pSave( 0 );
SAL_WNODEPRECATED_DECLARATIONS_POP
if ( GetLen() == 0 )
{
const SwLinePortion* pPrevPortion =
const_cast<SwTabPortion*>(this)->FindPrevPortion( rInf.GetParaPortion() );
if ( pPrevPortion &&
pPrevPortion->InNumberGrp() &&
static_cast<const SwNumberPortion*>(pPrevPortion)->HasFont() )
{
const SwFont* pNumberPortionFont =
static_cast<const SwNumberPortion*>(pPrevPortion)->GetFont();
pSave.reset( new SwFontSave( rInf, const_cast<SwFont*>(pNumberPortionFont) ) );
}
}
rInf.DrawBackBrush( *this );
// do we have to repaint a post it portion?
if( rInf.OnWin() && pPortion && !pPortion->Width() )
pPortion->PrePaint( rInf, this );
// Darstellung von Sonderzeichen
if( rInf.OnWin() && rInf.GetOpt().IsTab() )
{
// gefuellte Tabs werden grau hinterlegt.
if( IsFilled() )
rInf.DrawViewOpt( *this, POR_TAB );
else
rInf.DrawTab( *this );
}
// 6842: Tabs sollen auf einmal wieder unterstrichen werden.
if( rInf.GetFont()->IsPaintBlank() )
{
// Tabs mit Fuellung
XubString aTxt( ' ' );
const KSHORT nCharWidth = rInf.GetTxtSize( aTxt ).Width();
// robust:
if( nCharWidth )
{
// 6864: immer mit Kerning, auch auf dem Drucker!
KSHORT nChar = Width() / nCharWidth;
rInf.DrawText( aTxt.Fill( nChar, ' ' ), *this, 0, nChar, sal_True );
}
}
// Ausgabe von Fuellzeichen
if( IsFilled() )
{
// Tabs mit Fuellung
XubString aTxt( cFill );
const KSHORT nCharWidth = rInf.GetTxtSize( aTxt ).Width();
#if OSL_DEBUG_LEVEL > 1
OSL_ENSURE( nCharWidth, "!SwTabPortion::Paint: sophisticated tabchar" );
#endif
// robust:
if( nCharWidth )
{
// 6864: immer mit Kerning, auch auf dem Drucker!
KSHORT nChar = Width() / nCharWidth;
if ( cFill == '_' )
++nChar; // damit keine Luecken entstehen (Bug 13430)
rInf.DrawText( aTxt.Fill( nChar, cFill ), *this, 0, nChar, sal_True );
}
}
}
/*************************************************************************
* virtual SwAutoTabDecimalPortion::Paint()
*************************************************************************/
void SwAutoTabDecimalPortion::Paint( const SwTxtPaintInfo & ) const
{
}
/*************************************************************************
* virtual SwTabPortion::HandlePortion()
*************************************************************************/
void SwTabPortion::HandlePortion( SwPortionHandler& rPH ) const
{
rPH.Text( GetLen(), GetWhichPor() );
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */