/*
 * Copyright (C) 2008 Instituto Nokia de Tecnologia. All rights reserved.
 *
 * This file is part of QEdje.
 *
 * QEdje is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * QEdje 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with QEdje.  If not, see <http://www.gnu.org/licenses/>.
 *
 * This file incorporates work covered by the license described in the
 * file COPYING.libedje.
 */

#include "qedjerealpart.h"

#include <qzion.h>
#include <math.h>

#include "qedjeimage.h"
#include "qedjeloader.h"


/*!
  \internal

  Hold calculation data for QEdjeRealPart::recalculateSingle
*/
struct QEdjeCalcParams
{
    int x;
    int y;
    int w;
    int h;

    struct {
        int width;
        int height;
    } min, max;

    QColor color;
    QColor color2;
    QColor color3;

    struct {
        int x;
        int y;
        int w;
        int h;
        int angle;
        int spread;
    } fill;

    struct {
        int left;
        int right;
        int top;
        int bottom;
    } border;

    struct {
        QPointF   align; /* text alignment within bounds */
        double    elipsis;
    } text;

    struct {
        int       id;
        char      *type;
    } gradient;

    bool smooth;
    bool visible;
};

/*!
  \internal

  Composes two values based on position and returns the final value.
*/
static inline int composeValue(double v1, double v2, double p)
{
    return (int)(((v1) == (v2)) ? (v1) : ((v1) + (((v2) - (v1)) * (p))));
}

/*!
  \internal

  Interpolates two colors based on position and returns the final value.
*/
static inline QColor interpolateColorClass(QColor c1, QColor c2, double p)
{
    int r = composeValue(c1.red(), c2.red(), p);
    int g = composeValue(c1.green(), c2.green(), p);
    int b = composeValue(c1.blue(), c2.blue(), p);
    int a = composeValue(c1.alpha(), c2.alpha(), p);
    return QColor(r, g, b, a);
}

/*!
  \internal

  Composes two colors and returns the final color.
*/
static inline QColor composeColorClass(QColor cc, QColor cn)
{
    int r = (((int)cc.red() + 1) * cn.red()) >> 8;
    int g = (((int)cc.green() + 1) * cn.green()) >> 8;
    int b = (((int)cc.blue() + 1) * cn.blue()) >> 8;
    int a = (((int)cc.alpha() + 1) * cn.alpha()) >> 8;
    return QColor(r, g, b, a);
}

/*!
  \param edje the QEdje instance this part belongs to.
  \param part the statically defined Edje part this object is bond to.
*/
QEdjeRealPart::QEdjeRealPart(QEdje *edje, QEdjePart *part)
{
    _edje = edje;
    _part = part;
    _confineTo = NULL;
    _runningProgram = NULL;
    _descriptionPos = 0.0;
}

/*!
  Free dynamic allocated memory.
*/
QEdjeRealPart::~QEdjeRealPart()
{

}

/*!
  Returns the object position.
*/
QPoint QEdjeRealPart::pos()
{
    return object()->pos();
}

/*!
  Set the object position.
*/
void QEdjeRealPart::setPos(const QPoint pos)
{
    object()->setPos(pos);
}

/*!
  Returns the edje group of this part.
*/
QEdje *QEdjeRealPart::edje()
{
    return _edje;
}

/*!
  Returns the QEdjePart of this part.
*/
QEdjePart *QEdjeRealPart::part()
{
    return _part;
}

/*!
  Set a clipper for this part. The clipper must be a
  valid QZionRectangle.
*/
void QEdjeRealPart::setClipTo(QEdjeRealPart *clipTo)
{
    if (!clipTo)
        return;

    QZionRectangle *clipper = dynamic_cast<QZionRectangle *>(clipTo->object());

    if (!clipper) {
        qWarning("Warning, clipping '%s' to non-rect item '%s'.",
                 part()->name.toAscii().data(),
                 clipTo->part()->name.toAscii().data());
        return;
    }

    object()->setClipper(clipper);
}

/*!
  Returns the running program that is using this part.
*/
QRunningProgram *QEdjeRealPart::runningProgram()
{
    return _runningProgram;
}

/*!
  Set a running program that is using this part.
*/
void QEdjeRealPart::setRunningProgram(QRunningProgram *rp)
{
    _runningProgram = rp;
}

/*!
  Set the description position for this part.
*/
void QEdjeRealPart::setDescriptionPos(const double value)
{
    _descriptionPos = value;
}

