/*
 * contactlistdragview.cpp - ContactListView with support for Drag'n'Drop operations
 * 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */

#include "contactlistdragview.h"

#include "contactlistdragmodel.h"
#include "contactlistitem.h"
#include "contactlistitemmenu.h"
#include "contactlistmodelselection.h"
#include "contactlistviewdelegate.h"
#include "iconaction.h"
#include "psiiconset.h"
#include "psioptions.h"
#include "shortcutmanager.h"
#include "debug.h"
#include "textutil.h"
#include "psicontact.h"

#include <QAbstractButton>
#include <QAction>
#include <QApplication>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QLineEdit>
#include <QMenu>
#include <QMimeData>
#include <QMouseEvent>
#include <QPainter>
#include <QScrollBar>
#include <QMessageBox>

ContactListDragView::ContactListDragView(QWidget* parent)
    : ContactListView(parent)
    , backedUpSelection_(0)
    , removeAction_(0)
    , dropIndicatorRect_(QRect())
    , dropIndicatorPosition_(QAbstractItemView::OnViewport)
    , keyboardModifiers_(Qt::NoModifier)
    , dirty_(false)
    , pressedIndex_(0)
    , pressedIndexWasSelected_(false)
    , viewportMenu_(0)
    , editing(false)
{
    removeAction_ = new IconAction("", "psi/remove", QString(), ShortcutManager::instance()->shortcuts("contactlist.delete"), this, "act_remove");
    connect(removeAction_, SIGNAL(triggered()), SLOT(removeSelection()));
    addAction(removeAction_);

    connect(this, SIGNAL(entered(const QModelIndex&)), SLOT(updateCursorMouseHover(const QModelIndex&)));
    connect(this, SIGNAL(clicked(const QModelIndex&)), SLOT(itemClicked(const QModelIndex&)));
    connect(this, SIGNAL(viewportEntered()), SLOT(updateCursorMouseHover()));

    setSelectionMode(ExtendedSelection);
    viewport()->installEventFilter(this);

    setDragEnabled(true);
    setAcceptDrops(true);
    setDropIndicatorShown(false); // we're painting it by ourselves
}

ContactListDragView::~ContactListDragView()
{
    delete backedUpSelection_;
    delete pressedIndex_;
}

/**
 * Make sure that all keyboard shortcuts are unique in order to avoid
 * "QAction::eventFilter: Ambiguous shortcut overload: Del" messages.
 */
void ContactListDragView::addContextMenuAction(QAction* action)
{
    QMenu* menu = action->menu();
    if (menu) {
        // if the action contains a menu, add the menu's actions instead
        foreach(QAction* subact, menu->actions()) {
            addContextMenuAction(subact);
        }
    }
    else
    {
        foreach(QAction* act, findChildren<QAction*>()) {
            // TODO: maybe check individual shortcuts too?
            if (act->shortcuts() == action->shortcuts()) {
                return;
            }
        }

        ContactListView::addContextMenuAction(action);
    }
}

void ContactListDragView::setItemDelegate(QAbstractItemDelegate* delegate)
{
    QAbstractItemDelegate *oldDelegate = itemDelegate();
    if (delegate == oldDelegate)
        return;
    if (delegate) {
        connect(delegate, SIGNAL(commitData(QWidget*)), this, SLOT(finishedEditing()));
        connect(delegate, SIGNAL(closeEditor(QWidget*)), this, SLOT(finishedEditing()));
    }
    ContactListView::setItemDelegate(delegate);
    if (oldDelegate)
        delete oldDelegate;
    modelChanged();
//    doItemsLayout();
}

void ContactListDragView::finishedEditing()
{
    editing = false;
}

bool ContactListDragView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event)
{
    if (ContactListView::edit(index, trigger, event)) {
        editing = true;
        return true;
    }
    return false;
}

void ContactListDragView::leaveEvent(QEvent* e)
{
    ContactListView::leaveEvent(e);
    updateCursorMouseHover();
}

int ContactListDragView::suggestedItemHeight()
{
    int rowCount = model()->rowCount(QModelIndex());
    if (!rowCount)
        return 0;
    return qMin(viewport()->height() / rowCount - 2, dynamic_cast<ContactListViewDelegate*>(itemDelegate())->avatarSize());
}

