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"