/*!
  \brief returns a state description identified by its name and position.

  Searches the associated QEdjePart for a description matching the given
  search parameters and return its instance.
*/
QEdjePartDescription *QEdjeRealPart::findDescription(QString &name, double value)
{
    double min_dst = 999.0;
    QEdjePartDescription *ret = NULL;

    if (name == "default" && value == 0.0)
        return &_part->defaultDescription;

    if (name == "default") {
        ret = &_part->defaultDescription;
        min_dst = fabs(ret->state.value - value);
    }

    foreach (QEdjePartDescription *p, _part->otherDescriptions) {
        if (p->state.name == name) {
            double dst = fabs(p->state.value - value);
            if (dst < min_dst) {
                ret = p;
                min_dst = dst;
            }
        }
    }

    return ret;
}

/*!
  \brief Handle recalculations (animations) on image parts.
  \todo fill
  \todo smooth scale
  \todo border

  Check which image is best suitable for a given time of an animation. Can be
  simply another image or even handle tweenList.
*/
void QEdjeRealPart::recalculateImageApply(QEdje *edje,
                                          QEdjeRealPart *part,
                                          QEdjeCalcParams *params,
                                          QEdjePartDescription *desc,
                                          double pos)
{
    int imageId;
    int imageCount;
    int imageNum;

    imageId = part->param1.description->imageId;

    if (imageId < 0) {
        QEdjeImage *im = dynamic_cast<QEdjeImage *>(part->object());
        im->clear();
    } else {
        imageCount = 2;

        /* update imageCount by adding images from tween list */
        int tweenList_size = desc->tweenList.size();
        if (tweenList_size > 0)
            imageCount += tweenList_size;

        imageNum = (pos * ((double) imageCount - 0.5));

        if (imageNum > (imageCount - 1))
            imageNum = imageCount - 1;

        if (imageNum == 0)
            imageId = part->param1.description->imageId;
        else if (imageNum == (imageCount - 1))
            imageId = part->param2.description->imageId;
        else
            imageId = desc->tweenList.at(imageNum - 1);


        if (imageId >= 0) {
            QEdjeImage *im = dynamic_cast<QEdjeImage *>(part->object());
            im->load(edje->path(), QString("images/%1").arg(imageId));
            im->setBorder(params->border.left, params->border.right,
                          params->border.top, params->border.bottom);
        } else {
            qWarning("part \"%s\" has description, \"%s\" %3.3f with a "
                     "missing image id!\n",
                     part->part()->name.toLatin1().data(),
                     part->param1.description->state.name.toLatin1().data(),
                     part->param1.description->state.value);
        }
    }
}


/*!
  \brief Calculate current position of QEdjeRealPart object.

  Based on current programs running, other objects this part may be relative to
  and their positions, calculate the current state of this instance.

  \todo recalculate this->text.source and this->text.text_source
  \todo set p3.req and p3.req_drag values
  \todo set p3.text alignment and elipsis
  \todo Drag need_reset
  \todo Text, Image, Gradient special recalculation.
 */
