Incidenceeditor

attendeeline.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Casey Link <unnamedrambler@gmail.com>
3 SPDX-FileCopyrightText: 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "attendeeline.h"
9#include "attendeedata.h"
10
11#include <KCalUtils/Stringify>
12
13#include <KEmailAddress>
14
15#include "incidenceeditor_debug.h"
16#include <KCompletionBox>
17#include <KLocalizedString>
18
19#include <QBoxLayout>
20#include <QKeyEvent>
21#include <QMenu>
22
23using namespace IncidenceEditorNG;
24
25using TextIconPair = QPair<QString, QIcon>;
26
27AttendeeComboBox::AttendeeComboBox(QWidget *parent)
28 : QToolButton(parent)
29 , mMenu(new QMenu(this))
30{
31 setPopupMode(QToolButton::InstantPopup);
32 setToolButtonStyle(Qt::ToolButtonIconOnly);
33 setMenu(mMenu);
34}
35
36void AttendeeComboBox::addItem(const QIcon &icon, const QString &text)
37{
38 mList.append(TextIconPair(text, icon));
39 if (mCurrentIndex == -1) {
40 setCurrentIndex(0);
41 }
42 int index = mList.size() - 1;
43 QAction *act = menu()->addAction(icon, text, this, &AttendeeComboBox::slotActionTriggered);
44 act->setData(index);
45}
46
47void AttendeeComboBox::addItems(const QStringList &texts)
48{
49 for (const QString &str : texts) {
50 addItem(QIcon(), str);
51 }
52 if (mCurrentIndex == -1) {
53 setCurrentIndex(0);
54 }
55}
56
57int AttendeeComboBox::currentIndex() const
58{
59 return mCurrentIndex;
60}
61
62void AttendeeComboBox::clear()
63{
64 mCurrentIndex = -1;
65 mMenu->clear();
66 mList.clear();
67}
68
69void AttendeeComboBox::setCurrentIndex(int index)
70{
71 Q_ASSERT(index < mList.size());
72 const int old = mCurrentIndex;
73 mCurrentIndex = index;
74 setIcon(mList.at(index).second);
75 setToolTip(mList.at(index).first);
76 if (old != index) {
77 Q_EMIT itemChanged();
78 }
79}
80
81void AttendeeComboBox::slotActionTriggered()
82{
83 int index = qobject_cast<QAction *>(sender())->data().toInt();
84 setCurrentIndex(index);
85}
86
87void AttendeeComboBox::keyPressEvent(QKeyEvent *ev)
88{
89 if (ev->key() == Qt::Key_Left) {
90 Q_EMIT leftPressed();
91 } else if (ev->key() == Qt::Key_Right) {
92 Q_EMIT rightPressed();
93 } else if (!mMenu->isVisible() && (ev->key() == Qt::Key_Down || ev->key() == Qt::Key_Space)) {
94 showMenu();
95 } else {
97 }
98}
99
100AttendeeLineEdit::AttendeeLineEdit(QWidget *parent)
101 : AddresseeLineEdit(parent, true)
102{
103}
104
105void AttendeeLineEdit::keyPressEvent(QKeyEvent *ev)
106{
107 if ((ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) && !completionBox()->isVisible()) {
108 Q_EMIT downPressed();
109 PimCommon::AddresseeLineEdit::keyPressEvent(ev);
110 } else if (ev->key() == Qt::Key_Backspace && text().isEmpty()) {
111 ev->accept();
112 Q_EMIT deleteMe();
113 } else if (ev->key() == Qt::Key_Left && cursorPosition() == 0 && !ev->modifiers().testFlag(Qt::ShiftModifier)) {
114 // Shift would be pressed during selection
115 Q_EMIT leftPressed();
116 } else if (ev->key() == Qt::Key_Right && cursorPosition() == text().length() && !ev->modifiers().testFlag(Qt::ShiftModifier)) {
117 // Shift would be pressed during selection
118 Q_EMIT rightPressed();
119 } else if (ev->key() == Qt::Key_Down) {
120 Q_EMIT downPressed();
121 } else if (ev->key() == Qt::Key_Up) {
122 Q_EMIT upPressed();
123 } else {
124 PimCommon::AddresseeLineEdit::keyPressEvent(ev);
125 }
126}
127
128AttendeeLine::AttendeeLine(QWidget *parent)
129 : MultiplyingLine(parent)
130 , mRoleCombo(new AttendeeComboBox(this))
131 , mStateCombo(new AttendeeComboBox(this))
132 , mResponseCombo(new AttendeeComboBox(this))
133 , mEdit(new AttendeeLineEdit(this))
134 , mData(new AttendeeData(QString(), QString()))
135 , mModified(false)
136{
138
139 QBoxLayout *topLayout = new QHBoxLayout(this);
140 topLayout->setContentsMargins(0, 0, 0, 0);
141 mRoleCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant")), KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::ReqParticipant));
142 mRoleCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-optional")),
143 KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::OptParticipant));
144 mRoleCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-observer")), KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::NonParticipant));
145 mRoleCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-chair")), KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::Chair));
146
147 mResponseCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-request-response")), i18nc("@item:inlistbox", "Request Response"));
148 mResponseCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-no-response")), i18nc("@item:inlistbox", "Request No Response"));
149
150 mEdit->setToolTip(i18nc("@info:tooltip", "Enter the name or email address of the attendee."));
151 mEdit->setClearButtonEnabled(true);
152
153 mStateCombo->setWhatsThis(i18nc("@info:whatsthis", "Edits the current attendance status of the attendee."));
154
155 mRoleCombo->setWhatsThis(i18nc("@info:whatsthis", "Edits the role of the attendee."));
156
157 mEdit->setWhatsThis(i18nc("@info:whatsthis",
158 "The email address or name of the attendee. An invitation "
159 "can be sent to the user if an email address is provided."));
160
161 setActions(EventActions);
162
163 mResponseCombo->setToolTip(i18nc("@info:tooltip", "Request a response from the attendee"));
164 mResponseCombo->setWhatsThis(i18nc("@info:whatsthis",
165 "Edits whether to send an email to the "
166 "attendee to request a response concerning "
167 "attendance."));
168
169 // add them to the layout in the correct order
170 topLayout->addWidget(mRoleCombo);
171 topLayout->addWidget(mEdit);
172 topLayout->addWidget(mStateCombo);
173 topLayout->addWidget(mResponseCombo);
174
175 connect(mEdit, &AttendeeLineEdit::returnPressed, this, &AttendeeLine::slotReturnPressed);
176 connect(mEdit, &AttendeeLineEdit::deleteMe, this, &AttendeeLine::slotPropagateDeletion, Qt::QueuedConnection);
177 connect(mEdit, &AttendeeLineEdit::textChanged, this, &AttendeeLine::slotTextChanged, Qt::QueuedConnection);
178 connect(mEdit, &AttendeeLineEdit::upPressed, this, &AttendeeLine::slotFocusUp);
179 connect(mEdit, &AttendeeLineEdit::downPressed, this, &AttendeeLine::slotFocusDown);
180
181 connect(mRoleCombo, &AttendeeComboBox::rightPressed, mEdit, static_cast<void (AttendeeLineEdit::*)()>(&AttendeeLineEdit::setFocus));
182 connect(mEdit, &AttendeeLineEdit::leftPressed, mRoleCombo, static_cast<void (AttendeeComboBox::*)()>(&AttendeeComboBox::setFocus));
183
184 connect(mEdit, &AttendeeLineEdit::rightPressed, mStateCombo, static_cast<void (AttendeeComboBox::*)()>(&AttendeeComboBox::setFocus));
185 connect(mStateCombo, &AttendeeComboBox::leftPressed, mEdit, static_cast<void (AttendeeLineEdit::*)()>(&AttendeeLineEdit::setFocus));
186
187 connect(mStateCombo, &AttendeeComboBox::rightPressed, mResponseCombo, static_cast<void (AttendeeComboBox::*)()>(&AttendeeComboBox::setFocus));
188
189 connect(mResponseCombo, &AttendeeComboBox::leftPressed, mStateCombo, static_cast<void (AttendeeComboBox::*)()>(&AttendeeComboBox::setFocus));
190 connect(mResponseCombo, &AttendeeComboBox::rightPressed, this, &AttendeeLine::rightPressed);
191
192 connect(mEdit, &AttendeeLineEdit::editingFinished, this, &AttendeeLine::slotHandleChange, Qt::QueuedConnection);
193 connect(mEdit, &AttendeeLineEdit::textCompleted, this, &AttendeeLine::slotHandleChange, Qt::QueuedConnection);
194 connect(mEdit, &AttendeeLineEdit::clearButtonClicked, this, &AttendeeLine::slotPropagateDeletion, Qt::QueuedConnection);
195
196 connect(mRoleCombo, &AttendeeComboBox::itemChanged, this, &AttendeeLine::slotComboChanged);
197 connect(mStateCombo, &AttendeeComboBox::itemChanged, this, &AttendeeLine::slotComboChanged);
198 connect(mResponseCombo, &AttendeeComboBox::itemChanged, this, &AttendeeLine::slotComboChanged);
199}
200
201void AttendeeLine::activate()
202{
203 mEdit->setFocus();
204}
205
206void AttendeeLine::clear()
207{
208 mEdit->clear();
209 mRoleCombo->setCurrentIndex(0);
210 mStateCombo->setCurrentIndex(0);
211 mResponseCombo->setCurrentIndex(0);
212 mUid.clear();
213}
214
215void AttendeeLine::clearModified()
216{
217 mModified = false;
218 mEdit->setModified(false);
219}
220
221KPIM::MultiplyingLineData::Ptr AttendeeLine::data() const
222{
223 if (isModified()) {
224 const_cast<AttendeeLine *>(this)->dataFromFields();
225 }
226 return mData;
227}
228
229void AttendeeLine::dataFromFields()
230{
231 if (!mData) {
232 return;
233 }
234
235 KCalendarCore::Attendee oldAttendee(mData->attendee());
236
237 QString email;
239 KEmailAddress::extractEmailAddressAndName(mEdit->text(), email, name);
240
241 mData->setName(name);
242 mData->setEmail(email);
243
244 mData->setRole(AttendeeData::Role(mRoleCombo->currentIndex()));
245 mData->setStatus(AttendeeData::PartStat(mStateCombo->currentIndex()));
246 mData->setRSVP(mResponseCombo->currentIndex() == 0);
247 mData->setUid(mUid);
248
249 clearModified();
250 if (!(oldAttendee == mData->attendee()) && !email.isEmpty()) {
251 // if email is empty, we don't want to update anything
252 qCDebug(INCIDENCEEDITOR_LOG) << oldAttendee.email() << mData->email();
253 Q_EMIT changed(oldAttendee, mData->attendee());
254 }
255}
256
257void AttendeeLine::fieldsFromData()
258{
259 if (!mData) {
260 return;
261 }
262
263 mEdit->setText(mData->fullName());
264 mRoleCombo->setCurrentIndex(mData->role());
265 AttendeeData::PartStat partStat = mData->status();
266 if (partStat != AttendeeData::None) {
267 mStateCombo->setCurrentIndex(partStat);
268 } else {
269 mStateCombo->setCurrentIndex(AttendeeData::NeedsAction);
270 }
271 mResponseCombo->setCurrentIndex(mData->RSVP() ? 0 : 1);
272 mUid = mData->uid();
273}
274
275void AttendeeLine::fixTabOrder(QWidget *previous)
276{
277 setTabOrder(previous, mRoleCombo);
278 setTabOrder(mRoleCombo, mEdit);
279 setTabOrder(mEdit, mStateCombo);
280 setTabOrder(mStateCombo, mResponseCombo);
281}
282
283QWidget *AttendeeLine::tabOut() const
284{
285 return mResponseCombo;
286}
287
288bool AttendeeLine::isActive() const
289{
290 return mEdit->hasFocus();
291}
292
293bool AttendeeLine::isEmpty() const
294{
295 return mEdit->text().isEmpty();
296}
297
298bool AttendeeLine::isModified() const
299{
300 return mModified || mEdit->isModified();
301}
302
303int AttendeeLine::setColumnWidth(int w)
304{
305 w = qMax(w, mRoleCombo->sizeHint().width());
306 mRoleCombo->setFixedWidth(w);
307 mRoleCombo->updateGeometry();
309 return w;
310}
311
312void AttendeeLine::setActions(AttendeeActions actions)
313{
315 mStateCombo->clear();
316}
317
318void AttendeeLine::setCompletionMode(KCompletion::CompletionMode mode)
319{
320 mEdit->setCompletionMode(mode);
321}
322
323void AttendeeLine::setData(const KPIM::MultiplyingLineData::Ptr &data)
324{
326 if (!attendee) {
327 return;
328 }
329 mData = attendee;
330 fieldsFromData();
331}
332
333void AttendeeLine::slotHandleChange()
334{
335 if (mEdit->text().isEmpty()) {
336 Q_EMIT deleteLine(this);
337 } else {
338 // has bad side-effects, and I have no idea what this was supposed to be doing
339 // mEdit->setCursorPosition( 0 );
340 Q_EMIT editingFinished(this);
341 dataFromFields();
342 }
343}
344
345void AttendeeLine::slotTextChanged(const QString &str)
346{
348 mModified = true;
349 Q_EMIT changed(); // TODO: This doesn't seem connected to anywhere in incidenceattendee.cpp.
350 // but the important code is run in slotHandleChange() anyway so we don't see any bug
351}
352
353void AttendeeLine::slotComboChanged()
354{
355 mModified = true;
356 // If mUid is empty, we're still populating the widget, don't write fields to data yet
357 if (!mUid.isEmpty()) {
358 dataFromFields();
359 }
360}
361
362void AttendeeLine::aboutToBeDeleted()
363{
364 if (!mData) {
365 return;
366 }
367
368 Q_EMIT changed(mData->attendee(), KCalendarCore::Attendee(QLatin1StringView(""), QLatin1StringView("")));
369}
370
371bool AttendeeLine::canDeleteLineEdit() const
372{
373 return mEdit->canDeleteLineEdit();
374}
375
376#include "moc_attendeeline.cpp"
void deleteLine(KPIM::MultiplyingLine *)
KCODECS_EXPORT bool extractEmailAddressAndName(const QString &aStr, QString &mail, QString &name)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString name(StandardShortcut id)
virtual void keyPressEvent(QKeyEvent *e) override
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void accept()
QIcon fromTheme(const QString &name)
int key() const const
Qt::KeyboardModifiers modifiers() const const
void setContentsMargins(const QMargins &margins)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
void clear()
qsizetype size() const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
void clear()
Q_EMITQ_EMIT
T qobject_cast(QObject *object)
QObject * sender() const const
int width() const const
void clear()
bool isEmpty() const const
QueuedConnection
Key_Left
ShiftModifier
ToolButtonIconOnly
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QMenu * menu() const const
void showMenu()
virtual QSize sizeHint() const const override
QList< QAction * > actions() const const
QWidget * parentWidget() const const
void setFixedWidth(int w)
void setTabOrder(QWidget *first, QWidget *second)
void setToolTip(const QString &)
void updateGeometry()
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:37 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.