contactgroupeditordelegate.cpp 8.48 KB
Newer Older
1
2
3
/*
    This file is part of Akonadi Contact.

4
5
6
    SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>

    SPDX-License-Identifier: LGPL-2.0-or-later
7
8
9
10
11
12
13
*/

#include "contactgroupeditordelegate_p.h"

#include "contactcompletionmodel_p.h"
#include "contactgroupmodel_p.h"

Laurent Montel's avatar
Laurent Montel committed
14
#include <KComboBox>
Laurent Montel's avatar
Laurent Montel committed
15
#include <entitytreemodel.h>
Laurent Montel's avatar
Laurent Montel committed
16

17
18
#include <QAbstractItemView>
#include <QCompleter>
Laurent Montel's avatar
Laurent Montel committed
19
#include <QIcon>
20
21
#include <QMouseEvent>
#include <QSortFilterProxyModel>
Laurent Montel's avatar
Laurent Montel committed
22
#include <QTimer>
23
24
25

using namespace Akonadi;

26
27
28
29
30
/**
 * @short Model that filters out all contacts without email address.
 */
class ContactsWithEmailFilterModel : public QSortFilterProxyModel
{
Guy Maurel's avatar
Guy Maurel committed
31
32
33
public:
    ContactsWithEmailFilterModel(QObject *parent)
        : QSortFilterProxyModel(parent)
34
    {
Guy Maurel's avatar
Guy Maurel committed
35
36
        // contact names should be sorted correctly
        setSortLocaleAware(true);
37
38
    }

Guy Maurel's avatar
Guy Maurel committed
39
protected:
40
    bool filterAcceptsRow(int row, const QModelIndex &parent) const override
41
    {
Guy Maurel's avatar
Guy Maurel committed
42
43
44
45
        const QModelIndex index = sourceModel()->index(row, Akonadi::ContactCompletionModel::EmailColumn, parent);
        if (!index.isValid()) {
            return false;
        }
46

Guy Maurel's avatar
Guy Maurel committed
47
        return !index.data().toString().isEmpty();
48
49
50
    }
};

51
ContactLineEdit::ContactLineEdit(bool isReference, ContactCompletionModel::Columns column, QWidget *parent)
Guy Maurel's avatar
Guy Maurel committed
52
53
    : QLineEdit(parent)
    , mIsReference(isReference)
54
{
Guy Maurel's avatar
Guy Maurel committed
55
    setFrame(false);
56

Laurent Montel's avatar
Laurent Montel committed
57
    auto filter = new ContactsWithEmailFilterModel(this);
Guy Maurel's avatar
Guy Maurel committed
58
    filter->setSourceModel(Akonadi::ContactCompletionModel::self());
59

Laurent Montel's avatar
Laurent Montel committed
60
    auto completer = new QCompleter(filter, this);
61
    completer->setCompletionColumn(column);
Guy Maurel's avatar
Guy Maurel committed
62
    completer->setCaseSensitivity(Qt::CaseInsensitive);
Laurent Montel's avatar
Laurent Montel committed
63
    connect(completer, QOverload<const QModelIndex &>::of(&QCompleter::activated), this, QOverload<const QModelIndex &>::of(&ContactLineEdit::completed));
64

Guy Maurel's avatar
Guy Maurel committed
65
    setCompleter(completer);
66

67
    connect(this, &QLineEdit::textEdited, this, &ContactLineEdit::slotTextEdited);
68
69
70
71
}

bool ContactLineEdit::isReference() const
{
Guy Maurel's avatar
Guy Maurel committed
72
    return mIsReference;
73
74
75
76
}

Akonadi::Item ContactLineEdit::completedItem() const
{
Guy Maurel's avatar
Guy Maurel committed
77
    return mItem;
78
79
}