void ContactListDragView::mouseDoubleClickEvent(QMouseEvent* e)
{
    ContactListDragModel* model = qobject_cast<ContactListDragModel*>(realModel());
    if (model && pressedIndex_) {
        QModelIndexList indexes = model->indexesFor(0, pressedIndex_);
        if (e->button() == Qt::LeftButton
            && indexes.count() == 1
            && model->toItem(indexes.first())->isGroup()) {

            return;
        }
    }

    if (!activateItemsOnSingleClick()) {
        ContactListView::mouseDoubleClickEvent(e);
    }
}

void ContactListDragView::itemActivated(const QModelIndex& index)
{
    Q_ASSERT(index.isValid());

    if (!index.isValid())
        return;

    if (realModel()->toItem(realIndex(index))->isGroup()
        && activateItemsOnSingleClick()) {

        toggleExpandedState(index);
        return;
    }

    ContactListView::itemActivated(index);
}

/**
 * Returns a list of real-model indexes.
 */
QModelIndexList ContactListDragView::indexesFor(PsiContact* contact, QMimeData* contactSelection) const
{
    QModelIndexList indexes;
    ContactListDragModel* model = qobject_cast<ContactListDragModel*>(realModel());
    if (model) {
        indexes = model->indexesFor(contact, contactSelection);
    }
    return indexes;
}

void ContactListDragView::toolTipEntered(PsiContact* contact, QMimeData* contactSelection)
{
    Q_UNUSED(contact); // we don't want tooltips on multiple selections
    QModelIndexList indexes = indexesFor(0, contactSelection);
    if (indexes.count() == 1) {
        updateCursor(proxyIndex(indexes.first()), UC_TooltipEntered, false);
    }
}

void ContactListDragView::toolTipHidden(PsiContact* contact, QMimeData* contactSelection)
{
    Q_UNUSED(contact);
    Q_UNUSED(contactSelection);
    if (!drawSelectionBackground())
        updateCursor(QModelIndex(), UC_TooltipHidden, false);
}

void ContactListDragView::updateCursorMouseHover()
{
    updateCursor(QModelIndex(), UC_MouseHover, false);
}

bool ContactListDragView::updateCursor(const QModelIndex& index, UpdateCursorOrigin origin, bool force)
{
    if (isContextMenuVisible()     ||
        extendedSelectionAllowed() ||
        state() != NoState)
    {
        if (!force) {
            return false;
        }
    }

    ContactListItem::Type type = qvariant_cast<ContactListItem::Type>(index.data(ContactListModel::TypeRole));

    if (!index.isValid() || type == ContactListItem::Type::ContactType)
        setCursor(Qt::ArrowCursor);
    else
        setCursor(Qt::PointingHandCursor);

    if (origin == UC_MouseClick) {
        if (index.isValid())
            setCurrentIndex(index);
        else
            clearSelection();
    }

    return true;
}

void ContactListDragView::updateCursorMouseHover(const QModelIndex& index)
{
    updateCursor(index, UC_MouseHover, false);
}

int ContactListDragView::indexCombinedHeight(const QModelIndex& parent, QAbstractItemDelegate* delegate) const
{
    if (!delegate || !model())
        return 0;
    int result = delegate->sizeHint(QStyleOptionViewItem(), parent).height();
    for (int row = 0; row < model()->rowCount(parent); ++row) {
        QModelIndex index = model()->index(row, 0, parent);
        if (isExpanded(index))
            result += indexCombinedHeight(index, delegate);
        else
            result += delegate->sizeHint(QStyleOptionViewItem(), index).height();
    }
    return result;
}

void ContactListDragView::setModel(QAbstractItemModel* newModel)
{
    ContactListView::setModel(newModel);

    connect(verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(scrollbarValueChanged()));
}

bool ContactListDragView::textInputInProgress() const
{
    return state() == QAbstractItemView::EditingState;
}