void QEdjeRealPart::recalculate(int flags)
{
    if ((calculated & FLAG_XY) == FLAG_XY)
        return;

    qDebug() << "recalculating " << this->part()->name;

    double pos = 0.0;
    QEdjeCalcParams p1;
    QEdjeCalcParams p2;
    QEdjeCalcParams p3;
    QEdjeCalcParams *pf;
    QEdjePartDescription *chosen_desc;

    if (calculating & flags) {
        qDebug() << "QEdje Error: Circular dependency when calculating part"
                 << _part->name << "\n";
        return;
    }

    recalculateRelativeTo(flags);

    if (_confineTo)
        _confineTo->recalculate(flags);

    // actually calculate now
    chosen_desc = _chosenDescription;
    if (!chosen_desc) {
        calculating = FLAG_NONE;
        calculated |= flags;
        return;
    }

    if (param1.description)
        recalculateSingle(param1.description, chosen_desc,
                          param1.rel1ToX,
                          param1.rel1ToY,
                          param1.rel2ToX,
                          param1.rel2ToY,
                          _confineTo, &p1,flags);

    if (param2.description) {
        int part_type;
        bool beginning_pos;

        recalculateSingle(param2.description, chosen_desc,
                          param2.rel1ToX,
                          param2.rel1ToY,
                          param2.rel2ToX,
                          param2.rel2ToY,
                          _confineTo, &p2,flags);

        pos = _descriptionPos;
        beginning_pos = (pos < 0.5);
        part_type = part()->type;

        // visible is special
        if ((p1.visible) && (!p2.visible))
            p3.visible = (pos != 1.0);
        else if ((!p1.visible) && (p2.visible))
            p3.visible = (pos != 0.0);
        else
            p3.visible = p1.visible;

        p3.smooth = (beginning_pos) ? p1.smooth : p2.smooth;

        // FIXME: do x and y separately base on flag

        p3.x = composeValue(p1.x, p2.x, pos);
        p3.y = composeValue(p1.y, p2.y, pos);
        p3.w = composeValue(p1.w, p2.w, pos);
        p3.h = composeValue(p1.h, p2.h, pos);

        p3.color = interpolateColorClass(p1.color, p2.color, pos);

        if ((part_type == QEdjePart::IMAGE) ||
            (part_type == QEdjePart::GRADIENT)) {
            p3.fill.x = composeValue(p1.fill.x, p2.fill.x, pos);
            p3.fill.y = composeValue(p1.fill.y, p2.fill.y, pos);
            p3.fill.w = composeValue(p1.fill.w, p2.fill.w, pos);
            p3.fill.h = composeValue(p1.fill.h, p2.fill.h, pos);
            if (part_type == QEdjePart::GRADIENT) {
                p3.fill.angle = composeValue(p1.fill.angle, p2.fill.angle, pos);
                p3.fill.spread = (beginning_pos) ? p1.fill.spread : p2.fill.spread;
                p3.gradient = (beginning_pos) ? p1.gradient : p2.gradient;
            } else {
                p3.border.left = composeValue(p1.border.left, p2.border.left, pos);
                p3.border.right = composeValue(p1.border.right, p2.border.right, pos);
                p3.border.top = composeValue(p1.border.top, p2.border.top, pos);
                p3.border.bottom = composeValue(p1.border.bottom, p2.border.bottom, pos);
            }
        } else if ((part_type == QEdjePart::TEXT) ||
                   (part_type == QEdjePart::TEXTBLOCK)) {
            p3.color2 = interpolateColorClass(p1.color2, p2.color2, pos);
            p3.color3 = interpolateColorClass(p1.color3, p2.color3, pos);
        }
        pf = &p3;
    } else
        pf = &p1;

    if (!_edje->calculateOnly) {
        // Common move, resize and color_set for all part.
        switch (this->part()->type) {
        case QEdjePart::RECT:
        case QEdjePart::IMAGE:
        case QEdjePart::TEXT:
        case QEdjePart::TEXTBLOCK:
        case QEdjePart::GRADIENT:
            setColor(pf->color);
            object()->setVisible(pf->visible);

        case QEdjePart::GROUP:
        case QEdjePart::SWALLOW:
            setPos(QPoint(pf->x, pf->y));
            setSize(QSize(pf->w, pf->h));
            break;
        }

        if (part()->type == QEdjePart::GROUP ||
            part()->type == QEdjePart::SWALLOW) {
            QZionGroup *wrapper = static_cast<QZionGroup*>(object());
            if (!wrapper->items()->isEmpty()) {
                QZionObject *embedded = wrapper->items()->at(0);

                embedded->setPos(QPoint(0, 0));
                embedded->setSize(QSize(pf->w, pf->h));

                wrapper->setVisible(pf->visible);
                embedded->setVisible(pf->visible);
            } else
                wrapper->hide();
        }
    }

    switch (this->part()->type) {
        case QEdjePart::IMAGE:
            recalculateImageApply(_edje, this, pf, chosen_desc, pos);
            break;
        default:
           break;
    }

    calculated |= flags;
    calculating = FLAG_NONE;
}

