/*
 * 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 "qedje.h"

#include <qzion.h>

#include "qedjeimage.h"
#include "qedjeloader.h"
#include "qedjerealpart.h"
#include "qrunningprogram.h"


const QString QEdjePart::PATH_SEPARATOR_STRING = ":";


/*!
  Initialize Edje object.

  If some error happen during initialization, the constructor will reset
  group and path to empty strings, so you can check one of these attributes
  (via proper getters) to know if the construction went without errors.

  \param canvas The canvas that will be used to draw everything.
  \param filename Filename of the edje file.
  \param group Name of the group which it'll load.

  \sa QEdje()
*/
QEdje::QEdje(QZionAbstractCanvas *canvas, const QString &filename, const QString &group)
    : QZionGroup(canvas), _path(filename), _group(group),
      _notifyingConnections(0), _connectionsToDelete(false)
{
    if (group.isEmpty() || filename.isEmpty())
        return;

    dirty = true;
    frozen = 0;
    recalc = true;
    calculateOnly = false;
    edjeFile = NULL;
    lastObjectPressed = NULL;
    setAttribute(HandleOutOfBoundsEvents);

    if (populate()) {
        emitEdjeSignal("load", "");
    } else {
        _path = "";
        _group = "";
    }
}

/*!
  Unload Edje object
*/
QEdje::~QEdje()
{
    if (_notifyingConnections) {
        qWarning("QEdje: Do not delete object, '%p', "
                 "when processing its signals, use deleteLater()!", this);
    }

    // QRunningProgram destructor affect the _runningPrograms, so we have
    // to make a copy of the list (foreach does that for us) instead of
    // using qDeleteAll().
    foreach (QRunningProgram *rp, _runningPrograms) {
        delete rp;
    }

    QEdjeLoader::unload(_path);

    qDeleteAll(_connections);
    qDeleteAll(tableParts);
}

/*!
    \brief Change the Edje file being used for this object.

    This function expects the same group name from the original, and will
    re-swallow all swallowed objects.
*/
bool QEdje::setFile(const QString &filename)
{
    if (_group == "")
        return false;

    _path = filename;

    QList< QPair<QString, QZionObject *> > oldSwallows;

    foreach (QEdjeRealPart *rp, tableParts) {
        QRunningProgram *runProg = rp->runningProgram();
        if (runProg)
            delete runProg;

        if (rp->part()->type == QEdjePart::SWALLOW) {
            QPair<QString, QZionObject *> pair(QString(rp->part()->name),
                                                       rp->embeddedObject());
            oldSwallows.append(pair);
            rp->unswallow();
        }
    }

    qDeleteAll(tableParts);
    tableParts.clear();
    tablePrograms.clear();

    if (populate()) {
        emitEdjeSignal("load", "");
    } else {
        _path = "";
        _group = "";
        return false;
    }

    QPair<QString, QZionObject *> pair;
    foreach (pair, oldSwallows)
        partSwallow(pair.first, pair.second);

    return true;
}

/*!
  Set the object size and recalculate internal parts.
*/
void QEdje::setSize(const QSize &size)
{
    if (this->size() == size)
        return;

    QZionGroup::setSize(size);
    dirty = true;
    recalculate();
}

/*!
  Called every time the top level canvas has been resized.
  Note: Only the top level children will be notified.
*/
void QEdje::canvasResizeEvent(QResizeEvent *event)
{
    setSize(event->size());
}

/*!
  \internal

  Called every time a mouse move event occurs in one of the children.
*/
void QEdje::mouseObjectMoveEvent(QZionObject *src, QMouseEvent *)
{
    QEdjeRealPart *obj = dynamic_cast<QEdjeRealPart *>(src);
    if (!obj)
        return;

    emitEdjeSignal("mouse,move", obj->part()->name);
}

/*!
  \internal

  Called every time a mouse press event occurs in one of the children.
*/
void QEdje::mouseObjectPressEvent(QZionObject *src, QMouseEvent *e)
{
    QEdjeRealPart *obj = dynamic_cast<QEdjeRealPart *>(src);
    if (!obj)
        return;

    if (e->button() == Qt::LeftButton)
        emitEdjeSignal("mouse,down,1", obj->part()->name);
    else if (e->button() == Qt::MidButton)
        emitEdjeSignal("mouse,down,2", obj->part()->name);
    else if (e->button() == Qt::RightButton)
        emitEdjeSignal("mouse,down,3", obj->part()->name);

    lastObjectPressed = src;
}

