/**************************************************************
 *
 * 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_sw.hxx"

#include "cmdid.h"
#include "hintids.hxx"
#include <algorithm>
#include <svl/eitem.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/sizeitem.hxx>
#include <svx/pageitem.hxx>
#include <editeng/brshitem.hxx>
#include <editeng/frmdiritem.hxx>
#include <vcl/bitmap.hxx>
#include <vcl/graph.hxx>
#include <tgrditem.hxx>
#include <viewopt.hxx>
#include "colex.hxx"
#include "colmgr.hxx"

// UUUU
#include <svx/unobrushitemhelper.hxx>

/*-----------------------------------------------------------------------
	Description: Uebernahme der aktualisierten Werte aus dem Set
 -----------------------------------------------------------------------*/
void SwPageExample::UpdateExample( const SfxItemSet& rSet )
{
	const SvxPageItem* pPage = 0;
	SfxItemPool* pPool = rSet.GetPool();
	sal_uInt16 nWhich = pPool->GetWhich( SID_ATTR_PAGE );

	if ( rSet.GetItemState( nWhich, sal_False ) == SFX_ITEM_SET )
	{
		// Ausrichtung
		pPage = (const SvxPageItem*)&rSet.Get( nWhich );

		if ( pPage )
			SetUsage( pPage->GetPageUsage() );
	}

	nWhich = pPool->GetWhich( SID_ATTR_PAGE_SIZE );

	if ( rSet.GetItemState( nWhich, sal_False ) == SFX_ITEM_SET )
	{
		// Orientation und Size aus dem PageItem
		const SvxSizeItem& rSize = (const SvxSizeItem&)rSet.Get( nWhich );
		SetSize( rSize.GetSize() );
	}
	nWhich = RES_LR_SPACE;
	if ( rSet.GetItemState( nWhich, sal_False ) == SFX_ITEM_SET )
	{
		// linken und rechten Rand einstellen
		const SvxLRSpaceItem& rLRSpace = (const SvxLRSpaceItem&)rSet.Get( nWhich );

		SetLeft( rLRSpace.GetLeft() );
		SetRight( rLRSpace.GetRight() );
	}
	else
	{
		SetLeft( 0 );
		SetRight( 0 );
	}

	nWhich = RES_UL_SPACE;

	if ( rSet.GetItemState( nWhich, sal_False ) == SFX_ITEM_SET )
	{
		// oberen und unteren Rand einstellen
		const SvxULSpaceItem& rULSpace = (const SvxULSpaceItem&)rSet.Get( nWhich );

		SetTop( rULSpace.GetUpper() );
		SetBottom( rULSpace.GetLower() );
	}
	else
	{
		SetTop( 0 );
		SetBottom( 0 );
	}


	// Kopfzeilen-Attribute auswerten
	const SfxPoolItem* pItem;
	if( SFX_ITEM_SET == rSet.GetItemState( pPool->GetWhich( SID_ATTR_PAGE_HEADERSET),
			sal_False, &pItem ) )
	{
		const SfxItemSet& rHeaderSet = ((SvxSetItem*)pItem)->GetItemSet();
		const SfxBoolItem& rHeaderOn =
			(const SfxBoolItem&)rHeaderSet.Get( pPool->GetWhich( SID_ATTR_PAGE_ON ) );

		if ( rHeaderOn.GetValue() )
		{
			const SvxSizeItem& rSize =
				(const SvxSizeItem&)rHeaderSet.Get(pPool->GetWhich(SID_ATTR_PAGE_SIZE));

			const SvxULSpaceItem& rUL = (const SvxULSpaceItem&)rHeaderSet.Get(
										pPool->GetWhich(SID_ATTR_ULSPACE));
			const SvxLRSpaceItem& rLR = (const SvxLRSpaceItem&)rHeaderSet.Get(
										pPool->GetWhich(SID_ATTR_LRSPACE));

			SetHdHeight( rSize.GetSize().Height() - rUL.GetLower());
			SetHdDist( rUL.GetLower() );
			SetHdLeft( rLR.GetLeft() );
			SetHdRight( rLR.GetRight() );
			SetHeader( sal_True );

			if(SFX_ITEM_SET == rHeaderSet.GetItemState(RES_BACKGROUND))
			{
				// UUUU create FillAttributes from SvxBrushItem //SetHdColor(rItem.GetColor());
				const SvxBrushItem& rItem = static_cast< const SvxBrushItem& >(rHeaderSet.Get(RES_BACKGROUND));
				SfxItemSet aTempSet(*rHeaderSet.GetPool(), XATTR_FILL_FIRST, XATTR_FILL_LAST);

				setSvxBrushItemAsFillAttributesToTargetSet(rItem, aTempSet);
				setHeaderFillAttributes(
					drawinglayer::attribute::SdrAllFillAttributesHelperPtr(
						new drawinglayer::attribute::SdrAllFillAttributesHelper(
							aTempSet)));
			}

			if ( rHeaderSet.GetItemState( RES_BOX ) == SFX_ITEM_SET )
			{
				const SvxBoxItem& rItem =
					(const SvxBoxItem&)rHeaderSet.Get( RES_BOX );
				SetHdBorder( rItem );
			}
		}
		else
			SetHeader( sal_False );
	}

	if( SFX_ITEM_SET == rSet.GetItemState( pPool->GetWhich( SID_ATTR_PAGE_FOOTERSET),
			sal_False, &pItem ) )
	{
		const SfxItemSet& rFooterSet = ((SvxSetItem*)pItem)->GetItemSet();
		const SfxBoolItem& rFooterOn =
			(const SfxBoolItem&)rFooterSet.Get( SID_ATTR_PAGE_ON );

		if ( rFooterOn.GetValue() )
		{
			const SvxSizeItem& rSize =
				(const SvxSizeItem&)rFooterSet.Get( pPool->GetWhich( SID_ATTR_PAGE_SIZE ) );

			const SvxULSpaceItem& rUL = (const SvxULSpaceItem&)rFooterSet.Get(
										pPool->GetWhich( SID_ATTR_ULSPACE ) );
			const SvxLRSpaceItem& rLR = (const SvxLRSpaceItem&)rFooterSet.Get(
										pPool->GetWhich( SID_ATTR_LRSPACE ) );

			SetFtHeight( rSize.GetSize().Height() - rUL.GetUpper());
			SetFtDist( rUL.GetUpper() );
			SetFtLeft( rLR.GetLeft() );
			SetFtRight( rLR.GetRight() );
			SetFooter( sal_True );

			if( rFooterSet.GetItemState( RES_BACKGROUND ) == SFX_ITEM_SET )
			{
				// UUUU create FillAttributes from SvxBrushItem //SetFtColor(rItem.GetColor());
				const SvxBrushItem& rItem = static_cast< const SvxBrushItem& >(rFooterSet.Get(RES_BACKGROUND));
				SfxItemSet aTempSet(*rFooterSet.GetPool(), XATTR_FILL_FIRST, XATTR_FILL_LAST);

				setSvxBrushItemAsFillAttributesToTargetSet(rItem, aTempSet);
				setFooterFillAttributes(
					drawinglayer::attribute::SdrAllFillAttributesHelperPtr(
						new drawinglayer::attribute::SdrAllFillAttributesHelper(
							aTempSet)));
			}

			if( rFooterSet.GetItemState( RES_BOX ) == SFX_ITEM_SET )
			{
				const SvxBoxItem& rItem =
					(const SvxBoxItem&)rFooterSet.Get( RES_BOX );
				SetFtBorder( rItem );
			}
		}
		else
			SetFooter( sal_False );
	}

	if(SFX_ITEM_SET == rSet.GetItemState(RES_BACKGROUND, sal_False, &pItem))
	{
		// UUUU create FillAttributes from SvxBrushItem
		const SvxBrushItem& rItem = static_cast< const SvxBrushItem& >(*pItem);
		SfxItemSet aTempSet(*rSet.GetPool(), XATTR_FILL_FIRST, XATTR_FILL_LAST);

		setSvxBrushItemAsFillAttributesToTargetSet(rItem, aTempSet);
		setPageFillAttributes(
			drawinglayer::attribute::SdrAllFillAttributesHelperPtr(
				new drawinglayer::attribute::SdrAllFillAttributesHelper(
					aTempSet)));
	}

	Invalidate();
}
/*-----------------------------------------------------------------------
	Description:
 -----------------------------------------------------------------------*/