/*!
  \brief Set QEdjeRealPart initial state.

  QEdjeRealPart objects hold internally one or two state descriptions
  depending whether there's a program running or not. If no program is
  running only the initial state ("param1") is kept.

  The final state is used only during transitions, for calculating the
  intermediate states.

  At any moment, the property chosenDescription points to a valid state
  which is the initial state (if the part is stopped) or the final one (if
  it's running a program with transition). This invariant is valid also
  for setFinalState() and unsetFinalState().

  \sa setFinalState(), unsetFinalState()

  \param name Initial state name.
  \param value Initial state position.
*/
void QEdjeRealPart::setInitialState(const QString &name, double value)
{
    QString stateName = (name == "") ? "default" : name;

    param1.description = findDescription(stateName, value);
    if (!param1.description)
        param1.description = &_part->defaultDescription;

    QEdjePartDescription *desc = param1.description;
    param1.rel1ToX = _edje->findPart(desc->rel1.idX);
    param1.rel1ToY = _edje->findPart(desc->rel1.idY);
    param1.rel2ToX = _edje->findPart(desc->rel2.idX);
    param1.rel2ToY = _edje->findPart(desc->rel2.idY);

    if (_descriptionPos == 0.0)
        _chosenDescription = desc;

    _edje->dirty = true;
}

/*!
  \brief Set QEdjeRealPart final state.

  The final state is only used for calculating intermediate states
  when a program is running.

  This also sets the chosenDescription to the initial state, since is
  the only one valid.

  \sa setInitialState(), unsetFinalState(),

  \param name Final state name.
  \param value Final state position.
*/
void QEdjeRealPart::setFinalState(const QString &name, double value)
{
    QString stateName = (name == "") ? "default" : name;

    param2.description = findDescription(stateName, value);
    if (!param2.description)
        param2.description = &_part->defaultDescription;

    QEdjePartDescription *desc = param2.description;
    param2.rel1ToX = _edje->findPart(desc->rel1.idX);
    param2.rel1ToY = _edje->findPart(desc->rel1.idY);
    param2.rel2ToX = _edje->findPart(desc->rel2.idX);
    param2.rel2ToY = _edje->findPart(desc->rel2.idY);

    if (_descriptionPos != 0.0)
        _chosenDescription = desc;

    _edje->dirty = true;
}

/*!
  \brief Unset QEdjeRealPart final state.

  The final state is only used for calculating intermediate states
  when a program is running.  When there's no program running, to
  avoid unnecessary calculations, we unset the final state.

  This also sets the chosenDescription to the initial state, since is
  the only one valid.

  \sa setInitialState(), setFinalState()
*/
void QEdjeRealPart::unsetFinalState()
{
    param2.description = NULL;
    param2.rel1ToX = NULL;
    param2.rel1ToY = NULL;
    param2.rel2ToX = NULL;
    param2.rel2ToY = NULL;

    _descriptionPos = 0.0;
    _chosenDescription = param1.description;
    _edje->dirty = true;
}

/*!
  \brief Calculate absolute parameters from description.

  Description may be relative to other parts, this method returns absolute
  parameters based on the current position of other relevant parts.

  \todo Runtime swallow parameters recalculate.
  \todo Calculate Aspect related parameters.
  \todo Update object_text part.
  \todo Save req size before checking for min and max sizes.
  \todo Calculate simple and dragable params.
  \todo Calculate fill params.
  \todo Confine params.
  \todo Color class.
  \todo Text align.
*/
void QEdjeRealPart::recalculateSingle(QEdjePartDescription *desc,
                                      QEdjePartDescription *chosen_desc,
                                      QEdjeRealPart *rel1_to_x,
                                      QEdjeRealPart *rel1_to_y,
                                      QEdjeRealPart *rel2_to_x,
                                      QEdjeRealPart *rel2_to_y,
                                      QEdjeRealPart *confine_to,
                                      QEdjeCalcParams *params,
                                      int flags)
{
    // FIXME: flags parameter is being overwritten
    flags = FLAG_XY;

    // find min/max boundaries
    recalculateSingleMinMax(desc, params);

    // update relative coords of top left & bottom right
    recalculateSingleGeometry(desc, rel1_to_x, rel1_to_y, rel2_to_x,
                              rel2_to_y, params, flags);

    if ((chosen_desc) && (part()->type == QEdjePart::TEXT)) {
        // TODO: extend text support
        QFont fp;
        fp.setWeight(QFont::Normal);
        fp.setPixelSize(chosen_desc->text.size);

        QString font = chosen_desc->text.font;
        if (font.endsWith(":style=Bold")) {
            font.truncate(font.length() - 11);
            fp.setWeight(QFont::Bold);
        } else if (font.endsWith(":style=Regular"))
            font.truncate(font.length() - 14);

        fp.setFamily(font);
        QZionText *zionText = (QZionText *) object();
        zionText->setFont(fp);
        zionText->setAlignment(chosen_desc->text.align);
    }

    // apply min/max size to the object
    recalculateSingleMinMaxApply(desc, params, flags);

    // fill
    recalculateSingleFill(desc, params, flags);

    // recalculate colors
    recalculateSingleColors(desc, params);

    // visible
    params->visible = desc->visible;

    // border
    if (flags & FLAG_X) {
        params->border.left = desc->border.left;
        params->border.right = desc->border.right;
    }
    if (flags & FLAG_Y) {
        params->border.top = desc->border.top;
        params->border.bottom = desc->border.bottom;
    }
}