/*!
  \internal

  Called every time a mouse release event occurs in one of the children.
*/
void QEdje::mouseObjectReleaseEvent(QZionObject *src, QMouseEvent *e)
{
    QEdjeRealPart *obj = dynamic_cast<QEdjeRealPart *>(src);
    if (!obj)
        return;

    if (e->button() == Qt::LeftButton)
        emitEdjeSignal("mouse,up,1", obj->part()->name);
    else if (e->button() == Qt::MidButton)
        emitEdjeSignal("mouse,up,2", obj->part()->name);
    else if (e->button() == Qt::RightButton)
        emitEdjeSignal("mouse,up,3", obj->part()->name);

    if (lastObjectPressed == src) {
        if (e->button() == Qt::LeftButton)
            emitEdjeSignal("mouse,clicked,1", obj->part()->name);
        else if (e->button() == Qt::MidButton)
            emitEdjeSignal("mouse,clicked,2", obj->part()->name);
        else if (e->button() == Qt::RightButton)
            emitEdjeSignal("mouse,clicked,3", obj->part()->name);
    }

    lastObjectPressed = NULL;
}

void QEdje::wheelObjectEvent(QZionObject *src, QWheelEvent *e)
{
    int dir, z;

    QEdjeRealPart *obj = dynamic_cast<QEdjeRealPart *>(src);
    if (!obj)
        return;

    dir = (e->orientation() == Qt::Horizontal) ? 1 : 0;
    z = (e->delta() > 0) ? -1 : 1; // qt logic != edje

    QString str;
    str.sprintf("mouse,wheel,%d,%d", dir, z);
    emitEdjeSignal(str, obj->part()->name);
}

/*!
  Swallows a given QZionObject on an edje's swallow part.
  \param part Edje swallow part's name.
  \param objSwallow A pointer to the object to be swallowed.
 */
void QEdje::partSwallow(const QString &part, QZionObject *objSwallow)
{
    QEdjeRealPart *realPart = getRealPartRecursive(part);

    if (realPart && realPart->part()->type == QEdjePart::SWALLOW)
        realPart->swallow(objSwallow);
}

/*!
  Unswallows what is inside an edje's swallow part.
  \param part Edje swallow part's name.
 */
QZionObject* QEdje::partUnswallow(const QString &part)
{
    QEdjeRealPart *realPart = getRealPartRecursive(part);

    if (realPart && realPart->part()->type == QEdjePart::SWALLOW)
        return realPart->unswallow();
    else
        return NULL;
}

/*!
  Returns and edje's part as a QZionObject.
  \param part Edje swallow part's name.
 */
QZionObject* QEdje::part(const QString &part)
{
    QEdjeRealPart *realPart = getRealPartRecursive(part);

    if (!realPart)
        return NULL;

    if (realPart->part()->type == QEdjePart::SWALLOW ||
        realPart->part()->type == QEdjePart::GROUP)
        return realPart->embeddedObject();

    return realPart->object();
}

/*!
    Returns a QEdjeColorClass searching by its name. If no entry
    was found returns NULL.
*/
QEdjeColorClass *QEdje::colorClass(const QString &name) const
{
    foreach (QEdjeColorClass *cc, edjeFile->colorClasses) {
        if (cc->name == name)
            return cc;
    }
    return NULL;
}

/*!
  Recalculates each part's position.
 */
void QEdje::recalculate()
{
    if (!dirty)
        return;

    if (frozen) {
        recalc = true;
        if (!calculateOnly)
            return;
    }

    foreach (QEdjeRealPart *ep, tableParts) {
        ep->calculated = FLAG_NONE;
        ep->calculating = FLAG_NONE;
    }

    foreach (QEdjeRealPart *ep, tableParts)
        if (ep->calculated != FLAG_XY)
            ep->recalculate((~ep->calculated) & FLAG_XY);

    dirty = false;
    if (!calculateOnly)
        recalc = false;
}

QEdjeRealPart* QEdje::getRealPartRecursive(const QString &part)
{
    QEdjeRealPart *realPart;
    QStringList path;

    path = part.split(QEdjePart::PATH_SEPARATOR_STRING);
    if (path.isEmpty())
         return NULL;

    realPart = getRealPartRecursiveHelper(path, 0);
    return realPart;
}