void ContactListDragView::paintEvent(QPaintEvent* e)
{
    // Q_ASSERT(!dirty_);
    // if (dirty_)
    //     return;
    ContactListView::paintEvent(e);

    // drawDragIndicator!!
    if (state() == QAbstractItemView::DraggingState &&
        viewport()->cursor().shape() != Qt::ForbiddenCursor &&
        !dropIndicatorRect_.isEmpty())
    {
        QPainter p(viewport());
        // QColor dragIndicatorColor = QColor(0x17, 0x0B, 0xFF);
        QColor dragIndicatorColor = QColor(0x00, 0x00, 0x00);
        p.setPen(QPen(dragIndicatorColor, 2));
        p.setRenderHint(QPainter::Antialiasing, true);
        QPainterPath path;
        path.addRoundRect(dropIndicatorRect_.adjusted(2, 1, 0, 0), 5);
        p.drawPath(path);
    }
}

bool ContactListDragView::supportsDropOnIndex(QDropEvent *e, const QModelIndex &index) const
{
    if (!index.isValid()) {
        return false;
    }

    ContactListModelSelection selection(e->mimeData());
    ContactListDragModel *model = qobject_cast<ContactListDragModel*>(realModel());
    if (!selection.haveRosterSelection())
        model = 0;

    if (model && !model->supportsMimeDataOnIndex(e->mimeData(), realIndex(index))) {
        return false;
    }

    if (dropPosition(e, selection, index) == OnViewport) {
        return false;
    }

    return true;
}

static void updateDefaultDropAction(QDropEvent* e)
{
    if (e->keyboardModifiers() == Qt::NoModifier) {
        e->setDropAction(Qt::MoveAction);
    }
}

static void acceptDropAction(QDropEvent* e)
{
    if (e->keyboardModifiers() == Qt::NoModifier) {
        if (e->dropAction() != Qt::MoveAction) {
            //qWarning("acceptDropAction(): e->dropAction() != Qt::MoveAction");
            return;
        }
        Q_ASSERT(e->dropAction() == Qt::MoveAction);
        e->accept();
    }
    else {
        e->acceptProposedAction();
    }
}

void ContactListDragView::dragMoveEvent(QDragMoveEvent* e)
{
    QModelIndex index = indexAt(e->pos());
    dropIndicatorRect_ = QRect();
    dropIndicatorPosition_ = OnViewport;

    // this is crucial at least for auto-scrolling, possibly other things
    ContactListView::dragMoveEvent(e);

    if (supportsDropOnIndex(e, index)) {
        bool ok = true;
        ContactListModelSelection selection(e->mimeData());
        dropIndicatorPosition_ = dropPosition(e, selection, index);
        if (dropIndicatorPosition_ == AboveItem || dropIndicatorPosition_ == BelowItem) {
            dropIndicatorRect_ = groupReorderDropRect(dropIndicatorPosition_, selection, index);
        }
        else if (dropIndicatorPosition_ == OnItem) {
            dropIndicatorRect_ = onItemDropRect(index);
        }
        else {
            ok = false;
        }

        if (ok) {
            updateDefaultDropAction(e);
            acceptDropAction(e);
            return;
        }
    }

    e->ignore();
    viewport()->update();
}

void ContactListDragView::scrollbarValueChanged()
{
    dropIndicatorRect_ = QRect();
    dropIndicatorPosition_ = OnViewport;
    viewport()->update();
    updateCursorMouseHover(underMouse() ? indexAt(mousePosition()) : QModelIndex());
}

void ContactListDragView::dragEnterEvent(QDragEnterEvent* e)
{
    ContactListView::dragEnterEvent(e);
    updateDefaultDropAction(e);
    acceptDropAction(e);
}

void ContactListDragView::dragLeaveEvent(QDragLeaveEvent* e)
{
    ContactListView::dragLeaveEvent(e);
}

void ContactListDragView::dropEvent(QDropEvent* e)
{
    updateDefaultDropAction(e);

    QModelIndex index = indexAt(e->pos());
    dropIndicatorRect_ = QRect();

    if (dropIndicatorPosition_ == OnViewport || !supportsDropOnIndex(e, index)) {
        e->ignore();
        viewport()->update();
        return;
    }

    if (dropIndicatorPosition_ == OnItem) {
        if (model()->dropMimeData(e->mimeData(),
                                  e->dropAction(), -1, -1, index)) {
            acceptDropAction(e);
        }
    }
    else if (dropIndicatorPosition_ == AboveItem || dropIndicatorPosition_ == BelowItem) {
        reorderGroups(e, index);
        acceptDropAction(e);
    }

    stopAutoScroll();
    setState(NoState);
    viewport()->update();
}

