/****************************************************************************
**
** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (developer.feedback@nokia.com)
**
** This file is part of the HbWidgets module of the UI Extensions for Mobile.
**
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at developer.feedback@nokia.com.
**
****************************************************************************/

#include <hbdevicedialog.h>
#include <hbdevicedialogtrace_p.h>
#include "hbdeviceprogressdialog.h"
#include "hbdeviceprogressdialog_p.h"

#include <QTimer>
#include <QAction>

HbDeviceProgressDialogPrivate::HbDeviceProgressDialogPrivate() : QObject(),
    mVisible(false),
    mUpdateTimerId(0)
{
    TRACE_ENTRY
    for(int i = 0; i < NumActions; i++) {
        mDefaultActions[i] = 0;
    }
    TRACE_EXIT
}

HbDeviceProgressDialogPrivate::~HbDeviceProgressDialogPrivate()
{
    TRACE_ENTRY
    cancel();
    killTimer(mUpdateTimerId);
    for(int i = 0; i < NumActions; i++) {
        delete mDefaultActions[i];
    }
    TRACE_EXIT
}

void HbDeviceProgressDialogPrivate::initProperties(HbProgressDialog::ProgressDialogType type)
{
    for(int i = 0; i < NumProperties; i++) {
        mProperties[i].mFlags = NoFlags;
    }
    clearActions();
    if (!mDefaultActions[CancelButton]) {
        mDefaultActions[CancelButton] = new QAction(0);
    }
    mActions[CancelButton].mAction = mDefaultActions[CancelButton];

    QString text;
    mProperties[ProgressType].mValue.setValue(static_cast<int>(type));
    mProperties[ProgressType].mFlags = Modified;
    if (type == HbProgressDialog::WaitDialog){
        q->setRange(0,0);
        q->setAutoClose(false);
    } else {
        q->setRange(0,100);
        q->setAutoClose(true);
    }
    q->setProgressValue(0);
    q->setText(text);
    q->setIconName(text);
    q->setAnimationDefinition(text);
}

// Send properties to server
void HbDeviceProgressDialogPrivate::sendToServer(bool show)
{
    killTimer(mUpdateTimerId);
    mUpdateTimerId = 0;

    // If this is update but show has not been called, return.
    if (!show && !mVisible) {
      return;
    }

    // If this is update but no properties have been mofified, return
    if (!show && !propertiesModified()) {
        return;
    }

    static const char * const propertyNames[] = {
        "progressDialogType",
        "maximum",
        "minimum",
        "value",
        "autoClose",
        "text",
        "iconName",
        "animationDefinition"
    };

    QVariantMap parameters;
    for(int i = 0; i < NumProperties; i++) {
        if (mProperties[i].mFlags & Modified) {
            if (show || !(mProperties[i].mFlags & SentToServer)) {
                parameters.insert(propertyNames[i], mProperties[i].mValue);
                mProperties[i].mFlags |= SentToServer;
            }
        }
    }

    static const char * const actionNames[] = {
        "cancelAction"
    };

    for(int i = 0; i < NumActions; i++) {
        if (mActions[i].mFlags & Modified) {
            if (show || !(mActions[i].mFlags & SentToServer)) {
                QString actionData; // empty removes action at the plugin
                if (mActions[i].mAction) {
                    actionData.append("t:").append(mActions[i].mAction->text());
                }
                parameters.insert(actionNames[i], actionData);
                mActions[i].mFlags |= SentToServer;
            }
        }
    }

    if (show) {
        for(int i = 0; i < NumActions; i++) {
            mActions[i].mTriggered = false;
        }
        if (mDeviceDialog.show("com.nokia.hb.deviceprogressdialog/1.0", parameters)) {
            mVisible = true;
        } else {
            // Failed to show the device dialog. Start a one shot to emit aboutToClose() signal.
            QTimer::singleShot(0, this, SLOT(aboutToClose()));
        }
    } else {
        mDeviceDialog.update(parameters);
    }
}

// Check if any properties have been modified
bool HbDeviceProgressDialogPrivate::propertiesModified() const
{
    for(int i = 0; i < NumProperties; i++) {
        if ((mProperties[i].mFlags & Modified) && !(mProperties[i].mFlags & SentToServer)) {
            return true;
        }
    }
    for(int i = 0; i < NumActions; i++) {
        if ((mActions[i].mFlags & Modified) && !(mActions[i].mFlags & SentToServer)) {
            return true;
        }
    }
    return false;
}