QEdjeRealPart* QEdje::realPart(const QString &part)
{
    foreach (QEdjeRealPart *realPart, tableParts)
         if (realPart->part()->name == part)
            return realPart;

    return NULL;
}

QEdjeRealPart* QEdje::getRealPartRecursiveHelper(QStringList path, int index)
{
    QEdje *edjeSwallowed;
    QEdjeRealPart *realPart;

    realPart = this->realPart(path[index]);
    index++;
    if (index >= path.size())
        return realPart;

    if ((!realPart) || (realPart->part()->type != QEdjePart::GROUP) ||
        (!realPart->object()))
        return NULL;

    /* verify if swallowed object is Edje */
    if (!realPart->object()->inherits("QEdje"))
        return NULL;

    edjeSwallowed = static_cast<QEdje*>(realPart->object());
    return edjeSwallowed->getRealPartRecursiveHelper(path, index);
}



/*!
    \brief Run an iteration of a STATE_SET program.

    We assume here that value is always less than 1.0.

    \sa runLastIteration()

    \param value The value of time at the current timeline.
    \param targets A list of targets to set the value.
    \param program The program running right now.

    \todo Stop program when object is deleted (_edje_block_break).
    \todo Delete programs when object is deleted.
*/
void QEdje::iterateState(qreal value, QList<QEdjeRealPart*> &targets)
{
    freeze();
    dirty = true;

    qDebug() << "animation progress: " << value;

    foreach (QEdjeRealPart *rp, targets)
        rp->setDescriptionPos(value);

    recalculate();

    thaw();
}

/*!
    Run the last iteration of a STATE_SET program with transition.
*/
void QEdje::runLastIteration(QList<QEdjeRealPart *> &targets,
                             QEdjeProgram *program, AfterExecution after)
{
    freeze();
    dirty = true;

    foreach (QEdjeRealPart *rp, targets) {
        rp->setInitialState(program->state, program->value);
        rp->unsetFinalState();
    }

    recalculate();

    if (after == doExecuteAfter)
        launchAfterPrograms(program);

    thaw();
}

/*!
    Launch a program.

    \param program Program to be launched.

    \todo Take a look at _edje_program_run later.
*/
void QEdje::launchProgram(QEdjeProgram *program)
{
    qDebug() << "launching program" << program->name << program->action;

    freeze();
    switch (program->action) {

    case QEdjeProgram::ACTION_STATE_SET:
        programStateSet(program);
        break;

    case QEdjeProgram::ACTION_SIGNAL_EMIT:
        emitEdjeSignal(program->state, program->state2);
        launchAfterPrograms(program);
        break;

    case QEdjeProgram::ACTION_STOP:
        programStop(program);
        launchAfterPrograms(program);
        break;

    default:
        launchAfterPrograms(program);
    }
    thaw();
}

/*!
    Launch the programs in the 'after' section of a given program.
*/
void QEdje::launchAfterPrograms(QEdjeProgram *program)
{
    freeze();
    foreach (int id, program->after) {
        QEdjeProgram *pr = findProgram(id);
        if (pr)
            launchProgram(pr);
    }
    thaw();
}

/*!
    Executes all programs triggered by a signal.

    \param signal Signal's name that triggers the programs.
    \param part Part's name that emitted the signal.
*/
void QEdje::launchPrograms(const QString &signal, const QString &part)
{
    qDebug() << "searching programs with signal:" << signal << part;

    foreach (QEdjeProgram *p, tablePrograms) {
        if (p->signal == signal &&
            (p->source == "*" || part == "*" || part.isEmpty() ||
             p->source == part)) {
            qDebug() << "found program:" << p->name;
            launchProgram(p);
        }
    }
}

/*!
    Executes a STATE_SET program.

    A STATE_SET changes the state of one or more parts (they are the
    program targets) to a new state. A program execution will stop all
    other programs already running that are affecting its targets.

    If there is a transition (ie., the change will be animated) we
    create a QRunningProgram object to take care of the animation.

    \param program Program to be launched. Assumes its a STATE_SET program.
*/
void QEdje::programStateSet(QEdjeProgram *program)
{
    QList<QEdjeRealPart*> targets;

    // First, stop programs running that have parts in common
    QSet<QRunningProgram *> toDelete;
    foreach (int tid, program->targets) {
        QEdjeRealPart *rp = findPart(tid);

        if (!rp)
            continue;

        if (rp->runningProgram())
            toDelete.insert(rp->runningProgram());

        targets.append(rp);
    }

    foreach (QRunningProgram *current, toDelete)
        delete current;

    // Then, run our program
    int duration = program->tweenTime * 1000;
    if (duration > 0) {
        QRunningProgram *runProg =
            new QRunningProgram(program, this, duration,
                                (TweenMode)program->tweenMode, targets);

        foreach (QEdjeRealPart *rp, targets) {
            rp->setRunningProgram(runProg);
            rp->setDescriptionPos(0.0);
            rp->setFinalState(runProg->state(), runProg->value());
        }

        runProg->start();

    } else { // duration == 0
        runLastIteration(targets, program, doExecuteAfter);
    }
}

