File indexing completed on 2024-11-17 04:40:41

0001 /*
0002     This file is part of Akonadi Contact.
0003 
0004     SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "contactgroupeditordelegate_p.h"
0010 
0011 #include "contactcompletionmodel_p.h"
0012 #include "contactgroupmodel_p.h"
0013 
0014 #include <Akonadi/EntityTreeModel>
0015 #include <KComboBox>
0016 
0017 #include <QAbstractItemView>
0018 #include <QCompleter>
0019 #include <QIcon>
0020 #include <QMouseEvent>
0021 #include <QSortFilterProxyModel>
0022 #include <QTimer>
0023 
0024 using namespace Akonadi;
0025 
0026 /**
0027  * @short Model that filters out all contacts without email address.
0028  */
0029 class ContactsWithEmailFilterModel : public QSortFilterProxyModel
0030 {
0031 public:
0032     ContactsWithEmailFilterModel(QObject *parent)
0033         : QSortFilterProxyModel(parent)
0034     {
0035         // contact names should be sorted correctly
0036         setSortLocaleAware(true);
0037     }
0038 
0039 protected:
0040     bool filterAcceptsRow(int row, const QModelIndex &parent) const override
0041     {
0042         const QModelIndex index = sourceModel()->index(row, Akonadi::ContactCompletionModel::EmailColumn, parent);
0043         if (!index.isValid()) {
0044             return false;
0045         }
0046 
0047         return !index.data().toString().isEmpty();
0048     }
0049 };
0050 
0051 ContactLineEdit::ContactLineEdit(bool isReference, Akonadi::ContactCompletionModel::Columns column, QWidget *parent)
0052     : QLineEdit(parent)
0053     , mIsReference(isReference)
0054 {
0055     setFrame(false);
0056 
0057     auto filter = new ContactsWithEmailFilterModel(this);
0058     filter->setSourceModel(Akonadi::ContactCompletionModel::self());
0059 
0060     auto completer = new QCompleter(filter, this);
0061     completer->setCompletionColumn(column);
0062     completer->setCaseSensitivity(Qt::CaseInsensitive);
0063     connect(completer, qOverload<const QModelIndex &>(&QCompleter::activated), this, qOverload<const QModelIndex &>(&ContactLineEdit::completed));
0064 
0065     setCompleter(completer);
0066 
0067     connect(this, &QLineEdit::textEdited, this, &ContactLineEdit::slotTextEdited);
0068 }
0069 
0070 bool ContactLineEdit::isReference() const
0071 {
0072     return mIsReference;
0073 }
0074 
0075 Akonadi::Item ContactLineEdit::completedItem() const
0076 {
0077     return mItem;
0078 }
0079 
0080 void ContactLineEdit::completed(const QModelIndex &index)
0081 {
0082     if (index.isValid()) {
0083         mItem = index.data(Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
0084         mIsReference = true;
0085     } else {
0086         mItem = Akonadi::Item();
0087         mIsReference = false;
0088     }
0089 
0090     Q_EMIT completed(this);
0091 }
0092 
0093 void ContactLineEdit::slotTextEdited()
0094 {
0095     // if the user has edited the text, we break up the reference
0096     mIsReference = false;
0097 }
0098 
0099 class Akonadi::ContactGroupEditorDelegatePrivate
0100 {
0101 public:
0102     ContactGroupEditorDelegatePrivate()
0103         : mButtonSize(16, 16)
0104         , mIcon(QIcon::fromTheme(QStringLiteral("list-remove")))
0105     {
0106     }
0107 
0108     const QSize mButtonSize;
0109     const QIcon mIcon;
0110     QAbstractItemView *mItemView = nullptr;
0111 };
0112 
0113 ContactGroupEditorDelegate::ContactGroupEditorDelegate(QAbstractItemView *view, QObject *parent)
0114     : QStyledItemDelegate(parent)
0115     , d(new ContactGroupEditorDelegatePrivate)
0116 {
0117     d->mItemView = view;
0118 }
0119 
0120 ContactGroupEditorDelegate::~ContactGroupEditorDelegate() = default;
0121 
0122 QWidget *ContactGroupEditorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
0123 {
0124     const bool isReference = index.data(Akonadi::ContactGroupModel::IsReferenceRole).toBool();
0125     Q_UNUSED(option)
0126     if (index.column() == 0) {
0127         auto edit = new ContactLineEdit(isReference, Akonadi::ContactCompletionModel::NameAndEmailColumn, parent);
0128         connect(edit, qOverload<QWidget *>(&ContactLineEdit::completed), this, &ContactGroupEditorDelegate::completed);
0129 
0130         return edit;
0131     } else {
0132         if (index.data(Akonadi::ContactGroupModel::IsReferenceRole).toBool()) {
0133             auto comboBox = new KComboBox(parent);
0134             comboBox->setFrame(false);
0135             comboBox->setAutoFillBackground(true);
0136             return comboBox;
0137         } else {
0138             auto edit = new ContactLineEdit(isReference, Akonadi::ContactCompletionModel::NameAndEmailColumn, parent);
0139             connect(edit, qOverload<QWidget *>(&ContactLineEdit::completed), this, &ContactGroupEditorDelegate::completed);
0140             return edit;
0141         }
0142     }
0143 }
0144 
0145 void ContactGroupEditorDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
0146 {
0147     if (index.data(Akonadi::ContactGroupModel::IsReferenceRole).toBool()) {
0148         if (index.column() == 0) {
0149             auto lineEdit = qobject_cast<QLineEdit *>(editor);
0150             if (!lineEdit) {
0151                 return;
0152             }
0153 
0154             lineEdit->setText(index.data(Qt::EditRole).toString());
0155         } else {
0156             auto comboBox = qobject_cast<KComboBox *>(editor);
0157             if (!comboBox) {
0158                 return;
0159             }
0160 
0161             const QStringList emails = index.data(Akonadi::ContactGroupModel::AllEmailsRole).toStringList();
0162             comboBox->clear();
0163             comboBox->addItems(emails);
0164             comboBox->setCurrentIndex(comboBox->findText(index.data(Qt::EditRole).toString()));
0165         }
0166     } else {
0167         auto lineEdit = qobject_cast<QLineEdit *>(editor);
0168         if (!lineEdit) {
0169             return;
0170         }
0171 
0172         lineEdit->setText(index.data(Qt::EditRole).toString());
0173     }
0174 }
0175 
0176 void ContactGroupEditorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
0177 {
0178     if (index.data(Akonadi::ContactGroupModel::IsReferenceRole).toBool()) {
0179         if (index.column() == 0) {
0180             auto lineEdit = static_cast<ContactLineEdit *>(editor);
0181 
0182             const bool isReference = lineEdit->isReference();
0183             const Akonadi::Item item = lineEdit->completedItem();
0184             model->setData(index, isReference, Akonadi::ContactGroupModel::IsReferenceRole);
0185             if (isReference) {
0186                 if (item.isValid()) {
0187                     model->setData(index, item.id(), Qt::EditRole);
0188                 }
0189             } else {
0190                 model->setData(index, lineEdit->text(), Qt::EditRole);
0191             }
0192         }
0193 
0194         if (index.column() == 1) {
0195             auto comboBox = qobject_cast<KComboBox *>(editor);
0196             if (!comboBox) {
0197                 return;
0198             }
0199 
0200             model->setData(index, comboBox->currentText(), Qt::EditRole);
0201         }
0202     } else {
0203         auto lineEdit = static_cast<ContactLineEdit *>(editor);
0204 
0205         const bool isReference = lineEdit->isReference();
0206         const Akonadi::Item item = lineEdit->completedItem();
0207         model->setData(index, isReference, Akonadi::ContactGroupModel::IsReferenceRole);
0208         if (isReference) {
0209             if (item.isValid()) {
0210                 model->setData(index.sibling(index.row(), 0), item.id(), Qt::EditRole);
0211             }
0212         } else {
0213             model->setData(index, lineEdit->text(), Qt::EditRole);
0214         }
0215     }
0216 }
0217 
0218 static bool isLastRow(const QModelIndex &index)
0219 {
0220     return index.row() == (index.model()->rowCount() - 1);
0221 }
0222 
0223 void ContactGroupEditorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0224 {
0225     QStyledItemDelegate::paint(painter, option, index);
0226 
0227     if (index.column() == 1 && !isLastRow(index)) {
0228         QRect buttonRect = d->mItemView->visualRect(index);
0229         buttonRect.setLeft(buttonRect.right() - d->mButtonSize.width());
0230         d->mIcon.paint(painter, buttonRect, Qt::AlignRight);
0231     }
0232 }
0233 
0234 QSize ContactGroupEditorDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0235 {
0236     Q_UNUSED(option)
0237 
0238     QSize hint = QStyledItemDelegate::sizeHint(option, index);
0239     hint.setHeight(qMax(hint.height(), d->mButtonSize.height()));
0240 
0241     if (index.column() == 1) {
0242         hint.setWidth(hint.width() + d->mButtonSize.width());
0243     }
0244 
0245     return hint;
0246 }
0247 
0248 bool ContactGroupEditorDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
0249 {
0250     if (index.column() == 1 && !isLastRow(index)) {
0251         if (event->type() == QEvent::MouseButtonRelease) {
0252             const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
0253             QRect buttonRect = d->mItemView->visualRect(index);
0254             buttonRect.setLeft(buttonRect.right() - d->mButtonSize.width());
0255             if (buttonRect.contains(mouseEvent->pos())) {
0256                 model->removeRows(index.row(), 1);
0257                 QTimer::singleShot(0, this, &ContactGroupEditorDelegate::setFirstColumnAsCurrent);
0258                 return true;
0259             }
0260         }
0261     }
0262     return QStyledItemDelegate::editorEvent(event, model, option, index);
0263 }
0264 
0265 void ContactGroupEditorDelegate::completed(QWidget *widget)
0266 {
0267     Q_EMIT commitData(widget);
0268     Q_EMIT closeEditor(widget);
0269 }
0270 
0271 void ContactGroupEditorDelegate::setFirstColumnAsCurrent()
0272 {
0273     d->mItemView->setCurrentIndex(d->mItemView->model()->index(d->mItemView->currentIndex().row(), 0));
0274 }
0275 
0276 #include "moc_contactgroupeditordelegate_p.cpp"