void SwColExample::DrawPage( const Point& rOrg,
							const sal_Bool bSecond,
							const sal_Bool bEnabled )
{
	SwPageExample::DrawPage( rOrg, bSecond, bEnabled );
	sal_uInt16 nColumnCount;
	if( pColMgr && 0 != (nColumnCount = pColMgr->GetCount()))
	{
		long nL = GetLeft();
		long nR = GetRight();

		if ( GetUsage() == SVX_PAGE_MIRROR && !bSecond )
		{
			// fuer gespiegelt drehen
			nL = GetRight();
			nR = GetLeft();
		}

		SetFillColor( Color( COL_LIGHTGRAY ) );
		Rectangle aRect;
		aRect.Right() = rOrg.X() + GetSize().Width() - nR;
		aRect.Left()  = rOrg.X() + nL;
		aRect.Top()   = rOrg.Y() + GetTop()
						+ GetHdHeight() + GetHdDist();
		aRect.Bottom()= rOrg.Y() + GetSize().Height() - GetBottom()
						- GetFtHeight() - GetFtDist();
		DrawRect(aRect);

		// UUUU
		const Rectangle aDefineRect(aRect);

		// UUUU
		const drawinglayer::attribute::SdrAllFillAttributesHelperPtr& rFillAttributes = getPageFillAttributes();

		if(!rFillAttributes.get() || !rFillAttributes->isUsed())
		{
			// UUUU If there is no fill, use fallback color
			const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
			const Color& rFieldColor = rStyleSettings.GetFieldColor();

			setPageFillAttributes(
				drawinglayer::attribute::SdrAllFillAttributesHelperPtr(
					new drawinglayer::attribute::SdrAllFillAttributesHelper(
						rFieldColor)));
		}

		// #97495# make sure that the automatic column width's are always equal
		sal_Bool bAutoWidth = pColMgr->IsAutoWidth();
		sal_Int32 nAutoColWidth = 0;
		if(bAutoWidth)
		{
			sal_Int32 nColumnWidthSum = 0;
			sal_uInt16 i;
			for(i = 0; i < nColumnCount; ++i)
				nColumnWidthSum += pColMgr->GetColWidth( i );
			nAutoColWidth = nColumnWidthSum / nColumnCount;
		}

		sal_uInt16 i;
		for( i = 0; i < nColumnCount; i++)
		{
			if(!bAutoWidth)
				nAutoColWidth = pColMgr->GetColWidth( i );
			aRect.Right() = aRect.Left() + nAutoColWidth;

			// UUUU use primitive draw command
			drawFillAttributes(getPageFillAttributes(), aRect, aDefineRect);

			if(i < nColumnCount - 1)
				aRect.Left() = aRect.Right() + pColMgr->GetGutterWidth(i);
		}
		if(pColMgr->HasLine())
		{
			Point aUp( rOrg.X() + nL, rOrg.Y() + GetTop() );
			Point aDown( rOrg.X() + nL, rOrg.Y() + GetSize().Height()
						- GetBottom() - GetFtHeight() - GetFtDist() );

			if( pColMgr->GetLineHeightPercent() != 100 )
			{
				long nLength = aDown.Y() - aUp.Y();
				nLength -= nLength * pColMgr->GetLineHeightPercent() / 100;
				switch(pColMgr->GetAdjust())
				{
					case COLADJ_BOTTOM: aUp.Y() += nLength; break;
					case COLADJ_TOP: aDown.Y() -= nLength; break;
					case COLADJ_CENTER:
						aUp.Y() += nLength / 2;
						aDown.Y() -= nLength / 2;
					break;
					default:; // prevent warning
				}
			}

			int nDist;
			for( i = 0; i < nColumnCount - 1; i++)
			{
				int nGutter = pColMgr->GetGutterWidth(i);
				nDist = pColMgr->GetColWidth( i ) + nGutter;
				nDist -= (i == 0) ?
					nGutter/2 :
						0;
				aUp.X() += nDist;
				aDown.X() += nDist;
				DrawLine( aUp, aDown );

			}
		}
	}
}

