/* This file is part of the KDE project
   Copyright (C) 2001,2002,2003,2004 Laurent Montel <montel@kde.org>
   Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */


//#include <stdlib.h>
#include <iostream>
using std::cout;
using std::cerr;


#include <dcopobject.h>
#include <tdelocale.h>
#include <kdebug.h>

#include <KoXmlNS.h>
#include <KoXmlWriter.h>
#include <KoDom.h>
#include <KoOasisLoadingContext.h>

#include <tqregexp.h>

#include "kdchart/KDChartParams.h"
#include "kdchart/KDChartAxisParams.h"

#include "kchart_params.h"
#include "kchart_part.h"
#include "KChartParamsIface.h"


namespace KChart
{


KChartParams::KChartParams( KChartPart *_part )
    : KDChartParams(),
      m_part( _part )
{
    // Default values for subtypes.
    m_firstRowAsLabel = false;
    m_firstColAsLabel = false;

    // Default values for OpenDocument extensions.
    m_barNumLines     = 0;

    m_dcop = 0;
    //dcopObject(); // build it
}


KChartParams::~KChartParams()
{
    delete m_dcop;
}


// ----------------------------------------------------------------


TQString KChartParams::chartTypeToString( ChartType _type) const
{
#if 0
    if (_type == BarLines )
	return "BarLines";
    else
#endif
	return KDChartParams::chartTypeToString( (KDChartParams::ChartType) _type );
}


KChartParams::ChartType
KChartParams::stringToChartType( const TQString& _string )
{
#if 0
    if ( _string == "BarLines" )
	return BarLines;
    else
#endif
	return (ChartType) KDChartParams::stringToChartType( _string );
}


void KChartParams::setFirstRowAsLabel( bool _val )
{
    m_firstRowAsLabel = _val;

    // The rest of this method is only applicable if the data is from
    // an external source, e.g. from kspread.
    if ( m_part->canChangeValue() )
	return;

    m_part->doSetData( *m_part->data(),
		       m_firstRowAsLabel, m_firstColAsLabel );
}

void KChartParams::setFirstColAsLabel( bool _val )
{
    m_firstColAsLabel = _val;

    // The rest of this method is only applicable if the data is from
    // an external source, e.g. kspread.
    if ( m_part->canChangeValue() )
	return;

    m_part->doSetData( *m_part->data(),
		       m_firstRowAsLabel, m_firstColAsLabel );
}


DCOPObject* KChartParams::dcopObject()
{
    if ( !m_dcop )
	m_dcop = new KChartParamsIface( this );
    return m_dcop;
}


// ================================================================
//                     Loading and Saving



static const struct {
    const char              *oasisClass;
    KChartParams::ChartType  chartType;
} oasisChartTypes[] = {
    { "chart:bar",    KChartParams::Bar },
    { "chart:line",   KChartParams::Line },
    { "chart:area",   KChartParams::Area },
    { "chart:circle", KChartParams::Pie },
    //{ "chart:xxx",    KChartParams::HiLo },
    //{ "chart:stock",  KChartParams::??? },
    //{ "chart:add-in", KChartParams::??? },
    //{ "chart:scatter",KChartParams::??? },
    { "chart:ring",   KChartParams::Ring },
    { "chart:radar",  KChartParams::Polar }
    //{ "chart:xxx",    KChartParams::BoxWhisker },

    //{ "chart:xxx",    KChartParams::BarLines },
    // FIXME: More?
};


static const unsigned int numOasisChartTypes
  = sizeof oasisChartTypes / sizeof *oasisChartTypes;


#if 0  // Example code!!
    KoOasisLoadingContext  loadingContext( this, oasisStyles, store );
    KoStyleStack          &styleStack = loadingContext.styleStack();

    styleStack.save();
    styleStack.setTypeProperties( "graphic" ); // load graphic-properties
    loadingContext.fillStyleStack( chartElem, KoXmlNS::chart, "style-name" );

    const TQString fillColor = styleStack.attributeNS( KoXmlNS::draw, "fill-color" );
    kDebug() << "fillColor=" << fillColor << endl;