/*!
  \brief Calculate position parameters from description.

  Description may be relative to other parts, this method returns the absolute
  position parameters based on the current position of the relative parts.
*/
void QEdjeRealPart::recalculateSingleGeometry(QEdjePartDescription *desc,
                                              QEdjeRealPart *rel1_to_x,
                                              QEdjeRealPart *rel1_to_y,
                                              QEdjeRealPart *rel2_to_x,
                                              QEdjeRealPart *rel2_to_y,
                                              QEdjeCalcParams *params,
                                              int flags)
{
    if (flags & FLAG_X) {
        if (rel1_to_x)
            params->x = (int)(desc->rel1.offset.x() +
                              rel1_to_x->pos().x() +
                              (desc->rel1.relative.x() *
                               rel1_to_x->size().width()));
        else
            params->x = (int)(desc->rel1.offset.x() +
                              (desc->rel1.relative.x() *
                               _edje->size().width()));

        if (rel2_to_x)
            params->w = (int)(desc->rel2.offset.x() +
                              rel2_to_x->pos().x() +
                              (desc->rel2.relative.x() *
                               rel2_to_x->size().width()) - params->x + 1);
        else
            params->w = (int)(desc->rel2.offset.x() +
                              (desc->rel2.relative.x() *
                               _edje->size().width()) - params->x + 1);
    }

    if (flags & FLAG_Y) {
        if (rel1_to_y)
            params->y = (int)(desc->rel1.offset.y() +
                              rel1_to_y->pos().y() +
                              (desc->rel1.relative.y() *
                               rel1_to_y->size().height()));
        else
            params->y = (int)(desc->rel1.offset.y() +
                              (desc->rel1.relative.y() *
                               _edje->size().height()));

        if (rel2_to_y)
            params->h = (int)(desc->rel2.offset.y() +
                              rel2_to_y->pos().y() +
                              (desc->rel2.relative.y() *
                               rel2_to_y->size().height()) - params->y + 1);
        else
            params->h = (int)(desc->rel2.offset.y() +
                              (desc->rel2.relative.y() *
                               _edje->size().height()) - params->y + 1);
    }
}

/*!
  \brief Recalculate fill parameters.

  \todo Calculate fill params in case of gradient.
  \todo Consider fill type.
*/
void QEdjeRealPart::recalculateSingleFill(QEdjePartDescription *desc,
                                          QEdjeCalcParams *params, int flags)
{
    params->smooth = desc->fill.smooth;

    if (flags & FLAG_X) {
        int fw = params->w;
        params->fill.x = desc->fill.posAbs.x() + (fw * desc->fill.posRel.x());
        params->fill.w = desc->fill.abs.x() + (fw * desc->fill.rel.x());
    }

    if (flags & FLAG_Y) {
        int fh = params->h;
        params->fill.y = desc->fill.posAbs.y() + (fh * desc->fill.posRel.y());
        params->fill.h = desc->fill.abs.y() + (fh * desc->fill.rel.y());
    }

    params->fill.angle = desc->fill.angle;
    params->fill.spread = desc->fill.spread;
}

/*!
  \brief Recalculate colors.
*/
void QEdjeRealPart::recalculateSingleColors(QEdjePartDescription *desc,
                                            QEdjeCalcParams *params)
{
    params->color = desc->color;
    params->color2 = desc->color2;
    params->color3 = desc->color3;

    if (!desc->colorClass.isEmpty()) {
        QEdjeColorClass *cc = _edje->colorClass(desc->colorClass);

        if (cc) {
            params->color = composeColorClass(cc->color, desc->color);
            params->color2 = composeColorClass(cc->color2, desc->color2);
            params->color3 = composeColorClass(cc->color3, desc->color3);
        }
    }
}

