Akonadi Contacts

contactgroupeditordelegate.cpp
1 /*
2  This file is part of Akonadi Contact.
3 
4  SPDX-FileCopyrightText: 2009 Tobias Koenig <[email protected]>
5 
6  SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "contactgroupeditordelegate_p.h"
10 
11 #include "contactcompletionmodel_p.h"
12 #include "contactgroupmodel_p.h"
13 
14 #include <Akonadi/EntityTreeModel>
15 #include <KComboBox>
16 
17 #include <QAbstractItemView>
18 #include <QCompleter>
19 #include <QIcon>
20 #include <QMouseEvent>
21 #include <QSortFilterProxyModel>
22 #include <QTimer>
23 
24 using namespace Akonadi;
25 
26 /**
27  * @short Model that filters out all contacts without email address.
28  */
29 class ContactsWithEmailFilterModel : public QSortFilterProxyModel
30 {
31 public:
32  ContactsWithEmailFilterModel(QObject *parent)
33  : QSortFilterProxyModel(parent)
34  {
35  // contact names should be sorted correctly
36  setSortLocaleAware(true);
37  }
38 
39 protected:
40  bool filterAcceptsRow(int row, const QModelIndex &parent) const override
41  {
42  const QModelIndex index = sourceModel()->index(row, Akonadi::ContactCompletionModel::EmailColumn, parent);
43  if (!index.isValid()) {
44  return false;
45  }
46 
47  return !index.data().toString().isEmpty();
48  }
49 };
50 
51 ContactLineEdit::ContactLineEdit(bool isReference, ContactCompletionModel::Columns column, QWidget *parent)
52  : QLineEdit(parent)
53  , mIsReference(isReference)
54 {
55  setFrame(false);
56 
57  auto filter = new ContactsWithEmailFilterModel(this);
58  filter->setSourceModel(Akonadi::ContactCompletionModel::self());
59 
60  auto completer = new QCompleter(filter, this);
61  completer->setCompletionColumn(column);
62  completer->setCaseSensitivity(Qt::CaseInsensitive);
63  connect(completer, qOverload<const QModelIndex &>(&QCompleter::activated), this, qOverload<const QModelIndex &>(&ContactLineEdit::completed));
64 
65  setCompleter(completer);
66 
67  connect(this, &QLineEdit::textEdited, this, &ContactLineEdit::slotTextEdited);
68 }
69 
70 bool ContactLineEdit::isReference() const
71 {
72  return mIsReference;
73 }
74 
75 Akonadi::Item ContactLineEdit::completedItem() const
76 {
77  return mItem;
78 }
79 
80 void ContactLineEdit::completed(const QModelIndex &index)
81 {
82  if (index.isValid()) {
84  mIsReference = true;
85  } else {
86  mItem = Item();
87  mIsReference = false;
88  }
89 
90  Q_EMIT completed(this);
91 }
92 
93 void ContactLineEdit::slotTextEdited()
94 {
95  // if the user has edited the text, we break up the reference
96  mIsReference = false;
97 }
98 
99 class Akonadi::ContactGroupEditorDelegatePrivate
100 {
101 public:
102  ContactGroupEditorDelegatePrivate()
103  : mButtonSize(16, 16)
104  , mIcon(QIcon::fromTheme(QStringLiteral("list-remove")))
105  {
106  }
107 
108  const QSize mButtonSize;
109  const QIcon mIcon;
110  QAbstractItemView *mItemView = nullptr;
111 };
112 
113 ContactGroupEditorDelegate::ContactGroupEditorDelegate(QAbstractItemView *view, QObject *parent)
114  : QStyledItemDelegate(parent)
115  , d(new ContactGroupEditorDelegatePrivate)
116 {
117  d->mItemView = view;
118 }
119 
120 ContactGroupEditorDelegate::~ContactGroupEditorDelegate() = default;
121 
122 QWidget *ContactGroupEditorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
123 {
124  const bool isReference = index.data(ContactGroupModel::IsReferenceRole).toBool();
125  Q_UNUSED(option)
126  if (index.column() == 0) {
127  auto edit = new ContactLineEdit(isReference, ContactCompletionModel::NameAndEmailColumn, parent);
128  connect(edit, qOverload<QWidget *>(&ContactLineEdit::completed), this, &ContactGroupEditorDelegate::completed);
129 
130  return edit;
131  } else {
132  if (index.data(ContactGroupModel::IsReferenceRole).toBool()) {
133  auto comboBox = new KComboBox(parent);
134  comboBox->setFrame(false);
135  comboBox->setAutoFillBackground(true);
136  return comboBox;
137  } else {
138  auto edit = new ContactLineEdit(isReference, ContactCompletionModel::NameAndEmailColumn, parent);
139  connect(edit, qOverload<QWidget *>(&ContactLineEdit::completed), this, &ContactGroupEditorDelegate::completed);
140  return edit;
141  }
142  }
143 }
144 
145 void ContactGroupEditorDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
146 {
147  if (index.data(ContactGroupModel::IsReferenceRole).toBool()) {
148  if (index.column() == 0) {
149  auto lineEdit = qobject_cast<QLineEdit *>(editor);
150  if (!lineEdit) {
151  return;
152  }
153 
154  lineEdit->setText(index.data(Qt::EditRole).toString());
155  } else {
156  auto comboBox = qobject_cast<KComboBox *>(editor);
157  if (!comboBox) {
158  return;
159  }
160 
161  const QStringList emails = index.data(ContactGroupModel::AllEmailsRole).toStringList();
162  comboBox->clear();
163  comboBox->addItems(emails);
164  comboBox->setCurrentIndex(comboBox->findText(index.data(Qt::EditRole).toString()));
165  }
166  } else {
167  auto lineEdit = qobject_cast<QLineEdit *>(editor);
168  if (!lineEdit) {
169  return;
170  }
171 
172  lineEdit->setText(index.data(Qt::EditRole).toString());
173  }
174 }
175 
176 void ContactGroupEditorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
177 {
178  if (index.data(ContactGroupModel::IsReferenceRole).toBool()) {
179  if (index.column() == 0) {
180  auto lineEdit = static_cast<ContactLineEdit *>(editor);
181 
182  const bool isReference = lineEdit->isReference();
183  const Item item = lineEdit->completedItem();
184  model->setData(index, isReference, ContactGroupModel::IsReferenceRole);
185  if (isReference) {
186  if (item.isValid()) {
187  model->setData(index, item.id(), Qt::EditRole);
188  }
189  } else {
190  model->setData(index, lineEdit->text(), Qt::EditRole);
191  }
192  }
193 
194  if (index.column() == 1) {
195  auto comboBox = qobject_cast<KComboBox *>(editor);
196  if (!comboBox) {
197  return;
198  }
199 
200  model->setData(index, comboBox->currentText(), Qt::EditRole);
201  }
202  } else {
203  auto lineEdit = static_cast<ContactLineEdit *>(editor);
204 
205  const bool isReference = lineEdit->isReference();
206  const Item item = lineEdit->completedItem();
207  model->setData(index, isReference, ContactGroupModel::IsReferenceRole);
208  if (isReference) {
209  if (item.isValid()) {
210  model->setData(index.sibling(index.row(), 0), item.id(), Qt::EditRole);
211  }
212  } else {
213  model->setData(index, lineEdit->text(), Qt::EditRole);
214  }
215  }
216 }
217 
218 static bool isLastRow(const QModelIndex &index)
219 {
220  return index.row() == (index.model()->rowCount() - 1);
221 }
222 
223 void ContactGroupEditorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
224 {
225  QStyledItemDelegate::paint(painter, option, index);
226 
227  if (index.column() == 1 && !isLastRow(index)) {
228  QRect buttonRect = d->mItemView->visualRect(index);
229  buttonRect.setLeft(buttonRect.right() - d->mButtonSize.width());
230  d->mIcon.paint(painter, buttonRect, Qt::AlignRight);
231  }
232 }
233 
234 QSize ContactGroupEditorDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
235 {
236  Q_UNUSED(option)
237 
238  QSize hint = QStyledItemDelegate::sizeHint(option, index);
239  hint.setHeight(qMax(hint.height(), d->mButtonSize.height()));
240 
241  if (index.column() == 1) {
242  hint.setWidth(hint.width() + d->mButtonSize.width());
243  }
244 
245  return hint;
246 }
247 
248 bool ContactGroupEditorDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
249 {
250  if (index.column() == 1 && !isLastRow(index)) {
251  if (event->type() == QEvent::MouseButtonRelease) {
252  const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
253  QRect buttonRect = d->mItemView->visualRect(index);
254  buttonRect.setLeft(buttonRect.right() - d->mButtonSize.width());
255  if (buttonRect.contains(mouseEvent->pos())) {
256  model->removeRows(index.row(), 1);
257  QTimer::singleShot(0, this, &ContactGroupEditorDelegate::setFirstColumnAsCurrent);
258  return true;
259  }
260  }
261  }
262  return QStyledItemDelegate::editorEvent(event, model, option, index);
263 }
264 
265 void ContactGroupEditorDelegate::completed(QWidget *widget)
266 {
267  Q_EMIT commitData(widget);
268  Q_EMIT closeEditor(widget);
269 }
270 
271 void ContactGroupEditorDelegate::setFirstColumnAsCurrent()
272 {
273  d->mItemView->setCurrentIndex(d->mItemView->model()->index(d->mItemView->currentIndex().row(), 0));
274 }
275 
276 #include "moc_contactgroupeditordelegate_p.cpp"
bool isValid() const
QPoint pos() const const
AlignRight
EditRole
MouseButtonRelease
virtual int rowCount(const QModelIndex &parent) const const=0
virtual bool removeRows(int row, int count, const QModelIndex &parent)
CaseInsensitive
QModelIndex sibling(int row, int column) const const
int right() const const
int column() const const
T value() const const
bool contains(const QRect &rectangle, bool proper) const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QVariant data(int role) const const
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const const override
bool isEmpty() const const
QFuture< void > filter(Sequence &sequence, KeepFunctor filterFunction)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
bool isValid() const const
QAction * hint(const QObject *recvr, const char *slot, QObject *parent)
bool toBool() const const
int row() const const
void textEdited(const QString &text)
Id id() const
void clear()
QStringList toStringList() const const
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
void setLeft(int x)
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const const override
const QAbstractItemModel * model() const const
QString toString() const const
void activated(const QString &text)
virtual bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sat Apr 1 2023 04:09:04 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.