/*!
    Executes an ACTION_STOP program.

    \param program Program to be launched. Assumes its a ACTION_STOP program.
*/
void QEdje::programStop(QEdjeProgram *program)
{
    QSet<QRunningProgram *> toDelete;

    foreach (int tid, program->targets) {
        foreach (QRunningProgram *runProg, _runningPrograms) {
            if (runProg->program()->id == tid)
                toDelete.insert(runProg);
        }
    }

    foreach (QRunningProgram *runProg, toDelete)
        delete runProg;
}

/*!
  Freeze the object, avoiding some operations on it.
*/
int QEdje::freeze()
{
    frozen++;
    return frozen;
}

/*!
  Thaw the object, letting it perform some operations.
*/
int QEdje::thaw()
{
    frozen--;
    if (frozen < 0) {
        qDebug() << "Over thaw";
        frozen = 0;
    }
    if ((frozen == 0) && (recalc)) {
        qDebug() << "Thaw recalculate";
        recalculate();
    }
    return frozen;
}

/*!
    Load all parts of this group and put them in the canvas.
*/
bool QEdje::populate()
{
    edjeFile = QEdjeLoader::load(_path);
    if (!edjeFile) {
        qWarning("QEdje::populate: Unable to load edje file '%s'",
                 _path.toLatin1().data());
        return false;
    }

    QEdjePartCollection *gp = edjeFile->collectionHash[_group];
    if (!gp) {
        qWarning("QEdje::populate: Group '%s' not found in file '%s'",
                 _group.toLatin1().data(), _path.toLatin1().data());
        return false;
    }

    _propMin = gp->propMin;
    _propMax = gp->propMax;

    foreach (QEdjePart *part, gp->parts) {
        QEdjeRealPart *rpart;

        switch (part->type) {
        case QEdjePart::RECT:
            rpart = new QEdjeRealPartRectangle(this, part);
            break;

        case QEdjePart::IMAGE:
            if (part->defaultDescription.imageId < 0) {
                /* When a part has no type info, it defaults to IMAGE according to
                 * http://wiki.enlightenment.org/index.php/Edje_Part
                 * As edje_cc allows the image.normal to be left out, we should at
                 * least show a critical
                 */
                qCritical("Group \"%s\": image attributes missing for part \"%s\"",
                          _group.toLatin1().data(), part->name.toLatin1().data());
            }
            rpart = new QEdjeRealPartImage(this, part);
            break;

        case QEdjePart::TEXT:
            rpart = new QEdjeRealPartText(this, part);
            break;

        case QEdjePart::TEXTBLOCK:
            rpart = new QEdjeRealPartTextBlock(this, part);
            break;

        case QEdjePart::SWALLOW:
            rpart = new QEdjeRealPartSwallow(this, part);
            break;

        case QEdjePart::GROUP:
            rpart = new QEdjeRealPartGroup(this, part);
            break;

        case QEdjePart::GRADIENT:
            qWarning("QEdje::populate: Gradient type not supported yet");
            continue;

        default:
            qWarning("QEdje::populate: Invalid part found in edje group");
            continue;
        }

        rpart->object()->show();
        tableParts.append(rpart);
    }

    foreach (QEdjeRealPart *rpart, tableParts) {
        rpart->setInitialState("default", 0.0);
        rpart->unsetFinalState();
    }

    foreach (QEdjeRealPart *realPart, tableParts) {
        int clipToId = realPart->part()->clipToId;

        if (clipToId < 0 || clipToId >= tableParts.size())
            continue;

        realPart->setClipTo(tableParts[clipToId]);
    }

    foreach (QEdjeProgram *program, gp->programs) {
        tablePrograms.append(program);
    }

    recalculate();
    emitEdjeSignal("show", "");
    return true;
}

