/****************************************************************************
 **
 ** Copyright (C) 2016 Ivan Vizir <define-true-false@yandex.com>
 ** Copyright (C) 2016 The Qt Company Ltd.
 ** Contact: https://www.qt.io/licensing/
 **
 ** This file is part of the QtWinExtras module of the Qt Toolkit.
 **
 ** $QT_BEGIN_LICENSE:LGPL$
 ** Commercial License Usage
 ** Licensees holding valid commercial Qt licenses may use this file in
 ** accordance with the commercial license agreement provided with the
 ** Software or, alternatively, in accordance with the terms contained in
 ** a written agreement between you and The Qt Company. For licensing terms
 ** and conditions see https://www.qt.io/terms-conditions. For further
 ** information use the contact form at https://www.qt.io/contact-us.
 **
 ** GNU Lesser General Public License Usage
 ** Alternatively, this file may be used under the terms of the GNU Lesser
 ** General Public License version 3 as published by the Free Software
 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
 ** packaging of this file. Please review the following information to
 ** ensure the GNU Lesser General Public License version 3 requirements
 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
 **
 ** GNU General Public License Usage
 ** Alternatively, this file may be used under the terms of the GNU
 ** General Public License version 2.0 or (at your option) the GNU General
 ** Public license version 3 or any later version approved by the KDE Free
 ** Qt Foundation. The licenses are as published by the Free Software
 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
 ** included in the packaging of this file. Please review the following
 ** information to ensure the GNU General Public License requirements will
 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
 ** https://www.gnu.org/licenses/gpl-3.0.html.
 **
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/

#include "qwintaskbarbutton.h"
#include "qwintaskbarbutton_p.h"
#include "qwintaskbarprogress.h"
#include "qwinfunctions.h"
#include "qwinfunctions_p.h"
#include "qwineventfilter_p.h"
#include "qwinevent.h"
#include "winshobjidl_p.h"
#include "windowsguidsdefs_p.h"

#include <QtGui/qwindow.h>
#include <QtGui/qicon.h>
#include <QtCore/qpair.h>
#include <dwmapi.h>
#include <shobjidl.h>

QT_BEGIN_NAMESPACE

/*!
    \class QWinTaskbarButton
    \inmodule QtWinExtras
    \brief The QWinTaskbarButton class represents the Windows taskbar button for
    a top-level window (Windows 7 and newer).

    \since 5.2

    The QWinTaskbarButton class enables you to set overlay icons on a taskbar
    button, and provides access to its progress indicator.

    An overlay icon indicates change in the state of an application, whereas
    a progress indicator shows how time-consuming tasks are progressing.

    \image taskbar-button.png Taskbar Button

    The following example code illustrates how to use the QWinTaskbarButton
    and QWinTaskbarProgress classes to adjust the look of the taskbar button:

    \snippet code/taskbar.cpp taskbar_cpp

    \note QWidget::windowHandle() returns a valid instance of a QWindow only
    after the widget has been shown. It is therefore recommended to delay the
    initialization of the QWinTaskbarButton instances until QWidget::showEvent().

    \note The class wraps API only available since Windows 7. Instantiating it
    on Windows XP or Windows Vista causes a runtime warning.

    \sa QWinTaskbarProgress
 */

static TBPFLAG nativeProgressState(QWinTaskbarProgress *progress)
{
    if (!progress || !progress->isVisible())
        return TBPF_NOPROGRESS;
    if (progress->isStopped())
        return TBPF_ERROR;
    if (progress->isPaused())
        return TBPF_PAUSED;
    if (progress->minimum() == 0 && progress->maximum() == 0)
        return TBPF_INDETERMINATE;
    return TBPF_NORMAL;
}

QWinTaskbarButtonPrivate::QWinTaskbarButtonPrivate()
{
    HRESULT hresult = CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, qIID_ITaskbarList4, reinterpret_cast<void **>(&pTbList));
    if (FAILED(hresult)) {
        pTbList = nullptr;
        const QString err = QtWin::errorStringFromHresult(hresult);
        qWarning("QWinTaskbarButton: qIID_ITaskbarList4 was not created: %#010x, %s.", unsigned(hresult), qPrintable(err));
    } else if (FAILED(pTbList->HrInit())) {
        pTbList->Release();
        pTbList = nullptr;
        const QString err = QtWin::errorStringFromHresult(hresult);
        qWarning("QWinTaskbarButton: qIID_ITaskbarList4 was not initialized: %#010x, %s.", unsigned(hresult), qPrintable(err));
    }
}

QWinTaskbarButtonPrivate::~QWinTaskbarButtonPrivate()
{
    if (pTbList)
        pTbList->Release();
}

HWND QWinTaskbarButtonPrivate::handle()
{
    return reinterpret_cast<HWND>(window->winId());
}

int QWinTaskbarButtonPrivate::iconSize() const
{
    return GetSystemMetrics(SM_CXSMICON);
}