QAbstractItemView::DropIndicatorPosition ContactListDragView::dropPosition(QDropEvent* e, const ContactListModelSelection& selection, const QModelIndex& index) const
{
    if ((selection.groups().count() > 0 || selection.accounts().count() > 0) && selection.contacts().count() == 0) {
        // TODO: return OnItem in case the special modifiers are pressed
        // even in case there are multiple groups selected
        // in order to create nested groups
        int totalSelectedGroups = selection.groups().count() + selection.accounts().count();
        if (totalSelectedGroups == 1) {
            QModelIndex group = itemToReorderGroup(selection, index);
            if (group.isValid()) {
                QRect rect = groupVisualRect(group);
                if (e->pos().y() >= rect.center().y()) {
                    return BelowItem;
                }
                else {
                    return AboveItem;
                }
            }
        }

        return OnViewport;
    }

    if (selection.groups().count() + selection.contacts().count() > 0)
        return OnItem;
    else
        return OnViewport;
}

struct OrderedGroup {
    QString name;
    int order;
};

void ContactListDragView::reorderGroups(QDropEvent *e, const QModelIndex &index)
{
    ContactListModelSelection selection(e->mimeData());
    QModelIndex item = itemToReorderGroup(selection, index);
    if (!item.isValid()) {
        return;
    }
    ContactListDragModel* model = qobject_cast<ContactListDragModel*>(realModel());
    Q_ASSERT(model);
    Q_ASSERT(selection.groups().count() > 0 || selection.accounts().count() > 0);
    Q_ASSERT(selection.contacts().count() == 0);
    QModelIndexList selectedGroups = model->indexesFor(&selection);
    if (!selectedGroups.count())
        return;

    // we need to work in terms of proxy indexes because we need actually-sorted items
    QModelIndex selectedGroup = proxyIndex(selectedGroups.first());
    QModelIndex groupParent = selectedGroup.parent();

    QModelIndexList groups;
    for (int row = 0; row < this->model()->rowCount(groupParent); ++row) {
        QModelIndex index = this->model()->index(row, selectedGroup.column(), groupParent);
        if (model->toItem(index)->isGroup()) {
            groups << index;
        }
    }

    // re-order
    groups.removeAll(selectedGroup);
    if (dropIndicatorPosition_ == AboveItem) {
        groups.insert(groups.indexOf(item), selectedGroup);
    }
    else {
        Q_ASSERT(dropIndicatorPosition_ == BelowItem);
        groups.insert(groups.indexOf(item) + 1, selectedGroup);
    }

//    foreach(QModelIndex i, groups) {
//        model->setGroupOrder(i.data(ContactListModel::FullGroupNameRole).toString(),
//                             groups.indexOf(i));
//    }
}

QModelIndex ContactListDragView::itemToReorderGroup(const ContactListModelSelection& selection, const QModelIndex& index) const
{
    ContactListDragModel* model = qobject_cast<ContactListDragModel*>(realModel());
    Q_ASSERT(model);
//    ContactListModel::Type groupType =
//        selection.groups().count() == 1 ?
//        ContactListModel::GroupType :
//        ContactListModel::AccountType;
    Q_ASSERT(selection.groups().count() == 1 || selection.accounts().count() == 1);
    QModelIndexList selectedGroups = model->indexesFor(&selection);
    if (!selectedGroups.count())
        return QModelIndex();
    QModelIndex selectedGroup = selectedGroups.first();
    // we need to work in terms of proxy indexes because we need actually-sorted items
    selectedGroup = proxyIndex(selectedGroup);
    const QModelIndex groupParent = selectedGroup.parent();

    QModelIndex indexParent = index;
    while (indexParent.isValid() && indexParent.parent() != groupParent) {
        indexParent = indexParent.parent();
    }
    if (indexParent.parent() != groupParent || indexParent == selectedGroup)
        return QModelIndex();
//    if (static_cast<ContactListGroup::SpecialType>(indexParent.data(ContactListModel::SpecialGroupTypeRole).toInt()) != ContactListGroup::SpecialType_None)
//        return QModelIndex();

    // this code is necessary if we allow contacts on the same level as group items
//    bool breakAtFirstGroup = (ContactListModel::indexType(index) != groupType) && index.parent() == groupParent;
    QModelIndex result;
    for (int row = 0; row < this->model()->rowCount(groupParent); ++row) {
        result = this->model()->index(row, indexParent.column(), groupParent);
//        if (breakAtFirstGroup) {
//            if (ContactListModel::indexType(result) == groupType)
//                break;
//        }
//        else if (result.row() == indexParent.row()) {
//            break;
//        }
    }
    return result;
}

