/*
 * 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 program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "contactlistdragview.h"

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

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

ContactListDragView::ContactListDragView(QWidget *parent) :
    ContactListView(parent), backedUpSelection_(nullptr), removeAction_(nullptr), dropIndicatorRect_(QRect()),
    dropIndicatorPosition_(QAbstractItemView::OnViewport), keyboardModifiers_(Qt::NoModifier), dirty_(false),
    pressedIndex_(nullptr), pressedIndexWasSelected_(false), viewportMenu_(nullptr), 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
        const auto &actions = menu->actions();
        for (QAction *subact : actions) {
            addContextMenuAction(subact);
        }
    } else {
        const auto &actions = findChildren<QAction *>();
        for (QAction *act : actions) {
            // 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(nullptr, 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(nullptr, 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.addRoundedRect(dropIndicatorRect_.adjusted(2, 1, 0, 0), 5, 5, Qt::RelativeSize);
        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 = nullptr;

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

    return !(dropPosition(e, selection, index) == OnViewport);
}

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 nullptr;
}

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;
            for (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 : qAsConst(contacts)) {
        QString name = contact->name();
        if (name != contact->jid().full()) {
            name = QString("%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 : qAsConst(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_            = nullptr;
    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_ = nullptr;
    }

    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(nullptr, 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_ = nullptr;
}

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