File indexing completed on 2024-05-19 05:17:29

0001 /*  -*- c++ -*-
0002     SPDX-FileCopyrightText: 2008 Thomas McGuire <Thomas.McGuire@gmx.net>
0003     SPDX-FileCopyrightText: 2008 Edwin Schepers <yez@familieschepers.nl>
0004     SPDX-FileCopyrightText: 2004 Marc Mutz <mutz@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "signatureconfigurator.h"
0010 #include "identity.h"
0011 #include "signaturerichtexteditor_p.h"
0012 
0013 #include <KActionCollection>
0014 #include <KIO/JobUiDelegateFactory>
0015 #include <KIO/OpenUrlJob>
0016 #include <KLineEdit>
0017 #include <KLocalizedString>
0018 #include <KMessageBox>
0019 #include <KShellCompletion>
0020 #include <KToolBar>
0021 #include <KUrlRequester>
0022 #include <QUrl>
0023 
0024 #include <KPIMTextEdit/RichTextComposer>
0025 #include <KPIMTextEdit/RichTextComposerControler>
0026 #include <KPIMTextEdit/RichTextComposerImages>
0027 #include <TextCustomEditor/RichTextEditor>
0028 
0029 #include <QCheckBox>
0030 #include <QComboBox>
0031 #include <QDir>
0032 #include <QFileInfo>
0033 #include <QLabel>
0034 #include <QStackedWidget>
0035 
0036 #include <QHBoxLayout>
0037 #include <QVBoxLayout>
0038 
0039 #include <QStandardPaths>
0040 #include <cassert>
0041 
0042 #include <TextCustomEditor/RichTextEditorWidget>
0043 
0044 using namespace KIdentityManagementWidgets;
0045 
0046 namespace KIdentityManagementWidgets
0047 {
0048 /**
0049    Private class that helps to provide binary compatibility between releases.
0050    @internal
0051   */
0052 //@cond PRIVATE
0053 class Q_DECL_HIDDEN SignatureConfiguratorPrivate
0054 {
0055 public:
0056     explicit SignatureConfiguratorPrivate(SignatureConfigurator *parent);
0057     void init();
0058     // Returns the current text of the textedit as HTML code, but strips
0059     // unnecessary tags Qt inserts
0060     [[nodiscard]] QString asCleanedHTML() const;
0061 
0062     QString imageLocation;
0063     SignatureConfigurator *const q;
0064     QCheckBox *mEnableCheck = nullptr;
0065     QCheckBox *mHtmlCheck = nullptr;
0066     QComboBox *mSourceCombo = nullptr;
0067     KUrlRequester *mFileRequester = nullptr;
0068     QPushButton *mEditButton = nullptr;
0069     KLineEdit *mCommandEdit = nullptr;
0070     KToolBar *mEditToolBar = nullptr;
0071     KToolBar *mFormatToolBar = nullptr;
0072     KPIMTextEdit::RichTextComposer *mTextEdit = nullptr;
0073     bool inlinedHtml = false;
0074 };
0075 //@endcond
0076 
0077 SignatureConfiguratorPrivate::SignatureConfiguratorPrivate(SignatureConfigurator *parent)
0078     : q(parent)
0079     , inlinedHtml(true)
0080 {
0081 }
0082 
0083 QString SignatureConfiguratorPrivate::asCleanedHTML() const
0084 {
0085     QString text = mTextEdit->toHtml();
0086 
0087     // Beautiful little hack to find the html headers produced by Qt.
0088     QTextDocument textDocument;
0089     const QString html = textDocument.toHtml();
0090 
0091     // Now remove each line from the text, the result is clean html.
0092     const QStringList lst = html.split(QLatin1Char('\n'));
0093     for (const QString &line : lst) {
0094         text.remove(line + QLatin1Char('\n'));
0095     }
0096     return text;
0097 }
0098 
0099 void SignatureConfiguratorPrivate::init()
0100 {
0101     auto vlay = new QVBoxLayout(q);
0102     vlay->setObjectName(QLatin1StringView("main layout"));
0103 
0104     // "enable signature" checkbox:
0105     mEnableCheck = new QCheckBox(i18n("&Enable signature"), q);
0106     mEnableCheck->setWhatsThis(
0107         i18n("Check this box if you want KMail to append a signature to mails "
0108              "written with this identity."));
0109     vlay->addWidget(mEnableCheck);
0110 
0111     // "obtain signature text from" combo and label:
0112     auto hlay = new QHBoxLayout(); // inherits spacing
0113     vlay->addLayout(hlay);
0114     mSourceCombo = new QComboBox(q);
0115     mSourceCombo->setEditable(false);
0116     mSourceCombo->setWhatsThis(i18n("Click on the widgets below to obtain help on the input methods."));
0117     mSourceCombo->setEnabled(false); // since !mEnableCheck->isChecked()
0118     mSourceCombo->addItems(QStringList() << i18nc("continuation of \"obtain signature text from\"", "Input Field Below")
0119                                          << i18nc("continuation of \"obtain signature text from\"", "File")
0120                                          << i18nc("continuation of \"obtain signature text from\"", "Output of Command"));
0121     auto label = new QLabel(i18n("Obtain signature &text from:"), q);
0122     label->setBuddy(mSourceCombo);
0123     label->setEnabled(false); // since !mEnableCheck->isChecked()
0124     hlay->addWidget(label);
0125     hlay->addWidget(mSourceCombo, 1);
0126 
0127     // widget stack that is controlled by the source combo:
0128     auto widgetStack = new QStackedWidget(q);
0129     widgetStack->setEnabled(false); // since !mEnableCheck->isChecked()
0130     vlay->addWidget(widgetStack, 1);
0131     q->connect(mSourceCombo, &QComboBox::currentIndexChanged, widgetStack, &QStackedWidget::setCurrentIndex);
0132     q->connect(mSourceCombo, &QComboBox::highlighted, widgetStack, &QStackedWidget::setCurrentIndex);
0133     // connects for the enabling of the widgets depending on
0134     // signatureEnabled:
0135     q->connect(mEnableCheck, &QCheckBox::toggled, mSourceCombo, &QComboBox::setEnabled);
0136     q->connect(mEnableCheck, &QCheckBox::toggled, widgetStack, &QStackedWidget::setEnabled);
0137     q->connect(mEnableCheck, &QCheckBox::toggled, label, &QLabel::setEnabled);
0138     // The focus might be still in the widget that is disabled
0139     q->connect(mEnableCheck, &QCheckBox::clicked, mEnableCheck, qOverload<>(&QCheckBox::setFocus));
0140 
0141     int pageno = 0;
0142     // page 0: input field for direct entering:
0143     auto page = new QWidget(widgetStack);
0144     widgetStack->insertWidget(pageno, page);
0145     auto page_vlay = new QVBoxLayout(page);
0146     page_vlay->setContentsMargins(0, 0, 0, 0);
0147 
0148 #ifndef QT_NO_TOOLBAR
0149     mEditToolBar = new KToolBar(q);
0150     mEditToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
0151     page_vlay->addWidget(mEditToolBar, 0);
0152 
0153     mFormatToolBar = new KToolBar(q);
0154     mFormatToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
0155     page_vlay->addWidget(mFormatToolBar, 1);
0156 #endif
0157 
0158     mTextEdit = new KPIMTextEdit::RichTextComposer(q);
0159 
0160     auto richTextEditorwidget = new TextCustomEditor::RichTextEditorWidget(mTextEdit, q);
0161     page_vlay->addWidget(richTextEditorwidget, 2);
0162     mTextEdit->setWhatsThis(i18n("Use this field to enter an arbitrary static signature."));
0163 
0164     // Fill the toolbars.
0165     auto actionCollection = new KActionCollection(q);
0166     mTextEdit->createActions(actionCollection);
0167 #ifndef QT_NO_TOOLBAR
0168     mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_bold")));
0169     mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_italic")));
0170     mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_underline")));
0171     mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_strikeout")));
0172     mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_foreground_color")));
0173     mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_background_color")));
0174     mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_font_family")));
0175     mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_font_size")));
0176     mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_reset")));
0177 
0178     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_style")));
0179     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_indent_more")));
0180     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_indent_less")));
0181     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_indent_less")));
0182     mFormatToolBar->addSeparator();
0183 
0184     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_left")));
0185     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_center")));
0186     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_right")));
0187     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_justify")));
0188     mFormatToolBar->addSeparator();
0189 
0190     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("insert_horizontal_rule")));
0191     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("manage_link")));
0192     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_painter")));
0193 
0194     mFormatToolBar->addSeparator();
0195     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("add_image")));
0196     mFormatToolBar->addSeparator();
0197     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("insert_html")));
0198     mFormatToolBar->addAction(actionCollection->action(QStringLiteral("insert_table")));
0199 #endif
0200 
0201     hlay = new QHBoxLayout(); // inherits spacing
0202     page_vlay->addLayout(hlay);
0203     mHtmlCheck = new QCheckBox(i18n("&Use HTML"), page);
0204     q->connect(mHtmlCheck, &QCheckBox::clicked, q, &SignatureConfigurator::slotSetHtml);
0205     hlay->addWidget(mHtmlCheck);
0206     inlinedHtml = true;
0207 
0208     widgetStack->setCurrentIndex(0); // since mSourceCombo->currentItem() == 0
0209 
0210     // page 1: "signature file" requester, label, "edit file" button:
0211     ++pageno;
0212     page = new QWidget(widgetStack);
0213     widgetStack->insertWidget(pageno, page); // force sequential numbers (play safe)
0214     page_vlay = new QVBoxLayout(page);
0215     page_vlay->setContentsMargins(0, 0, 0, 0);
0216     hlay = new QHBoxLayout(); // inherits spacing
0217     page_vlay->addLayout(hlay);
0218     mFileRequester = new KUrlRequester(page);
0219     mFileRequester->setNameFilter(i18n("Text File (*.txt)"));
0220     mFileRequester->setWhatsThis(
0221         i18n("Use this requester to specify a text file that contains your "
0222              "signature. It will be read every time you create a new mail or "
0223              "append a new signature."));
0224     label = new QLabel(i18n("S&pecify file:"), page);
0225     label->setBuddy(mFileRequester);
0226     hlay->addWidget(label);
0227     hlay->addWidget(mFileRequester, 1);
0228     mFileRequester->button()->setAutoDefault(false);
0229     q->connect(mFileRequester, &KUrlRequester::textEdited, q, &SignatureConfigurator::slotUrlChanged);
0230     q->connect(mFileRequester, &KUrlRequester::urlSelected, q, &SignatureConfigurator::slotUrlChanged);
0231     mEditButton = new QPushButton(i18n("Edit &File"), page);
0232     mEditButton->setWhatsThis(i18n("Opens the specified file in a text editor."));
0233     q->connect(mEditButton, &QPushButton::clicked, q, &SignatureConfigurator::slotEdit);
0234     mEditButton->setAutoDefault(false);
0235     mEditButton->setEnabled(false); // initially nothing to edit
0236     hlay->addWidget(mEditButton);
0237     page_vlay->addStretch(1); // spacer
0238 
0239     // page 2: "signature command" requester and label:
0240     ++pageno;
0241     page = new QWidget(widgetStack);
0242     widgetStack->insertWidget(pageno, page);
0243     page_vlay = new QVBoxLayout(page);
0244     page_vlay->setContentsMargins(0, 0, 0, 0);
0245     hlay = new QHBoxLayout(); // inherits spacing
0246     page_vlay->addLayout(hlay);
0247     mCommandEdit = new KLineEdit(page);
0248     mCommandEdit->setClearButtonEnabled(true);
0249     mCommandEdit->setCompletionObject(new KShellCompletion());
0250     mCommandEdit->setAutoDeleteCompletionObject(true);
0251     mCommandEdit->setWhatsThis(
0252         i18n("You can add an arbitrary command here, either with or without path "
0253              "depending on whether or not the command is in your Path. For every "
0254              "new mail, KMail will execute the command and use what it outputs (to "
0255              "standard output) as a signature. Usual commands for use with this "
0256              "mechanism are \"fortune\" or \"ksig -random\". "
0257              "(Be careful, script needs a shebang line)."));
0258     label = new QLabel(i18n("S&pecify command:"), page);
0259     label->setBuddy(mCommandEdit);
0260     hlay->addWidget(label);
0261     hlay->addWidget(mCommandEdit, 1);
0262     page_vlay->addStretch(1); // spacer
0263 }
0264 
0265 SignatureConfigurator::SignatureConfigurator(QWidget *parent)
0266     : QWidget(parent)
0267     , d(new SignatureConfiguratorPrivate(this))
0268 {
0269     d->init();
0270 }
0271 
0272 SignatureConfigurator::~SignatureConfigurator() = default;
0273 
0274 bool SignatureConfigurator::isSignatureEnabled() const
0275 {
0276     return d->mEnableCheck->isChecked();
0277 }
0278 
0279 void SignatureConfigurator::setSignatureEnabled(bool enable)
0280 {
0281     d->mEnableCheck->setChecked(enable);
0282 }
0283 
0284 Signature::Type SignatureConfigurator::signatureType() const
0285 {
0286     switch (d->mSourceCombo->currentIndex()) {
0287     case 0:
0288         return Signature::Inlined;
0289     case 1:
0290         return Signature::FromFile;
0291     case 2:
0292         return Signature::FromCommand;
0293     default:
0294         return Signature::Disabled;
0295     }
0296 }
0297 
0298 void SignatureConfigurator::setSignatureType(Signature::Type type)
0299 {
0300     int idx = 0;
0301     switch (type) {
0302     case Signature::Inlined:
0303         idx = 0;
0304         break;
0305     case Signature::FromFile:
0306         idx = 1;
0307         break;
0308     case Signature::FromCommand:
0309         idx = 2;
0310         break;
0311     default:
0312         idx = 0;
0313         break;
0314     }
0315 
0316     d->mSourceCombo->setCurrentIndex(idx);
0317 }
0318 
0319 void SignatureConfigurator::setInlineText(const QString &text)
0320 {
0321     d->mTextEdit->setTextOrHtml(text);
0322 }
0323 
0324 QString SignatureConfigurator::filePath() const
0325 {
0326     QString file = d->mFileRequester->url().path();
0327 
0328     // Force the filename to be relative to ~ instead of $PWD depending
0329     // on the rest of the code (KRun::run in Edit and KFileItem on save)
0330     if (!file.isEmpty() && QFileInfo(file).isRelative()) {
0331         file = QDir::home().absolutePath() + QLatin1Char('/') + file;
0332     }
0333     return file;
0334 }
0335 
0336 void SignatureConfigurator::setFileURL(const QString &url)
0337 {
0338     d->mFileRequester->setUrl(QUrl::fromLocalFile(url));
0339     d->mEditButton->setDisabled(url.trimmed().isEmpty());
0340 }
0341 
0342 QString SignatureConfigurator::commandPath() const
0343 {
0344     return d->mCommandEdit->text();
0345 }
0346 
0347 void SignatureConfigurator::setCommandURL(const QString &url)
0348 {
0349     d->mCommandEdit->setText(url);
0350 }
0351 
0352 Signature SignatureConfigurator::signature() const
0353 {
0354     Signature sig;
0355     const Signature::Type sigType = signatureType();
0356     switch (sigType) {
0357     case Signature::Inlined:
0358         sig.setInlinedHtml(d->inlinedHtml);
0359         sig.setText(d->inlinedHtml ? d->asCleanedHTML() : d->mTextEdit->textOrHtml());
0360         if (d->inlinedHtml) {
0361             if (!d->imageLocation.isEmpty()) {
0362                 sig.setImageLocation(d->imageLocation);
0363             }
0364             const KPIMTextEdit::ImageWithNameList images = d->mTextEdit->composerControler()->composerImages()->imagesWithName();
0365             for (const KPIMTextEdit::ImageWithNamePtr &image : images) {
0366                 sig.addImage(image->image, image->name);
0367             }
0368         }
0369         break;
0370     case Signature::FromCommand:
0371         sig.setPath(commandPath(), true);
0372         break;
0373     case Signature::FromFile:
0374         sig.setPath(filePath(), false);
0375         break;
0376     case Signature::Disabled:
0377         /* do nothing */
0378         break;
0379     }
0380     sig.setEnabledSignature(isSignatureEnabled());
0381     sig.setType(sigType);
0382     return sig;
0383 }
0384 
0385 void SignatureConfigurator::setSignature(const Signature &sig)
0386 {
0387     setSignatureType(sig.type());
0388     setSignatureEnabled(sig.isEnabledSignature());
0389 
0390     if (sig.isInlinedHtml()) {
0391         d->mHtmlCheck->setCheckState(Qt::Checked);
0392     } else {
0393         d->mHtmlCheck->setCheckState(Qt::Unchecked);
0394     }
0395     slotSetHtml();
0396 
0397     // Let insertIntoTextEdit() handle setting the text, as that function also adds the images.
0398     d->mTextEdit->clear();
0399     SignatureRichTextEditor::insertIntoTextEdit(sig, Signature::Start, Signature::AddNothing, d->mTextEdit, true);
0400     if (sig.type() == Signature::FromFile) {
0401         setFileURL(sig.path());
0402     } else {
0403         setFileURL(QString());
0404     }
0405 
0406     if (sig.type() == Signature::FromCommand) {
0407         setCommandURL(sig.path());
0408     } else {
0409         setCommandURL(QString());
0410     }
0411 }
0412 
0413 void SignatureConfigurator::slotUrlChanged()
0414 {
0415     const QString file = filePath();
0416     const QFileInfo infoFile(file);
0417     if (infoFile.isFile() && (infoFile.size() > 1000)) {
0418         KMessageBox::information(this, i18n("This text file size exceeds 1kb."), i18nc("@title:window", "Text File Size"));
0419     }
0420     d->mEditButton->setDisabled(file.isEmpty());
0421 }
0422 
0423 void SignatureConfigurator::slotEdit()
0424 {
0425     const QString url = filePath();
0426     // slotEnableEditButton should prevent this assert from being hit:
0427     assert(!url.isEmpty());
0428 
0429     auto job = new KIO::OpenUrlJob(QUrl::fromLocalFile(url), QStringLiteral("text/plain"));
0430     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
0431     job->start();
0432 }
0433 
0434 // "use HTML"-checkbox (un)checked
0435 void SignatureConfigurator::slotSetHtml()
0436 {
0437     if (d->mHtmlCheck->checkState() == Qt::Unchecked) {
0438         d->mHtmlCheck->setText(i18n("&Use HTML"));
0439 #ifndef QT_NO_TOOLBAR
0440         d->mEditToolBar->setVisible(false);
0441         d->mEditToolBar->setEnabled(false);
0442         d->mFormatToolBar->setVisible(false);
0443         d->mFormatToolBar->setEnabled(false);
0444 #endif
0445         d->mTextEdit->switchToPlainText();
0446         d->inlinedHtml = false;
0447     } else {
0448         d->mHtmlCheck->setText(i18n("&Use HTML (disabling removes formatting)"));
0449         d->inlinedHtml = true;
0450 #ifndef QT_NO_TOOLBAR
0451         d->mEditToolBar->setVisible(true);
0452         d->mEditToolBar->setEnabled(true);
0453         d->mFormatToolBar->setVisible(true);
0454         d->mFormatToolBar->setEnabled(true);
0455 #endif
0456         d->mTextEdit->activateRichText();
0457     }
0458 }
0459 
0460 void SignatureConfigurator::setImageLocation(const QString &path)
0461 {
0462     d->imageLocation = path;
0463 }
0464 
0465 void SignatureConfigurator::setImageLocation(const KIdentityManagementCore::Identity &identity)
0466 {
0467     const QString dir =
0468         QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/emailidentities/%1/").arg(QString::number(identity.uoid()));
0469     QDir().mkpath(dir);
0470     setImageLocation(dir);
0471 }
0472 }
0473 
0474 #include "moc_signatureconfigurator.cpp"