// Clear actions
void HbDeviceProgressDialogPrivate::clearActions()
{
    for(int i = 0; i < NumActions; i++) {
        mActions[i].mAction = 0;
        mActions[i].mFlags = NoFlags;
        mActions[i].mTriggered = false;
    }
}

// Set int property
void HbDeviceProgressDialogPrivate::setProperty(PropertySelector propertySelector, int value)
{
    Property &property = mProperties[propertySelector];
    property.mValue.setValue(value);
    property.mFlags = Modified;
    scheduleUpdateEvent();
}

// Set string property
void HbDeviceProgressDialogPrivate::setProperty(PropertySelector propertySelector,
    const QString &value)
{
    Property &property = mProperties[propertySelector];
    property.mValue.setValue(value);
    property.mFlags = Modified;
    scheduleUpdateEvent();
}

void HbDeviceProgressDialogPrivate::init(HbProgressDialog::ProgressDialogType type)
{
    TRACE_ENTRY

    initProperties(type);
    connect(&mDeviceDialog, SIGNAL(deviceDialogClosed()), this, SLOT(aboutToClose()));
    connect(&mDeviceDialog, SIGNAL(dataReceived(QVariantMap)), this, SLOT(dataReceived(QVariantMap)));

    TRACE_EXIT
}

void HbDeviceProgressDialogPrivate::cancel()
{
    TRACE_ENTRY
    mDeviceDialog.cancel();
    TRACE_EXIT
    return;
}

// Schedule event to update changed properties to device dialog server. update() is not
// called after each time a property is set. Instead an event is scheduled in order to
// update all changed properties in one shot.
void HbDeviceProgressDialogPrivate::scheduleUpdateEvent()
{
    if (mVisible && mUpdateTimerId == 0) {
        mUpdateTimerId = startTimer(0);
    }
}

void HbDeviceProgressDialogPrivate::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == mUpdateTimerId) {
        sendToServer(false);
    }
}

HbDeviceProgressDialogPrivate::ActionSelector HbDeviceProgressDialogPrivate::actionSelector(
    HbDeviceProgressDialog::ActionRole role)
{
    static const ActionSelector selectors[] = {
        CancelButton
    };
    const unsigned numSelectors = sizeof(selectors) / sizeof(selectors[0]);
    unsigned index = role;
    if (index >= numSelectors) {
        Q_ASSERT(false);
        return InvalidSelector;
    }
    else {
        return selectors[index];
    }
}

void HbDeviceProgressDialogPrivate::aboutToClose()
{
    TRACE_ENTRY

    mVisible = false;
    emit q->aboutToClose();

    TRACE_EXIT
    return;
}

void HbDeviceProgressDialogPrivate::dataReceived(QVariantMap data)
{
    const char *key = "act";
    QVariantMap::const_iterator i = data.find(key);
    if (i != data.constEnd()) {
        if (i.value().toString() == "c") {
            // Client has pressed button. Signal action if one is set. Otherwise emit
            // cancelled() signal.
            mActions[CancelButton].mTriggered = true;
            QAction *action = mActions[CancelButton].mAction;
            if (action) {
                action->trigger();
                emit q->cancelled();
            }
        }
    }
}