/*!
  Find min/max boundaries.
*/
void QEdjeRealPart::recalculateSingleMinMax(QEdjePartDescription *desc,
                                            QEdjeCalcParams *params)
{
    params->min.width = desc->min.width();
    if (swallowParams.min.width() > desc->min.width())
        params->min.width = swallowParams.min.width();

    if (swallowParams.max.width() <= 0)
        params->max.width = desc->max.width();
    else {
        if (desc->max.width() <= 0)
            params->max.width = swallowParams.max.width();
        else
            params->max.width = qMin(swallowParams.max.width(),
                                     desc->max.width());
    }

    params->min.height = desc->min.height();
    if (swallowParams.min.height() > desc->min.height())
        params->min.height = swallowParams.min.height();

    if (swallowParams.max.height() <= 0)
        params->max.height = desc->max.height();
    else {
        if (desc->max.height() <= 0)
            params->max.height = swallowParams.max.height();
        else
            params->max.height = qMin(swallowParams.max.height(),
                                      desc->max.height());
    }
}

/*!
  \brief Adjusts geometry parameters based on min and max sizes.
*/
void QEdjeRealPart::recalculateSingleMinMaxApply(QEdjePartDescription *desc,
                                                 QEdjeCalcParams *params, int flags)
{
    int minw = params->min.width;
    int minh = params->min.height;
    int maxw = params->max.width;
    int maxh = params->max.height;

    if ((flags & FLAG_X) && (minw >= 0) && (params->w < minw)) {
        params->x = (int)(params->x +
                          ((params->w - minw) * desc->align.x()));
        params->w = minw;
    }
    if ((flags & FLAG_Y) && (minh >= 0) && (params->h < minh)) {
        params->y = (int)(params->y +
                          ((params->h - minh) * desc->align.y()));
        params->h = minh;
    }

    if ((flags & FLAG_X) && (maxw >= 0) && (params->w > maxw)) {
        params->x = (int)(params->x +
                          ((params->w - maxw) * desc->align.x()));
        params->w = maxw;
    }
    if ((flags & FLAG_Y) && (maxh >= 0) && (params->h > maxh)) {
        params->y = (int)(params->y +
                          ((params->h - maxh) * desc->align.y()));
        params->h = maxh;
    }
}

/*!
  \brief Calculate current position of relative parts of this QEdjeRealPart object.
 */
void QEdjeRealPart::recalculateRelativeTo(int flags)
{
    if (flags & FLAG_X) {
        calculating |= flags & FLAG_X;

        if (param1.rel1ToX)
            param1.rel1ToX->recalculate(FLAG_X);
        if (param1.rel2ToX)
            param1.rel2ToX->recalculate(FLAG_X);
        if (param2.rel1ToX)
            param2.rel1ToX->recalculate(FLAG_X);
        if (param2.rel2ToX)
            param2.rel2ToX->recalculate(FLAG_X);
    }

    if (flags & FLAG_Y) {
        calculating |= flags & FLAG_Y;

        if (param1.rel1ToY)
            param1.rel1ToY->recalculate(FLAG_Y);
        if (param1.rel2ToY)
            param1.rel2ToY->recalculate(FLAG_Y);
        if (param2.rel1ToY)
            param2.rel1ToY->recalculate(FLAG_Y);
        if (param2.rel2ToY)
            param2.rel2ToY->recalculate(FLAG_Y);
    }
}

/*!
  Swallows a QZionObject into this real part.

  \param objSwallow Pointer to the object to be swallowed.

  \todo Remove every callback (even for events) and unset clipper for oldSwallow.
  \todo If someone deletes the QZion, unswallow automatically (create a callback).
  \todo Set swallow_params values.
  \todo Check for mouse_events and repeat_events on swallow and apply to object.
 */
void QEdjeRealPart::swallow(QZionObject *objSwallow)
{
    if (_part->type != QEdjePart::SWALLOW)
        return;

    unswallow(false);

    if (!objSwallow)
        return;

    QZionGroup *wrapper = static_cast<QZionGroup*>(object());
    wrapper->addObject(objSwallow);
    setSwallowParamsFromObject(objSwallow);

    _edje->dirty = true;
    _edje->recalculate();
}