QRect ContactListDragView::groupReorderDropRect(DropIndicatorPosition dropIndicatorPosition, const ContactListModelSelection& selection, const QModelIndex& index) const
{
    QModelIndex group = itemToReorderGroup(selection, index);
    QRect r(groupVisualRect(group));
    if (dropIndicatorPosition == AboveItem) {
        return QRect(r.left(), r.top(), r.width(), 1);
    }
    else {
        Q_ASSERT(dropIndicatorPosition == BelowItem);
        QRect result(r.left(), r.bottom(), r.width(), 1);
        if (result.bottom() < viewport()->height() - 2)
            result.translate(0, 2);
        return result;
    }
}

QRect ContactListDragView::onItemDropRect(const QModelIndex& index) const
{
    if (!index.isValid()) {
        return viewport()->rect().adjusted(0, 0, -1, -1);
    }

    ContactListItem *item = realModel()->toItem(realIndex(index));

    if (item->isContact()) {
        return onItemDropRect(index.parent());
    }
    else {
        return groupVisualRect(index);
    }
}

QRect ContactListDragView::groupVisualRect(const QModelIndex& index) const
{
    Q_ASSERT(realModel()->toItem(realIndex(index))->isGroup());

    QRect result;
    combineVisualRects(index, &result);
    return result.adjusted(0, 0, -1, -1);
}

void ContactListDragView::combineVisualRects(const QModelIndex& parent, QRect* result) const
{
    Q_ASSERT(result);
    *result = result->united(visualRect(parent));
    for (int row = 0; row < model()->rowCount(parent); ++row) {
        QModelIndex index = model()->index(row, 0, parent);
        combineVisualRects(index, result);
    }
}

void ContactListDragView::startDrag(Qt::DropActions supportedActions)
{
    ContactListView::startDrag(supportedActions);
}

// Return ContactListModelSelection
QMimeData* ContactListDragView::selection() const
{
    ContactListDragModel* model = qobject_cast<ContactListDragModel*>(realModel());
    if (model && model->contactList() && !selectedIndexes().isEmpty()) {
        return model->mimeData(realIndexes(selectedIndexes()));
    }
    return 0;
}

void ContactListDragView::restoreSelection(QMimeData* _mimeData)
{
    // setCurrentIndex() actually fires up the timers which in turn could trigger
    // some delayed events that would result in mimeData deletion (if the mimeData
    // passed was actually backedUpSelection_). So for additional insurance if
    // clearSelection() suddenly decides to trigger timers too we put mimeData
    // into a QPointer
    QPointer<QMimeData> mimeData(_mimeData);
    setUpdatesEnabled(false);

    // setCurrentIndex(QModelIndex());
    clearSelection();

    ContactListDragModel* model = qobject_cast<ContactListDragModel*>(realModel());
    if (model && !mimeData.isNull()) {
        QModelIndexList indexes = proxyIndexes(model->indexesFor(mimeData));
        if (!indexes.isEmpty()) {
            setCurrentIndex(indexes.first());
            QItemSelection selection;
            foreach(QModelIndex index, indexes)
                selection << QItemSelectionRange(index);
            selectionModel()->select(selection, QItemSelectionModel::Select);
        }
    }

    setUpdatesEnabled(true);
}