/*!
    \stable
    \hbwidgets

    \class HbDeviceProgressDialog
    \brief HbDeviceProgressDialog displays a progress dialog on top all applications.

    HbDeviceProgressDialog is a device-dialog version of HbProgressDialog. It is a modal
    dialog and displayed on top all applications by a device-dialog service.
    HbDeviceProgressDialog is a client of the service.

    HbDeviceProgressDialog provides a similar kind of interface as HbProgressDialog.
    Progress dialogs are always shown asynchronously as application needs to perform an operation
    and update the dialog while the dialog is showing.

    For content it provides wait animation or progress bar, text, icon or icon-animation and a
    cancel button.

    Two different dialogs are supported: wait and progress. Progress dialog displays a progress
    bar indicating progress of the operation. Wait dialog displays a wait animation in place of
    the progress bar.

    Wait dialog is used when length of an operation cannot be determined beforehand.
    The dialog is closed by user pressing dialog cancel button or by an application closing
    the dialog after the operation has finished by HbDeviceProgressDialog::close().

    Progress dialog is used when the length of operation can be determined. beforehand.
    For example when deleting a number of files, the progress could be shown as a
    percentage of the files deleted. Application updates the progress bar during the
    operation. The dialog closes by user pressing the cancel button, the application closing the
    dialog after the operation is finished or automatically when progress value reaches a
    maximum, HbDeviceProgressDialog::autoClose().

    Device progress dialog is launched when show() is called. Launched dialog can be updated by
    setters. Changed properties are updated to the displayed dialog automatically next time event
    loop is entered or updated values can be sent immediately by calling update().
    Because updating a dialog requires interprocess communication, it's advisable to fully construct the
    progress dialog before calling show().

    An animation can replace an icon on device progress dialog. Supported icon animation formats are
    following:

    - GIF (.gif)
    - MNG (.mng)
        - Frame animations

    \section _platform_spec Platform-specific implementation notes for HbDeviceProgressDialog

    \subsection _nonsymbian Non-Symbian
    Device dialog service is implemented only for the Symbian platform. On other platforms device 
    progress dialogs are displayed on client's main window.

    \section _code_samples Sample code

    An example showing a wait dialog:

    \code
    mDialog = new HbDeviceProgressDialog(HbProgressDialog::WaitDialog);
    mDialog->setText("Connecting...");
    mDialog->show();
    \endcode

    An example showing a progress dialog:

    \include tsrc/fute/ultimatecodesnippet/deviceprogressdialog.cpp

    Showing an icon animation.

    Create an animation definition file:
    \code
    <animations>
        <icon name="frame_anim_looping" playmode="loop">
            <frame duration="100">c:\icon1.svg</frame>
            <frame duration="200">c:\icon2.svg</frame>
            <frame duration="300">c:\icon3.svg</frame>
            </icon>
    </animations>
    \endcode

    Create HbDeviceMessageBox in a way described before and
    set definition file and animation's logical name.

    \code
    QString animationDefinitionXML("c:\animation.axml");
    QString logicalIconName("frame_anim_looping");

    msg->setAnimationDefinition(animationDefinitionXML);
    msg->setIconName(logicalIconName);
    msg->show();
    \endcode

    \sa HbProgressDialog, HbDialog, HbDeviceDialog, CHbDeviceProgressDialogSymbian
*/

/*!
    \fn void HbDeviceProgressDialog::aboutToClose();

    This signal is emitted when a device progress dialog has closed. The closing may
    be a result of close() being called, a dialog with autoClose property has reached
    its maximum value or user pressing cancel button. It is not emitted if close() is
    called before show().
 */

/*!
    \fn void HbDeviceProgressDialog::cancelled();

    This signal is emitted when the device progress dialog is closed by user pressing the
    "cancel" button. It is not emitted if dialog is closed for any other reason.
 */

/*!
    \enum HbDeviceProgressDialog::ActionRole
    Defines roles for actions set into a progress dialog.
*/
/*!
    \var HbDeviceProgressDialog::ActionRole HbDeviceProgressDialog::InvalidRole
    No action.
*/
/*!
    \var HbDeviceProgressDialog::ActionRole HbDeviceProgressDialog::CancelButtonRole
    Cancel button action.
*/

/*!
    Constructs HbDeviceProgressDialog.

    \param type Must be one of the defined HbProgressDialog::ProgressDialogType enumerations.
    \param parent Parent pointer or 0.
*/
HbDeviceProgressDialog::HbDeviceProgressDialog(HbProgressDialog::ProgressDialogType type, QObject *parent) :
    QObject(parent), d(new HbDeviceProgressDialogPrivate)
{
    TRACE_ENTRY
    d->q = this;
    d->init(type);
    TRACE_EXIT
}

/*!
    Constructs HbDeviceProgressDialog with \a parent.
*/
HbDeviceProgressDialog::HbDeviceProgressDialog(QObject *parent) :
    QObject(parent), d(new HbDeviceProgressDialogPrivate)
{
    TRACE_ENTRY
    d->q = this;
    d->init(HbProgressDialog::ProgressDialog);
    TRACE_EXIT
}

/*!
    Destructs HbDeviceProgressDialog. The dialog launched by show() is closed as well.
*/
HbDeviceProgressDialog::~HbDeviceProgressDialog()
{
    TRACE_ENTRY
    delete d;
    TRACE_EXIT
}