struct QEdje::Connection
{
    Connection(const QString &emission, const QString &source,
               QObject *receiver, int method) {
        this->emission = emission;
        this->source = source;
        this->receiver = receiver;
        this->method = method;
        this->deleteMe = false;
    }

    QString emission;
    QString source;
    QObject *receiver;
    int method;

    // Used to mark this connection to be deleted, this is used when we are
    // emitting an edje signal, and iterating in a list of connections, see
    // notifyConnections() for details.
    bool deleteMe;
};

static bool signalMatches(const QString &emission, const QString &source,
                          const QString &emissionCandidate,
                          const QString &sourceCandidate)
{
    // XXX: add Wildcards
    return ((emission == emissionCandidate || emissionCandidate == "*") &&
            (source.isEmpty() || source == "*" ||
             sourceCandidate == "*" || source == sourceCandidate));
}


/*!
    \brief Emits a signal for the edje group.

    Calls the programs that listen to this signal and notify all the methods
    connected via connectEdjeSignal().

    \param emission Signal's name.
    \param source Signal's source name.

    \sa connectEdjeSignal()

    \todo Propagate signal to edje swallows.
 */
void QEdje::emitEdjeSignal(const QString &emission, const QString &source)
{
    qDebug() << "emit signal" << emission << source;
    launchPrograms(emission, source);
    notifyConnections(emission, source);
}

/*!
    \brief Emits a signal to connected methods.

    Calls all the methods (slots or Qt signals) that are connected to a
    specific emission-source pair.

    \param emission Signal's name.
    \param source Signal's source name.

    \sa emitEdjeSignal(), connectEdjeSignal()

    \todo Document the rules for how emission/source matches (wildcards).
*/
void QEdje::notifyConnections(const QString &emission, const QString &source)
{
    // For qt_metacall() we need to pass an array of void pointers to
    // our arguments, since one of our arguments is a pointer to the
    // object that is sending the signal, we need a pointer to this
    // pointer. In this case a pointer to 'this', however since 'this'
    // is special we have no way to get its address. So we create
    // another pointer in the stack, pointing to our object (just like
    // 'this').
    void *_this = reinterpret_cast<void*>(this);

    // Remember, an array of void POINTERS to the arguments we are passing,
    // qt_metacall will do the right thing with them if we connect them
    // correctly.
    void *_args[] =
        { 0,
          const_cast<void*>(reinterpret_cast<const void*>(&emission)),
          const_cast<void*>(reinterpret_cast<const void*>(&source)),
          reinterpret_cast<void*>(&_this) };

    // Keep track of whether we are notifying connections, i.e., calling
    // slots/signals that are connected to edje signals.  We track that
    // to properly warn if the destructor is called during this process,
    // the proper way to delete an object that just emitted a signal is
    // to call deleteLater().  It's also useful to know when is safe to
    // process connections marked for deletion, see below.
    _notifyingConnections++;

    foreach (const Connection *c, _connections) {
        // Skip connections marked to deletion.  They will be deleted after
        // the last loop of signal emittion happens.
        if (c->deleteMe)
            continue;

        if (signalMatches(emission, source, c->emission, c->source))
            c->receiver->qt_metacall(QMetaObject::InvokeMetaMethod,
                                     c->method, _args);
    }

    _notifyingConnections--;

    // If we are not emitting connections anymore, clean up our connection
    // list, actually removing those that were marked to delete.
    if (!_notifyingConnections && _connectionsToDelete) {
        QStack<int> toDelete;

        for (int i = 0; i < _connections.count(); ++i) {
            const Connection *c = _connections.at(i);
            if (c->deleteMe)
                toDelete.push(i);
        }

        // Note: the use of pop() here make sure that take() doesn't change
        // the remaining indexes still in toDelete.
        while (!toDelete.isEmpty()) {
            delete _connections.takeAt(toDelete.pop());
        }

        _connectionsToDelete = false;
    }
}

