KIdentityManagement

signatureconfigurator.cpp
1/* -*- c++ -*-
2 SPDX-FileCopyrightText: 2008 Thomas McGuire <Thomas.McGuire@gmx.net>
3 SPDX-FileCopyrightText: 2008 Edwin Schepers <yez@familieschepers.nl>
4 SPDX-FileCopyrightText: 2004 Marc Mutz <mutz@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7*/
8
9#include "signatureconfigurator.h"
10using namespace Qt::Literals::StringLiterals;
11
12#include "identity.h"
13#include "signaturerichtexteditor_p.h"
14
15#include <KActionCollection>
16#include <KIO/JobUiDelegateFactory>
17#include <KIO/OpenUrlJob>
18#include <KLineEdit>
19#include <KLocalizedString>
20#include <KMessageBox>
21#include <KShellCompletion>
22#include <KToolBar>
23#include <KUrlRequester>
24#include <QUrl>
25
26#include <KPIMTextEdit/RichTextComposer>
27#include <KPIMTextEdit/RichTextComposerControler>
28#include <KPIMTextEdit/RichTextComposerImages>
29#include <TextCustomEditor/RichTextEditor>
30
31#include <QCheckBox>
32#include <QComboBox>
33#include <QDir>
34#include <QFileInfo>
35#include <QLabel>
36#include <QStackedWidget>
37
38#include <QHBoxLayout>
39#include <QVBoxLayout>
40
41#include <QStandardPaths>
42#include <cassert>
43
44#include <TextCustomEditor/RichTextEditorWidget>
45
46using namespace KIdentityManagementWidgets;
47
48namespace KIdentityManagementWidgets
49{
50/**
51 Private class that helps to provide binary compatibility between releases.
52 @internal
53 */
54//@cond PRIVATE
55class Q_DECL_HIDDEN SignatureConfiguratorPrivate
56{
57public:
58 explicit SignatureConfiguratorPrivate(SignatureConfigurator *parent);
59 void init();
60 // Returns the current text of the textedit as HTML code, but strips
61 // unnecessary tags Qt inserts
62 [[nodiscard]] QString asCleanedHTML() const;
63
64 QString imageLocation;
65 SignatureConfigurator *const q;
66 QCheckBox *mEnableCheck = nullptr;
67 QCheckBox *mHtmlCheck = nullptr;
68 QComboBox *mSourceCombo = nullptr;
69 KUrlRequester *mFileRequester = nullptr;
70 QPushButton *mEditButton = nullptr;
71 KLineEdit *mCommandEdit = nullptr;
72 KToolBar *mEditToolBar = nullptr;
73 KToolBar *mFormatToolBar = nullptr;
74 KPIMTextEdit::RichTextComposer *mTextEdit = nullptr;
75 bool inlinedHtml = false;
76};
77//@endcond
78
79SignatureConfiguratorPrivate::SignatureConfiguratorPrivate(SignatureConfigurator *parent)
80 : q(parent)
81 , inlinedHtml(true)
82{
83}
84
85QString SignatureConfiguratorPrivate::asCleanedHTML() const
86{
87 QString text = mTextEdit->toHtml();
88
89 // Beautiful little hack to find the html headers produced by Qt.
90 QTextDocument textDocument;
91 const QString html = textDocument.toHtml();
92
93 // Now remove each line from the text, the result is clean html.
94 const QStringList lst = html.split(QLatin1Char('\n'));
95 for (const QString &line : lst) {
96 text.remove(line + QLatin1Char('\n'));
97 }
98 return text;
99}
100
101void SignatureConfiguratorPrivate::init()
102{
103 auto vlay = new QVBoxLayout(q);
104 vlay->setObjectName("main layout"_L1);
105
106 // "enable signature" checkbox:
107 mEnableCheck = new QCheckBox(i18nc("@option:check", "&Enable signature"), q);
108 mEnableCheck->setWhatsThis(
109 i18n("Check this box if you want KMail to append a signature to mails "
110 "written with this identity."));
111 vlay->addWidget(mEnableCheck);
112
113 // "obtain signature text from" combo and label:
114 auto hlay = new QHBoxLayout(); // inherits spacing
115 vlay->addLayout(hlay);
116 mSourceCombo = new QComboBox(q);
117 mSourceCombo->setEditable(false);
118 mSourceCombo->setWhatsThis(i18n("Click on the widgets below to obtain help on the input methods."));
119 mSourceCombo->setEnabled(false); // since !mEnableCheck->isChecked()
120 mSourceCombo->addItems(QStringList() << i18nc("continuation of \"obtain signature text from\"", "Input Field Below")
121 << i18nc("continuation of \"obtain signature text from\"", "File")
122 << i18nc("continuation of \"obtain signature text from\"", "Output of Command"));
123 auto label = new QLabel(i18nc("@label:textbox", "Obtain signature &text from:"), q);
124 label->setBuddy(mSourceCombo);
125 label->setEnabled(false); // since !mEnableCheck->isChecked()
126 hlay->addWidget(label);
127 hlay->addWidget(mSourceCombo, 1);
128
129 // widget stack that is controlled by the source combo:
130 auto widgetStack = new QStackedWidget(q);
131 widgetStack->setEnabled(false); // since !mEnableCheck->isChecked()
132 vlay->addWidget(widgetStack, 1);
133 q->connect(mSourceCombo, &QComboBox::currentIndexChanged, widgetStack, &QStackedWidget::setCurrentIndex);
134 q->connect(mSourceCombo, &QComboBox::highlighted, widgetStack, &QStackedWidget::setCurrentIndex);
135 // connects for the enabling of the widgets depending on
136 // signatureEnabled:
137 q->connect(mEnableCheck, &QCheckBox::toggled, mSourceCombo, &QComboBox::setEnabled);
138 q->connect(mEnableCheck, &QCheckBox::toggled, widgetStack, &QStackedWidget::setEnabled);
139 q->connect(mEnableCheck, &QCheckBox::toggled, label, &QLabel::setEnabled);
140 // The focus might be still in the widget that is disabled
141 q->connect(mEnableCheck, &QCheckBox::clicked, mEnableCheck, qOverload<>(&QCheckBox::setFocus));
142
143 int pageno = 0;
144 // page 0: input field for direct entering:
145 auto page = new QWidget(widgetStack);
146 widgetStack->insertWidget(pageno, page);
147 auto page_vlay = new QVBoxLayout(page);
148 page_vlay->setContentsMargins(0, 0, 0, 0);
149
150#ifndef QT_NO_TOOLBAR
151 mEditToolBar = new KToolBar(q);
152 mEditToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
153 page_vlay->addWidget(mEditToolBar, 0);
154
155 mFormatToolBar = new KToolBar(q);
156 mFormatToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
157 page_vlay->addWidget(mFormatToolBar, 1);
158#endif
159
160 mTextEdit = new KPIMTextEdit::RichTextComposer(q);
161
162 auto richTextEditorwidget = new TextCustomEditor::RichTextEditorWidget(mTextEdit, q);
163 page_vlay->addWidget(richTextEditorwidget, 2);
164 mTextEdit->setWhatsThis(i18n("Use this field to enter an arbitrary static signature."));
165
166 // Fill the toolbars.
167 auto actionCollection = new KActionCollection(q);
168 mTextEdit->createActions(actionCollection);
169#ifndef QT_NO_TOOLBAR
170 mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_bold")));
171 mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_italic")));
172 mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_underline")));
173 mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_strikeout")));
174 mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_foreground_color")));
175 mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_background_color")));
176 mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_font_family")));
177 mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_font_size")));
178 mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_reset")));
179
180 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_style")));
181 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_indent_more")));
182 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_indent_less")));
183 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_indent_less")));
184 mFormatToolBar->addSeparator();
185
186 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_left")));
187 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_center")));
188 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_right")));
189 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_justify")));
190 mFormatToolBar->addSeparator();
191
192 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("insert_horizontal_rule")));
193 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("manage_link")));
194 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_painter")));
195
196 mFormatToolBar->addSeparator();
197 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("add_image")));
198 mFormatToolBar->addSeparator();
199 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("insert_html")));
200 mFormatToolBar->addAction(actionCollection->action(QStringLiteral("insert_table")));
201#endif
202
203 hlay = new QHBoxLayout(); // inherits spacing
204 page_vlay->addLayout(hlay);
205 mHtmlCheck = new QCheckBox(i18nc("@option:check", "&Use HTML"), page);
206 q->connect(mHtmlCheck, &QCheckBox::clicked, q, &SignatureConfigurator::slotSetHtml);
207 hlay->addWidget(mHtmlCheck);
208 inlinedHtml = true;
209
210 widgetStack->setCurrentIndex(0); // since mSourceCombo->currentItem() == 0
211
212 // page 1: "signature file" requester, label, "edit file" button:
213 ++pageno;
214 page = new QWidget(widgetStack);
215 widgetStack->insertWidget(pageno, page); // force sequential numbers (play safe)
216 page_vlay = new QVBoxLayout(page);
217 page_vlay->setContentsMargins(0, 0, 0, 0);
218 hlay = new QHBoxLayout(); // inherits spacing
219 page_vlay->addLayout(hlay);
220 mFileRequester = new KUrlRequester(page);
221 mFileRequester->setNameFilter(i18n("Text File (*.txt)"));
222 mFileRequester->setWhatsThis(
223 i18n("Use this requester to specify a text file that contains your "
224 "signature. It will be read every time you create a new mail or "
225 "append a new signature."));
226 label = new QLabel(i18nc("@label:textbox", "S&pecify file:"), page);
227 label->setBuddy(mFileRequester);
228 hlay->addWidget(label);
229 hlay->addWidget(mFileRequester, 1);
230 mFileRequester->button()->setAutoDefault(false);
231 q->connect(mFileRequester, &KUrlRequester::textEdited, q, &SignatureConfigurator::slotUrlChanged);
232 q->connect(mFileRequester, &KUrlRequester::urlSelected, q, &SignatureConfigurator::slotUrlChanged);
233 mEditButton = new QPushButton(i18nc("@action:button", "Edit &File"), page);
234 mEditButton->setWhatsThis(i18n("Opens the specified file in a text editor."));
235 q->connect(mEditButton, &QPushButton::clicked, q, &SignatureConfigurator::slotEdit);
236 mEditButton->setAutoDefault(false);
237 mEditButton->setEnabled(false); // initially nothing to edit
238 hlay->addWidget(mEditButton);
239 page_vlay->addStretch(1); // spacer
240
241 // page 2: "signature command" requester and label:
242 ++pageno;
243 page = new QWidget(widgetStack);
244 widgetStack->insertWidget(pageno, page);
245 page_vlay = new QVBoxLayout(page);
246 page_vlay->setContentsMargins(0, 0, 0, 0);
247 hlay = new QHBoxLayout(); // inherits spacing
248 page_vlay->addLayout(hlay);
249 mCommandEdit = new KLineEdit(page);
250 mCommandEdit->setClearButtonEnabled(true);
251 mCommandEdit->setCompletionObject(new KShellCompletion());
252 mCommandEdit->setAutoDeleteCompletionObject(true);
253 mCommandEdit->setWhatsThis(
254 i18n("You can add an arbitrary command here, either with or without path "
255 "depending on whether or not the command is in your Path. For every "
256 "new mail, KMail will execute the command and use what it outputs (to "
257 "standard output) as a signature. Usual commands for use with this "
258 "mechanism are \"fortune\" or \"ksig -random\". "
259 "(Be careful, script needs a shebang line)."));
260 label = new QLabel(i18nc("@label:textbox", "S&pecify command:"), page);
261 label->setBuddy(mCommandEdit);
262 hlay->addWidget(label);
263 hlay->addWidget(mCommandEdit, 1);
264 page_vlay->addStretch(1); // spacer
265}
266
268 : QWidget(parent)
269 , d(new SignatureConfiguratorPrivate(this))
270{
271 d->init();
272}
273
275
277{
278 return d->mEnableCheck->isChecked();
279}
280
282{
283 d->mEnableCheck->setChecked(enable);
284}
285
287{
288 switch (d->mSourceCombo->currentIndex()) {
289 case 0:
290 return Signature::Inlined;
291 case 1:
292 return Signature::FromFile;
293 case 2:
294 return Signature::FromCommand;
295 default:
296 return Signature::Disabled;
297 }
298}
299
301{
302 int idx = 0;
303 switch (type) {
304 case Signature::Inlined:
305 idx = 0;
306 break;
307 case Signature::FromFile:
308 idx = 1;
309 break;
310 case Signature::FromCommand:
311 idx = 2;
312 break;
313 default:
314 idx = 0;
315 break;
316 }
317
318 d->mSourceCombo->setCurrentIndex(idx);
319}
320
322{
323 d->mTextEdit->setTextOrHtml(text);
324}
325
327{
328 QString file = d->mFileRequester->url().path();
329
330 // Force the filename to be relative to ~ instead of $PWD depending
331 // on the rest of the code (KRun::run in Edit and KFileItem on save)
332 if (!file.isEmpty() && QFileInfo(file).isRelative()) {
333 file = QDir::home().absolutePath() + QLatin1Char('/') + file;
334 }
335 return file;
336}
337
339{
340 d->mFileRequester->setUrl(QUrl::fromLocalFile(url));
341 d->mEditButton->setDisabled(url.trimmed().isEmpty());
342}
343
345{
346 return d->mCommandEdit->text();
347}
348
350{
351 d->mCommandEdit->setText(url);
352}
353
355{
356 Signature sig;
357 const Signature::Type sigType = signatureType();
358 switch (sigType) {
359 case Signature::Inlined:
360 sig.setInlinedHtml(d->inlinedHtml);
361 sig.setText(d->inlinedHtml ? d->asCleanedHTML() : d->mTextEdit->textOrHtml());
362 if (d->inlinedHtml) {
363 if (!d->imageLocation.isEmpty()) {
364 sig.setImageLocation(d->imageLocation);
365 }
366 const KPIMTextEdit::ImageWithNameList images = d->mTextEdit->composerControler()->composerImages()->imagesWithName();
367 for (const KPIMTextEdit::ImageWithNamePtr &image : images) {
368 sig.addImage(image->image, image->name);
369 }
370 }
371 break;
372 case Signature::FromCommand:
373 sig.setPath(commandPath(), true);
374 break;
375 case Signature::FromFile:
376 sig.setPath(filePath(), false);
377 break;
378 case Signature::Disabled:
379 /* do nothing */
380 break;
381 }
383 sig.setType(sigType);
384 return sig;
385}
386
388{
389 setSignatureType(sig.type());
390 setSignatureEnabled(sig.isEnabledSignature());
391
392 if (sig.isInlinedHtml()) {
393 d->mHtmlCheck->setCheckState(Qt::Checked);
394 } else {
395 d->mHtmlCheck->setCheckState(Qt::Unchecked);
396 }
397 slotSetHtml();
398
399 // Let insertIntoTextEdit() handle setting the text, as that function also adds the images.
400 d->mTextEdit->clear();
401 SignatureRichTextEditor::insertIntoTextEdit(sig, Signature::Start, Signature::AddNothing, d->mTextEdit, true);
402 if (sig.type() == Signature::FromFile) {
403 setFileURL(sig.path());
404 } else {
406 }
407
408 if (sig.type() == Signature::FromCommand) {
409 setCommandURL(sig.path());
410 } else {
412 }
413}
414
415void SignatureConfigurator::slotUrlChanged()
416{
417 const QString file = filePath();
418 const QFileInfo infoFile(file);
419 if (infoFile.isFile() && (infoFile.size() > 1000)) {
420 KMessageBox::information(this, i18n("This text file size exceeds 1kb."), i18nc("@title:window", "Text File Size"));
421 }
422 d->mEditButton->setDisabled(file.isEmpty());
423}
424
425void SignatureConfigurator::slotEdit()
426{
427 const QString url = filePath();
428 // slotEnableEditButton should prevent this assert from being hit:
429 assert(!url.isEmpty());
430
431 auto job = new KIO::OpenUrlJob(QUrl::fromLocalFile(url), QStringLiteral("text/plain"));
433 job->start();
434}
435
436// "use HTML"-checkbox (un)checked
437void SignatureConfigurator::slotSetHtml()
438{
439 if (d->mHtmlCheck->checkState() == Qt::Unchecked) {
440 d->mHtmlCheck->setText(i18n("&Use HTML"));
441#ifndef QT_NO_TOOLBAR
442 d->mEditToolBar->setVisible(false);
443 d->mEditToolBar->setEnabled(false);
444 d->mFormatToolBar->setVisible(false);
445 d->mFormatToolBar->setEnabled(false);
446#endif
447 d->mTextEdit->switchToPlainText();
448 d->inlinedHtml = false;
449 } else {
450 d->mHtmlCheck->setText(i18n("&Use HTML (disabling removes formatting)"));
451 d->inlinedHtml = true;
452#ifndef QT_NO_TOOLBAR
453 d->mEditToolBar->setVisible(true);
454 d->mEditToolBar->setEnabled(true);
455 d->mFormatToolBar->setVisible(true);
456 d->mFormatToolBar->setEnabled(true);
457#endif
458 d->mTextEdit->activateRichText();
459 }
460}
461
463{
464 d->imageLocation = path;
465}
466
468{
469 const QString dir =
470 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/emailidentities/%1/").arg(QString::number(identity.uoid()));
471 QDir().mkpath(dir);
472 setImageLocation(dir);
473}
474}
475
476#include "moc_signatureconfigurator.cpp"
User identity information.
Definition identity.h:74
Abstraction of a signature (aka "footer").
Definition signature.h:61
void setPath(const QString &path, bool isExecutable=false)
Set the signature URL and mark this signature as being of "from file" resp.
void setText(const QString &text)
Set the signature text and mark this signature as being of "inline text" type.
void setImageLocation(const QString &path)
Sets the location where the copies of the signature images will be stored.
void setInlinedHtml(bool isHtml)
Sets the inlined signature to text or html.
void setEnabledSignature(bool enabled)
setEnabledSignature
void addImage(const QImage &image, const QString &imageName)
Adds the given image to the signature.
This widget gives an interface so users can edit their signature.
QString commandPath() const
Returns the url of the command which the users wants to use as signature.
void setSignatureEnabled(bool enable)
Use this to activate the signature.
void setInlineText(const QString &text)
Make text the text for the signature.
QString filePath() const
Returns the file url which the user wants to use as a signature.
void setFileURL(const QString &url)
Set url for the file url part of the widget.
void setSignature(const Signature &sig)
Convenience method.
void setSignatureType(Signature::Type type)
Set the signature type to type.
void setImageLocation(const QString &path)
Sets the directory where the images used in the HTML signature will be stored.
Signature::Type signatureType() const
This returns the type of the signature, so that can be Disabled, Inline, fromFile,...
SignatureConfigurator(QWidget *parent=nullptr)
Constructor.
void setCommandURL(const QString &url)
Sets url as the command to execute.
bool isSignatureEnabled() const
Indicated if the user wants a signature.
void urlSelected(const QUrl &)
void textEdited(const QString &)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KIOCORE_EXPORT KJobUiDelegate * createDefaultJobUiDelegate()
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
QString label(StandardShortcut id)
QCA_EXPORT void init()
void clicked(bool checked)
void toggled(bool checked)
void currentIndexChanged(int index)
void highlighted(int index)
QString absolutePath() const const
QDir home()
bool mkpath(const QString &dirPath) const const
void setCurrentIndex(int index)
QString writableLocation(StandardLocation type)
QString arg(Args &&... args) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString trimmed() const const
ToolButtonIconOnly
QString toHtml() const const
QUrl fromLocalFile(const QString &localFile)
void setEnabled(bool)
void setFocus()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:18:05 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.