/*!
    Shows a dialog and returns immediately without waiting for it to close. Closing
    is indicated by aboutToClose() signal. User cancellation is indicated by cancelled()
    signal. Button press is also indicated by QAction::triggered() signal.
    The dialog can be updated while showing by property setters. A new dialog is launched
    each time show() is called.

    \sa update(), aboutToClose(), cancelled()
*/
void HbDeviceProgressDialog::show()
{
    TRACE_ENTRY
    d->sendToServer(true);
    TRACE_EXIT
 }

/*!
    Updates changed properties of a launched progress dialog to device dialog service using
    interprocess communication. Has no effect if show() has not been called or dialog has
    closed already. Calling update() is optional as updating any property schedules an event
    and the dialog is updated next time Qt event loop executes.

    \sa show()
*/
void HbDeviceProgressDialog::update()
{
    TRACE_ENTRY
    d->sendToServer(false);
    TRACE_EXIT
}

/*!
    Closes the dialog.
*/
void HbDeviceProgressDialog::close()
{
    TRACE_ENTRY
    return d->cancel();
    TRACE_EXIT
}

/*!
    Returns an action user triggered causing the dialog to close. Returns 0 if none of the actions were
    triggered and dialog was closed for other reason.

    \sa show(), action()
*/
const QAction *HbDeviceProgressDialog::triggeredAction() const
{
    for(int i = 0; i < HbDeviceProgressDialogPrivate::NumActions; i++) {
        if (d->mActions[i].mTriggered) {
            return d->mActions[i].mAction;
        }
    }
    return 0;
}

/*!
    Sets progress bar maximum value.

    \sa maximum()
*/
void HbDeviceProgressDialog::setMaximum(int max)
{
    TRACE_ENTRY
    // Don't allow wait dialog to set max/min other than zero as wait
    // animation bar doesn't work in that case.
    if (progressType() == HbProgressDialog::WaitDialog) {
        max = 0;
    }
    d->setProperty(HbDeviceProgressDialogPrivate::Maximum, max);
    TRACE_EXIT
}

/*!
    Returns progress bar maximum value. Default value is 100.

    \sa setMaximum()
*/
int HbDeviceProgressDialog::maximum() const
{
    return d->mProperties[HbDeviceProgressDialogPrivate::Maximum].mValue.toInt();
}

/*!
    Sets progress bar minimum value.

    \sa minimum()
*/
void HbDeviceProgressDialog::setMinimum(int min)
{
    TRACE_ENTRY
    // Don't allow wait dialog to set max/min other than zero as wait
    // animation bar doesn't work in that case.
    if (progressType() == HbProgressDialog::WaitDialog) {
        min = 0;
    }
    d->setProperty(HbDeviceProgressDialogPrivate::Minimum, min);
    TRACE_EXIT
}

/*!
    Returns progress bar minimum value. Default value is 0.

    \sa setMinimum()
*/
int HbDeviceProgressDialog::minimum() const
{
    return d->mProperties[HbDeviceProgressDialogPrivate::Minimum].mValue.toInt();
}

/*!
    Sets progress bar minimum and maximum values.

    \sa minimum(), maximum()
*/
void HbDeviceProgressDialog::setRange(int min, int max)
{
    setMinimum(min);
    setMaximum(max);
}

/*!
    Sets progress bar value.

    \sa progressValue()
*/
void HbDeviceProgressDialog::setProgressValue(int progressValue)
{
    TRACE_ENTRY
    d->setProperty(HbDeviceProgressDialogPrivate::Value, progressValue);
    TRACE_EXIT
}

/*!
    Returns progress bar value.

    \sa setProgressValue()
 */
int HbDeviceProgressDialog::progressValue() const
{
    return d->mProperties[HbDeviceProgressDialogPrivate::Value].mValue.toInt();
}

/*!
    Sets dialog auto-closing.

    \param autoClose When set, the dialog is closed when value of the progress bar reaches
    the maximum value of the progress bar.

    \sa autoClose()
*/
void HbDeviceProgressDialog::setAutoClose(bool autoClose)
{
    TRACE_ENTRY
    d->setProperty(HbDeviceProgressDialogPrivate::AutoClose, autoClose);
    TRACE_EXIT
}

/*!
    Returns auto-closing property of a dialog.

    The default value is true for HbProgressDialog::ProgressDialog and false
    for HbProgressDialog::WaitDialog.

    \sa setAutoClose()
 */
bool HbDeviceProgressDialog::autoClose() const
{
    return d->mProperties[HbDeviceProgressDialogPrivate::AutoClose].mValue.toInt();
}