Guy Maurel's avatar
Guy Maurel committed
80
void ContactLineEdit::completed(const QModelIndex &index)
81
{
Guy Maurel's avatar
Guy Maurel committed
82
83
84
85
86
87
88
    if (index.isValid()) {
        mItem = index.data(Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
        mIsReference = true;
    } else {
        mItem = Item();
        mIsReference = false;
    }
89

Laurent Montel's avatar
Laurent Montel committed
90
    Q_EMIT completed(this);
91
92
93
94
}

void ContactLineEdit::slotTextEdited()
{
Guy Maurel's avatar
Guy Maurel committed
95
96
    // if the user has edited the text, we break up the reference
    mIsReference = false;
97
98
}

99
class Q_DECL_HIDDEN ContactGroupEditorDelegate::Private
100
{
Guy Maurel's avatar
Guy Maurel committed
101
public:
102
    Private()
Guy Maurel's avatar
Guy Maurel committed
103
        : mButtonSize(16, 16)
Laurent Montel's avatar
Laurent Montel committed
104
        , mIcon(QIcon::fromTheme(QStringLiteral("list-remove")))
105
106
107
108
    {
    }

    QSize mButtonSize;
Laurent Montel's avatar
Laurent Montel committed
109
    const QIcon mIcon;
Laurent Montel's avatar
Laurent Montel committed
110
    QAbstractItemView *mItemView = nullptr;
111
112
};

Guy Maurel's avatar
Guy Maurel committed
113
ContactGroupEditorDelegate::ContactGroupEditorDelegate(QAbstractItemView *view, QObject *parent)
Guy Maurel's avatar
Guy Maurel committed
114
115
    : QStyledItemDelegate(parent)
    , d(new Private)
116
{
Guy Maurel's avatar
Guy Maurel committed
117
    d->mItemView = view;
118
119
120
121
}

ContactGroupEditorDelegate::~ContactGroupEditorDelegate()
{
Guy Maurel's avatar
Guy Maurel committed
122
    delete d;
123
124
}

Laurent Montel's avatar
Laurent Montel committed
125
QWidget *ContactGroupEditorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
126
{
127
    const bool isReference = index.data(ContactGroupModel::IsReferenceRole).toBool();
Guy Maurel's avatar
Guy Maurel committed
128
129
    Q_UNUSED(option)
    if (index.column() == 0) {
130
        auto edit = new ContactLineEdit(isReference, ContactCompletionModel::NameAndEmailColumn, parent);
Laurent Montel's avatar
Laurent Montel committed
131
        connect(edit, QOverload<QWidget *>::of(&ContactLineEdit::completed), this, &ContactGroupEditorDelegate::completed);
132

Guy Maurel's avatar
Guy Maurel committed
133
        return edit;
134
    } else {
Guy Maurel's avatar
Guy Maurel committed
135
        if (index.data(ContactGroupModel::IsReferenceRole).toBool()) {
Laurent Montel's avatar
Laurent Montel committed
136
            auto comboBox = new KComboBox(parent);
Guy Maurel's avatar
Guy Maurel committed
137
138
139
140
            comboBox->setFrame(false);
            comboBox->setAutoFillBackground(true);
            return comboBox;
        } else {
141
            auto edit = new ContactLineEdit(isReference, ContactCompletionModel::NameAndEmailColumn, parent);
Laurent Montel's avatar
Laurent Montel committed
142
            connect(edit, QOverload<QWidget *>::of(&ContactLineEdit::completed), this, &ContactGroupEditorDelegate::completed);
143
            return edit;
Guy Maurel's avatar
Guy Maurel committed
144
        }
145
146
147
    }
}

Guy Maurel's avatar
Guy Maurel committed
148
void ContactGroupEditorDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
149
{
Guy Maurel's avatar
Guy Maurel committed
150
151
    if (index.data(ContactGroupModel::IsReferenceRole).toBool()) {
        if (index.column() == 0) {
Laurent Montel's avatar
Laurent Montel committed
152
            auto lineEdit = qobject_cast<QLineEdit *>(editor);
Guy Maurel's avatar
Guy Maurel committed
153
154
155
156
157
158
            if (!lineEdit) {
                return;
            }

            lineEdit->setText(index.data(Qt::EditRole).toString());
        } else {
Laurent Montel's avatar
Laurent Montel committed
159
            auto comboBox = qobject_cast<KComboBox *>(editor);
Guy Maurel's avatar
Guy Maurel committed
160
161
162
163
164
165
166
167
168
            if (!comboBox) {
                return;
            }

            const QStringList emails = index.data(ContactGroupModel::AllEmailsRole).toStringList();
            comboBox->clear();
            comboBox->addItems(emails);
            comboBox->setCurrentIndex(comboBox->findText(index.data(Qt::EditRole).toString()));
        }
169
    } else {
Laurent Montel's avatar
Laurent Montel committed
170
        auto lineEdit = qobject_cast<QLineEdit *>(editor);
Guy Maurel's avatar
Guy Maurel committed
171
172
173
174
175
        if (!lineEdit) {
            return;
        }

        lineEdit->setText(index.data(Qt::EditRole).toString());
176
177
178
    }
}

Guy Maurel's avatar
Guy Maurel committed
179
void ContactGroupEditorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
180
{
Guy Maurel's avatar
Guy Maurel committed
181
182
    if (index.data(ContactGroupModel::IsReferenceRole).toBool()) {
        if (index.column() == 0) {
Laurent Montel's avatar
Laurent Montel committed
183
            auto lineEdit = static_cast<ContactLineEdit *>(editor);
Guy Maurel's avatar
Guy Maurel committed
184
185
186
187
188
189
190
191
192
193
194

            const bool isReference = lineEdit->isReference();
            const Item item = lineEdit->completedItem();
            model->setData(index, isReference, ContactGroupModel::IsReferenceRole);
            if (isReference) {
                if (item.isValid()) {
                    model->setData(index, item.id(), Qt::EditRole);
                }
            } else {
                model->setData(index, lineEdit->text(), Qt::EditRole);
            }
195
        }
196

Guy Maurel's avatar
Guy Maurel committed
197
        if (index.column() == 1) {
Laurent Montel's avatar
Laurent Montel committed
198
            auto comboBox = qobject_cast<KComboBox *>(editor);
Guy Maurel's avatar
Guy Maurel committed
199
200
201
            if (!comboBox) {
                return;
            }
202

Guy Maurel's avatar
Guy Maurel committed
203
204
205
            model->setData(index, comboBox->currentText(), Qt::EditRole);
        }
    } else {
Laurent Montel's avatar
Laurent Montel committed
206
        auto lineEdit = static_cast<ContactLineEdit *>(editor);
207
208
209
210
211
212
213

        const bool isReference = lineEdit->isReference();
        const Item item = lineEdit->completedItem();
        model->setData(index, isReference, ContactGroupModel::IsReferenceRole);
        if (isReference) {
            if (item.isValid()) {
                model->setData(index.sibling(index.row(), 0), item.id(), Qt::EditRole);
Guy Maurel's avatar
Guy Maurel committed
214
            }
215
        } else {
Guy Maurel's avatar
Guy Maurel committed
216
217
            model->setData(index, lineEdit->text(), Qt::EditRole);
        }
218
219
220
    }
}

Guy Maurel's avatar
Guy Maurel committed
221
static bool isLastRow(const QModelIndex &index)
222
{
Laurent Montel's avatar
Laurent Montel committed
223
    return index.row() == (index.model()->rowCount() - 1);
224
225
}

Guy Maurel's avatar
Guy Maurel committed
226
void ContactGroupEditorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
227
{
Guy Maurel's avatar
Guy Maurel committed
228
    QStyledItemDelegate::paint(painter, option, index);
229

Guy Maurel's avatar
Guy Maurel committed
230
231
232
    if (index.column() == 1 && !isLastRow(index)) {
        d->mIcon.paint(painter, option.rect, Qt::AlignRight);
    }
233
234
}

Guy Maurel's avatar
Guy Maurel committed
235
QSize ContactGroupEditorDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
236
{
Guy Maurel's avatar
Guy Maurel committed
237
    Q_UNUSED(option)
238

Guy Maurel's avatar
Guy Maurel committed
239
240
    QSize hint = QStyledItemDelegate::sizeHint(option, index);
    hint.setHeight(qMax(hint.height(), d->mButtonSize.height()));
241

Guy Maurel's avatar
Guy Maurel committed
242
243
244
    if (index.column() == 1) {
        hint.setWidth(hint.width() + d->mButtonSize.width());
    }
245

Guy Maurel's avatar
Guy Maurel committed
246
    return hint;
247
248
}

Guy Maurel's avatar
Guy Maurel committed
249
bool ContactGroupEditorDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
250
{
Guy Maurel's avatar
Guy Maurel committed
251
252
253
254
255
256
257
258
    if (index.column() == 1 && !isLastRow(index)) {
        if (event->type() == QEvent::MouseButtonRelease) {
            const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
            QRect buttonRect = d->mItemView->visualRect(index);
            buttonRect.setLeft(buttonRect.right() - d->mButtonSize.width());

            if (buttonRect.contains(mouseEvent->pos())) {
                model->removeRows(index.row(), 1);
259
                QTimer::singleShot(0, this, &ContactGroupEditorDelegate::setFirstColumnAsCurrent);
Guy Maurel's avatar
Guy Maurel committed
260
261
262
                return true;
            }
        }
263
    }
Guy Maurel's avatar
Guy Maurel committed
264
    return QStyledItemDelegate::editorEvent(event, model, option, index);
265
266
}

Guy Maurel's avatar
Guy Maurel committed
267
void ContactGroupEditorDelegate::completed(QWidget *widget)
268
{
Laurent Montel's avatar
Laurent Montel committed
269
270
    Q_EMIT commitData(widget);
    Q_EMIT closeEditor(widget);
271
272
}

Tobias Koenig's avatar
Tobias Koenig committed
273
void ContactGroupEditorDelegate::setFirstColumnAsCurrent()
274
{
Guy Maurel's avatar
Guy Maurel committed
275
    d->mItemView->setCurrentIndex(d->mItemView->model()->index(d->mItemView->currentIndex().row(), 0));
276
277
}

278
#include "moc_contactgroupeditordelegate_p.cpp"