/*-----------------25.10.96 09.15-------------------

--------------------------------------------------*/


SwColumnOnlyExample::SwColumnOnlyExample( Window* pParent, const ResId& rResId) :
	Window(pParent, rResId),
	m_aFrmSize(1,1)
{
	SetMapMode( MapMode( MAP_TWIP ) );
	m_aWinSize = GetOutputSizePixel();
	m_aWinSize.Height() -= 4;
	m_aWinSize.Width() -= 4;

	m_aWinSize = PixelToLogic( m_aWinSize );

	SetBorderStyle( WINDOW_BORDER_MONO );

	m_aFrmSize = SvxPaperInfo::GetPaperSize(PAPER_A4); // DIN A4 TODO: Can we get the size of the frame here?
	::FitToActualSize(m_aCols, (sal_uInt16)m_aFrmSize.Width());

	long nHeight = m_aFrmSize.Height();
	Fraction aScale( m_aWinSize.Height(), nHeight );
	MapMode aMapMode( GetMapMode() );
	aMapMode.SetScaleX( aScale );
	aMapMode.SetScaleY( aScale );
	SetMapMode( aMapMode );
}

/*-----------------25.10.96 09.16-------------------

--------------------------------------------------*/


void SwColumnOnlyExample::Paint( const Rectangle& /*rRect*/ )
{
	const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
	const Color& rFieldColor = rStyleSettings.GetFieldColor();
	const Color& rDlgColor = rStyleSettings.GetDialogColor();
	const Color& rFieldTextColor = SwViewOption::GetFontColor();
	Color aGrayColor(COL_LIGHTGRAY);
	if(rFieldColor == aGrayColor)
		aGrayColor.Invert();

	Size aLogSize(PixelToLogic(GetOutputSizePixel()));
	Rectangle aCompleteRect(Point(0,0), aLogSize);
	SetLineColor(rDlgColor);
	SetFillColor(rDlgColor);
	DrawRect(aCompleteRect);

	SetLineColor( rFieldTextColor );
	Point aTL( (aLogSize.Width() - m_aFrmSize.Width()) / 2,
			   (aLogSize.Height() - m_aFrmSize.Height()) / 2);
	Rectangle aRect(aTL, m_aFrmSize);

	// draw a shadow rectangle
//	SetFillColor( Color(COL_GRAY) );
//	Rectangle aShadowRect(aRect);
//	aShadowRect.Move(aTL.Y(), aTL.Y());
//	DrawRect(aShadowRect);

	SetFillColor( rFieldColor );
	DrawRect(aRect);

	SetFillColor( aGrayColor );

	// Spaltentrenner?
	long nLength = aLogSize.Height() - 2 * aTL.Y();
	Point aUp( aTL );
	Point aDown( aTL.X(), nLength );
	sal_Bool bLines = sal_False;
	if(m_aCols.GetLineAdj() != COLADJ_NONE)
	{
		bLines = sal_True;

		sal_uInt16 nPercent = m_aCols.GetLineHeight();
		if( nPercent != 100 )
		{
			nLength -= nLength * nPercent / 100;
			switch(m_aCols.GetLineAdj())
			{
				case COLADJ_BOTTOM: aUp.Y() += nLength; break;
				case COLADJ_TOP: aDown.Y() -= nLength; break;
				case COLADJ_CENTER:
						aUp.Y() += nLength / 2;
						aDown.Y() -= nLength / 2;
				break;
				default:; // prevent warning
			}
		}

	}
	const SwColumns& rCols = m_aCols.GetColumns();
	sal_uInt16 nColCount = rCols.Count();
	if( nColCount )
	{
		DrawRect(aRect);
		SetFillColor( rFieldColor );
		Rectangle aFrmRect(aTL, m_aFrmSize);
		long nSum = aTL.X();
		for(sal_uInt16 i = 0; i < nColCount; i++)
		{
			SwColumn* pCol = rCols[i];
			aFrmRect.Left()    = nSum + pCol->GetLeft(); // nSum + pCol->GetLeft() + aTL.X();
			nSum              += pCol->GetWishWidth();
			aFrmRect.Right()   = nSum - pCol->GetRight();
			DrawRect(aFrmRect);
		}
		if(bLines )
		{
			nSum = aTL.X();
			for(sal_uInt16 i = 0; i < nColCount - 1; i++)
			{
				nSum += rCols[i]->GetWishWidth();
				aUp.X() = nSum;
				aDown.X() = nSum;
				DrawLine(aUp, aDown);
			}
		}
	}
}