ContactListItemMenu* ContactListDragView::createContextMenuFor(ContactListItem* item) const
{
    ContactListItemMenu* menu = ContactListView::createContextMenuFor(item);
    if (menu) {
        if (menu->metaObject()->indexOfSignal("removeSelection()") != -1)
            connect(menu, SIGNAL(removeSelection()), SLOT(removeSelection()));
    }
    return menu;
}

void ContactListDragView::removeSelection()
{
    SLOW_TIMER(100);
    QList<PsiContact*> contacts;
    QStringList contactNames;

    QMimeData *mimeData = selection();
    QModelIndexList indexes = qobject_cast<ContactListDragModel*>(realModel())->indexesFor(mimeData);
    delete mimeData;

    for (const QModelIndex &index: indexes) {
        ContactListItem *item = realModel()->toItem(index);

        if (item->isContact())
            contacts << item->contact();
        else if (item->isGroup())
            contacts += item->contacts();
    }

    if (contacts.isEmpty())
        return;

    // Ask for deleting only some contacts. Exclude private contacts and not in list contacts
    for (PsiContact *contact: contacts) {
        QString name = contact->name();
        if (name != contact->jid().full()) {
            name = tr("%1 (%2)").arg(name, TextUtil::escape(contact->jid().full()));
        }

        if (!contact->isPrivate() && contact->inList()) {
            contactNames << QString("<b>%1</b>").arg(TextUtil::escape(name));
        }
    }

    bool doRemove = true;

    if (!contactNames.isEmpty()) {
        QString message = tr("This will permanently remove<br>"
                             "%1"
                             "<br>from your contact list."
                             ).arg(contactNames.join(", "));

        QMessageBox box(QMessageBox::Icon::Warning, tr("Deleting contacts"), message, QMessageBox::StandardButton::Cancel | QMessageBox::StandardButton::Yes, this);
        box.button(QMessageBox::StandardButton::Yes)->setText(tr("Delete"));
        doRemove = static_cast<QMessageBox::StandardButton>(box.exec()) == QMessageBox::StandardButton::Yes;
    }

    if (doRemove) {
        for (PsiContact *contact: contacts) {
            contact->remove();
        }
    }
}

bool ContactListDragView::extendedSelectionAllowed() const
{
    return selectedIndexes().count() > 1 || keyboardModifiers_ != 0;
}

bool ContactListDragView::activateItemsOnSingleClick() const
{
//     return style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this);
    return PsiOptions::instance()->getOption("options.ui.contactlist.use-single-click").toBool();
}

void ContactListDragView::updateKeyboardModifiers(const QEvent* e)
{
    if (e->type() == QEvent::KeyPress ||
        e->type() == QEvent::KeyRelease ||
        e->type() == QEvent::MouseButtonDblClick ||
        e->type() == QEvent::MouseButtonPress ||
        e->type() == QEvent::MouseButtonRelease ||
        e->type() == QEvent::MouseMove)
    {
        keyboardModifiers_ = static_cast<const QInputEvent*>(e)->modifiers();
    }
}

Qt::KeyboardModifiers ContactListDragView::keyboardModifiers() const
{
    return keyboardModifiers_;
}

bool ContactListDragView::eventFilter(QObject* obj, QEvent* e)
{
    if (obj == this || obj == viewport()) {
        updateKeyboardModifiers(e);
    }

    return ContactListView::eventFilter(obj, e);
}

void ContactListDragView::mousePressEvent(QMouseEvent* e)
{
    pressPosition_ = e->pos();
    delete pressedIndex_;
    pressedIndex_ = 0;
    pressedIndexWasSelected_ = false;

    QModelIndex index = indexAt(e->pos());
    ContactListDragModel* model = qobject_cast<ContactListDragModel*>(realModel());
    if (model && index.isValid()) {
        QModelIndexList indexes;
        indexes << realIndex(index);
        pressedIndex_ = model->mimeData(indexes);
    }
    pressedIndexWasSelected_ = e->button() == Qt::LeftButton &&
                               selectionModel() &&
                               selectionModel()->isSelected(index);
    if (!index.isValid()) {
        // clear selection
        updateCursor(index, UC_MouseClick, true);
    }
    if (e->button() == Qt::MiddleButton && index.isValid()) {
        itemActivated(index);
    }
    ContactListView::mousePressEvent(e);

    static const int iconSize = 16;
    bool iconClick = e->pos().x() < iconSize + 2;

    if (( activateItemsOnSingleClick() || iconClick ) && e->button() == Qt::LeftButton) {
        ContactListView::mouseDoubleClickEvent(e);
    }
}

