File indexing completed on 2024-04-28 05:11:30

0001 /*
0002   SPDX-FileCopyrightText: 2010 Casey Link <unnamedrambler@gmail.com>
0003   SPDX-FileCopyrightText: 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0004 
0005   SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "attendeeline.h"
0009 #include "attendeedata.h"
0010 
0011 #include <KCalUtils/Stringify>
0012 
0013 #include <KEmailAddress>
0014 
0015 #include "incidenceeditor_debug.h"
0016 #include <KCompletionBox>
0017 #include <KLocalizedString>
0018 
0019 #include <QBoxLayout>
0020 #include <QKeyEvent>
0021 #include <QMenu>
0022 
0023 using namespace IncidenceEditorNG;
0024 
0025 using TextIconPair = QPair<QString, QIcon>;
0026 
0027 AttendeeComboBox::AttendeeComboBox(QWidget *parent)
0028     : QToolButton(parent)
0029     , mMenu(new QMenu(this))
0030 {
0031     setPopupMode(QToolButton::InstantPopup);
0032     setToolButtonStyle(Qt::ToolButtonIconOnly);
0033     setMenu(mMenu);
0034 }
0035 
0036 void AttendeeComboBox::addItem(const QIcon &icon, const QString &text)
0037 {
0038     mList.append(TextIconPair(text, icon));
0039     if (mCurrentIndex == -1) {
0040         setCurrentIndex(0);
0041     }
0042     int index = mList.size() - 1;
0043     QAction *act = menu()->addAction(icon, text, this, &AttendeeComboBox::slotActionTriggered);
0044     act->setData(index);
0045 }
0046 
0047 void AttendeeComboBox::addItems(const QStringList &texts)
0048 {
0049     for (const QString &str : texts) {
0050         addItem(QIcon(), str);
0051     }
0052     if (mCurrentIndex == -1) {
0053         setCurrentIndex(0);
0054     }
0055 }
0056 
0057 int AttendeeComboBox::currentIndex() const
0058 {
0059     return mCurrentIndex;
0060 }
0061 
0062 void AttendeeComboBox::clear()
0063 {
0064     mCurrentIndex = -1;
0065     mMenu->clear();
0066     mList.clear();
0067 }
0068 
0069 void AttendeeComboBox::setCurrentIndex(int index)
0070 {
0071     Q_ASSERT(index < mList.size());
0072     const int old = mCurrentIndex;
0073     mCurrentIndex = index;
0074     setIcon(mList.at(index).second);
0075     setToolTip(mList.at(index).first);
0076     if (old != index) {
0077         Q_EMIT itemChanged();
0078     }
0079 }
0080 
0081 void AttendeeComboBox::slotActionTriggered()
0082 {
0083     int index = qobject_cast<QAction *>(sender())->data().toInt();
0084     setCurrentIndex(index);
0085 }
0086 
0087 void AttendeeComboBox::keyPressEvent(QKeyEvent *ev)
0088 {
0089     if (ev->key() == Qt::Key_Left) {
0090         Q_EMIT leftPressed();
0091     } else if (ev->key() == Qt::Key_Right) {
0092         Q_EMIT rightPressed();
0093     } else if (!mMenu->isVisible() && (ev->key() == Qt::Key_Down || ev->key() == Qt::Key_Space)) {
0094         showMenu();
0095     } else {
0096         QToolButton::keyPressEvent(ev);
0097     }
0098 }
0099 
0100 AttendeeLineEdit::AttendeeLineEdit(QWidget *parent)
0101     : AddresseeLineEdit(parent, true)
0102 {
0103 }
0104 
0105 void AttendeeLineEdit::keyPressEvent(QKeyEvent *ev)
0106 {
0107     if ((ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) && !completionBox()->isVisible()) {
0108         Q_EMIT downPressed();
0109         PimCommon::AddresseeLineEdit::keyPressEvent(ev);
0110     } else if (ev->key() == Qt::Key_Backspace && text().isEmpty()) {
0111         ev->accept();
0112         Q_EMIT deleteMe();
0113     } else if (ev->key() == Qt::Key_Left && cursorPosition() == 0 && !ev->modifiers().testFlag(Qt::ShiftModifier)) {
0114         // Shift would be pressed during selection
0115         Q_EMIT leftPressed();
0116     } else if (ev->key() == Qt::Key_Right && cursorPosition() == text().length() && !ev->modifiers().testFlag(Qt::ShiftModifier)) {
0117         // Shift would be pressed during selection
0118         Q_EMIT rightPressed();
0119     } else if (ev->key() == Qt::Key_Down) {
0120         Q_EMIT downPressed();
0121     } else if (ev->key() == Qt::Key_Up) {
0122         Q_EMIT upPressed();
0123     } else {
0124         PimCommon::AddresseeLineEdit::keyPressEvent(ev);
0125     }
0126 }
0127 
0128 AttendeeLine::AttendeeLine(QWidget *parent)
0129     : MultiplyingLine(parent)
0130     , mRoleCombo(new AttendeeComboBox(this))
0131     , mStateCombo(new AttendeeComboBox(this))
0132     , mResponseCombo(new AttendeeComboBox(this))
0133     , mEdit(new AttendeeLineEdit(this))
0134     , mData(new AttendeeData(QString(), QString()))
0135     , mModified(false)
0136 {
0137     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0138 
0139     QBoxLayout *topLayout = new QHBoxLayout(this);
0140     topLayout->setContentsMargins(0, 0, 0, 0);
0141     mRoleCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant")), KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::ReqParticipant));
0142     mRoleCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-optional")),
0143                         KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::OptParticipant));
0144     mRoleCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-observer")), KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::NonParticipant));
0145     mRoleCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-chair")), KCalUtils::Stringify::attendeeRole(KCalendarCore::Attendee::Chair));
0146 
0147     mResponseCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-request-response")), i18nc("@item:inlistbox", "Request Response"));
0148     mResponseCombo->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-no-response")), i18nc("@item:inlistbox", "Request No Response"));
0149 
0150     mEdit->setToolTip(i18nc("@info:tooltip", "Enter the name or email address of the attendee."));
0151     mEdit->setClearButtonEnabled(true);
0152 
0153     mStateCombo->setWhatsThis(i18nc("@info:whatsthis", "Edits the current attendance status of the attendee."));
0154 
0155     mRoleCombo->setWhatsThis(i18nc("@info:whatsthis", "Edits the role of the attendee."));
0156 
0157     mEdit->setWhatsThis(i18nc("@info:whatsthis",
0158                               "The email address or name of the attendee. An invitation "
0159                               "can be sent to the user if an email address is provided."));
0160 
0161     setActions(EventActions);
0162 
0163     mResponseCombo->setToolTip(i18nc("@info:tooltip", "Request a response from the attendee"));
0164     mResponseCombo->setWhatsThis(i18nc("@info:whatsthis",
0165                                        "Edits whether to send an email to the "
0166                                        "attendee to request a response concerning "
0167                                        "attendance."));
0168 
0169     // add them to the layout in the correct order
0170     topLayout->addWidget(mRoleCombo);
0171     topLayout->addWidget(mEdit);
0172     topLayout->addWidget(mStateCombo);
0173     topLayout->addWidget(mResponseCombo);
0174 
0175     connect(mEdit, &AttendeeLineEdit::returnPressed, this, &AttendeeLine::slotReturnPressed);
0176     connect(mEdit, &AttendeeLineEdit::deleteMe, this, &AttendeeLine::slotPropagateDeletion, Qt::QueuedConnection);
0177     connect(mEdit, &AttendeeLineEdit::textChanged, this, &AttendeeLine::slotTextChanged, Qt::QueuedConnection);
0178     connect(mEdit, &AttendeeLineEdit::upPressed, this, &AttendeeLine::slotFocusUp);
0179     connect(mEdit, &AttendeeLineEdit::downPressed, this, &AttendeeLine::slotFocusDown);
0180 
0181     connect(mRoleCombo, &AttendeeComboBox::rightPressed, mEdit, static_cast<void (AttendeeLineEdit::*)()>(&AttendeeLineEdit::setFocus));
0182     connect(mEdit, &AttendeeLineEdit::leftPressed, mRoleCombo, static_cast<void (AttendeeComboBox::*)()>(&AttendeeComboBox::setFocus));
0183 
0184     connect(mEdit, &AttendeeLineEdit::rightPressed, mStateCombo, static_cast<void (AttendeeComboBox::*)()>(&AttendeeComboBox::setFocus));
0185     connect(mStateCombo, &AttendeeComboBox::leftPressed, mEdit, static_cast<void (AttendeeLineEdit::*)()>(&AttendeeLineEdit::setFocus));
0186 
0187     connect(mStateCombo, &AttendeeComboBox::rightPressed, mResponseCombo, static_cast<void (AttendeeComboBox::*)()>(&AttendeeComboBox::setFocus));
0188 
0189     connect(mResponseCombo, &AttendeeComboBox::leftPressed, mStateCombo, static_cast<void (AttendeeComboBox::*)()>(&AttendeeComboBox::setFocus));
0190     connect(mResponseCombo, &AttendeeComboBox::rightPressed, this, &AttendeeLine::rightPressed);
0191 
0192     connect(mEdit, &AttendeeLineEdit::editingFinished, this, &AttendeeLine::slotHandleChange, Qt::QueuedConnection);
0193     connect(mEdit, &AttendeeLineEdit::textCompleted, this, &AttendeeLine::slotHandleChange, Qt::QueuedConnection);
0194     connect(mEdit, &AttendeeLineEdit::clearButtonClicked, this, &AttendeeLine::slotPropagateDeletion, Qt::QueuedConnection);
0195 
0196     connect(mRoleCombo, &AttendeeComboBox::itemChanged, this, &AttendeeLine::slotComboChanged);
0197     connect(mStateCombo, &AttendeeComboBox::itemChanged, this, &AttendeeLine::slotComboChanged);
0198     connect(mResponseCombo, &AttendeeComboBox::itemChanged, this, &AttendeeLine::slotComboChanged);
0199 }
0200 
0201 void AttendeeLine::activate()
0202 {
0203     mEdit->setFocus();
0204 }
0205 
0206 void AttendeeLine::clear()
0207 {
0208     mEdit->clear();
0209     mRoleCombo->setCurrentIndex(0);
0210     mStateCombo->setCurrentIndex(0);
0211     mResponseCombo->setCurrentIndex(0);
0212     mUid.clear();
0213 }
0214 
0215 void AttendeeLine::clearModified()
0216 {
0217     mModified = false;
0218     mEdit->setModified(false);
0219 }
0220 
0221 KPIM::MultiplyingLineData::Ptr AttendeeLine::data() const
0222 {
0223     if (isModified()) {
0224         const_cast<AttendeeLine *>(this)->dataFromFields();
0225     }
0226     return mData;
0227 }
0228 
0229 void AttendeeLine::dataFromFields()
0230 {
0231     if (!mData) {
0232         return;
0233     }
0234 
0235     KCalendarCore::Attendee oldAttendee(mData->attendee());
0236 
0237     QString email;
0238     QString name;
0239     KEmailAddress::extractEmailAddressAndName(mEdit->text(), email, name);
0240 
0241     mData->setName(name);
0242     mData->setEmail(email);
0243 
0244     mData->setRole(AttendeeData::Role(mRoleCombo->currentIndex()));
0245     mData->setStatus(AttendeeData::PartStat(mStateCombo->currentIndex()));
0246     mData->setRSVP(mResponseCombo->currentIndex() == 0);
0247     mData->setUid(mUid);
0248 
0249     clearModified();
0250     if (!(oldAttendee == mData->attendee()) && !email.isEmpty()) {
0251         // if email is empty, we don't want to update anything
0252         qCDebug(INCIDENCEEDITOR_LOG) << oldAttendee.email() << mData->email();
0253         Q_EMIT changed(oldAttendee, mData->attendee());
0254     }
0255 }
0256 
0257 void AttendeeLine::fieldsFromData()
0258 {
0259     if (!mData) {
0260         return;
0261     }
0262 
0263     mEdit->setText(mData->fullName());
0264     mRoleCombo->setCurrentIndex(mData->role());
0265     AttendeeData::PartStat partStat = mData->status();
0266     if (partStat != AttendeeData::None) {
0267         mStateCombo->setCurrentIndex(partStat);
0268     } else {
0269         mStateCombo->setCurrentIndex(AttendeeData::NeedsAction);
0270     }
0271     mResponseCombo->setCurrentIndex(mData->RSVP() ? 0 : 1);
0272     mUid = mData->uid();
0273 }
0274 
0275 void AttendeeLine::fixTabOrder(QWidget *previous)
0276 {
0277     setTabOrder(previous, mRoleCombo);
0278     setTabOrder(mRoleCombo, mEdit);
0279     setTabOrder(mEdit, mStateCombo);
0280     setTabOrder(mStateCombo, mResponseCombo);
0281 }
0282 
0283 QWidget *AttendeeLine::tabOut() const
0284 {
0285     return mResponseCombo;
0286 }
0287 
0288 bool AttendeeLine::isActive() const
0289 {
0290     return mEdit->hasFocus();
0291 }
0292 
0293 bool AttendeeLine::isEmpty() const
0294 {
0295     return mEdit->text().isEmpty();
0296 }
0297 
0298 bool AttendeeLine::isModified() const
0299 {
0300     return mModified || mEdit->isModified();
0301 }
0302 
0303 int AttendeeLine::setColumnWidth(int w)
0304 {
0305     w = qMax(w, mRoleCombo->sizeHint().width());
0306     mRoleCombo->setFixedWidth(w);
0307     mRoleCombo->updateGeometry();
0308     parentWidget()->updateGeometry();
0309     return w;
0310 }
0311 
0312 void AttendeeLine::setActions(AttendeeActions actions)
0313 {
0314     Q_UNUSED(actions);
0315     mStateCombo->clear();
0316 }
0317 
0318 void AttendeeLine::setCompletionMode(KCompletion::CompletionMode mode)
0319 {
0320     mEdit->setCompletionMode(mode);
0321 }
0322 
0323 void AttendeeLine::setData(const KPIM::MultiplyingLineData::Ptr &data)
0324 {
0325     AttendeeData::Ptr attendee = qSharedPointerDynamicCast<AttendeeData>(data);
0326     if (!attendee) {
0327         return;
0328     }
0329     mData = attendee;
0330     fieldsFromData();
0331 }
0332 
0333 void AttendeeLine::slotHandleChange()
0334 {
0335     if (mEdit->text().isEmpty()) {
0336         Q_EMIT deleteLine(this);
0337     } else {
0338         // has bad side-effects, and I have no idea what this was supposed to be doing
0339         //    mEdit->setCursorPosition( 0 );
0340         Q_EMIT editingFinished(this);
0341         dataFromFields();
0342     }
0343 }
0344 
0345 void AttendeeLine::slotTextChanged(const QString &str)
0346 {
0347     Q_UNUSED(str)
0348     mModified = true;
0349     Q_EMIT changed(); // TODO: This doesn't seem connected to anywhere in incidenceattendee.cpp.
0350     // but the important code is run in slotHandleChange() anyway so we don't see any bug
0351 }
0352 
0353 void AttendeeLine::slotComboChanged()
0354 {
0355     mModified = true;
0356     // If mUid is empty, we're still populating the widget, don't write fields to data yet
0357     if (!mUid.isEmpty()) {
0358         dataFromFields();
0359     }
0360 }
0361 
0362 void AttendeeLine::aboutToBeDeleted()
0363 {
0364     if (!mData) {
0365         return;
0366     }
0367 
0368     Q_EMIT changed(mData->attendee(), KCalendarCore::Attendee(QLatin1StringView(""), QLatin1StringView("")));
0369 }
0370 
0371 bool AttendeeLine::canDeleteLineEdit() const
0372 {
0373     return mEdit->canDeleteLineEdit();
0374 }
0375 
0376 #include "moc_attendeeline.cpp"