Messagelib

customtemplates.cpp
1 /*
2  * SPDX-FileCopyrightText: 2006 Dmitry Morozhnikov <[email protected]>
3  * SPDX-FileCopyrightText: 2011-2023 Laurent Montel <[email protected]>
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 <KPIMTextEdit/PlainTextEditor>
15 #include <Libkdepim/LineEditCatchReturnKey>
16 
17 #include <KLocalizedString>
18 #include <KMessageBox>
19 #include <QIcon>
20 
21 #include <QWhatsThis>
22 
23 using namespace TemplateParser;
24 
25 CustomTemplates::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 
42  new KPIM::LineEditCatchReturnKey(mUi->mName, this);
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 
84 void 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 
105 CustomTemplates::~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 
113 void CustomTemplates::slotNameChanged(const QString &text)
114 {
115  mUi->mAdd->setEnabled(!text.trimmed().isEmpty());
116 }
117 
118 QString 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 
144 void 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 
161 void 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 
179 void CustomTemplates::load()
180 {
181  const QStringList list = TemplateParserSettings::self()->customTemplates();
182  mUi->mList->clear();
184  for (QStringList::const_iterator it = list.constBegin(); it != end; ++it) {
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 
198 void 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 
237 void 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 
246 bool 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."), i18n("Cannot create template"));
253  return true;
254  }
255  ++lit;
256  }
257  return false;
258 }
259 
260 void CustomTemplates::slotAddClicked()
261 {
262  const QString str = mUi->mName->text();
263  if (!str.isEmpty()) {
264  if (nameAlreadyExists(str)) {
265  return;
266  }
267 
268  QKeySequence nullShortcut;
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 
283 QString 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 += QLatin1String(" (") + QString::number(counter) + QLatin1String(")");
301  break;
302  }
303  lit++;
304  }
305  }
306 
307  return uniqueName;
308 }
309 
310 void 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));
318  QKeySequence nullShortcut;
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 
334 void 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?"),
348  == KMessageBox::Continue) {
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 
359 void 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 
394 void 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 
414 void 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 
428 void 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 
452 CustomTemplateItemDelegate::CustomTemplateItemDelegate(QObject *parent)
453  : QStyledItemDelegate(parent)
454 {
455 }
456 
457 CustomTemplateItemDelegate::~CustomTemplateItemDelegate() = default;
458 
459 void 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 
468 QWidget *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 
476 CustomTemplateItem::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 
494 CustomTemplateItem::~CustomTemplateItem() = default;
495 
496 void CustomTemplateItem::setCustomType(CustomTemplates::Type type)
497 {
498  mType = type;
499 }
500 
501 CustomTemplates::Type CustomTemplateItem::customType() const
502 {
503  return mType;
504 }
505 
506 QString CustomTemplateItem::to() const
507 {
508  return mTo;
509 }
510 
511 QString CustomTemplateItem::cc() const
512 {
513  return mCC;
514 }
515 
516 QString CustomTemplateItem::content() const
517 {
518  return mContent;
519 }
520 
521 void CustomTemplateItem::setContent(const QString &content)
522 {
523  mContent = content;
524 }
525 
526 void CustomTemplateItem::setTo(const QString &to)
527 {
528  mTo = to;
529 }
530 
531 void CustomTemplateItem::setCc(const QString &cc)
532 {
533  mCC = cc;
534 }
535 
536 QKeySequence CustomTemplateItem::shortcut() const
537 {
538  return mShortcut;
539 }
540 
541 void CustomTemplateItem::setShortcut(const QKeySequence &shortcut)
542 {
543  mShortcut = shortcut;
544 }
545 
546 QString CustomTemplateItem::oldName() const
547 {
548  return mName;
549 }
550 
551 void CustomTemplateItem::setOldName(const QString &name)
552 {
553  mName = name;
554 }
void append(const T &value)
EditRole
void itemChanged(QTreeWidgetItem *item, int column)
QString number(int n, int base)
const QList< QKeySequence > & shortcut(StandardShortcut id)
void showText(const QPoint &pos, const QString &text, QWidget *w)
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)
int column() const const
QString trimmed() const const
void clicked(bool checked)
QIcon fromTheme(const QString &name)
void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
NoContextMenu
QList::const_iterator constBegin() const const
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
KCRASH_EXPORT void setFlags(KCrash::CrashFlags flags)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
void linkActivated(const QString &link)
KGuiItem cancel()
const QList< QKeySequence > & help()
ItemIsEditable
QString i18n(const char *text, const TYPE &arg...)
constexpr bool isEmpty() const
void textChanged(const QString &text)
bool isEmpty() const const
void returnPressed()
void textChanged()
void keySequenceChanged(const QKeySequence &seq)
void setText(int column, const QString &text)
KGuiItem remove()
void setupUi(QWidget *widget)
KSharedConfigPtr config()
QPoint pos()
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
virtual QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const const override
void setPosition(int pos, QTextCursor::MoveMode m)
QList::const_iterator constEnd() const const
QString name(StandardShortcut id)
void insertText(const QString &text)
void clear()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
int compare(const QString &other, Qt::CaseSensitivity cs) const const
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
QString text(int column) const const
void activated(int index)
const QList< QKeySequence > & end()
QString & append(QChar ch)
int position() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Mar 26 2023 04:08:10 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.