/*!
    \brief Finds the correct method index for a given QObject and method name.

    Methods are either slots or Qt signals. They are identified by an int
    value. This value is used to call the method (via qt_metacall).

    \returns The int identifying the method or -1 if no method was found.

    \sa connectEdjeSignal()
*/
static int findMethodIndex(QObject *receiver, const char *method)
{
    int code = method[0] - '0';
    ++method; // remove code from method signature

    const QMetaObject *rmeta = receiver->metaObject();

    // Optimistic, assuming no normalization will be needed
    int method_index = -1;
    switch (code) {
    case QSLOT_CODE:
        method_index = rmeta->indexOfSlot(method);
        break;
    case QSIGNAL_CODE:
        method_index = rmeta->indexOfSignal(method);
        break;
    default:
        qWarning("QEdje::connectEdjeSignal(): Use the SLOT or SIGNAL macro to "
                 "specify the method %s::%s", rmeta->className(), method);
        return -1;
    }

    // Try again, but normalizing
    if (method_index < 0) {
        QByteArray normalizedMethod = QMetaObject::normalizedSignature(method);
        method = normalizedMethod.constData();
        switch (code) {
        case QSLOT_CODE:
            method_index = rmeta->indexOfSlot(method);
            break;
        case QSIGNAL_CODE:
            method_index = rmeta->indexOfSignal(method);
            break;
        }
    }

    if (method_index < 0) {
        qWarning("QEdje::connectEdjeSignal(): No such method %s::%s",
                 rmeta->className(), method);
        return -1;
    }

    return method_index;
}

/*!
  \internal
  \brief Return whether a given object have Edje connections with this QEdje.
*/
bool QEdje::isReceiver(QObject *object) const
{
    foreach (const Connection *c, _connections) {
        if (c->receiver == object)
            return true;
    }

    return false;
}

/*!
    \brief Connects a method to be called by some emission-source pair.

    Connects a method (either a slot or an Qt signal) to a specific
    emission-source pair. The method is specified the same way as in
    QObject::connect(), using the SLOT() or the SIGNAL() macros.

    To get called for all the signals emitted, use

        e->connectEdjeSignal("*", "*", obj, SLOT(myslot()))

    The expected signature for methods is:

        method(const QString &emission, const QString &source, QObject *edje)

    Remember you can always connect using method with less arguments than
    the signal emits. For example, connecting something to a slot of type
    "myslot()" is fine.

    Like Qt signals, if either of the objects is destroyed, the signal is
    disconnected.

    \param emission Name of the signal to connect.
    \param source Expected source to connect.
    \param receiver Object you want to be called when signal is emitted.
    \param method Method, use either SLOT() or SIGNAL() macro to fill this.

    \return Whether the signal was connected or not.

    \sa emitEdjeSignal(), disconnectEdjeSignal()

    \todo Describe the rules for matching emission-source pairs.
*/
bool QEdje::connectEdjeSignal(const QString &emission, const QString &source,
                              QObject *receiver, const char *method)
{
    static const char *signature =
        "edjeSignal(const QString &, const QString &, QEdje *)";

    int method_index = findMethodIndex(receiver, method);
    if (method_index < 0)
        return false;

    if (!QMetaObject::checkConnectArgs(signature, method)) {
        method++; // remove code from method signature
        qWarning("QEdje::connectEdjeSignal: Incompatible receiver arguments"
                 "\n\tQEdje::%s --> %s::%s",
                 signature, receiver->metaObject()->className(), method);
        return false;
    }

    // Check if the object is already a receiver, if not we connect to
    // its destroyed() signal, to delete edje connections if the object
    // is deleted.  For each object we connect to destroyed only once.
    if (!isReceiver(receiver)) {
        connect(receiver, SIGNAL(destroyed(QObject *)),
                SLOT(clearConnections(QObject *)));
    }

    Connection *c = new Connection(emission, source, receiver, method_index);
    _connections.append(c);

    return true;
}