/*-----------------25.10.96 12.05-------------------

--------------------------------------------------*/


void SwColumnOnlyExample::SetColumns(const SwFmtCol& rCol)
{
	m_aCols = rCol;
	sal_uInt16 nWishSum = m_aCols.GetWishWidth();
	long nFrmWidth = m_aFrmSize.Width();
	SwColumns& rCols = m_aCols.GetColumns();
	sal_uInt16 nColCount = rCols.Count();

	for(sal_uInt16 i = 0; i < nColCount; i++)
	{
		SwColumn* pCol = rCols[i];
		long nWish = pCol->GetWishWidth();
		nWish *= nFrmWidth;
		nWish /= nWishSum;
		pCol->SetWishWidth((sal_uInt16)nWish);
		long nLeft = pCol->GetLeft();
		nLeft *= nFrmWidth;
		nLeft /= nWishSum;
		pCol->SetLeft((sal_uInt16)nLeft);
		long nRight = pCol->GetRight();
		nRight *= nFrmWidth;
		nRight /= nWishSum;
		pCol->SetRight((sal_uInt16)nRight);
	}
	// #97495# make sure that the automatic column width's are always equal
	if(nColCount && m_aCols.IsOrtho())
	{
		sal_Int32 nColumnWidthSum = 0;
		sal_uInt16 i;
		for(i = 0; i < nColCount; ++i)
		{
			SwColumn* pCol = rCols[i];
			nColumnWidthSum += pCol->GetWishWidth();
			nColumnWidthSum -= (pCol->GetRight() + pCol->GetLeft());
		}
		nColumnWidthSum /= nColCount;
		for(i = 0; i < nColCount; ++i)
		{
			SwColumn* pCol = rCols[i];
			pCol->SetWishWidth( static_cast< sal_uInt16 >(nColumnWidthSum + pCol->GetRight() + pCol->GetLeft()));
		}
	}
}
/* -----------------------------08.02.2002 11:44------------------------------

 ---------------------------------------------------------------------------*/