void ContactListDragView::mouseMoveEvent(QMouseEvent* e)
{
    // don't allow any selections after single-clicking on the group button
    if (!pressedIndex_ && !pressedIndexWasSelected_ && e->buttons() & Qt::LeftButton) {
        e->accept();
        return;
    }

    ContactListView::mouseMoveEvent(e);
}

void ContactListDragView::itemClicked(const QModelIndex& index)
{
    if (activateItemsOnSingleClick())
        itemActivated(index);

    // if clicked item was selected prior to mouse press event, activate it on release
    // if (pressedIndexWasSelected_) {
    //     activate(index);
    // }
    Q_UNUSED(index);
}

void ContactListDragView::contextMenuEvent(QContextMenuEvent* e)
{
    QModelIndex index = indexAt(e->pos());
    if (!selectedIndexes().contains(index)) {
        updateCursor(index, UC_MouseClick, true);
    }

    ContactListView::contextMenuEvent(e);

    if (!e->isAccepted() && viewportMenu_) {
        viewportMenu_->exec(e->globalPos());
    }

    repairMouseTracking();
}

bool ContactListDragView::drawSelectionBackground() const
{
    return focusPolicy() == Qt::NoFocus     ||
           testAttribute(Qt::WA_UnderMouse) ||
           extendedSelectionAllowed();
}

void ContactListDragView::closeEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint)
{
    ContactListView::closeEditor(editor, hint);
}

void ContactListDragView::backupCurrentSelection()
{
    if (backedUpSelection_) {
        delete backedUpSelection_;
        backedUpSelection_ = 0;
    }

    backedUpSelection_ = selection();
}

void ContactListDragView::restoreBackedUpSelection()
{
    restoreSelection(backedUpSelection_);
}

void ContactListDragView::modelChanged()
{
    if (!dirty_) {
//        setUpdatesEnabled(false);
        if (currentEditor() && editing) {
            backedUpEditorValue_ = currentEditor()->text();
            closeEditor(currentEditor(), QAbstractItemDelegate::NoHint);
            setEditingIndex(currentIndex(), true);
        }
        else {
            backedUpEditorValue_ = QString();
        }
        dirty_ = true;
        scheduleDelayedItemsLayout();
    }

    // TODO: save the object we're currently editing and the QVariant of the value the user inputted

    // make sure selectionModel() doesn't cache any currently selected indexes
    // otherwise it'll overwrite our correctly restored selection with its own
//    if (selectionModel()) {
//        selectionModel()->reset();
//    }

    updateContextMenu();
}

void ContactListDragView::updateGeometries()
{
    ContactListView::updateGeometries();
}

/**
 * Prevent emitting activated events when clicking with right mouse button.
 */
void ContactListDragView::mouseReleaseEvent(QMouseEvent* event)
{
    bool filter = false;
    ContactListDragModel* model = qobject_cast<ContactListDragModel*>(realModel());
    if (model && pressedIndex_) {
        QModelIndexList indexes = model->indexesFor(0, pressedIndex_);
        QModelIndex index = indexes.count() == 1 ? proxyIndex(indexes.first()) : QModelIndex();
        if (event->button() == Qt::LeftButton &&
            index.isValid() &&
            keyboardModifiers() == 0 &&
            model->toItem(realIndex(index))->isGroup() &&
            activateItemsOnSingleClick()) {

            if ((pressPosition_ - event->pos()).manhattanLength() < QApplication::startDragDistance()) {
                QStyleOptionViewItem option;
                toggleExpandedState(index);
                event->accept();
                filter = true;
            }
        }
    }

    if ((!filter && event->button() & Qt::LeftButton) || !activateItemsOnSingleClick()) {
        ContactListView::mouseReleaseEvent(event);
    }

    pressPosition_ = QPoint();
    delete pressedIndex_;
    pressedIndex_ = 0;
}

void ContactListDragView::setViewportMenu(QMenu* menu)
{
    viewportMenu_ = menu;
}