void QWinTaskbarButtonPrivate::updateOverlayIcon()
{
    if (!pTbList || !window)
        return;

    wchar_t *descrPtr = nullptr;
    HICON hicon = nullptr;
    if (!overlayAccessibleDescription.isEmpty())
        descrPtr = qt_qstringToNullTerminated(overlayAccessibleDescription);
    if (!overlayIcon.isNull())
        hicon = QtWin::toHICON(overlayIcon.pixmap(iconSize()));

    if (hicon)
        pTbList->SetOverlayIcon(handle(), hicon, descrPtr);
    else if (!hicon && !overlayIcon.isNull())
        pTbList->SetOverlayIcon(handle(), static_cast<HICON>(LoadImage(nullptr, IDI_APPLICATION, IMAGE_ICON, SM_CXSMICON, SM_CYSMICON, LR_SHARED)), descrPtr);
    else
        pTbList->SetOverlayIcon(handle(), nullptr, descrPtr);

    if (hicon)
        DestroyIcon(hicon);
    delete[] descrPtr;
}

void QWinTaskbarButtonPrivate::_q_updateProgress()
{
    if (!pTbList || !window)
        return;

    if (progressBar) {
        const int min = progressBar->minimum();
        const int max = progressBar->maximum();
        const int range = max - min;
        if (range > 0) {
            const int value = qRound(double(100) * (double(progressBar->value() - min)) / double(range));
            pTbList->SetProgressValue(handle(), ULONGLONG(value), 100);
        }
    }
    pTbList->SetProgressState(handle(), nativeProgressState(progressBar));
}

/*!
    Constructs a QWinTaskbarButton with the specified \a parent.

    If \a parent is an instance of QWindow, it is automatically
    assigned as the taskbar button's \l window.
 */
QWinTaskbarButton::QWinTaskbarButton(QObject *parent) :
    QObject(parent), d_ptr(new QWinTaskbarButtonPrivate)
{
    QWinEventFilter::setup();
    setWindow(qobject_cast<QWindow *>(parent));
}

/*!
    Destroys the QWinTaskbarButton.
 */
QWinTaskbarButton::~QWinTaskbarButton() = default;

/*!
    \property QWinTaskbarButton::window
    \brief the window whose taskbar button is manipulated
 */
void QWinTaskbarButton::setWindow(QWindow *window)
{
    Q_D(QWinTaskbarButton);
    if (d->window)
        d->window->removeEventFilter(this);
    d->window = window;
    if (d->window) {
        d->window->installEventFilter(this);
        if (d->window->isVisible()) {
            d->_q_updateProgress();
            d->updateOverlayIcon();
        }
    }
}

QWindow *QWinTaskbarButton::window() const
{
    Q_D(const QWinTaskbarButton);
    return d->window;
}

/*!
    \property QWinTaskbarButton::overlayIcon
    \brief the overlay icon of the taskbar button
 */
QIcon QWinTaskbarButton::overlayIcon() const
{
    Q_D(const QWinTaskbarButton);
    return d->overlayIcon;
}

void QWinTaskbarButton::setOverlayIcon(const QIcon &icon)
{
    Q_D(QWinTaskbarButton);

    d->overlayIcon = icon;
    d->updateOverlayIcon();
}

void QWinTaskbarButton::clearOverlayIcon()
{
    setOverlayAccessibleDescription(QString());
    setOverlayIcon(QIcon());
}

/*!
    \property QWinTaskbarButton::overlayAccessibleDescription
    \brief the description of the overlay for accessibility purposes

    \sa overlayIcon
 */
QString QWinTaskbarButton::overlayAccessibleDescription() const
{
    Q_D(const QWinTaskbarButton);
    return d->overlayAccessibleDescription;
}

void QWinTaskbarButton::setOverlayAccessibleDescription(const QString &description)
{
    Q_D(QWinTaskbarButton);

    d->overlayAccessibleDescription = description;
    d->updateOverlayIcon();
}

/*!
    \property QWinTaskbarButton::progress
    \brief the progress indicator of the taskbar button

    \note The progress indicator is not \l{QWinTaskbarProgress::visible}{visible} by default.
 */
QWinTaskbarProgress *QWinTaskbarButton::progress() const
{
    Q_D(const QWinTaskbarButton);
    if (!d->progressBar) {
        auto *that = const_cast<QWinTaskbarButton *>(this);
        auto *pbar = new QWinTaskbarProgress(that);
        connect(pbar, SIGNAL(destroyed()), this, SLOT(_q_updateProgress()));
        connect(pbar, SIGNAL(valueChanged(int)), this, SLOT(_q_updateProgress()));
        connect(pbar, SIGNAL(minimumChanged(int)), this, SLOT(_q_updateProgress()));
        connect(pbar, SIGNAL(maximumChanged(int)), this, SLOT(_q_updateProgress()));
        connect(pbar, SIGNAL(visibilityChanged(bool)), this, SLOT(_q_updateProgress()));
        connect(pbar, SIGNAL(pausedChanged(bool)), this, SLOT(_q_updateProgress()));
        connect(pbar, SIGNAL(stoppedChanged(bool)), this, SLOT(_q_updateProgress()));
        that->d_func()->progressBar = pbar;
        that->d_func()->_q_updateProgress();
    }
    return d->progressBar;
}

/*!
    \internal
    Intercepts TaskbarButtonCreated messages.
 */
bool QWinTaskbarButton::eventFilter(QObject *object, QEvent *event)
{
    Q_D(QWinTaskbarButton);
    if (object == d->window && event->type() == QWinEvent::TaskbarButtonCreated) {
        d->_q_updateProgress();
        d->updateOverlayIcon();
    }
    return false;
}

QT_END_NAMESPACE

#include "moc_qwintaskbarbutton.cpp"