/*!
  Unswallows a QZionObject from this real part.

  \param recalc Recalculate after unswallow. Default: true.
  \todo Remove every callback (even for events) and unset clipper for oldSwallow.
  \todo Unset swallow_params values.
*/
QZionObject *QEdjeRealPart::unswallow(const bool recalc)
{
    if (_part->type != QEdjePart::SWALLOW)
        return NULL;

    QZionObject *oldSwallow = embeddedObject();

    if (oldSwallow != NULL) {
        QZionGroup *wrapper = static_cast<QZionGroup*>(object());
        wrapper->removeObject(oldSwallow);
        setSwallowParamsFromObject(NULL);

        if (recalc) {
            _edje->dirty = true;
            _edje->recalculate();
        }
    }
    return oldSwallow;
}

/*!
  Get a QZionObject inside a swallow or group;
*/
QZionObject *QEdjeRealPart::embeddedObject()
{
    if (_part->type != QEdjePart::SWALLOW && _part->type != QEdjePart::GROUP)
        return NULL;

    QZionGroup *wrapper = static_cast<QZionGroup*>(object());

    if (wrapper->items()->isEmpty())
        return NULL;

    return wrapper->items()->at(0);
}

/*!
  Adjust swallow params to respect fixed min/max from object.

  \todo aspect mode
*/
void QEdjeRealPart::setSwallowParamsFromObject(QZionObject *obj)
{
    swallowParams.min = QSize(0, 0);
    swallowParams.max = QSize(-1, -1);

    if (!obj)
        return;

    QEdje *ed = dynamic_cast<QEdje *>(obj);
    if (ed) {
        swallowParams.min = ed->propMin();
        swallowParams.max = ed->propMax();
        return;
    }
}


QEdjeRealPartRectangle::QEdjeRealPartRectangle(QEdje *edje, QEdjePart *part)
  : QZionRectangle((QZionAbstractCanvas *)edje),
    QEdjeRealPart(edje, part)
{
    setMouseEvents(part->mouseEvents);
}

QEdjeRealPartImage::QEdjeRealPartImage(QEdje *edje, QEdjePart *part)
    : QEdjeImage((QZionAbstractCanvas *)edje,
                 edje->path(), QString("images/%1")
                 .arg(part->defaultDescription.imageId)),
      QEdjeRealPart(edje, part)
{
    setMouseEvents(part->mouseEvents);
}

QEdjeRealPartText::QEdjeRealPartText(QEdje *edje, QEdjePart *part)
    : QZionText((QZionAbstractCanvas *)edje,
                part->defaultDescription.text.text,
                QFont("SansSerif", 20, QFont::Bold)),
      QEdjeRealPart(edje, part)
{
    setMouseEvents(part->mouseEvents);
}

QEdjeRealPartTextBlock::QEdjeRealPartTextBlock(QEdje *edje, QEdjePart *part)
    : QZionTextBlock((QZionAbstractCanvas *)edje,
                     part->defaultDescription.text.text),
      QEdjeRealPart(edje, part)
{
    setMouseEvents(part->mouseEvents);
}

QEdjeRealPartGroup::QEdjeRealPartGroup(QEdje *edje, QEdjePart *part)
    : QZionGroup((QZionAbstractCanvas *)edje),
      QEdjeRealPart(edje, part)
{
    setMouseEvents(part->mouseEvents);
    setAttribute(HandleOutOfBoundsEvents);

    QEdje *inner = new QEdje(this, edje->path(), part->source);

    /* This will be automatically disconnected when we are destroyed. */
    inner->connectEdjeSignal("*", "*", this,
                             SLOT(signalProxy(const QString &,
                                              const QString &)));
    inner->show();
}

/*!
  Re-emits a signal that happened in the inner Edje object.

  GROUP parts contains Edje objects inside it.  Their signals are propagated
  as external signals by prepending the name of the GROUP part in the source.

  For example, if the inner object emits

     emission="mouse,clicked,1", source="button_box"

  and the part GROUP is named "contents",  then the outer object will emit

     emission="mouse,clicked,1", source="contents:button_box"
*/
void QEdjeRealPartGroup::signalProxy(const QString &emission,
                                     const QString &source)
{
    QString new_source = QString("%1:%2").arg(_part->name, source);
    _edje->emitEdjeSignal(emission, new_source);
}

QEdjeRealPartSwallow::QEdjeRealPartSwallow(QEdje *edje, QEdjePart *part)
    : QZionGroup((QZionAbstractCanvas *)edje),
      QEdjeRealPart(edje, part)
{
    setMouseEvents(part->mouseEvents);
}