/*!
    \brief Disconnects a method from a emission-source pair.

    Use this to disconnect from an Edje emission-source signal. This
    function will remove just one connection per time, no wildcards
    are processed, the emission and source are taken verbatim, so use
    the same strings that were used to connect.

    \param emission Name of the signal to disconnect.
    \param source Expected source to disconnect.
    \param receiver Object you want to disconnect, you can pass 0 to
                    match every receiver connected. Note that if this
                    parameter is 0, the method should be too.
    \param method Method, use either SLOT() or SIGNAL() macro to fill
                  this, or 0 to match every method.

    \return Whether the signal was disconnected or not.

    \sa connectEdjeSignal()
*/
bool QEdje::disconnectEdjeSignal(const QString &emission, const QString &source,
                                QObject *receiver, const char *method)
{
    if (receiver == 0 && method != 0) {
        qWarning("QEdje::disconnectEdjeSignal: Unexpected null parameter");
        return false;
    }

    int method_index = -1;
    if (method != 0) {
        method_index = findMethodIndex(receiver, method);
        if (method_index < 0)
            return false;
    }

    QStack<int> toDelete;
    QSet<QObject *> receiversToDelete;
    QSet<QObject *> stillReceivers;

    int count = _connections.size();

    for (int i = 0; i < count; ++i) {
        const Connection *c = _connections.at(i);

        if (c->emission == emission
            && c->source == source
            && (!receiver || c->receiver == receiver)
            && (method_index < 0 || c->method == method_index))
        {
            toDelete.push(i);
            receiversToDelete.insert(c->receiver);
        } else {
            stillReceivers.insert(c->receiver);
        }
    }

    if (toDelete.isEmpty())
        return false;

    // If we are inside a notifying iteration, just mark objects as deleted.
    if (_notifyingConnections) {
        while (!toDelete.isEmpty())
            _connections[toDelete.pop()]->deleteMe = true;
        _connectionsToDelete = true;
    } else {
        while (!toDelete.isEmpty())
            delete _connections.takeAt(toDelete.pop());
    }

    // If the receiver objects have no other edje signals, we don't
    // care if they are destroyed.
    receiversToDelete -= stillReceivers;

    foreach (QObject *obj, receiversToDelete) {
        disconnect(obj, SIGNAL(destroyed(QObject *)),
                   this, SLOT(clearConnections(QObject *)));
    }

    return true;
}

/*!
    \brief Disconnects all connections for a given method.

    \param receiver Object you want to disconnect
    \param method Method, use either SLOT() or SIGNAL() macro to fill this,
                  or 0 to mean every method.

    \return Whether the signal was disconnected or not.

    \sa connectEdjeSignal()
*/
bool QEdje::disconnectEdjeSignal(QObject *receiver, const char *method)
{
    if (receiver == 0 && method != 0) {
        qWarning("QEdje::disconnectEdjeSignal: Unexpected null parameter");
        return false;
    }

    int method_index = -1;
    if (method != 0) {
        method_index = findMethodIndex(receiver, method);
        if (method_index < 0)
            return false;
    }

    QStack<int> toDelete;
    bool stillReceiver = false;
    int count = _connections.size();

    for (int i = 0; i < count; ++i) {
        const Connection *c = _connections.at(i);
        if (c->receiver == receiver) {
            if (method_index < 0 || c->method == method_index)
                toDelete.push(i);
            else
                stillReceiver = true;
        }
    }

    if (toDelete.isEmpty())
        return false;

    // If we are inside a notifying iteration, just mark objects as deleted.
    if (_notifyingConnections) {
        while (!toDelete.isEmpty())
            _connections[toDelete.pop()]->deleteMe = true;
        _connectionsToDelete = true;
    } else {
        while (!toDelete.isEmpty())
            delete _connections.takeAt(toDelete.pop());
    }

    // If the receiver object has no other edje signals, we don't care if
    // it is destroyed.
    if (!stillReceiver) {
        disconnect(receiver, SIGNAL(destroyed(QObject *)),
                   this, SLOT(clearConnections(QObject *)));
    }

    return true;
}

void QEdje::clearConnections(QObject *obj)
{
    disconnectEdjeSignal(obj);
}

/*!
  Fix a minimum size for a internal part.
*/
void QEdje::setFixedPartMinSize(const QString &part, QSize &size)
{
    QEdjeRealPart *rp = realPart(part);

    if (rp && rp->fixedMinSize() != size) {
        rp->setFixedMinSize(size);
        dirty = true;
        recalculate();
    }
}

/*!
  Fix a maximum size for a internal part.
*/
void QEdje::setFixedPartMaxSize(const QString &part, QSize &size)
{
    QEdjeRealPart *rp = realPart(part);

    if (rp && rp->fixedMaxSize() != size) {
        rp->setFixedMaxSize(size);
        dirty = true;
        recalculate();
    }
}


/*!
  Returns a list of group names from an edj file.
*/
QStringList groupNamesFromFile(const QString &filename)
{
    QStringList result;
    QEdjeFile *f = QEdjeLoader::load(filename);

    if (!f)
        return result;

    foreach (QEdjePartCollectionDirectoryEntry *e, f->collectionDir)
        result.append(e->entry);

    QEdjeLoader::unload(filename);

    return result;
}
