File indexing completed on 2025-01-05 04:49:46
0001 /* 0002 SPDX-FileCopyrightText: 2014-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "todoedit.h" 0008 #include "createtodoplugin_debug.h" 0009 #include <CalendarSupport/KCalPrefs> 0010 #include <CalendarSupport/Utils> 0011 #include <messageviewer/globalsettings_messageviewer.h> 0012 0013 #include <KLocalizedString> 0014 #include <QIcon> 0015 #include <QLineEdit> 0016 0017 #include <KMessageWidget> 0018 #include <QEvent> 0019 #include <QHBoxLayout> 0020 #include <QKeyEvent> 0021 #include <QLabel> 0022 #include <QPushButton> 0023 0024 #include <Akonadi/CollectionComboBox> 0025 #include <Akonadi/SpecialMailCollections> 0026 #include <IncidenceEditor/IncidenceDialog> 0027 #include <IncidenceEditor/IncidenceDialogFactory> 0028 #include <KGuiItem> 0029 #include <KStandardGuiItem> 0030 0031 namespace MessageViewer 0032 { 0033 QAbstractItemModel *_k_todoEditStubModel = nullptr; 0034 } 0035 0036 using namespace MessageViewer; 0037 0038 TodoEdit::TodoEdit(QWidget *parent) 0039 : QWidget(parent) 0040 , mNoteEdit(new QLineEdit(this)) 0041 , mMsgWidget(new KMessageWidget(this)) 0042 , mSaveButton(new QPushButton(QIcon::fromTheme(QStringLiteral("task-new")), i18n("&Save"), this)) 0043 , mOpenEditorButton(new QPushButton(i18n("Open &Editor..."), this)) 0044 { 0045 auto vbox = new QVBoxLayout(this); 0046 vbox->setContentsMargins(5, 5, 5, 5); 0047 vbox->setSpacing(2); 0048 0049 mMsgWidget->setCloseButtonVisible(true); 0050 mMsgWidget->setMessageType(KMessageWidget::Positive); 0051 mMsgWidget->setObjectName(QLatin1StringView("msgwidget")); 0052 mMsgWidget->setWordWrap(true); 0053 mMsgWidget->setVisible(false); 0054 vbox->addWidget(mMsgWidget); 0055 0056 auto hbox = new QHBoxLayout; 0057 hbox->setContentsMargins({}); 0058 hbox->setSpacing(2); 0059 vbox->addLayout(hbox); 0060 0061 auto lab = new QLabel(i18n("To-do:"), this); 0062 hbox->addWidget(lab); 0063 0064 mNoteEdit->setClearButtonEnabled(true); 0065 mNoteEdit->setObjectName(QLatin1StringView("noteedit")); 0066 mNoteEdit->setFocus(); 0067 connect(mNoteEdit, &QLineEdit::textChanged, this, &TodoEdit::slotTextEdited); 0068 connect(mNoteEdit, &QLineEdit::returnPressed, this, &TodoEdit::slotReturnPressed); 0069 hbox->addWidget(mNoteEdit, 1); 0070 0071 hbox->addSpacing(5); 0072 0073 mCollectionCombobox = new Akonadi::CollectionComboBox(_k_todoEditStubModel, this); 0074 mCollectionCombobox->setAccessRightsFilter(Akonadi::Collection::CanCreateItem); 0075 mCollectionCombobox->setMinimumWidth(250); 0076 mCollectionCombobox->setMimeTypeFilter(QStringList() << KCalendarCore::Todo::todoMimeType()); 0077 mCollectionCombobox->setObjectName(QLatin1StringView("akonadicombobox")); 0078 connect(mCollectionCombobox->model(), &QAbstractItemModel::rowsInserted, this, &TodoEdit::comboboxRowInserted); 0079 #ifndef QT_NO_ACCESSIBILITY 0080 mCollectionCombobox->setAccessibleDescription(i18n("Todo list where the new task will be stored.")); 0081 #endif 0082 mCollectionCombobox->setToolTip(i18n("Todo list where the new task will be stored")); 0083 connect(mCollectionCombobox, &Akonadi::CollectionComboBox::currentIndexChanged, this, &TodoEdit::slotCollectionChanged); 0084 connect(mCollectionCombobox, &Akonadi::CollectionComboBox::activated, this, &TodoEdit::slotCollectionChanged); 0085 hbox->addWidget(mCollectionCombobox); 0086 0087 hbox = new QHBoxLayout; 0088 hbox->setContentsMargins({}); 0089 hbox->setSpacing(2); 0090 vbox->addLayout(hbox); 0091 0092 hbox->addStretch(1); 0093 mSaveButton->setObjectName(QLatin1StringView("save-button")); 0094 mSaveButton->setEnabled(false); 0095 #ifndef QT_NO_ACCESSIBILITY 0096 mSaveButton->setAccessibleDescription(i18n("Create new todo and close this widget.")); 0097 #endif 0098 connect(mSaveButton, &QPushButton::clicked, this, &TodoEdit::slotReturnPressed); 0099 hbox->addWidget(mSaveButton); 0100 0101 mOpenEditorButton->setObjectName(QLatin1StringView("open-editor-button")); 0102 #ifndef QT_NO_ACCESSIBILITY 0103 mOpenEditorButton->setAccessibleDescription(i18n("Open todo editor, where more details can be changed.")); 0104 #endif 0105 mOpenEditorButton->setEnabled(false); 0106 connect(mOpenEditorButton, &QPushButton::clicked, this, &TodoEdit::slotOpenEditor); 0107 hbox->addWidget(mOpenEditorButton); 0108 0109 auto btn = new QPushButton(this); 0110 KGuiItem::assign(btn, KStandardGuiItem::cancel()); 0111 btn->setObjectName(QLatin1StringView("close-button")); 0112 #ifndef QT_NO_ACCESSIBILITY 0113 btn->setAccessibleDescription(i18n("Close the widget for creating new todos.")); 0114 #endif 0115 connect(btn, &QPushButton::clicked, this, &TodoEdit::slotCloseWidget); 0116 hbox->addWidget(btn); 0117 0118 readConfig(); 0119 setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); 0120 mCollectionCombobox->installEventFilter(this); 0121 installEventFilter(this); 0122 } 0123 0124 TodoEdit::~TodoEdit() 0125 { 0126 writeConfig(); 0127 } 0128 0129 void TodoEdit::comboboxRowInserted() 0130 { 0131 updateButtons(mNoteEdit->text()); 0132 } 0133 0134 void TodoEdit::updateButtons(const QString &subject) 0135 { 0136 const bool subjectIsNotEmpty = !subject.trimmed().isEmpty(); 0137 const bool collectionComboboxEmpty = (mCollectionCombobox->count() < 1); 0138 mSaveButton->setEnabled(subjectIsNotEmpty && !collectionComboboxEmpty); 0139 mOpenEditorButton->setEnabled(subjectIsNotEmpty && !collectionComboboxEmpty); 0140 } 0141 0142 void TodoEdit::showToDoWidget() 0143 { 0144 const KMime::Headers::Subject *const subject = mMessage ? mMessage->subject(false) : nullptr; 0145 if (subject) { 0146 bool isSentFolder = false; 0147 if (mCurrentCollection.isValid()) { 0148 isSentFolder = (Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::SentMail) == mCurrentCollection); 0149 } 0150 mNoteEdit->setText(isSentFolder ? i18n("Check I received a reply about \"%1\"", subject->asUnicodeString()) 0151 : i18n("Reply to \"%1\"", subject->asUnicodeString())); 0152 mNoteEdit->selectAll(); 0153 mNoteEdit->setFocus(); 0154 } else { 0155 mNoteEdit->clear(); 0156 } 0157 mNoteEdit->setFocus(); 0158 show(); 0159 } 0160 0161 void TodoEdit::setCurrentCollection(const Akonadi::Collection &col) 0162 { 0163 mCurrentCollection = col; 0164 } 0165 0166 void TodoEdit::writeConfig() 0167 { 0168 const Akonadi::Collection col = mCollectionCombobox->currentCollection(); 0169 // col might not be valid if the collection wasn't found yet (the combo is async), skip saving in that case 0170 if (col.isValid() && col.id() != MessageViewer::MessageViewerSettingsBase::self()->lastSelectedFolder()) { 0171 MessageViewer::MessageViewerSettingsBase::self()->setLastSelectedFolder(col.id()); 0172 MessageViewer::MessageViewerSettingsBase::self()->save(); 0173 } 0174 } 0175 0176 void TodoEdit::readConfig() 0177 { 0178 const qint64 id = MessageViewer::MessageViewerSettingsBase::self()->lastSelectedFolder(); 0179 if (id != -1) { 0180 mCollectionCombobox->setDefaultCollection(Akonadi::Collection(id)); 0181 } 0182 } 0183 0184 Akonadi::Collection TodoEdit::collection() const 0185 { 0186 return mCollection; 0187 } 0188 0189 void TodoEdit::slotCollectionChanged(int /*index*/) 0190 { 0191 setCollection(mCollectionCombobox->currentCollection()); 0192 } 0193 0194 void TodoEdit::setCollection(const Akonadi::Collection &value) 0195 { 0196 if (mCollection != value) { 0197 mCollection = value; 0198 Q_EMIT collectionChanged(mCollection); 0199 } 0200 } 0201 0202 KMime::Message::Ptr TodoEdit::message() const 0203 { 0204 return mMessage; 0205 } 0206 0207 void TodoEdit::setMessage(const KMime::Message::Ptr &value) 0208 { 0209 if (mMessage != value) { 0210 mMessage = value; 0211 Q_EMIT messageChanged(mMessage); 0212 } 0213 } 0214 0215 void TodoEdit::slotCloseWidget() 0216 { 0217 if (isVisible()) { 0218 writeConfig(); 0219 mNoteEdit->clear(); 0220 mMessage = KMime::Message::Ptr(); 0221 mMsgWidget->hide(); 0222 hide(); 0223 } 0224 } 0225 0226 void TodoEdit::slotReturnPressed() 0227 { 0228 if (!mMessage) { 0229 qCDebug(CREATETODOPLUGIN_LOG) << " Message is null"; 0230 return; 0231 } 0232 const Akonadi::Collection collection = mCollectionCombobox->currentCollection(); 0233 if (!collection.isValid()) { 0234 qCDebug(CREATETODOPLUGIN_LOG) << " Collection is not valid"; 0235 return; 0236 } 0237 0238 if (!mNoteEdit->text().trimmed().isEmpty()) { 0239 mMsgWidget->setText(i18nc("%1 is summary of the todo, %2 is name of the folder in which it is stored", 0240 "New todo '%1' was added to task list '%2'", 0241 mNoteEdit->text(), 0242 collection.displayName())); 0243 KCalendarCore::Todo::Ptr todo = createTodoItem(); 0244 0245 // We don't hide the widget here, so that multiple todo's can be added 0246 Q_EMIT createTodo(todo, collection); 0247 0248 mMsgWidget->animatedShow(); 0249 } 0250 } 0251 0252 KCalendarCore::Todo::Ptr TodoEdit::createTodoItem() 0253 { 0254 if (mMessage) { 0255 KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo); 0256 todo->setSummary(mNoteEdit->text()); 0257 KCalendarCore::Attachment attachment(mMessage->encodedContent().toBase64(), KMime::Message::mimeType()); 0258 const KMime::Headers::Subject *const subject = mMessage->subject(false); 0259 if (subject) { 0260 attachment.setLabel(subject->asUnicodeString()); 0261 } 0262 if (CalendarSupport::KCalPrefs::instance()->defaultTodoReminders()) { 0263 KCalendarCore::Alarm::Ptr alm = todo->newAlarm(); 0264 CalendarSupport::createAlarmReminder(alm, todo->type()); 0265 } 0266 0267 todo->addAttachment(attachment); 0268 return todo; 0269 } 0270 return {}; 0271 } 0272 0273 bool TodoEdit::eventFilter(QObject *object, QEvent *e) 0274 { 0275 // Close the bar when pressing Escape. 0276 // Not using a QShortcut for this because it could conflict with 0277 // window-global actions (e.g. Emil Sedgh binds Esc to "close tab"). 0278 // With a shortcut override we can catch this before it gets to kactions. 0279 const bool shortCutOverride = (e->type() == QEvent::ShortcutOverride); 0280 if (shortCutOverride || e->type() == QEvent::KeyPress) { 0281 auto kev = static_cast<QKeyEvent *>(e); 0282 if (kev->key() == Qt::Key_Escape) { 0283 e->accept(); 0284 slotCloseWidget(); 0285 return true; 0286 } else if (kev->key() == Qt::Key_Enter || kev->key() == Qt::Key_Return || kev->key() == Qt::Key_Space) { 0287 e->accept(); 0288 if (shortCutOverride) { 0289 return true; 0290 } 0291 if (object == mCollectionCombobox) { 0292 mCollectionCombobox->showPopup(); 0293 return true; 0294 } 0295 } 0296 } 0297 return QWidget::eventFilter(object, e); 0298 } 0299 0300 void TodoEdit::slotOpenEditor() 0301 { 0302 KCalendarCore::Todo::Ptr event = createTodoItem(); 0303 if (event) { 0304 Akonadi::Item item; 0305 item.setPayload<KCalendarCore::Todo::Ptr>(event); 0306 item.setMimeType(KCalendarCore::Todo::todoMimeType()); 0307 0308 IncidenceEditorNG::IncidenceDialog *dlg = 0309 IncidenceEditorNG::IncidenceDialogFactory::create(true, KCalendarCore::IncidenceBase::TypeTodo, nullptr, this); 0310 dlg->selectCollection(mCollectionCombobox->currentCollection()); 0311 connect(dlg, &IncidenceEditorNG::IncidenceDialog::finished, this, &TodoEdit::slotCloseWidget); 0312 dlg->load(item); 0313 dlg->open(); 0314 } 0315 } 0316 0317 void TodoEdit::slotTextEdited(const QString &subject) 0318 { 0319 updateButtons(subject); 0320 if (mMsgWidget->isVisible()) { 0321 mMsgWidget->hide(); 0322 } 0323 } 0324 0325 #include "moc_todoedit.cpp"