/*
 * hoverabletreeview.cpp - QTreeView that allows to show hovered items apart
 * Copyright (C) 2008-2010  Yandex LLC (Michail Pishchagin)
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "hoverabletreeview.h"

#include <QMouseEvent>
#include <QScrollBar>
#ifdef Q_OS_WIN
#include <QLibrary>
#include <windows.h>
#endif

#ifdef Q_OS_WIN
// taken from qapplication_win.cpp
#if (_WIN32_WINNT < 0x0400)
// This struct is defined in winuser.h if the _WIN32_WINNT >= 0x0400 -- in the
// other cases we have to define it on our own.
typedef struct tagTRACKMOUSEEVENT {
    DWORD cbSize;
    DWORD dwFlags;
    HWND  hwndTrack;
    DWORD dwHoverTime;
} TRACKMOUSEEVENT, *LPTRACKMOUSEEVENT;
#endif
#endif

//----------------------------------------------------------------------------
// HoverableStyleOptionViewItem
//----------------------------------------------------------------------------

HoverableStyleOptionViewItem::HoverableStyleOptionViewItem() :
    HoverableStyleOptionViewItemBaseClass(Version), hovered(false)
{
}

HoverableStyleOptionViewItem::HoverableStyleOptionViewItem(const QStyleOptionViewItem &other) :
    HoverableStyleOptionViewItemBaseClass(Version)
{
    (void)HoverableStyleOptionViewItem::operator=(other);
}

HoverableStyleOptionViewItem &HoverableStyleOptionViewItem::operator=(const QStyleOptionViewItem &other)
{
    HoverableStyleOptionViewItemBaseClass::operator  =(other);
    const HoverableStyleOptionViewItem *   hoverable = qstyleoption_cast<const HoverableStyleOptionViewItem *>(&other);
    this->hovered                                    = hoverable ? hoverable->hovered : false;
    this->hoveredPosition                            = hoverable ? hoverable->hoveredPosition : QPoint();
    return *this;
}

HoverableStyleOptionViewItem::HoverableStyleOptionViewItem(int version) :
    HoverableStyleOptionViewItemBaseClass(version), hovered(false)
{
}

//----------------------------------------------------------------------------
// HoverableTreeView
//----------------------------------------------------------------------------

HoverableTreeView::HoverableTreeView(QWidget *parent) : QTreeView(parent)
{
    setAutoScrollMargin(50);
    setMouseTracking(true);
    viewport()->setMouseTracking(true);
}

void HoverableTreeView::mouseMoveEvent(QMouseEvent *event)
{
    mousePosition_ = event->pos();
    QTreeView::mouseMoveEvent(event);

    // required for accurate hovered sub-regions painting
    if (event->buttons() != Qt::NoButton)
        viewport()->update();
}

void HoverableTreeView::startDrag(Qt::DropActions supportedActions)
{
    mousePosition_ = QPoint();
    QTreeView::startDrag(supportedActions);
}

void HoverableTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const
{
    HoverableStyleOptionViewItem opt = options;

    QRect r = visualRect(index).adjusted(0, 1, 0, 1);
    r.setLeft(0);

    opt.decorationSize = QSize();
    if (underMouse() && !mousePosition_.isNull() && r.contains(mousePosition_) && !verticalScrollBar()->underMouse()
        && !verticalScrollBar()->isSliderDown()) {
        // we're hacking our way through the QTreeView which casts HoverableStyleOptionViewItem
        // down to HoverableStyleOptionViewItemBaseClass and loses the hovered flag
        opt.displayAlignment = Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter | Qt::AlignJustify;

        // decorationSize will hold the mouse coordinates starting from opt.rect.topLeft()
        QPoint offset      = mousePosition_ /*- QPoint(opt.rect.left(), 0)*/;
        opt.decorationSize = QSize(offset.x(), offset.y());

        opt.hovered         = true;
        opt.hoveredPosition = QPoint(offset.x(), offset.y());
    }
    QTreeView::drawRow(painter, opt, index);
}

void HoverableTreeView::repairMouseTracking()
{
#ifdef Q_OS_WIN
    // work-around for broken mouse event tracking after context menu gets hidden on Qt 4.3.5

    // copied from qapplication_win.cpp
    typedef BOOL(WINAPI * PtrTrackMouseEvent)(LPTRACKMOUSEEVENT);
    static PtrTrackMouseEvent ptrTrackMouseEvent = 0;
    ptrTrackMouseEvent = (PtrTrackMouseEvent)QLibrary::resolve(QLatin1String("comctl32"), "_TrackMouseEvent");

    if (ptrTrackMouseEvent) {
        TRACKMOUSEEVENT tme;
        tme.cbSize      = sizeof(TRACKMOUSEEVENT);
        tme.dwFlags     = 0x00000002;                // TME_LEAVE
        tme.hwndTrack   = (HWND)viewport()->winId(); // Track on window receiving msgs
        tme.dwHoverTime = (DWORD)-1;                 // HOVER_DEFAULT
        ptrTrackMouseEvent(&tme);
    }
#endif
}

QPoint HoverableTreeView::mousePosition() const { return mousePosition_; }
