Messagelib

customtemplates.cpp
1/*
2 * SPDX-FileCopyrightText: 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
3 * SPDX-FileCopyrightText: 2011-2024 Laurent Montel <montel@kde.org>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "customtemplates.h"
9#include "customtemplates_kfg.h"
10#include "globalsettings_templateparser.h"
11#include "templateparser/templatesinsertcommandpushbutton.h"
12#include "templateparseremailaddressrequesterinterfacewidget.h"
13#include "ui_customtemplates_base.h"
14#include <KLineEditEventHandler>
15#include <TextCustomEditor/PlainTextEditor>
16
17#include <KLocalizedString>
18#include <KMessageBox>
19#include <QIcon>
20
21#include <QWhatsThis>
22
23using namespace TemplateParser;
24
25CustomTemplates::CustomTemplates(const QList<KActionCollection *> &actionCollection, QWidget *parent)
26 : QWidget(parent)
27 , mUi(new Ui_CustomTemplatesBase)
28{
29 mUi->setupUi(this);
30
31 mUi->mAdd->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
32 mUi->mAdd->setEnabled(false);
33 mUi->mRemove->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
34 mUi->mDuplicate->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
35
36 mUi->mList->setColumnWidth(0, 100);
37 mUi->mList->header()->setStretchLastSection(true);
38 mUi->mList->setItemDelegate(new CustomTemplateItemDelegate(this));
39 mUi->mList->header()->setSectionsMovable(false);
40 mUi->mEditFrame->setEnabled(false);
41
43 connect(mUi->mEdit->editor(), &QPlainTextEdit::textChanged, this, &CustomTemplates::slotTextChanged);
44 connect(mUi->mToEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged);
45 connect(mUi->mCCEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged);
46
47 connect(mUi->mName, &QLineEdit::textChanged, this, &CustomTemplates::slotNameChanged);
48
49 connect(mUi->mName, &QLineEdit::returnPressed, this, &CustomTemplates::slotAddClicked);
50
51 connect(mUi->mInsertCommand,
52 qOverload<const QString &, int>(&TemplateParser::TemplatesInsertCommandPushButton::insertCommand),
53 this,
54 &CustomTemplates::slotInsertCommand);
55
56 connect(mUi->mAdd, &QPushButton::clicked, this, &CustomTemplates::slotAddClicked);
57 connect(mUi->mRemove, &QPushButton::clicked, this, &CustomTemplates::slotRemoveClicked);
58 connect(mUi->mDuplicate, &QPushButton::clicked, this, &CustomTemplates::slotDuplicateClicked);
59 connect(mUi->mList, &QTreeWidget::currentItemChanged, this, &CustomTemplates::slotListSelectionChanged);
60 connect(mUi->mList, &QTreeWidget::itemChanged, this, &CustomTemplates::slotItemChanged);
61 connect(mUi->mType, &QComboBox::activated, this, &CustomTemplates::slotTypeActivated);
62
63 connect(mUi->mKeySequenceWidget, &KKeySequenceWidget::keySequenceChanged, this, &CustomTemplates::slotShortcutChanged);
64
65 mUi->mKeySequenceWidget->setCheckActionCollections(actionCollection);
66
67 mReplyPix = QIcon::fromTheme(QStringLiteral("mail-reply-sender"));
68 mReplyAllPix = QIcon::fromTheme(QStringLiteral("mail-reply-all"));
69 mForwardPix = QIcon::fromTheme(QStringLiteral("mail-forward"));
70
71 mUi->mType->clear();
72 mUi->mType->addItem(QPixmap(), i18nc("Message->", "Universal"));
73 mUi->mType->addItem(mReplyPix, i18nc("Message->", "Reply"));
74 mUi->mType->addItem(mReplyAllPix, i18nc("Message->", "Reply to All"));
75 mUi->mType->addItem(mForwardPix, i18nc("Message->", "Forward"));
76
77 mUi->mHelp->setText(i18n("<a href=\"whatsthis\">How does this work?</a>"));
78 connect(mUi->mHelp, &QLabel::linkActivated, this, &CustomTemplates::slotHelpLinkClicked);
79 mUi->mHelp->setContextMenuPolicy(Qt::NoContextMenu);
80
81 slotNameChanged(mUi->mName->text());
82}
83
84void CustomTemplates::slotHelpLinkClicked(const QString &)
85{
86 const QString help = i18n(
87 "<qt>"
88 "<p>Here you can add, edit, and delete custom message "
89 "templates to use when you compose a reply or forwarding message. "
90 "Create the custom template by typing the name into the input box "
91 "and press the '+' button. Also, you can bind a keyboard "
92 "combination to the template for faster operations.</p>"
93 "<p>Message templates support substitution commands, "
94 "by simply typing them or selecting them from the "
95 "<i>Insert command</i> menu.</p>"
96 "<p>There are four types of custom templates: used to "
97 "<i>Reply</i>, <i>Reply to All</i>, <i>Forward</i>, and "
98 "<i>Universal</i> which can be used for all kinds of operations. "
99 "You cannot bind a keyboard shortcut to <i>Universal</i> templates.</p>"
100 "</qt>");
101
103}
104
105CustomTemplates::~CustomTemplates()
106{
107 disconnect(mUi->mEdit->editor(), &QPlainTextEdit::textChanged, this, &CustomTemplates::slotTextChanged);
108 disconnect(mUi->mToEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged);
109 disconnect(mUi->mCCEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged);
110 delete mUi;
111}
112
113void CustomTemplates::slotNameChanged(const QString &text)
114{
115 mUi->mAdd->setEnabled(!text.trimmed().isEmpty());
116}
117
118QString CustomTemplates::indexToType(int index)
119{
120 QString typeStr;
121 switch (index) {
122 case TUniversal:
123 typeStr = i18nc("Message->", "Universal");
124 break;
125 /* case TNewMessage:
126 typeStr = i18n( "New Message" );
127 break; */
128 case TReply:
129 typeStr = i18nc("Message->", "Reply");
130 break;
131 case TReplyAll:
132 typeStr = i18nc("Message->", "Reply to All");
133 break;
134 case TForward:
135 typeStr = i18nc("Message->", "Forward");
136 break;
137 default:
138 typeStr = i18nc("Message->", "Unknown");
139 break;
140 }
141 return typeStr;
142}
143
144void CustomTemplates::slotTextChanged()
145{
146 QTreeWidgetItem *item = mUi->mList->currentItem();
147 if (item) {
148 auto vitem = static_cast<CustomTemplateItem *>(item);
149 vitem->setContent(mUi->mEdit->toPlainText());
150 if (!mBlockChangeSignal) {
151 vitem->setTo(mUi->mToEdit->text());
152 vitem->setCc(mUi->mCCEdit->text());
153 }
154 }
155
156 if (!mBlockChangeSignal) {
157 Q_EMIT changed();
158 }
159}
160
161void CustomTemplates::iconFromType(CustomTemplates::Type type, CustomTemplateItem *item)
162{
163 switch (type) {
164 case TReply:
165 item->setIcon(0, mReplyPix);
166 break;
167 case TReplyAll:
168 item->setIcon(0, mReplyAllPix);
169 break;
170 case TForward:
171 item->setIcon(0, mForwardPix);
172 break;
173 default:
174 item->setIcon(0, QPixmap());
175 break;
176 }
177}
178
179void CustomTemplates::load()
180{
181 const QStringList list = TemplateParserSettings::self()->customTemplates();
182 mUi->mList->clear();
185 CTemplates t(*it);
186 QKeySequence shortcut(t.shortcut());
187 auto type = static_cast<Type>(t.type());
188 auto item = new CustomTemplateItem(mUi->mList, *it, t.content(), shortcut, type, t.to(), t.cC());
189 item->setText(1, *it);
190 item->setText(0, indexToType(type));
191 iconFromType(type, item);
192 }
193 const bool enabled = mUi->mList->topLevelItemCount() > 0 && mUi->mList->currentItem();
194 mUi->mRemove->setEnabled(enabled);
195 mUi->mDuplicate->setEnabled(enabled);
196}
197
198void CustomTemplates::save()
199{
200 // Before saving the new templates, delete the old ones. That needs to be done before
201 // saving, since otherwise a new template with the new name wouldn't get saved.
202 KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("customtemplatesrc"), KConfig::NoGlobals);
203 for (const QString &item : std::as_const(mItemsToDelete)) {
204 CTemplates t(item);
205 const QString configGroup = t.currentGroup();
206 config->deleteGroup(configGroup);
207 }
208
210 QTreeWidgetItemIterator lit(mUi->mList);
211 while (*lit) {
212 auto it = static_cast<CustomTemplateItem *>(*lit);
213 const QString name = it->text(1);
214 list.append(name);
215
216 CTemplates t(name);
217 QString content = it->content();
218 if (content.trimmed().isEmpty()) {
219 content = QStringLiteral("%BLANK");
220 }
221
222 t.setContent(content);
223 t.setShortcut(it->shortcut().toString());
224 t.setType(it->customType());
225 t.setTo(it->to());
226 t.setCC(it->cc());
227 t.save();
228 ++lit;
229 }
230
231 TemplateParserSettings::self()->setCustomTemplates(list);
232 TemplateParserSettings::self()->save();
233
234 Q_EMIT templatesUpdated();
235}
236
237void CustomTemplates::slotInsertCommand(const QString &cmd, int adjustCursor)
238{
239 QTextCursor cursor = mUi->mEdit->editor()->textCursor();
240 cursor.insertText(cmd);
241 cursor.setPosition(cursor.position() + adjustCursor);
242 mUi->mEdit->editor()->setTextCursor(cursor);
243 mUi->mEdit->editor()->setFocus();
244}
245
246bool CustomTemplates::nameAlreadyExists(const QString &str, QTreeWidgetItem *item)
247{
248 QTreeWidgetItemIterator lit(mUi->mList);
249 while (*lit) {
250 const QString name = (*lit)->text(1);
251 if ((name == str) && ((*lit) != item)) {
252 KMessageBox::error(this, i18n("A template with same name already exists."), i18nc("@title:window", "Cannot create template"));
253 return true;
254 }
255 ++lit;
256 }
257 return false;
258}
259
260void CustomTemplates::slotAddClicked()
261{
262 const QString str = mUi->mName->text();
263 if (!str.isEmpty()) {
264 if (nameAlreadyExists(str)) {
265 return;
266 }
267
269 auto item = new CustomTemplateItem(mUi->mList, str, QString(), nullShortcut, TUniversal, QString(), QString());
270 item->setText(0, indexToType(TUniversal));
271 item->setText(1, str);
272 mUi->mList->setCurrentItem(item);
273 mUi->mRemove->setEnabled(true);
274 mUi->mDuplicate->setEnabled(true);
275 mUi->mName->clear();
276 mUi->mKeySequenceWidget->setEnabled(false);
277 if (!mBlockChangeSignal) {
278 Q_EMIT changed();
279 }
280 }
281}
282
283QString CustomTemplates::createUniqueName(const QString &name) const
284{
285 QString uniqueName = name;
286
287 int counter = 0;
288 bool found = true;
289
290 while (found) {
291 found = false;
292
293 QTreeWidgetItemIterator lit(mUi->mList);
294 while (*lit) {
295 const QString itemName = (*lit)->text(1);
296 if (!itemName.compare(uniqueName)) {
297 found = true;
298 ++counter;
299 uniqueName = name;
300 uniqueName += QLatin1StringView(" (") + QString::number(counter) + QLatin1StringView(")");
301 break;
302 }
303 lit++;
304 }
305 }
306
307 return uniqueName;
308}
309
310void CustomTemplates::slotDuplicateClicked()
311{
312 QTreeWidgetItem *currentItem = mUi->mList->currentItem();
313 if (!currentItem) {
314 return;
315 }
316 auto origItem = static_cast<CustomTemplateItem *>(currentItem);
317 const QString templateName = createUniqueName(origItem->text(1));
319 CustomTemplates::Type type = origItem->customType();
320 auto item = new CustomTemplateItem(mUi->mList, templateName, origItem->content(), nullShortcut, type, origItem->to(), origItem->cc());
321 item->setText(0, indexToType(type));
322 item->setText(1, templateName);
323 iconFromType(type, item);
324
325 mUi->mList->setCurrentItem(item);
326 mUi->mRemove->setEnabled(true);
327 mUi->mDuplicate->setEnabled(true);
328 mUi->mName->clear();
329 mUi->mKeySequenceWidget->setEnabled(type != TUniversal);
330
331 Q_EMIT changed();
332}
333
334void CustomTemplates::slotRemoveClicked()
335{
336 QTreeWidgetItem *item = mUi->mList->currentItem();
337 if (!item) {
338 return;
339 }
340
341 const QString templateName = item->text(1);
342
344 i18nc("@info", "Do you really want to remove template \"%1\"?", templateName),
345 i18nc("@title:window", "Remove Template?"),
349 mItemsToDelete.append(templateName);
350 delete mUi->mList->takeTopLevelItem(mUi->mList->indexOfTopLevelItem(item));
351 mUi->mRemove->setEnabled(mUi->mList->topLevelItemCount() > 0);
352 mUi->mDuplicate->setEnabled(mUi->mList->topLevelItemCount() > 0);
353 if (!mBlockChangeSignal) {
354 Q_EMIT changed();
355 }
356 }
357}
358
359void CustomTemplates::slotListSelectionChanged()
360{
361 QTreeWidgetItem *item = mUi->mList->currentItem();
362 if (item) {
363 mUi->mEditFrame->setEnabled(true);
364 mUi->mRemove->setEnabled(true);
365 mUi->mDuplicate->setEnabled(true);
366 auto vitem = static_cast<CustomTemplateItem *>(item);
367 mBlockChangeSignal = true;
368 mUi->mEdit->setPlainText(vitem->content());
369 mUi->mKeySequenceWidget->setKeySequence(vitem->shortcut(), KKeySequenceWidget::NoValidate);
370 CustomTemplates::Type type = vitem->customType();
371
372 mUi->mType->setCurrentIndex(mUi->mType->findText(indexToType(type)));
373 mUi->mToEdit->setText(vitem->to());
374 mUi->mCCEdit->setText(vitem->cc());
375 mBlockChangeSignal = false;
376
377 // I think the logic (originally 'vitem->mType==TUniversal') was inverted here:
378 // a key shortcut is only allowed for a specific type of template and not for
379 // a universal, as otherwise we won't know what sort of action to do when the
380 // key sequence is activated!
381 // This agrees with KMMainWidget::updateCustomTemplateMenus() -- marten
382 mUi->mKeySequenceWidget->setEnabled(type != TUniversal);
383 } else {
384 mUi->mEditFrame->setEnabled(false);
385 mUi->mEdit->editor()->clear();
386 // see above
387 mUi->mKeySequenceWidget->clearKeySequence();
388 mUi->mType->setCurrentIndex(0);
389 mUi->mToEdit->clear();
390 mUi->mCCEdit->clear();
391 }
392}
393
394void CustomTemplates::slotTypeActivated(int index)
395{
396 QTreeWidgetItem *item = mUi->mList->currentItem();
397 if (item) {
398 auto vitem = static_cast<CustomTemplateItem *>(item);
399 auto customtype = static_cast<Type>(index);
400 vitem->setCustomType(customtype);
401 vitem->setText(0, indexToType(customtype));
402
403 iconFromType(customtype, vitem);
404
405 // see slotListSelectionChanged() above
406 mUi->mKeySequenceWidget->setEnabled(customtype != TUniversal);
407
408 if (!mBlockChangeSignal) {
409 Q_EMIT changed();
410 }
411 }
412}
413
414void CustomTemplates::slotShortcutChanged(const QKeySequence &newSeq)
415{
416 QTreeWidgetItem *item = mUi->mList->currentItem();
417 if (item) {
418 auto vitem = static_cast<CustomTemplateItem *>(item);
419 vitem->setShortcut(newSeq);
420 mUi->mKeySequenceWidget->applyStealShortcut();
421 }
422
423 if (!mBlockChangeSignal) {
424 Q_EMIT changed();
425 }
426}
427
428void CustomTemplates::slotItemChanged(QTreeWidgetItem *item, int column)
429{
430 if (item) {
431 auto vitem = static_cast<CustomTemplateItem *>(item);
432 if (column == 1) {
433 const QString newName = vitem->text(1).trimmed();
434 if (!newName.isEmpty()) {
435 const QString oldName = vitem->oldName();
436 if (nameAlreadyExists(newName, item)) {
437 vitem->setText(1, oldName);
438 return;
439 }
440 if (newName != oldName) {
441 mItemsToDelete.append(oldName);
442 vitem->setOldName(newName);
443 if (!mBlockChangeSignal) {
444 Q_EMIT changed();
445 }
446 }
447 }
448 }
449 }
450}
451
452CustomTemplateItemDelegate::CustomTemplateItemDelegate(QObject *parent)
453 : QStyledItemDelegate(parent)
454{
455}
456
457CustomTemplateItemDelegate::~CustomTemplateItemDelegate() = default;
458
459void CustomTemplateItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
460{
461 auto lineEdit = static_cast<QLineEdit *>(editor);
462 const QString text = lineEdit->text();
463 if (!text.isEmpty()) {
464 model->setData(index, text, Qt::EditRole);
465 }
466}
467
468QWidget *CustomTemplateItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
469{
470 if (index.column() == 1) {
471 return QStyledItemDelegate::createEditor(parent, option, index);
472 }
473 return nullptr;
474}
475
476CustomTemplateItem::CustomTemplateItem(QTreeWidget *parent,
477 const QString &name,
478 const QString &content,
479 const QKeySequence &shortcut,
480 CustomTemplates::Type type,
481 const QString &to,
482 const QString &cc)
483 : QTreeWidgetItem(parent)
484 , mName(name)
485 , mContent(content)
486 , mShortcut(shortcut)
487 , mType(type)
488 , mTo(to)
489 , mCC(cc)
490{
491 setFlags(flags() | Qt::ItemIsEditable);
492}
493
494CustomTemplateItem::~CustomTemplateItem() = default;
495
496void CustomTemplateItem::setCustomType(CustomTemplates::Type type)
497{
498 mType = type;
499}
500
501CustomTemplates::Type CustomTemplateItem::customType() const
502{
503 return mType;
504}
505
506QString CustomTemplateItem::to() const
507{
508 return mTo;
509}
510
511QString CustomTemplateItem::cc() const
512{
513 return mCC;
514}
515
516QString CustomTemplateItem::content() const
517{
518 return mContent;
519}
520
521void CustomTemplateItem::setContent(const QString &content)
522{
523 mContent = content;
524}
525
526void CustomTemplateItem::setTo(const QString &to)
527{
528 mTo = to;
529}
530
531void CustomTemplateItem::setCc(const QString &cc)
532{
533 mCC = cc;
534}
535
536QKeySequence CustomTemplateItem::shortcut() const
537{
538 return mShortcut;
539}
540
541void CustomTemplateItem::setShortcut(const QKeySequence &shortcut)
542{
543 mShortcut = shortcut;
544}
545
546QString CustomTemplateItem::oldName() const
547{
548 return mName;
549}
550
551void CustomTemplateItem::setOldName(const QString &name)
552{
553 mName = name;
554}
555
556#include "moc_customtemplates.cpp"
void keySequenceChanged(const QKeySequence &seq)
constexpr bool isEmpty() const
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KCRASH_EXPORT void setFlags(KCrash::CrashFlags flags)
void catchReturnKey(QObject *lineEdit)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem remove()
KGuiItem cancel()
const QList< QKeySequence > & end()
QString name(StandardShortcut id)
const QList< QKeySequence > & shortcut(StandardShortcut id)
const QList< QKeySequence > & help()
void clicked(bool checked)
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
void activated(int index)
QPoint pos()
QIcon fromTheme(const QString &name)
void linkActivated(const QString &link)
void returnPressed()
void textChanged(const QString &text)
void append(QList< T > &&value)
const_iterator constBegin() const const
const_iterator constEnd() const const
int column() const const
Q_EMITQ_EMIT
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
T qobject_cast(QObject *object)
void textChanged()
bool isEmpty() const const
QString number(double n, char format, int precision)
QString trimmed() const const
virtual QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const const override
NoContextMenu
EditRole
ItemIsEditable
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
void itemChanged(QTreeWidgetItem *item, int column)
void setIcon(int column, const QIcon &icon)
void setText(int column, const QString &text)
QString text(int column) const const
int type() const const
void showText(const QPoint &pos, const QString &text, QWidget *w)
void setupUi(QWidget *widget)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.