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"