SwPageGridExample::~SwPageGridExample()
{
	delete pGridItem;
}
/* -----------------------------08.02.2002 11:48------------------------------

 ---------------------------------------------------------------------------*/
#define MAX_ROWS	10
#define MAX_LINES	15
void SwPageGridExample::DrawPage( const Point& rOrg,
								  const sal_Bool bSecond,
								  const sal_Bool bEnabled )
{
	SwPageExample::DrawPage(rOrg, bSecond, bEnabled);
	if(pGridItem && pGridItem->GetGridType())
	{
		// paint the grid now
		Color aLineColor = pGridItem->GetColor();
		if(aLineColor.GetColor() == COL_AUTO)
		{
			aLineColor = GetFillColor();
			aLineColor.Invert();
		}
		SetLineColor(aLineColor);
		long nL = GetLeft();
		long nR = GetRight();

		if ( GetUsage() == SVX_PAGE_MIRROR && !bSecond )
		{
			// fuer gespiegelt drehen
			nL = GetRight();
			nR = GetLeft();
		}

		Rectangle aRect;
		aRect.Right() = rOrg.X() + GetSize().Width() - nR;
		aRect.Left()  = rOrg.X() + nL;
		aRect.Top()	  = rOrg.Y() + GetTop()
						+ GetHdHeight() + GetHdDist();
		aRect.Bottom()= rOrg.Y() + GetSize().Height() - GetBottom()
						- GetFtHeight() - GetFtDist();

		// increase the values to get a 'viewable' preview
		sal_Int32 nBaseHeight = pGridItem->GetBaseHeight() * 3;
		sal_Int32 nRubyHeight = pGridItem->GetRubyHeight() * 3;

		// detect height of rectangles
		Rectangle aRubyRect(aRect.TopLeft(),
					m_bVertical ?
					Size(nRubyHeight, aRect.GetHeight()) :
					Size(aRect.GetWidth(), nRubyHeight));
		Rectangle aCharRect(aRect.TopLeft(),
					m_bVertical ?
					Size(nBaseHeight, aRect.GetHeight()) :
					Size(aRect.GetWidth(), nBaseHeight));

		sal_Int32 nLineHeight = nBaseHeight + nRubyHeight;

		// detect count of rectangles
		sal_Int32 nLines = (m_bVertical ? aRect.GetWidth(): aRect.GetHeight()) / nLineHeight;
		if(nLines > pGridItem->GetLines())
			nLines = pGridItem->GetLines();

		// determine start position
		if(m_bVertical)
		{
			sal_Int16 nXStart = static_cast< sal_Int16 >(aRect.GetWidth() / 2 - nLineHeight * nLines /2);
			aRubyRect.Move(nXStart, 0);
			aCharRect.Move(nXStart, 0);
		}
		else
		{
			sal_Int16 nYStart = static_cast< sal_Int16 >(aRect.GetHeight() / 2 - nLineHeight * nLines /2);
			aRubyRect.Move(0, nYStart);
			aCharRect.Move(0, nYStart);
		}

		if(pGridItem->IsRubyTextBelow())
			m_bVertical ? aRubyRect.Move(nBaseHeight, 0) : aRubyRect.Move(0, nBaseHeight);
		else
			m_bVertical ? aCharRect.Move(nRubyHeight, 0) : aCharRect.Move(0, nRubyHeight);

		// vertical lines
		sal_Bool bBothLines = pGridItem->GetGridType() == GRID_LINES_CHARS;
		SetFillColor( Color( COL_TRANSPARENT ) );
		sal_Int32 nXMove = m_bVertical ? nLineHeight : 0;
		sal_Int32 nYMove = m_bVertical ? 0 : nLineHeight;
		for(sal_Int32 nLine = 0; nLine < nLines; nLine++)
		{
			DrawRect(aRubyRect);
			DrawRect(aCharRect);
			if(bBothLines)
			{
				Point aStart = aCharRect.TopLeft();
				Point aEnd = m_bVertical ? aCharRect.TopRight() : aCharRect.BottomLeft();
				while(m_bVertical ? aStart.Y() < aRect.Bottom(): aStart.X() < aRect.Right())
				{
					DrawLine(aStart, aEnd);
					if(m_bVertical)
						aStart.Y() = aEnd.Y() += nBaseHeight;
					else
						aStart.X() = aEnd.X() += nBaseHeight;
				}
			}
			aRubyRect.Move(nXMove, nYMove);
			aCharRect.Move(nXMove, nYMove);
		}
	}
}
/* -----------------------------08.02.2002 11:48------------------------------

 ---------------------------------------------------------------------------*/
void SwPageGridExample::UpdateExample( const SfxItemSet& rSet )
{
	DELETEZ(pGridItem);
	// get the grid information
	if(SFX_ITEM_AVAILABLE <= rSet.GetItemState(RES_TEXTGRID, sal_True))
		pGridItem = (SwTextGridItem*)((const SwTextGridItem&)rSet.Get(RES_TEXTGRID)).Clone();
	if( SFX_ITEM_AVAILABLE <= rSet.GetItemState( RES_FRAMEDIR, sal_True ))
	{
		const SvxFrameDirectionItem& rDirItem =
					(const SvxFrameDirectionItem&)rSet.Get(RES_FRAMEDIR);
		m_bVertical = rDirItem.GetValue() == FRMDIR_VERT_TOP_RIGHT||
					rDirItem.GetValue() == FRMDIR_VERT_TOP_LEFT;
	}
	SwPageExample::UpdateExample(rSet);
}

/* vim: set noet sw=4 ts=4: */