/*!
    Sets dialog's progress type. All dialog properties are initialized to default values.
    After setProgressType(), a new dialog is launched by a show().

    \sa progressType()
*/
void HbDeviceProgressDialog::setProgressType(HbProgressDialog::ProgressDialogType type)
{
    TRACE_ENTRY
    // After setProgressType(), a new dialog is launched by a show()
    d->mVisible = false;
    // All properties initialized to default
    d->initProperties(type);
    TRACE_EXIT
}

/*!
    Returns dialog's progress type.

    \sa setProgressType()
*/
HbProgressDialog::ProgressDialogType HbDeviceProgressDialog::progressType() const
{
    return static_cast<HbProgressDialog::ProgressDialogType>
        (d->mProperties[HbDeviceProgressDialogPrivate::ProgressType].mValue.toInt());
}

/*!
    Sets dialog text.

    \sa text()
*/
void HbDeviceProgressDialog::setText(const QString &text)
{
    TRACE_ENTRY
    d->setProperty(HbDeviceProgressDialogPrivate::Text, text);
    TRACE_EXIT
}

/*!
    Returns dialog text.

    \sa setText()
*/
QString HbDeviceProgressDialog::text() const
{
    return d->mProperties[HbDeviceProgressDialogPrivate::Text].mValue.toString();
}

/*!
    Sets message box icon name or animation logical name.

    \param iconName Icon name. Icon can be from Hb resources or themes. Or can be a file in
    a file system.

    \sa IconName()
*/
void HbDeviceProgressDialog::setIconName(const QString &iconName)
{
    TRACE_ENTRY
    d->setProperty(HbDeviceProgressDialogPrivate::IconName, iconName);
    TRACE_EXIT
}

/*!
    Returns name and path of the icon or animation's logical name.

    \sa setIconName()
*/
QString HbDeviceProgressDialog::iconName() const
{
    return d->mProperties[HbDeviceProgressDialogPrivate::IconName].mValue.toString();
}

/*!
    Sets animation definition to a dialog. Animation's logical name has to be set
    using setIcon(). Animation definition files must be stored to a place where they
    can be accessed by device dialog service.

    Supported animation formats are following:
    - GIF (.gif)
    - MNG (.mng)
        - Frame animations

    \param animationDefinition Path and name of the animation definition file.

    \sa setIcon(), animationDefinition(), HbIconAnimationManager::addDefinitionFile()

*/
void HbDeviceProgressDialog::setAnimationDefinition(QString &animationDefinition)
{
    TRACE_ENTRY
    d->setProperty(HbDeviceProgressDialogPrivate::AnimationDefinition, animationDefinition);
    TRACE_EXIT
}

/*!
    Returns animation definition file name.

    \sa setAnimationDefinition()
*/
QString HbDeviceProgressDialog::animationDefinition() const
{
    return d->mProperties[HbDeviceProgressDialogPrivate::AnimationDefinition].mValue.toString();
}

/*!
    Sets a new action into progress dialog. When users presses a button on dialog, triggered()
    signal of the action is emitted. HbDeviceProgressDialog sets a default action
    into a dialog on construction.

    \param action Action or Null. Ownership is not transferred.
    \param role Selects an action to set.

    \sa action()
*/
void HbDeviceProgressDialog::setAction(QAction *action, ActionRole role)
{
    TRACE_ENTRY
    HbDeviceProgressDialogPrivate::ActionSelector  actionSelector =
        HbDeviceProgressDialogPrivate::actionSelector(role);
    if (actionSelector != HbDeviceProgressDialogPrivate::InvalidSelector) {
        HbDeviceProgressDialogPrivate::Action &dialogAction = d->mActions[actionSelector];
        dialogAction.mAction = action;
        dialogAction.mFlags = HbDeviceProgressDialogPrivate::Modified;
        d->scheduleUpdateEvent();
    }
    TRACE_EXIT
}

/*!
    Returns progress dialog action. The action may be a default action owned by the dialog
    or the one set by setAction().

    \param role Selects an action to get.

    \sa setAction()
*/
QAction *HbDeviceProgressDialog::action(ActionRole role) const
{
    HbDeviceProgressDialogPrivate::ActionSelector  actionSelector =
        HbDeviceProgressDialogPrivate::actionSelector(role);
    return actionSelector != HbDeviceProgressDialogPrivate::InvalidSelector ?
        d->mActions[actionSelector].mAction : 0;
}