    styleStack.restore();
#endif


// Load the data from an OpenDocument chart:chart element.

void KChartParams::loadOasisFont( KoOasisLoadingContext& context, TQFont& font, TQColor& color )
{
    KoStyleStack& styleStack = context.styleStack();
    styleStack.setTypeProperties( "text" ); // load all style attributes from "style:text-properties"

    if ( styleStack.hasAttributeNS( KoXmlNS::fo, "color" ) ) { // 3.10.3
        color.setNamedColor( styleStack.attributeNS( KoXmlNS::fo, "color" ) ); // #rrggbb format
    }
    if ( styleStack.hasAttributeNS( KoXmlNS::fo, "font-family" )  // 3.10.9
            || styleStack.hasAttributeNS( KoXmlNS::style, "font-name" ) ) { // 3.10.8
        // Hmm, the remove "'" could break it's in the middle of the fontname...
        TQString fontName = styleStack.attributeNS( KoXmlNS::fo, "font-family" ).remove( "'" );
        if ( fontName.isEmpty() ) {
            // ##### TODO. This is wrong. style:font-name refers to a font-decl entry.
            // We have to look it up there, and retrieve _all_ font attributes from it, not just the name.
            fontName = styleStack.attributeNS( KoXmlNS::style, "font-name" ).remove( "'" );
        }
        // 'Thorndale' is not known outside OpenOffice so we substitute it
        // with 'Times New Roman' that looks nearly the same.
        if ( fontName == "Thorndale" )
            fontName = "Times New Roman";

        fontName.remove( TQRegExp( "\\sCE$" ) ); // Arial CE -> Arial
        font.setFamily( fontName );
    }
    if ( styleStack.hasAttributeNS( KoXmlNS::fo, "font-size" ) ) { // 3.10.14
        double pointSize = styleStack.fontSize();
        font.setPointSizeFloat( pointSize );
        kdDebug(35001) << "font size: " << pointSize << endl;
    }
    if ( styleStack.hasAttributeNS( KoXmlNS::fo, "font-weight" ) ) { // 3.10.24
        TQString fontWeight = styleStack.attributeNS( KoXmlNS::fo, "font-weight" );
        int boldness;
        if ( fontWeight == "normal" )
            boldness = 50;
        else if ( fontWeight == "bold" )
            boldness = 75;
        else
            // XSL/CSS has 100,200,300...900. Not the same scale as TQt!
            // See http://www.w3.org/TR/2001/REC-xsl-20011015/slice7.html#font-weight
            boldness = fontWeight.toInt() / 10;
        font.setWeight( boldness );
    }
    if ( styleStack.hasAttributeNS( KoXmlNS::fo, "font-style" ) ) { // 3.10.19
        if ( styleStack.attributeNS( KoXmlNS::fo, "font-style" ) == "italic" ||
             styleStack.attributeNS( KoXmlNS::fo, "font-style" ) == "oblique" ) { // no difference in kotext
            font.setItalic( true );
        }
    }
}
    
bool KChartParams::loadOasis( const TQDomElement     &chartElem,
			      KoOasisLoadingContext &loadingContext,
                              TQString               &errorMessage,
			      KoStore               */*store*/ )
{
    const TQString chartClass = chartElem.attributeNS( KoXmlNS::chart,
						      "class", TQString() );
    bool          knownType = false;

    // Find out what KChart charttype the OASIS charttype corresponds to.
    for ( unsigned int i = 0 ; i < numOasisChartTypes ; ++i ) {
        if ( chartClass == oasisChartTypes[i].oasisClass ) {
            kdDebug(35001) << "found chart of type " << chartClass << endl;
	    //cerr << "found chart of type " << chartClass.latin1() << "\n";

            setChartType( oasisChartTypes[i].chartType );
            knownType = true;
            break;
        }
    }

    // If we can't find out what charttype it is, we might as well end here.
    if ( !knownType ) {
        errorMessage = i18n( "Unknown chart type %1" ).arg( chartClass );
        kdDebug(35001) << errorMessage << endl;
        return false;
    }

    // Title TODO (more details, e.g. font, placement etc)
    TQDomElement titleElem = KoDom::namedItemNS( chartElem,
						 KoXmlNS::chart, "title" );
    if ( !titleElem.isNull() ) {
        loadingContext.styleStack().save();
        loadingContext.fillStyleStack( titleElem, KoXmlNS::chart, "style-name", "chart" );
        TQFont font;
        TQColor color;
        loadOasisFont( loadingContext, font, color );
        setHeaderFooterFont( KDChartParams::HdFtPosHeader, font, true, font.pointSize()*4 );
        setHeaderFooterColor( KDChartParams::HdFtPosHeader, color );
        loadingContext.styleStack().restore();

	TQDomElement  pElem = KoDom::namedItemNS( titleElem,
						 KoXmlNS::text, "p" );
	setHeader1Text( pElem.text() );
    }

    // Subtitle TODO (more details)
    TQDomElement subtitleElem = KoDom::namedItemNS( chartElem, KoXmlNS::chart,
						   "subtitle" );
    if ( !subtitleElem.isNull() ) {
        loadingContext.styleStack().save();
        loadingContext.fillStyleStack( subtitleElem, KoXmlNS::chart, "style-name", "chart" );
        TQFont font;
        TQColor color;
        loadOasisFont( loadingContext, font, color );
        setHeaderFooterFont( KDChartParams::HdFtPosHeader2, font, true, font.pointSize()*4 );
        setHeaderFooterColor( KDChartParams::HdFtPosHeader2, color );
        loadingContext.styleStack().restore();

	TQDomElement  pElem = KoDom::namedItemNS( subtitleElem,
						 KoXmlNS::text, "p" );
	setHeader2Text( pElem.text() );
    }

    // Footer TODO (more details)
    TQDomElement footerElem = KoDom::namedItemNS( chartElem, KoXmlNS::chart,
						 "footer" );
    if ( !footerElem.isNull() ) {
        loadingContext.styleStack().save();
        loadingContext.fillStyleStack( footerElem, KoXmlNS::chart, "style-name", "chart" );
        TQFont font;
        TQColor color;
        loadOasisFont( loadingContext, font, color );
        setHeaderFooterFont( KDChartParams::HdFtPosFooter, font, true, font.pointSize()*4 );
        setHeaderFooterColor( KDChartParams::HdFtPosFooter, color );
        loadingContext.styleStack().restore();

	TQDomElement  pElem = KoDom::namedItemNS( footerElem,
						 KoXmlNS::text, "p" );
	setFooterText( pElem.text() );
    }

    // TODO: Get legend settings
    TQDomElement legendElem = KoDom::namedItemNS( chartElem, KoXmlNS::chart,
						 "legend" );
    if ( !legendElem.isNull() )
    {
        loadingContext.styleStack().save();
        loadingContext.fillStyleStack( legendElem, KoXmlNS::chart, "style-name", "chart" );
        TQFont font;
        TQColor color;
        loadOasisFont( loadingContext, font, color );
        //tz I didn't find that Oasis support seperate font/colors for the title and the rest of the legent
        setLegendFont(             font, false );
        setLegendFontRelSize(      font.pointSize()*4 );
        setLegendTitleFont(        font, false);
        setLegendTitleFontRelSize( font.pointSize()*4 );
        setLegendTextColor( color );
        setLegendTitleTextColor( color );
        loadingContext.styleStack().restore();
        TQString lp;
        if ( legendElem.hasAttributeNS( KoXmlNS::chart, "legend-position" ) )
        {
            lp = legendElem.attributeNS( KoXmlNS::chart, "legend-position", TQString() );
        }
        TQString lalign;
        if ( legendElem.hasAttributeNS( KoXmlNS::chart, "legend-align" ) )
        {
            lalign = legendElem.attributeNS( KoXmlNS::chart, "legend-align", TQString() );
        }

        LegendPosition lpos = NoLegend;
        int align = 1;
        if ( lalign == "start" )
        {
            align = 0;
        }
        else if ( lalign == "end" )
        {
            align = 2;
        }

        if ( lp == "start" )
        {
            lpos = LegendLeft;
            if ( align == 0 )
                lpos = LegendTopLeftLeft;
            else if ( align == 2 )    
                lpos = LegendBottomLeftLeft;
        }
        else if ( lp == "end" )
        {
            lpos = LegendRight;
            if ( align == 0 )
                lpos = LegendTopRightRight;
            else if ( align == 2 )    
                lpos = LegendBottomRightRight;
        }
        else if ( lp == "top" )
        {
            lpos = LegendTop;
            if ( align == 0 )
                lpos = LegendTopLeftTop;
            else if ( align == 2 )    
                lpos = LegendTopRightTop;
        }
        else if ( lp == "bottom" )
        {
            lpos = LegendBottom;
            if ( align == 0 )
                lpos = LegendBottomLeftBottom;
            else if ( align == 2 )    
                lpos = LegendBottomRightBottom;
        }
        else if ( lp == "top-start" )
        {
            lpos = LegendTopLeft;
        }
        else if ( lp == "bottom-start" )
        {
            lpos = LegendBottomLeft;
        }
        else if ( lp == "top-end" )
        {
            lpos = LegendTopRight;
        }
        else if ( lp == "bottom-end" )
        {
            lpos = LegendBottomRight;
        }

        setLegendPosition( lpos );
        //bodyWriter->addAttribute( "koffice:title", legendTitleText() );
        if ( legendElem.hasAttributeNS( KoXmlNS::koffice, "title" ) )
        {
            setLegendTitleText( legendElem.attributeNS( KoXmlNS::koffice, "title", TQString() ) );
        }
    }
    else
    {
        setLegendPosition( NoLegend );
    }

    // Get the plot-area.  This is where the action is.
    TQDomElement  plotareaElem = KoDom::namedItemNS( chartElem,
						    KoXmlNS::chart, "plot-area" );
    if ( !plotareaElem.isNull() ) {
	return loadOasisPlotarea( plotareaElem, loadingContext, errorMessage );
    }

    return false;
}


bool KChartParams::loadOasisPlotarea( const TQDomElement     &plotareaElem,
				      KoOasisLoadingContext &loadingContext,
				      TQString               &errorMessage )
{
    TQString  tmp;

    //cerr << ">>> ==========================================================\n";

    // FIXME: attribute table:cell-range-address  - the cells in a spreadsheet

    // Get whether there is a label on the first row or column of the data.
    // This info is in the attribute chart:data-source-has-labels.
    //
    // NOTE: Only used in spreadsheets.
    tmp = plotareaElem.attributeNS( KoXmlNS::chart,
				    "data-source-has-labels", TQString() );
    m_firstRowAsLabel = false;
    m_firstColAsLabel = false;
    if ( tmp == "none" || tmp == "" )
	; // NOTHING
    else if ( tmp == "row" )
	m_firstRowAsLabel = true;
    else if ( tmp == "column" )
	m_firstColAsLabel = true;
    else if ( tmp == "both" ) {
	m_firstRowAsLabel = true;
	m_firstColAsLabel = true;
    }
    else {
	errorMessage = "Unknown value for chart:data-source-has-labels:"
	    + tmp;
	return false;
    }

    // ----------------------------------------------------------------
    // Now get the style and use it to get the contents.
    // This is hidden in the attribute chart:style-name.
    KoStyleStack          &styleStack = loadingContext.styleStack();


    tmp = plotareaElem.attributeNS( KoXmlNS::chart, "style-name",
				    TQString() );
    //kdDebug(35001) << "Style name for the plot area: " << tmp << endl;
    styleStack.save();
    styleStack.setTypeProperties( "chart" ); // load chart properties
    loadingContext.fillStyleStack( plotareaElem, KoXmlNS::chart, "style-name", "chart" );

    if ( styleStack.attributeNS( KoXmlNS::chart, "three-dimensional" ) == "true" ) {
	setThreeDBars( true );
	setThreeDLines( true );
	setThreeDPies( true );
    }
    else {
	setThreeDBars( false );
	setThreeDLines( false );
	setThreeDPies( false );
    }

    switch ( m_chartType ) {
    case NoType:
	break;

    case Bar:
	// Find out subtype
	tmp = styleStack.attributeNS( KoXmlNS::chart, "vertical" );
	// FIXME: vertical is ignored. At least store it so we can
	//        save it again even if we don't support it.

	//kdDebug(35001) << "  ======> vertical: " << tmp << "  <======" << endl;
	// Set the bar chart subtype.
	if ( styleStack.attributeNS( KoXmlNS::chart, "stacked" ) == "true" )
	    setBarChartSubType( BarStacked );
	else if ( styleStack.attributeNS( KoXmlNS::chart, "percentage" ) == "true" )
	    setBarChartSubType( BarPercent );
	else
	    setBarChartSubType( BarNormal );

	break;

	// chart:vertical      - true if vertical bars (only bar charts)
	// chart:stacked       - true for stacked bars
	// chart:percentage    - true for percentage  (mut. excl with stacked)
	// chart:connect-bars  - true if lines to connect bars should be drawn
	//                           only used for stacked and percentage bars.
	// FIXME: Support lines on bar charts.
	// chart:lines-used    - 0-n, number of lines on a bar chart. (def: 0)

    case Line:
	// Set the line chart subtype.
	if ( styleStack.attributeNS( KoXmlNS::chart, "stacked" ) == "true" )
	    setLineChartSubType( LineStacked );
	else if ( styleStack.attributeNS( KoXmlNS::chart, "percentage" ) == "true" )
	    setLineChartSubType( LinePercent );
	else
	    setLineChartSubType( LineNormal );

	break;

	// FIXME: Why is this even there?  Seems like an unnecessary attr.
	// chart:lines       - true for line charts, false otherwise

	// chart:stacked     - true for stacked bars
	// chart:percentage  - true for percentage  (mut. excl with stacked)

	// chart:symbol-type - used with line charts, should be "automatic"

    case Area:
	// Set the area chart subtype.
	if ( styleStack.attributeNS( KoXmlNS::chart, "stacked" ) == "true" )
	    setAreaChartSubType( AreaStacked );
	else if ( styleStack.attributeNS( KoXmlNS::chart, "percentage" ) == "true" )
	    setAreaChartSubType( AreaPercent );
	else
	    setAreaChartSubType( AreaNormal );

	break;

	// chart:stacked       - true for stacked bars
	// chart:percentage    - true for percentage  (mut. excl with stacked)

    case Pie:
	break;

    case HiLo:
	break;

    case Ring:
	break;

    case Polar:
	break;

    case BoxWhisker:
	break;
    }
    // TODO:
    // And get the info from the style.  Here is the contents:


    // TODO: These items are currently not used.  They should be at least
    //       be stored so that we can save them again.
    // chart:interpolation     - "cubic-spline" if using cubic splines
    // chart:splines           -
    // chart:spline-order      - "2" for cubic splines
    // chart:spline-resolution - how smooth (default: 20)

    // -- Used when chart:class == "stock:
    // chart:stock-updown-bars      - boolean
    // chart:stock-with-volume      - boolean
    // chart:japanese-candle-sticks - boolean

    // chart:series-source     - "rows" or "columns"
    // "columns" is the default
    if ( styleStack.attributeNS( KoXmlNS::chart, "series-source" ) == "rows" ) {
      setDataDirection( DataRows );
    }

    // chart:data-label-number - "value" / "percentage" / "none" (def: none)

    // chart:data-label-text   - true if data hapoints have text labels
    // chart:data-label-symbol - true if data hapoints have legend symbol
    //                           (default: false for both)

    styleStack.restore();

    // ----------------------------------------------------------------
    // In the plot-area element there are two chart:axis elements

    TQDomElement  xAxisElem;
    TQDomElement  yAxisElem;

    TQDomElement  axisElem;
    forEachElement( axisElem, plotareaElem ) {

	// If this element is not an axis, then continue
	if ( axisElem.tagName() != "axis" )
	    continue;

	tmp = axisElem.attributeNS( KoXmlNS::chart, "name",
				    TQString());
	//kdDebug(35001) << "Got axis " << tmp << endl;
	//cerr << "Got axis " << tmp.latin1() << "\n";
	if ( tmp == "primary-x" )
	    xAxisElem = axisElem;
	else if ( tmp == "primary-y" )
	    yAxisElem = axisElem;
	else
	    // Only supports two axes so far.
	    continue;
    }

    // Load the axes.  Pie charts use only the y axis.
    if ( m_chartType != Pie
	 && !loadOasisAxis( xAxisElem, loadingContext, errorMessage,
			 KDChartAxisParams::AxisPosBottom) )
	return false;
    if ( !loadOasisAxis( yAxisElem, loadingContext, errorMessage,
			    KDChartAxisParams::AxisPosLeft) )
	return false;

    // Attributes for the axes:
    // chart:name       - either "primary-x" or "primary-y"

#if 0
    const TQString fillColor = styleStack.attributeNS( KoXmlNS::draw,
						      "fill-color" );
    kdDebug() << "fillColor=" << fillColor << endl;
#endif

    //cerr << "<<< ==========================================================\n";

    return true;
}


bool KChartParams::loadOasisAxis( const TQDomElement      &axisElem,
				  KoOasisLoadingContext  &loadingContext,
				  TQString                &errorMessage,
				  KDChartAxisParams::AxisPos  axisPos )
{
    Q_UNUSED( errorMessage );

    TQString        tmp;
    TQDomElement    tmpElem;
    KoStyleStack  &styleStack = loadingContext.styleStack();

    // Get the axis to manipulate.
    // TODO

    // Get the axis title (== axis label) if any.
    TQDomElement  titleElem = KoDom::namedItemNS( axisElem,
						 KoXmlNS::chart, "title" );
    if ( !titleElem.isNull() ) {
	tmpElem = KoDom::namedItemNS( titleElem, KoXmlNS::text, "p" );
	setAxisTitle( axisPos, tmpElem.text() );
    }



    //cerr << ">>> ----------------------------------------------------------\n";
    //cerr << "Loading axis " << axisElem.attributeNS( KoXmlNS::chart, "name",
    //						     TQString()).latin1()
    //	 << "\n";

    tmp = axisElem.attributeNS( KoXmlNS::chart, "style-name", TQString() );
    //kdDebug(35001) << "Style name for the axis: " << tmp << endl;
    //cerr << "Style name for the axis: " << tmp.latin1() << "\n";
    styleStack.save();
    styleStack.setTypeProperties( "chart" ); // load chart properties
    loadingContext.fillStyleStack( axisElem, KoXmlNS::chart, "style-name", "chart" );

    // chart:class      - "category" / "value" / "domain" (domain for scatter)
    //    child: chart:categories

    // child:  chart:title   - Name of title if any.
    // child:  chart:grid
    //           attr: chart:class  - "major" / "minor"

    // chart:style-name - Associated style with the following info:
    // --------------------------------
    // chart:display-label          - true if an axis label should be shown.

    // chart:tick-marks-major-inner - true if display tickmarks at major
    // chart:tick-marks-major-outer   or minor intervals outside / inside
    // chart:tick-minor-major-inner   the chart area.
    // chart:tick-minor-major-outer

    // chart:logarithmic            - true if logarithmic scale

    // text:line-break              - true if categories can be broken

    // chart:text-overlap           - true if labels can overlap

    // chart:label-arrangement      - "side-by-side" / "stagger-even" /
    //                                "stagger-odd"  (def: side-by-side)

    // chart:visible                - true if labels + ticks should be shown.

    // children:
    // chart:
    // chart:
    // chart:
    // chart:
    // chart:
    styleStack.restore();

    //cerr << "<<< ----------------------------------------------------------\n";
    return true;
}


// ----------------------------------------------------------------


void KChartParams::saveOasis( KoXmlWriter* bodyWriter, KoGenStyles& mainStyles ) const
{
    bool knownType = false;
    for ( unsigned int i = 0 ; i < numOasisChartTypes ; ++i ) {
        if ( m_chartType == oasisChartTypes[i].chartType ) {
            bodyWriter->addAttribute( "chart:class", oasisChartTypes[i].oasisClass );
            knownType = true;
            break;
        }
    }

    if ( !knownType ) {
        kdError(32001) << "Unknown chart type in KChartParams::saveOasis, extend oasisChartTypes!" << endl;
    }

    bodyWriter->startElement( "chart:title" );
    TQRect rect( headerFooterRect( KDChartParams::HdFtPosHeader ) );
    bodyWriter->addAttributePt( "svg:x", rect.x() );
    bodyWriter->addAttributePt( "svg:y", rect.y() );
    bodyWriter->addAttribute( "chart:style-name", saveOasisFont( mainStyles, header1Font(), headerFooterColor( KDChartParams::HdFtPosHeader ) ) );
    bodyWriter->startElement( "text:p" );
    bodyWriter->addTextNode( header1Text() );
    bodyWriter->endElement(); // text:p
    bodyWriter->endElement(); // chart:title

    TQString subTitle( header2Text() );
    if ( !subTitle.isEmpty() ) {

        kdDebug(32001) << "header rect: " << headerFooterRect( KDChartParams::HdFtPosHeader2 ) << endl;
        TQRect rect( headerFooterRect( KDChartParams::HdFtPosHeader2 ) );
        bodyWriter->startElement( "chart:subtitle" );
        bodyWriter->addAttributePt( "svg:x", rect.x() );
        bodyWriter->addAttributePt( "svg:y", rect.y() );
        bodyWriter->addAttribute( "chart:style-name", 
				  saveOasisFont( mainStyles, 
						 header2Font(), 
						 headerFooterColor( KDChartParams::HdFtPosHeader2 ) ) );

        bodyWriter->startElement( "text:p" );
        bodyWriter->addTextNode( subTitle );
        bodyWriter->endElement(); // text:p
        bodyWriter->endElement(); // chart:subtitle
    }


    TQString footer( footerText() );
    if ( !footer.isEmpty() ) {
        TQRect rect( headerFooterRect( KDChartParams::HdFtPosFooter ) );
        bodyWriter->startElement( "chart:footer" );
        bodyWriter->addAttributePt( "svg:x", rect.x() );
        bodyWriter->addAttributePt( "svg:y", rect.y() );
        bodyWriter->addAttribute( "chart:style-name",
				  saveOasisFont( mainStyles, 
						 footerFont(), 
						 headerFooterColor( KDChartParams::HdFtPosFooter ) ) );

        bodyWriter->startElement( "text:p" );
        bodyWriter->addTextNode( footer );
        bodyWriter->endElement(); // text:p
        bodyWriter->endElement(); // chart:footer
    }

    // TODO legend
    LegendPosition lpos = legendPosition();
    if ( lpos != NoLegend ) {
        bodyWriter->startElement( "chart:legend" );
        TQString lp;
        TQString lalign;
        switch ( lpos ) {
	case LegendTop: 
	    lp = "top";
	    lalign = "center";
	    break;
	case LegendBottom:
	    lp = "bottom";
	    lalign = "center";
	    break;
	case LegendLeft: 
	    lp = "start";
	    lalign = "center";
	    break;
	case LegendRight:
	    lp = "end";
	    lalign = "center";
	    break;
	case LegendTopLeft:
	    lp = "top-start";
	    break;
	case LegendTopLeftTop:
	    lp = "top";
	    lalign = "start";
	    break;
	case LegendTopLeftLeft:
	    lp = "start";
	    lalign = "start";
	    break;
	case LegendTopRight:
	    lp = "top-end";
	    break;
	case LegendTopRightTop:
	    lp = "top";
	    lalign = "end";
	    break;
	case LegendTopRightRight:
	    lp = "end";
	    lalign = "start";
	    break;
	case LegendBottomLeft:
	    lp = "bottom-start";
	    break;
	case LegendBottomLeftBottom:
	    lp = "bottom";
	    lalign = "start";
	    break;
	case LegendBottomLeftLeft:
	    lp = "start";
	    lalign = "end";
	    break;
	case LegendBottomRight:
	    lp = "bottom-end";
	    break;
	case LegendBottomRightBottom:
	    lp = "bottom";
	    lalign = "end";
	    break;
	case LegendBottomRightRight:
	    lp = "end";
	    lalign = "end";
	    break;
	default:
	    lp = "end";
	    lalign = "center";
	    break;
        }

        bodyWriter->addAttribute( "chart:legend-position", lp );
        bodyWriter->addAttribute( "chart:legend-align", lalign );
        bodyWriter->addAttribute( "chart:style-name",
				  saveOasisFont( mainStyles, 
						 legendFont(), 
						 legendTextColor() ) );
        bodyWriter->addAttribute( "koffice:title", legendTitleText() );
        bodyWriter->endElement(); // chart:legend
    }

    bodyWriter->startElement( "chart:plot-area" );
    saveOasisPlotArea( bodyWriter, mainStyles );
    bodyWriter->endElement();

    // TODO...
}


void KChartParams::saveOasisPlotArea( KoXmlWriter* bodyWriter, KoGenStyles& mainStyles ) const
{
    TQString dataSourceHasLabels;
    if ( m_firstRowAsLabel )
        if ( m_firstColAsLabel )
            dataSourceHasLabels = "both";
        else
            dataSourceHasLabels = "row";
    else
        if ( m_firstColAsLabel )
            dataSourceHasLabels = "column";
        else
            dataSourceHasLabels = "none";
    bodyWriter->addAttribute( "chart:data-source-has-labels", dataSourceHasLabels );

    // Prepare the style for the plot area
    KoGenStyle plotAreaStyle( KoGenStyle::STYLE_AUTO, "chart" );

    switch ( m_chartType ) {
    case NoType:
	break;

    case Bar:
        switch( barChartSubType() ) {
        case BarStacked:
            plotAreaStyle.addProperty( "chart:stacked", "true" );
            break;
        case BarPercent:
            plotAreaStyle.addProperty( "chart:percentage", "true" );
            break;
        case BarNormal:
            break;
	case BarMultiRows:
	    break;
        }
        plotAreaStyle.addProperty( "chart:vertical", "false" ); // #### always?
        plotAreaStyle.addProperty( "chart:lines-used", 0 ); // FIXME: for now

	if ( threeDBars() )
	    plotAreaStyle.addProperty( "chart:three-dimensional", "true" );

    case Line:
        switch( lineChartSubType() ) {
        case LineStacked:
            plotAreaStyle.addProperty( "chart:stacked", "true" );
            break;
        case LinePercent:
            plotAreaStyle.addProperty( "chart:percentage", "true" );
            break;
        case LineNormal:
            break;
        }
        plotAreaStyle.addProperty( "chart:symbol-type", "automatic" );

	if ( threeDLines() )
	    plotAreaStyle.addProperty( "chart:three-dimensional", "true" );

        break;

    case Area:
        switch( areaChartSubType() ) {
        case AreaStacked:
            plotAreaStyle.addProperty( "chart:stacked", "true" );
            break;
        case AreaPercent:
            plotAreaStyle.addProperty( "chart:percentage", "true" );
            break;
        case AreaNormal:
            break;
        }
        //plotAreaStyle.addProperty( "chart:lines-used", 0 ); // #### for now

        // TODO - very similar

    case Pie:
	if ( threeDPies() )
	    plotAreaStyle.addProperty( "chart:three-dimensional", "true" );

	break;

    case HiLo:
	break;

    case Ring:
	break;

    case Polar:
	break;

    case BoxWhisker:
	break;
    }

    // chart:series-source
    plotAreaStyle.addProperty( "chart:series-source",
			       ( dataDirection() == DataRows ) ? "rows" : "columns" );

    // Register the style, and get back its auto-generated name
    const TQString styleName = mainStyles.lookup( plotAreaStyle, "ch" );

    bodyWriter->addAttribute( "chart:style-name", styleName );

    saveOasisAxis( bodyWriter, mainStyles, KDChartAxisParams::AxisPosBottom, "x" );
    saveOasisAxis( bodyWriter, mainStyles, KDChartAxisParams::AxisPosLeft, "y" );

    // TODO chart:series
    // TODO chart:wall
    // TODO chart:floor
}


void KChartParams::saveOasisAxis( KoXmlWriter* bodyWriter, 
				  KoGenStyles& mainStyles,
                                  KDChartAxisParams::AxisPos axisPos, 
				  const char* axisName ) const
{
    bodyWriter->startElement( "chart:axis" );

    bodyWriter->addAttribute( "chart:dimension", axisName );
    bodyWriter->addAttribute( "chart:name", TQCString( "primary-" ) + axisName );

    KoGenStyle axisStyle( KoGenStyle::STYLE_AUTO, "chart" );

    // TODO: Save axis style properties, like
    axisStyle.addProperty( "chart:display-label", "true" ); // ###


    const TQString styleName = mainStyles.lookup( axisStyle, "ch" );
    bodyWriter->addAttribute( "chart:style-name", styleName );

    // Write axis titles if any.
    TQString tmpStr = axisTitle( axisPos );
    if ( tmpStr != "" ) {
	bodyWriter->startElement( "chart:title" );
	// TODO: Save style, svg:x, svg:y

	// Write the text in the axis title.
	bodyWriter->startElement( "text:p" );
	bodyWriter->addTextNode( tmpStr
				 .remove( TQRegExp( "^<qt><center>" ) )
				 .remove( TQRegExp( "</center></qt>$" ) ) );
	bodyWriter->endElement(); // text:p

	bodyWriter->endElement(); // chart:title
    }

    // TODO x axis has chart:categories, y axis has chart:grid ?
    // Maybe that part should be done by the caller of saveOasisAxis then
    // including the opening/closing of the chart:axis element...

    bodyWriter->endElement(); // chart:axis
}


TQString KChartParams::saveOasisFont( KoGenStyles& mainStyles, const TQFont& font, const TQColor& color ) const
{
    KoGenStyle::PropertyType tt = KoGenStyle::TextType;
    KoGenStyle autoStyle( KoGenStyle::STYLE_AUTO, "chart", 0 );
    autoStyle.addProperty( "fo:font-family", font.family(), tt );
    autoStyle.addPropertyPt( "fo:font-size", font.pointSize(), tt );
    autoStyle.addProperty( "fo:color", color.isValid() ? color.name() : "#000000", tt );
    int w = font.weight();
    autoStyle.addProperty( "fo:font-weight", w == 50 ? "normal" : w == 75 ? "bold" : TQString::number( tqRound(  w / 10 ) * 100 ), tt );
    autoStyle.addProperty( "fo:font-style", font.italic() ? "italic" : "normal", tt );

    return mainStyles.lookup( autoStyle, "ch", KoGenStyles::ForceNumbering );
}


}  //KChart namespace

