diff --git a/src/identitymanager.cpp b/src/identitymanager.cpp index 33cf8b17..08de63ce 100644 --- a/src/identitymanager.cpp +++ b/src/identitymanager.cpp @@ -1,714 +1,713 @@ /* Copyright (c) 2002 Marc Mutz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // config keys: static const char configKeyDefaultIdentity[] = "Default Identity"; #include "identitymanager.h" #include "identity.h" // for IdentityList::{export,import}Data #include // for static helper functions #include // for IdentityEntry::fromControlCenter() #include #include "kidentitymanagement_debug.h" #include #include #include #include #include #include -#include #include #include #include #include #include "identitymanageradaptor.h" namespace KIdentityManagement { static QString newDBusObjectName() { static int s_count = 0; QString name(QStringLiteral("/KIDENTITYMANAGER_IdentityManager")); if (s_count++) { name += QLatin1Char('_'); name += QString::number(s_count); } return name; } /** * Private class that helps to provide binary compatibility between releases. * @internal */ //@cond PRIVATE class Q_DECL_HIDDEN KIdentityManagement::IdentityManager::Private { public: Private(KIdentityManagement::IdentityManager *); ~Private(); void writeConfig() const; void readConfig(KConfig *config); void createDefaultIdentity(); QStringList groupList(KConfig *config) const; void slotIdentitiesChanged(const QString &id); KConfig *mConfig = nullptr; QList mIdentities; QList shadowIdentities; // returns a new Unique Object Identifier int newUoid(); bool mReadOnly = true; KIdentityManagement::IdentityManager *q; }; IdentityManager::Private::Private(KIdentityManagement::IdentityManager *manager) : q(manager) { } void IdentityManager::Private::writeConfig() const { const QStringList identities = groupList(mConfig); QStringList::const_iterator groupEnd = identities.constEnd(); for (QStringList::const_iterator group = identities.constBegin(); group != groupEnd; ++group) { mConfig->deleteGroup(*group); } int i = 0; ConstIterator end = mIdentities.constEnd(); for (ConstIterator it = mIdentities.constBegin(); it != end; ++it, ++i) { KConfigGroup cg(mConfig, QStringLiteral("Identity #%1").arg(i)); (*it).writeConfig(cg); if ((*it).isDefault()) { // remember which one is default: KConfigGroup general(mConfig, "General"); general.writeEntry(configKeyDefaultIdentity, (*it).uoid()); // Also write the default identity to emailsettings KEMailSettings es; es.setSetting(KEMailSettings::RealName, (*it).fullName()); es.setSetting(KEMailSettings::EmailAddress, (*it).primaryEmailAddress()); es.setSetting(KEMailSettings::Organization, (*it).organization()); es.setSetting(KEMailSettings::ReplyToAddress, (*it).replyToAddr()); } } mConfig->sync(); } void IdentityManager::Private::readConfig(KConfig *config) { mIdentities.clear(); const QStringList identities = groupList(config); if (identities.isEmpty()) { return; // nothing to be done... } KConfigGroup general(config, "General"); uint defaultIdentity = general.readEntry(configKeyDefaultIdentity, 0); bool haveDefault = false; QStringList::const_iterator groupEnd = identities.constEnd(); for (QStringList::const_iterator group = identities.constBegin(); group != groupEnd; ++group) { KConfigGroup configGroup(config, *group); Identity identity; identity.readConfig(configGroup); //Don't load invalid identity if (!identity.isNull() && !identity.primaryEmailAddress().isEmpty()) { if (!haveDefault && identity.uoid() == defaultIdentity) { haveDefault = true; identity.setIsDefault(true); } } mIdentities << identity; } if (!haveDefault) { if (mIdentities.isEmpty()) { mIdentities << Identity(); } qCWarning(KIDENTITYMANAGEMENT_LOG) << "IdentityManager: There was no default identity." << "Marking first one as default."; mIdentities.first().setIsDefault(true); } std::sort(mIdentities.begin(), mIdentities.end()); shadowIdentities = mIdentities; } void IdentityManager::Private::createDefaultIdentity() { QString fullName, emailAddress; bool done = false; // Check if the application has any settings q->createDefaultIdentity(fullName, emailAddress); // If not, then use the kcontrol settings if (fullName.isEmpty() && emailAddress.isEmpty()) { KEMailSettings emailSettings; fullName = emailSettings.getSetting(KEMailSettings::RealName); emailAddress = emailSettings.getSetting(KEMailSettings::EmailAddress); if (!fullName.isEmpty() && !emailAddress.isEmpty()) { q->newFromControlCenter(i18nc("use default address from control center", "Default")); done = true; } else { // If KEmailSettings doesn't have name and address, generate something from KUser KUser user; if (fullName.isEmpty()) { fullName = user.property(KUser::FullName).toString(); } if (emailAddress.isEmpty()) { emailAddress = user.loginName(); if (!emailAddress.isEmpty()) { KConfigGroup general(mConfig, "General"); QString defaultdomain = general.readEntry("Default domain"); if (!defaultdomain.isEmpty()) { emailAddress += QLatin1Char('@') + defaultdomain; } else { emailAddress.clear(); } } } } } if (!done) { // Default identity name QString name(i18nc("Default name for new email accounts/identities.", "Unnamed")); if (!emailAddress.isEmpty()) { // If we have an email address, create a default identity name from it QString idName = emailAddress; int pos = idName.indexOf(QLatin1Char('@')); if (pos != -1) { name = idName.mid(pos + 1, -1); } // Make the name a bit more human friendly name.replace(QLatin1Char('.'), QLatin1Char(' ')); pos = name.indexOf(QLatin1Char(' ')); if (pos != 0) { name[pos + 1] = name[pos + 1].toUpper(); } name[0] = name[0].toUpper(); } else if (!fullName.isEmpty()) { // If we have a full name, create a default identity name from it name = fullName; } shadowIdentities << Identity(name, fullName, emailAddress); } shadowIdentities.last().setIsDefault(true); shadowIdentities.last().setUoid(newUoid()); if (mReadOnly) { // commit won't do it in readonly mode mIdentities = shadowIdentities; } } QStringList IdentityManager::Private::groupList(KConfig *config) const { return config->groupList().filter(QRegularExpression(QStringLiteral("^Identity #\\d+$"))); } int IdentityManager::Private::newUoid() { int uoid; // determine the UOIDs of all saved identities QList usedUOIDs; usedUOIDs.reserve(1 + mIdentities.count() + (q->hasPendingChanges() ? shadowIdentities.count() : 0)); QList::ConstIterator end(mIdentities.constEnd()); for (QList::ConstIterator it = mIdentities.constBegin(); it != end; ++it) { usedUOIDs << (*it).uoid(); } if (q->hasPendingChanges()) { // add UOIDs of all shadow identities. Yes, we will add a lot of duplicate // UOIDs, but avoiding duplicate UOIDs isn't worth the effort. QList::ConstIterator endShadow(shadowIdentities.constEnd()); for (QList::ConstIterator it = shadowIdentities.constBegin(); it != endShadow; ++it) { usedUOIDs << (*it).uoid(); } } usedUOIDs << 0; // no UOID must be 0 because this value always refers to the // default identity do { uoid = KRandom::random(); } while (usedUOIDs.indexOf(uoid) != -1); return uoid; } void IdentityManager::Private::slotIdentitiesChanged(const QString &id) { qCDebug(KIDENTITYMANAGEMENT_LOG) << " KIdentityManagement::IdentityManager::slotIdentitiesChanged :" << id; const QString ourIdentifier = QStringLiteral("%1/%2"). arg(QDBusConnection::sessionBus().baseService(), q->property("uniqueDBusPath").toString()); if (id != ourIdentifier) { mConfig->reparseConfiguration(); Q_ASSERT(!q->hasPendingChanges()); readConfig(mConfig); emit q->changed(); } } Q_GLOBAL_STATIC(IdentityManager, s_self) IdentityManager *IdentityManager::self() { return s_self; } IdentityManager::IdentityManager(bool readonly, QObject *parent, const char *name) : QObject(parent) , d(new Private(this)) { static bool triedMigration = false; if (!triedMigration) { triedMigration = true; Kdelibs4ConfigMigrator migrate(QStringLiteral("identitymanager")); migrate.setConfigFiles(QStringList() << QStringLiteral("emailidentities")); migrate.migrate(); } setObjectName(QLatin1String(name)); new IdentityManagerAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); const QString dbusPath = newDBusObjectName(); setProperty("uniqueDBusPath", dbusPath); const QString dbusInterface = QStringLiteral("org.kde.pim.IdentityManager"); dbus.registerObject(dbusPath, this); dbus.connect(QString(), QString(), dbusInterface, QStringLiteral("identitiesChanged"), this, SLOT(slotIdentitiesChanged(QString))); d->mReadOnly = readonly; d->mConfig = new KConfig(QStringLiteral("emailidentities")); d->readConfig(d->mConfig); // we need at least a default identity: if (d->mIdentities.isEmpty()) { qCDebug(KIDENTITYMANAGEMENT_LOG) << "IdentityManager: No identity found. Creating default."; d->createDefaultIdentity(); commit(); } KSharedConfig::Ptr kmailConf(KSharedConfig::openConfig(QStringLiteral("kmail2rc"))); if (!d->mReadOnly) { bool needCommit = false; if (kmailConf->hasGroup(QStringLiteral("Composer"))) { KConfigGroup composerGroup = kmailConf->group(QStringLiteral("Composer")); if (composerGroup.hasKey(QStringLiteral("pgp-auto-sign"))) { const bool pgpAutoSign = composerGroup.readEntry(QStringLiteral("pgp-auto-sign"), false); QList::iterator end = d->mIdentities.end(); for (QList::iterator it = d->mIdentities.begin(); it != end; ++it) { it->setPgpAutoSign(pgpAutoSign); } composerGroup.deleteEntry(QStringLiteral("pgp-auto-sign")); composerGroup.sync(); needCommit = true; } } if (kmailConf->hasGroup(QStringLiteral("General"))) { KConfigGroup generalGroup = kmailConf->group(QStringLiteral("General")); if (generalGroup.hasKey(QStringLiteral("Default domain"))) { QString defaultDomain = generalGroup.readEntry(QStringLiteral("Default domain")); if (defaultDomain.isEmpty()) { defaultDomain = QHostInfo::localHostName(); } QList::iterator end = d->mIdentities.end(); for (QList::iterator it = d->mIdentities.begin(); it != end; ++it) { it->setDefaultDomainName(defaultDomain); } generalGroup.deleteEntry(QStringLiteral("Default domain")); generalGroup.sync(); needCommit = true; } } if (needCommit) { commit(); } } // Migration: people without settings in kemailsettings should get some if (KEMailSettings().getSetting(KEMailSettings::EmailAddress).isEmpty()) { d->writeConfig(); } } IdentityManager::~IdentityManager() { if (hasPendingChanges()) { qCWarning(KIDENTITYMANAGEMENT_LOG) << "IdentityManager: There were uncommitted changes!"; } delete d; } QString IdentityManager::makeUnique(const QString &name) const { int suffix = 1; QString result = name; while (identities().contains(result)) { result = i18nc("%1: name; %2: number appended to it to make it unique " "among a list of names", "%1 #%2", name, suffix); ++suffix; } return result; } bool IdentityManager::isUnique(const QString &name) const { return !identities().contains(name); } void IdentityManager::commit() { // early out: if (!hasPendingChanges() || d->mReadOnly) { return; } QList seenUOIDs; seenUOIDs.reserve(d->mIdentities.count()); QList::ConstIterator end = d->mIdentities.constEnd(); for (QList::ConstIterator it = d->mIdentities.constBegin(); it != end; ++it) { seenUOIDs << (*it).uoid(); } QList changedUOIDs; // find added and changed identities: for (QList::ConstIterator it = d->shadowIdentities.constBegin(); it != d->shadowIdentities.constEnd(); ++it) { int index = seenUOIDs.indexOf((*it).uoid()); if (index != -1) { uint uoid = seenUOIDs.at(index); const Identity &orig = identityForUoid(uoid); // look up in mIdentities if (*it != orig) { // changed identity qCDebug(KIDENTITYMANAGEMENT_LOG) << "emitting changed() for identity" << uoid; emit changed(*it); changedUOIDs << uoid; } seenUOIDs.removeAll(uoid); } else { // new identity qCDebug(KIDENTITYMANAGEMENT_LOG) << "emitting added() for identity" << (*it).uoid(); emit added(*it); } } // what's left are deleted identities: for (QList::ConstIterator it = seenUOIDs.constBegin(); it != seenUOIDs.constEnd(); ++it) { qCDebug(KIDENTITYMANAGEMENT_LOG) << "emitting deleted() for identity" << (*it); emit deleted(*it); } d->mIdentities = d->shadowIdentities; d->writeConfig(); // now that mIdentities has all the new info, we can emit the added/changed // signals that ship a uoid. This is because the slots might use // identityForUoid(uoid)... QList::ConstIterator changedEnd(changedUOIDs.constEnd()); for (QList::ConstIterator it = changedUOIDs.constBegin(); it != changedEnd; ++it) { emit changed(*it); } emit changed(); // normal signal // DBus signal for other IdentityManager instances const QString ourIdentifier = QStringLiteral("%1/%2"). arg(QDBusConnection::sessionBus().baseService(), property("uniqueDBusPath").toString()); emit identitiesChanged(ourIdentifier); } void IdentityManager::rollback() { d->shadowIdentities = d->mIdentities; } bool IdentityManager::hasPendingChanges() const { return d->mIdentities != d->shadowIdentities; } QStringList IdentityManager::identities() const { QStringList result; result.reserve(d->mIdentities.count()); ConstIterator end = d->mIdentities.constEnd(); for (ConstIterator it = d->mIdentities.constBegin(); it != end; ++it) { result << (*it).identityName(); } return result; } QStringList IdentityManager::shadowIdentities() const { QStringList result; result.reserve(d->shadowIdentities.count()); ConstIterator end = d->shadowIdentities.constEnd(); for (ConstIterator it = d->shadowIdentities.constBegin(); it != end; ++it) { result << (*it).identityName(); } return result; } void IdentityManager::sort() { std::sort(d->shadowIdentities.begin(), d->shadowIdentities.end()); } IdentityManager::ConstIterator IdentityManager::begin() const { return d->mIdentities.constBegin(); } IdentityManager::ConstIterator IdentityManager::end() const { return d->mIdentities.constEnd(); } IdentityManager::Iterator IdentityManager::modifyBegin() { return d->shadowIdentities.begin(); } IdentityManager::Iterator IdentityManager::modifyEnd() { return d->shadowIdentities.end(); } const Identity &IdentityManager::identityForUoid(uint uoid) const { for (ConstIterator it = begin(); it != end(); ++it) { if ((*it).uoid() == uoid) { return *it; } } return Identity::null(); } const Identity &IdentityManager::identityForUoidOrDefault(uint uoid) const { const Identity &ident = identityForUoid(uoid); if (ident.isNull()) { return defaultIdentity(); } else { return ident; } } const Identity &IdentityManager::identityForAddress( const QString &addresses) const { const QStringList addressList = KEmailAddress::splitAddressList(addresses); for (const QString &fullAddress : addressList) { const QString addrSpec = KEmailAddress::extractEmailAddress(fullAddress).toLower(); for (ConstIterator it = begin(); it != end(); ++it) { const Identity &identity = *it; if (identity.matchesEmailAddress(addrSpec)) { return identity; } } } return Identity::null(); } bool IdentityManager::thatIsMe(const QString &addressList) const { return !identityForAddress(addressList).isNull(); } Identity &IdentityManager::modifyIdentityForName(const QString &name) { for (Iterator it = modifyBegin(); it != modifyEnd(); ++it) { if ((*it).identityName() == name) { return *it; } } qCWarning(KIDENTITYMANAGEMENT_LOG) << "IdentityManager::modifyIdentityForName() used as" << "newFromScratch() replacement!" << endl << " name == \"" << name << "\""; return newFromScratch(name); } Identity &IdentityManager::modifyIdentityForUoid(uint uoid) { for (Iterator it = modifyBegin(); it != modifyEnd(); ++it) { if ((*it).uoid() == uoid) { return *it; } } qCWarning(KIDENTITYMANAGEMENT_LOG) << "IdentityManager::identityForUoid() used as" << "newFromScratch() replacement!" << endl << " uoid == \"" << uoid << "\""; return newFromScratch(i18n("Unnamed")); } const Identity &IdentityManager::defaultIdentity() const { for (ConstIterator it = begin(); it != end(); ++it) { if ((*it).isDefault()) { return *it; } } if (d->mIdentities.isEmpty()) { qCritical() << "IdentityManager: No default identity found!"; } else { qCWarning(KIDENTITYMANAGEMENT_LOG) << "IdentityManager: No default identity found!"; } return *begin(); } bool IdentityManager::setAsDefault(uint uoid) { // First, check if the identity actually exists: bool found = false; ConstIterator end(d->shadowIdentities.constEnd()); for (ConstIterator it = d->shadowIdentities.constBegin(); it != end; ++it) { if ((*it).uoid() == uoid) { found = true; break; } } if (!found) { return false; } // Then, change the default as requested: for (Iterator it = modifyBegin(); it != modifyEnd(); ++it) { (*it).setIsDefault((*it).uoid() == uoid); } // and re-sort: sort(); return true; } bool IdentityManager::removeIdentity(const QString &name) { if (d->shadowIdentities.size() <= 1) { return false; } for (Iterator it = modifyBegin(); it != modifyEnd(); ++it) { if ((*it).identityName() == name) { bool removedWasDefault = (*it).isDefault(); d->shadowIdentities.erase(it); if (removedWasDefault && !d->shadowIdentities.isEmpty()) { d->shadowIdentities.first().setIsDefault(true); } return true; } } return false; } bool IdentityManager::removeIdentityForced(const QString &name) { for (Iterator it = modifyBegin(); it != modifyEnd(); ++it) { if ((*it).identityName() == name) { bool removedWasDefault = (*it).isDefault(); d->shadowIdentities.erase(it); if (removedWasDefault && !d->shadowIdentities.isEmpty()) { d->shadowIdentities.first().setIsDefault(true); } return true; } } return false; } Identity &IdentityManager::newFromScratch(const QString &name) { return newFromExisting(Identity(name)); } Identity &IdentityManager::newFromControlCenter(const QString &name) { KEMailSettings es; es.setProfile(es.defaultProfileName()); return newFromExisting(Identity(name, es.getSetting(KEMailSettings::RealName), es.getSetting(KEMailSettings::EmailAddress), es.getSetting(KEMailSettings::Organization), es.getSetting(KEMailSettings::ReplyToAddress))); } Identity &IdentityManager::newFromExisting(const Identity &other, const QString &name) { d->shadowIdentities << other; Identity &result = d->shadowIdentities.last(); result.setIsDefault(false); // we don't want two default identities! result.setUoid(d->newUoid()); // we don't want two identies w/ same UOID if (!name.isNull()) { result.setIdentityName(name); } return result; } QStringList KIdentityManagement::IdentityManager::allEmails() const { QStringList lst; for (ConstIterator it = begin(); it != end(); ++it) { lst << (*it).primaryEmailAddress(); if (!(*it).emailAliases().isEmpty()) { lst << (*it).emailAliases(); } } return lst; } void KIdentityManagement::IdentityManager::slotRollback() { rollback(); } IdentityManager::Private::~Private() { delete mConfig; } } #include "moc_identitymanager.cpp" diff --git a/src/signature.cpp b/src/signature.cpp index 757867eb..f22d47b4 100644 --- a/src/signature.cpp +++ b/src/signature.cpp @@ -1,663 +1,661 @@ /* Copyright (c) 2002-2004 Marc Mutz Copyright (c) 2007 Tom Albers Copyright (c) 2009 Thomas McGuire This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "signature.h" #include "kidentitymanagement_debug.h" #include #include #include -#include #include -#include #include #include #include #include #include #include #include using namespace KIdentityManagement; class Q_DECL_HIDDEN KIdentityManagement::SignaturePrivate { public: SignaturePrivate(Signature *qq) : enabled(false) , type(Signature::Disabled) , inlinedHtml(false) , q(qq) { } void assignFrom(const KIdentityManagement::Signature &that); void cleanupImages(); void saveImages() const; QString textFromFile(bool *ok) const; QString textFromCommand(bool *ok) const; void insertSignatureText(Signature::Placement placement, Signature::AddedText addedText, KPIMTextEdit::RichTextComposer *textEdit, bool forceDisplay) const; /// List of images that belong to this signature. Either added by addImage() or /// by readConfig(). QList embeddedImages; /// The directory where the images will be saved to. QString saveLocation; bool enabled = false; QString path; QString text; Signature::Type type; bool inlinedHtml = false; Signature *q = nullptr; }; static bool isCursorAtEndOfLine(const QTextCursor &cursor) { QTextCursor testCursor = cursor; testCursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); return !testCursor.hasSelection(); } static void insertSignatureHelper(const QString &signature, KPIMTextEdit::RichTextComposer *textEdit, Signature::Placement placement, bool isHtml, bool addNewlines) { if (!signature.isEmpty()) { // Save the modified state of the document, as inserting a signature // shouldn't change this. Restore it at the end of this function. bool isModified = textEdit->document()->isModified(); // Move to the desired position, where the signature should be inserted QTextCursor cursor = textEdit->textCursor(); QTextCursor oldCursor = cursor; cursor.beginEditBlock(); if (placement == Signature::End) { cursor.movePosition(QTextCursor::End); } else if (placement == Signature::Start) { cursor.movePosition(QTextCursor::Start); } else if (placement == Signature::AtCursor) { cursor.movePosition(QTextCursor::StartOfLine); } textEdit->setTextCursor(cursor); QString lineSep; if (addNewlines) { if (isHtml) { lineSep = QStringLiteral("
"); } else { lineSep = QLatin1Char('\n'); } } // Insert the signature and newlines depending on where it was inserted. int newCursorPos = -1; QString headSep; QString tailSep; if (placement == Signature::End) { // There is one special case when re-setting the old cursor: The cursor // was at the end. In this case, QTextEdit has no way to know // if the signature was added before or after the cursor, and just // decides that it was added before (and the cursor moves to the end, // but it should not when appending a signature). See bug 167961 if (oldCursor.position() == textEdit->toPlainText().length()) { newCursorPos = oldCursor.position(); } headSep = lineSep; } else if (placement == Signature::Start) { // When prepending signatures, add a couple of new lines before // the signature, and move the cursor to the beginning of the QTextEdit. // People tends to insert new text there. newCursorPos = 0; headSep = lineSep + lineSep; if (!isCursorAtEndOfLine(cursor)) { tailSep = lineSep; } } else if (placement == Signature::AtCursor) { if (!isCursorAtEndOfLine(cursor)) { tailSep = lineSep; } } const QString full_signature = headSep + signature + tailSep; if (isHtml) { textEdit->insertHtml(full_signature); } else { textEdit->insertPlainText(full_signature); } cursor.endEditBlock(); if (newCursorPos != -1) { oldCursor.setPosition(newCursorPos); } textEdit->setTextCursor(oldCursor); textEdit->ensureCursorVisible(); textEdit->document()->setModified(isModified); if (isHtml) { textEdit->activateRichText(); } } } // Returns the names of all images in the HTML code static QStringList findImageNames(const QString &htmlCode) { QStringList ret; // To complicated for us, so cheat and let a text edit do the hard work KPIMTextEdit::RichTextComposer edit; edit.setHtml(htmlCode); const QList images = edit.composerControler()->composerImages()->imagesWithName(); ret.reserve(images.count()); for (const KPIMTextEdit::ImageWithNamePtr &image : images) { ret << image->name; } return ret; } void SignaturePrivate::assignFrom(const KIdentityManagement::Signature &that) { path = that.path(); inlinedHtml = that.isInlinedHtml(); text = that.text(); type = that.type(); enabled = that.isEnabledSignature(); saveLocation = that.imageLocation(); embeddedImages = that.embeddedImages(); } void SignaturePrivate::cleanupImages() { // Remove any images from the internal structure that are no longer there if (inlinedHtml) { foreach (const Signature::EmbeddedImagePtr &imageInList, embeddedImages) { //Don't use for(...:...) here. bool found = false; const QStringList lstImage = findImageNames(text); for (const QString &imageInHtml : lstImage) { if (imageInHtml == imageInList->name) { found = true; break; } } if (!found) { embeddedImages.removeAll(imageInList); } } } // Delete all the old image files if (!saveLocation.isEmpty()) { QDir dir(saveLocation); const QStringList lst = dir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks); for (const QString &fileName : lst) { if (fileName.toLower().endsWith(QLatin1String(".png"))) { qCDebug(KIDENTITYMANAGEMENT_LOG) << "Deleting old image" << dir.path() + fileName; dir.remove(fileName); } } } } void SignaturePrivate::saveImages() const { if (inlinedHtml && !saveLocation.isEmpty()) { for (const Signature::EmbeddedImagePtr &image : qAsConst(embeddedImages)) { QString location = saveLocation + QLatin1Char('/') + image->name; if (!image->image.save(location, "PNG")) { qCWarning(KIDENTITYMANAGEMENT_LOG) << "Failed to save image" << location; } } } } QString SignaturePrivate::textFromFile(bool *ok) const { assert(type == Signature::FromFile); QFile f(path); if (!f.open(QIODevice::ReadOnly)) { qCWarning(KIDENTITYMANAGEMENT_LOG) << "Failed to open" << path << ":" << f.errorString(); if (ok) { *ok = false; } return QString(); } if (ok) { *ok = true; } const QByteArray ba = f.readAll(); return QString::fromLocal8Bit(ba.data(), ba.size()); } QString SignaturePrivate::textFromCommand(bool *ok) const { assert(type == Signature::FromCommand); // handle pathological cases: if (path.isEmpty()) { if (ok) { *ok = true; } return QString(); } // create a shell process: KProcess proc; proc.setOutputChannelMode(KProcess::SeparateChannels); proc.setShellCommand(path); int rc = proc.execute(); // handle errors, if any: if (rc != 0) { if (ok) { *ok = false; } const QString wmsg = i18n("Failed to execute signature script

%1:

" "

%2

", path, QLatin1String(proc.readAllStandardError())); KMessageBox::error(nullptr, wmsg); return QString(); } // no errors: if (ok) { *ok = true; } // get output: QByteArray output = proc.readAllStandardOutput(); // TODO: hmm, should we allow other encodings, too? return QString::fromLocal8Bit(output.data(), output.size()); } void SignaturePrivate::insertSignatureText(Signature::Placement placement, Signature::AddedText addedText, KPIMTextEdit::RichTextComposer *textEdit, bool forceDisplay) const { if (!forceDisplay) { if (!enabled) { return; } } QString signature; if (addedText & Signature::AddSeparator) { signature = q->withSeparator(); } else { signature = q->rawText(); } insertSignatureHelper(signature, textEdit, placement, (inlinedHtml && type == KIdentityManagement::Signature::Inlined), (addedText & Signature::AddNewLines)); // We added the text of the signature above, now it is time to add the images as well. if (inlinedHtml) { for (const Signature::EmbeddedImagePtr &image : qAsConst(embeddedImages)) { textEdit->composerControler()->composerImages()->loadImage(image->image, image->name, image->name); } } } QDataStream &operator<<(QDataStream &stream, const KIdentityManagement::Signature::EmbeddedImagePtr &img) { return stream << img->image << img->name; } QDataStream &operator>>(QDataStream &stream, KIdentityManagement::Signature::EmbeddedImagePtr &img) { return stream >> img->image >> img->name; } Signature::Signature() : d(new SignaturePrivate(this)) { d->type = Disabled; d->inlinedHtml = false; } Signature::Signature(const QString &text) : d(new SignaturePrivate(this)) { d->type = Inlined; d->inlinedHtml = false; d->text = text; } Signature::Signature(const QString &path, bool isExecutable) : d(new SignaturePrivate(this)) { d->type = isExecutable ? FromCommand : FromFile; d->path = path; } Signature::Signature(const Signature &that) : d(new SignaturePrivate(this)) { d->assignFrom(that); } Signature &Signature::operator=(const KIdentityManagement::Signature &that) { if (this == &that) { return *this; } d->assignFrom(that); return *this; } Signature::~Signature() { delete d; } QString Signature::rawText(bool *ok) const { switch (d->type) { case Disabled: if (ok) { *ok = true; } return QString(); case Inlined: if (ok) { *ok = true; } return d->text; case FromFile: return d->textFromFile(ok); case FromCommand: return d->textFromCommand(ok); } qCritical() << "Signature::type() returned unknown value!"; return QString(); // make compiler happy } QString Signature::withSeparator(bool *ok) const { QString signature = rawText(ok); if (ok && (*ok) == false) { return QString(); } if (signature.isEmpty()) { return signature; // don't add a separator in this case } const bool htmlSig = (isInlinedHtml() && d->type == Inlined); QString newline = htmlSig ? QStringLiteral("
") : QStringLiteral("\n"); if (htmlSig && signature.startsWith(QStringLiteral("path = path; d->type = isExecutable ? FromCommand : FromFile; } void Signature::setInlinedHtml(bool isHtml) { d->inlinedHtml = isHtml; } bool Signature::isInlinedHtml() const { return d->inlinedHtml; } // config keys and values: static const char sigTypeKey[] = "Signature Type"; static const char sigTypeInlineValue[] = "inline"; static const char sigTypeFileValue[] = "file"; static const char sigTypeCommandValue[] = "command"; static const char sigTypeDisabledValue[] = "disabled"; static const char sigTextKey[] = "Inline Signature"; static const char sigFileKey[] = "Signature File"; static const char sigCommandKey[] = "Signature Command"; static const char sigTypeInlinedHtmlKey[] = "Inlined Html"; static const char sigImageLocation[] = "Image Location"; static const char sigEnabled[] = "Signature Enabled"; void Signature::readConfig(const KConfigGroup &config) { QString sigType = config.readEntry(sigTypeKey); if (sigType == QLatin1String(sigTypeInlineValue)) { d->type = Inlined; d->inlinedHtml = config.readEntry(sigTypeInlinedHtmlKey, false); } else if (sigType == QLatin1String(sigTypeFileValue)) { d->type = FromFile; d->path = config.readPathEntry(sigFileKey, QString()); } else if (sigType == QLatin1String(sigTypeCommandValue)) { d->type = FromCommand; d->path = config.readPathEntry(sigCommandKey, QString()); } else if (sigType == QLatin1String(sigTypeDisabledValue)) { d->enabled = false; } if (d->type != Disabled) { d->enabled = config.readEntry(sigEnabled, true); } d->text = config.readEntry(sigTextKey); d->saveLocation = config.readEntry(sigImageLocation); if (isInlinedHtml() && !d->saveLocation.isEmpty()) { QDir dir(d->saveLocation); const QStringList lst = dir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks); for (const QString &fileName : lst) { if (fileName.toLower().endsWith(QLatin1String(".png"))) { QImage image; if (image.load(dir.path() + QLatin1Char('/') + fileName)) { addImage(image, fileName); } else { qCWarning(KIDENTITYMANAGEMENT_LOG) << "Unable to load image" << dir.path() + QLatin1Char('/') + fileName; } } } } } void Signature::writeConfig(KConfigGroup &config) const { switch (d->type) { case Inlined: config.writeEntry(sigTypeKey, sigTypeInlineValue); config.writeEntry(sigTypeInlinedHtmlKey, d->inlinedHtml); break; case FromFile: config.writeEntry(sigTypeKey, sigTypeFileValue); config.writePathEntry(sigFileKey, d->path); break; case FromCommand: config.writeEntry(sigTypeKey, sigTypeCommandValue); config.writePathEntry(sigCommandKey, d->path); break; default: break; } config.writeEntry(sigTextKey, d->text); config.writeEntry(sigImageLocation, d->saveLocation); config.writeEntry(sigEnabled, d->enabled); d->cleanupImages(); d->saveImages(); } void Signature::insertIntoTextEdit(Placement placement, AddedText addedText, KPIMTextEdit::RichTextComposer *textEdit, bool forceDisplay) const { d->insertSignatureText(placement, addedText, textEdit, forceDisplay); } QList Signature::embeddedImages() const { return d->embeddedImages; } void Signature::setEmbeddedImages(const QList &embedded) { d->embeddedImages = embedded; } // --------------------- Operators -------------------// QDataStream &KIdentityManagement::operator<< (QDataStream &stream, const KIdentityManagement::Signature &sig) { return stream << static_cast(sig.type()) << sig.path() << sig.text() << sig.imageLocation() << sig.embeddedImages() << sig.isEnabledSignature(); } QDataStream &KIdentityManagement::operator>> (QDataStream &stream, KIdentityManagement::Signature &sig) { quint8 s; QString path; QString text; QString saveLocation; QList lst; bool enabled; stream >> s >> path >> text >> saveLocation >> lst >> enabled; sig.setText(text); sig.setPath(path); sig.setImageLocation(saveLocation); sig.setEmbeddedImages(lst); sig.setEnabledSignature(enabled); sig.setType(static_cast(s)); return stream; } bool Signature::operator==(const Signature &other) const { if (d->type != other.type()) { return false; } if (d->enabled != other.isEnabledSignature()) { return false; } if (d->type == Inlined && d->inlinedHtml) { if (d->saveLocation != other.imageLocation()) { return false; } if (d->embeddedImages != other.embeddedImages()) { return false; } } switch (d->type) { case Inlined: return d->text == other.text(); case FromFile: case FromCommand: return d->path == other.path(); default: case Disabled: return true; } } QString Signature::toPlainText() const { QString sigText = rawText(); if (!sigText.isEmpty() && isInlinedHtml() && type() == Inlined) { // Use a QTextDocument as a helper, it does all the work for us and // strips all HTML tags. QTextDocument helper; QTextCursor helperCursor(&helper); helperCursor.insertHtml(sigText); sigText = helper.toPlainText(); } return sigText; } void Signature::addImage(const QImage &imageData, const QString &imageName) { Q_ASSERT(!(d->saveLocation.isEmpty())); Signature::EmbeddedImagePtr image(new Signature::EmbeddedImage()); image->image = imageData; image->name = imageName; d->embeddedImages.append(image); } void Signature::setImageLocation(const QString &path) { d->saveLocation = path; } QString Signature::imageLocation() const { return d->saveLocation; } // --------------- Getters -----------------------// QString Signature::text() const { return d->text; } QString Signature::path() const { return d->path; } Signature::Type Signature::type() const { return d->type; } // --------------- Setters -----------------------// void Signature::setText(const QString &text) { d->text = text; d->type = Inlined; } void Signature::setType(Type type) { d->type = type; } void Signature::setEnabledSignature(bool enabled) { d->enabled = enabled; } bool Signature::isEnabledSignature() const { return d->enabled; } diff --git a/src/signature.h b/src/signature.h index 58214f79..1521fec4 100644 --- a/src/signature.h +++ b/src/signature.h @@ -1,265 +1,263 @@ /* Copyright (c) 2002-2004 Marc Mutz Copyright (c) 2007 Tom Albers Copyright (c) 2009 Thomas McGuire Author: Stefan Taferner This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIDENTITYMANAGER_SIGNATURE_H #define KIDENTITYMANAGER_SIGNATURE_H #include "kidentitymanagement_export.h" #include -#include #include #include -#include #include namespace KIdentityManagement { class Signature; class Identity; } class KConfigGroup; namespace KPIMTextEdit { class RichTextComposer; } namespace KIdentityManagement { class SignaturePrivate; KIDENTITYMANAGEMENT_EXPORT QDataStream &operator<<(QDataStream &stream, const KIdentityManagement::Signature &sig); KIDENTITYMANAGEMENT_EXPORT QDataStream &operator>>(QDataStream &stream, KIdentityManagement::Signature &sig); /** * @short Abstraction of a signature (aka "footer"). * * The signature can either be plain text, HTML text, text returned from a command or text stored * in a file. * * In case of HTML text, the signature can contain images. * Since you set the HTML source with setText(), there also needs to be a way to add the images * to the signature, as the HTML source contains only the img tags that reference those images. * To add the image to the signature, call addImage(). The name given there must match the name * of the img tag in the HTML source. * * The images need to be stored somewhere. The Signature class handles that by storing all images * in a directory. You must set that directory with setImageLocation(), before calling addImage(). * The images added with addImage() are then saved to that directory when calling writeConfig(). * When loading a signature, readConfig() automatically loads the images as well. * To actually add the images to a text edit, call insertIntoTextEdit(). * * Example of creating a HTML signature and then inserting it into a text edit: * @code * Signature htmlSig; * htmlSig.setText( " World" ); * htmlSig.setInlinedHtml( true ); * htmlSig.setImageLocation( KStandardDirs::locateLocal( "data", "emailidentities/example/" ); * QImage image = ...; * htmlSig.addImage( image, "hello.png" ); * ... * KTextEdit edit; * htmlSig.insertIntoTextEdit( KIdentityManagement::Signature::End, * KIdentityManagement::Signature::AddSeparator, &edit ); * @endcode */ class KIDENTITYMANAGEMENT_EXPORT Signature { friend class Identity; friend KIDENTITYMANAGEMENT_EXPORT QDataStream &operator<<(QDataStream &stream, const Signature &sig); friend KIDENTITYMANAGEMENT_EXPORT QDataStream &operator>>(QDataStream &stream, Signature &sig); public: /** Type of signature (ie. way to obtain the signature text) */ enum Type { Disabled = 0, Inlined = 1, FromFile = 2, FromCommand = 3 }; /** * Describes the placement of the signature text when it is to be inserted into a * text edit */ enum Placement { Start, ///< The signature is placed at the start of the textedit End, ///< The signature is placed at the end of the textedit AtCursor ///< The signature is placed at the current cursor position }; struct EmbeddedImage { QImage image; QString name; }; typedef QSharedPointer EmbeddedImagePtr; /** Used for comparison */ bool operator==(const Signature &other) const; /** Constructor for disabled signature */ Signature(); /** Constructor for inline text */ Signature(const QString &text); /** Constructor for text from a file or from output of a command */ Signature(const QString &path, bool isExecutable); /** Copy constructor */ Signature(const Signature &that); /** Assignment operator */ Signature &operator=(const Signature &that); /** Destructor */ ~Signature(); /** @return the raw signature text as entered resp. read from file. @param ok set to @c true if reading succeeded */ QString rawText(bool *ok = nullptr) const; /** @return the signature text with a "-- \n" separator added, if necessary. A newline will not be appended or prepended. @param ok set to @c true if reading succeeded */ QString withSeparator(bool *ok = nullptr) const; /** Set the signature text and mark this signature as being of "inline text" type. */ void setText(const QString &text); QString text() const; /** * Returns the text of the signature. If the signature is HTML, the HTML * tags will be stripped. * @since 4.4 */ QString toPlainText() const; /** Set the signature URL and mark this signature as being of "from file" resp. "from output of command" type. */ void setPath(const QString &path, bool isExecutable = false); QString path() const; /// @return the type of signature (ie. way to obtain the signature text) Type type() const; void setType(Type type); /** * Sets the inlined signature to text or html * @param isHtml sets the inlined signature to html * @since 4.1 */ void setInlinedHtml(bool isHtml); /** * @return boolean whether the inlined signature is html * @since 4.1 */ bool isInlinedHtml() const; /** * Sets the location where the copies of the signature images will be stored. * The images will be stored there when calling writeConfig(). The image location * is stored in the config, so the next readConfig() call knows where to look for * images. * It is recommended to use KStandardDirs::locateLocal( "data", "emailidentities/%1" ) * for the location, where %1 is the unique identifier of the identity. * * @warning readConfig will delete all other PNG files in this directory, as they could * be stale inline image files * * Like with addImage(), the SignatureConfigurator will handle this for you. * @param path the path to set as image location * @since 4.4 */ void setImageLocation(const QString &path); QString imageLocation() const; /** * Adds the given image to the signature. * This is needed if you use setText() to set some HTML source that references images. Those * referenced images needed to be added by calling this function. The @imageName has to match * the src attribute of the img tag. * * If you use SignatureConfigurator, you don't need to call this function, as the configurator * will handle this for you. * setImageLocation() needs to be called once before. * @since 4.4 */ void addImage(const QImage &image, const QString &imageName); /** * @brief setEnabledSignature * @param enabled enables signature if set as @c true * @since 4.9 */ void setEnabledSignature(bool enabled); bool isEnabledSignature() const; enum AddedTextFlag { AddNothing = 0, ///< Don't add any text to the signature AddSeparator = 1 << 0, ///< The separator '-- \n' will be added in front /// of the signature AddNewLines = 1 << 1 ///< Add a newline character in front or after the signature, /// depending on the placement }; /// Describes which additional parts should be added to the signature typedef QFlags AddedText; /** Inserts this signature into the given text edit. * If the signature is inserted at the beginning, a couple of new * lines will be inserted before it, and the cursor is moved to * the beginning. Otherwise, the cursor position is preserved. * For undo/redo, this is treated as one operation. * * Rich text mode of the text edit will be enabled if the signature is in * inlined HTML format. * * If this signature uses images, they will be added automatically. * * @param placement defines where in the text edit the signature should be * inserted. * @param addedText defines which other texts should be added to the signature * @param textEdit the signature will be inserted into this text edit. * * @since 4.9 */ // TODO: KDE5: BIC: Reorder parameters, the order here is a workaround for ambiguous parameters // with the deprecated method void insertIntoTextEdit(Placement placement, AddedText addedText, KPIMTextEdit::RichTextComposer *textEdit, bool forceDisplay = false) const; QList embeddedImages() const; void setEmbeddedImages(const QList &embedded); protected: // TODO: KDE5: BIC: Move all to private class void writeConfig(KConfigGroup &config) const; void readConfig(const KConfigGroup &config); private: //@cond PRIVATE SignaturePrivate *const d; //@endcond }; Q_DECLARE_OPERATORS_FOR_FLAGS(Signature::AddedText) } #endif /*kpim_signature_h*/ diff --git a/src/signatureconfigurator.cpp b/src/signatureconfigurator.cpp index e9f5e6c4..3848e11e 100644 --- a/src/signatureconfigurator.cpp +++ b/src/signatureconfigurator.cpp @@ -1,488 +1,487 @@ /* -*- c++ -*- Copyright 2008 Thomas McGuire Copyright 2008 Edwin Schepers Copyright 2004 Marc Mutz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "signatureconfigurator.h" #include "identity.h" #include #include #include "kidentitymanagement_debug.h" #include #include #include #include #include #include -#include #include #include #include #include #include #include #include - +#include #include #include #include #include #include #include using namespace KIdentityManagement; namespace KIdentityManagement { /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class Q_DECL_HIDDEN SignatureConfigurator::Private { public: Private(SignatureConfigurator *parent); void init(); // Returns the current text of the textedit as HTML code, but strips // unnecessary tags Qt inserts QString asCleanedHTML() const; SignatureConfigurator *q; bool inlinedHtml = false; QString imageLocation; QCheckBox *mEnableCheck = nullptr; QCheckBox *mHtmlCheck = nullptr; QComboBox *mSourceCombo = nullptr; KUrlRequester *mFileRequester = nullptr; QPushButton *mEditButton = nullptr; KLineEdit *mCommandEdit = nullptr; KToolBar *mEditToolBar = nullptr; KToolBar *mFormatToolBar = nullptr; KPIMTextEdit::RichTextComposer *mTextEdit = nullptr; }; //@endcond SignatureConfigurator::Private::Private(SignatureConfigurator *parent) : q(parent) , inlinedHtml(true) { } QString SignatureConfigurator::Private::asCleanedHTML() const { QString text = mTextEdit->toHtml(); // Beautiful little hack to find the html headers produced by Qt. QTextDocument textDocument; QString html = textDocument.toHtml(); // Now remove each line from the text, the result is clean html. const QStringList lst = html.split(QLatin1Char('\n')); for (const QString &line : lst) { text.remove(line + QLatin1Char('\n')); } return text; } void SignatureConfigurator::Private::init() { // tmp. vars: QLabel *label = nullptr; QWidget *page = nullptr; QHBoxLayout *hlay = nullptr; QVBoxLayout *vlay = nullptr; QVBoxLayout *page_vlay = nullptr; vlay = new QVBoxLayout(q); vlay->setObjectName(QStringLiteral("main layout")); // "enable signatue" checkbox: mEnableCheck = new QCheckBox(i18n("&Enable signature"), q); mEnableCheck->setWhatsThis( i18n("Check this box if you want KMail to append a signature to mails " "written with this identity.")); vlay->addWidget(mEnableCheck); // "obtain signature text from" combo and label: hlay = new QHBoxLayout(); // inherits spacing vlay->addLayout(hlay); mSourceCombo = new QComboBox(q); mSourceCombo->setEditable(false); mSourceCombo->setWhatsThis( i18n("Click on the widgets below to obtain help on the input methods.")); mSourceCombo->setEnabled(false); // since !mEnableCheck->isChecked() mSourceCombo->addItems(QStringList() << i18nc("continuation of \"obtain signature text from\"", "Input Field Below") << i18nc("continuation of \"obtain signature text from\"", "File") << i18nc("continuation of \"obtain signature text from\"", "Output of Command")); label = new QLabel(i18n("Obtain signature &text from:"), q); label->setBuddy(mSourceCombo); label->setEnabled(false); // since !mEnableCheck->isChecked() hlay->addWidget(label); hlay->addWidget(mSourceCombo, 1); // widget stack that is controlled by the source combo: QStackedWidget *widgetStack = new QStackedWidget(q); widgetStack->setEnabled(false); // since !mEnableCheck->isChecked() vlay->addWidget(widgetStack, 1); q->connect(mSourceCombo, QOverload::of(&QComboBox::currentIndexChanged), widgetStack, &QStackedWidget::setCurrentIndex); q->connect(mSourceCombo, QOverload::of(&QComboBox::highlighted), widgetStack, &QStackedWidget::setCurrentIndex); // connects for the enabling of the widgets depending on // signatureEnabled: q->connect(mEnableCheck, &QCheckBox::toggled, mSourceCombo, &QComboBox::setEnabled); q->connect(mEnableCheck, &QCheckBox::toggled, widgetStack, &QStackedWidget::setEnabled); q->connect(mEnableCheck, &QCheckBox::toggled, label, &QLabel::setEnabled); // The focus might be still in the widget that is disabled q->connect(mEnableCheck, SIGNAL(clicked()), mEnableCheck, SLOT(setFocus())); int pageno = 0; // page 0: input field for direct entering: page = new QWidget(widgetStack); widgetStack->insertWidget(pageno, page); page_vlay = new QVBoxLayout(page); page_vlay->setMargin(0); #ifndef QT_NO_TOOLBAR mEditToolBar = new KToolBar(q); mEditToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); page_vlay->addWidget(mEditToolBar, 0); mFormatToolBar = new KToolBar(q); mFormatToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); page_vlay->addWidget(mFormatToolBar, 1); #endif mTextEdit = new KPIMTextEdit::RichTextComposer(q); KPIMTextEdit::RichTextEditorWidget *richTextEditorwidget = new KPIMTextEdit::RichTextEditorWidget(mTextEdit, q); page_vlay->addWidget(richTextEditorwidget, 2); mTextEdit->setWhatsThis(i18n("Use this field to enter an arbitrary static signature.")); // Fill the toolbars. KActionCollection *actionCollection = new KActionCollection(q); mTextEdit->createActions(actionCollection); #ifndef QT_NO_TOOLBAR mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_bold"))); mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_italic"))); mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_underline"))); mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_strikeout"))); mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_foreground_color"))); mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_text_background_color"))); mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_font_family"))); mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_font_size"))); mEditToolBar->addAction(actionCollection->action(QStringLiteral("format_reset"))); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_style"))); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_indent_more"))); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_indent_less"))); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_list_indent_less"))); mFormatToolBar->addSeparator(); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_left"))); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_center"))); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_right"))); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_align_justify"))); mFormatToolBar->addSeparator(); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("insert_horizontal_rule"))); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("manage_link"))); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("format_painter"))); mFormatToolBar->addSeparator(); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("add_image"))); mFormatToolBar->addSeparator(); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("insert_html"))); mFormatToolBar->addAction(actionCollection->action(QStringLiteral("insert_table"))); #endif hlay = new QHBoxLayout(); // inherits spacing page_vlay->addLayout(hlay); mHtmlCheck = new QCheckBox(i18n("&Use HTML"), page); q->connect(mHtmlCheck, &QCheckBox::clicked, q, &SignatureConfigurator::slotSetHtml); hlay->addWidget(mHtmlCheck); inlinedHtml = true; widgetStack->setCurrentIndex(0); // since mSourceCombo->currentItem() == 0 // page 1: "signature file" requester, label, "edit file" button: ++pageno; page = new QWidget(widgetStack); widgetStack->insertWidget(pageno, page); // force sequential numbers (play safe) page_vlay = new QVBoxLayout(page); page_vlay->setMargin(0); hlay = new QHBoxLayout(); // inherits spacing page_vlay->addLayout(hlay); mFileRequester = new KUrlRequester(page); mFileRequester->setWhatsThis( i18n("Use this requester to specify a text file that contains your " "signature. It will be read every time you create a new mail or " "append a new signature.")); label = new QLabel(i18n("S&pecify file:"), page); label->setBuddy(mFileRequester); hlay->addWidget(label); hlay->addWidget(mFileRequester, 1); mFileRequester->button()->setAutoDefault(false); q->connect(mFileRequester, &KUrlRequester::textChanged, q, &SignatureConfigurator::slotEnableEditButton); mEditButton = new QPushButton(i18n("Edit &File"), page); mEditButton->setWhatsThis(i18n("Opens the specified file in a text editor.")); q->connect(mEditButton, &QPushButton::clicked, q, &SignatureConfigurator::slotEdit); mEditButton->setAutoDefault(false); mEditButton->setEnabled(false); // initially nothing to edit hlay->addWidget(mEditButton); page_vlay->addStretch(1); // spacer // page 2: "signature command" requester and label: ++pageno; page = new QWidget(widgetStack); widgetStack->insertWidget(pageno, page); page_vlay = new QVBoxLayout(page); page_vlay->setMargin(0); hlay = new QHBoxLayout(); // inherits spacing page_vlay->addLayout(hlay); mCommandEdit = new KLineEdit(page); mCommandEdit->setClearButtonShown(true); mCommandEdit->setCompletionObject(new KShellCompletion()); mCommandEdit->setAutoDeleteCompletionObject(true); mCommandEdit->setWhatsThis( i18n("You can add an arbitrary command here, either with or without path " "depending on whether or not the command is in your Path. For every " "new mail, KMail will execute the command and use what it outputs (to " "standard output) as a signature. Usual commands for use with this " "mechanism are \"fortune\" or \"ksig -random\".")); label = new QLabel(i18n("S&pecify command:"), page); label->setBuddy(mCommandEdit); hlay->addWidget(label); hlay->addWidget(mCommandEdit, 1); page_vlay->addStretch(1); // spacer } SignatureConfigurator::SignatureConfigurator(QWidget *parent) : QWidget(parent) , d(new Private(this)) { d->init(); } SignatureConfigurator::~SignatureConfigurator() { delete d; } bool SignatureConfigurator::isSignatureEnabled() const { return d->mEnableCheck->isChecked(); } void SignatureConfigurator::setSignatureEnabled(bool enable) { d->mEnableCheck->setChecked(enable); } Signature::Type SignatureConfigurator::signatureType() const { switch (d->mSourceCombo->currentIndex()) { case 0: return Signature::Inlined; case 1: return Signature::FromFile; case 2: return Signature::FromCommand; default: return Signature::Disabled; } } void SignatureConfigurator::setSignatureType(Signature::Type type) { int idx = 0; switch (type) { case Signature::Inlined: idx = 0; break; case Signature::FromFile: idx = 1; break; case Signature::FromCommand: idx = 2; break; default: idx = 0; break; } d->mSourceCombo->setCurrentIndex(idx); } void SignatureConfigurator::setInlineText(const QString &text) { d->mTextEdit->setTextOrHtml(text); } QString SignatureConfigurator::filePath() const { QString file = d->mFileRequester->url().path(); // Force the filename to be relative to ~ instead of $PWD depending // on the rest of the code (KRun::run in Edit and KFileItem on save) if (!file.isEmpty() && QFileInfo(file).isRelative()) { file = QDir::home().absolutePath() + QDir::separator() + file; } return file; } void SignatureConfigurator::setFileURL(const QString &url) { d->mFileRequester->setUrl(QUrl::fromLocalFile(url)); } QString SignatureConfigurator::commandPath() const { return d->mCommandEdit->text(); } void SignatureConfigurator::setCommandURL(const QString &url) { d->mCommandEdit->setText(url); } Signature SignatureConfigurator::signature() const { Signature sig; const Signature::Type sigType = signatureType(); switch (sigType) { case Signature::Inlined: sig.setInlinedHtml(d->inlinedHtml); sig.setText(d->inlinedHtml ? d->asCleanedHTML() : d->mTextEdit->textOrHtml()); if (d->inlinedHtml) { if (!d->imageLocation.isEmpty()) { sig.setImageLocation(d->imageLocation); } const KPIMTextEdit::ImageWithNameList images = d->mTextEdit->composerControler()->composerImages()->imagesWithName(); for (const KPIMTextEdit::ImageWithNamePtr &image : images) { sig.addImage(image->image, image->name); } } break; case Signature::FromCommand: sig.setPath(commandPath(), true); break; case Signature::FromFile: sig.setPath(filePath(), false); break; case Signature::Disabled: /* do nothing */ break; } sig.setEnabledSignature(isSignatureEnabled()); sig.setType(sigType); return sig; } void SignatureConfigurator::setSignature(const Signature &sig) { setSignatureType(sig.type()); setSignatureEnabled(sig.isEnabledSignature()); if (sig.isInlinedHtml()) { d->mHtmlCheck->setCheckState(Qt::Checked); } else { d->mHtmlCheck->setCheckState(Qt::Unchecked); } slotSetHtml(); // Let insertIntoTextEdit() handle setting the text, as that function also adds the images. d->mTextEdit->clear(); sig.insertIntoTextEdit(KIdentityManagement::Signature::Start, KIdentityManagement::Signature::AddNothing, d->mTextEdit, true); if (sig.type() == Signature::FromFile) { setFileURL(sig.path()); } else { setFileURL(QString()); } if (sig.type() == Signature::FromCommand) { setCommandURL(sig.path()); } else { setCommandURL(QString()); } } void SignatureConfigurator::slotEnableEditButton(const QString &url) { d->mEditButton->setDisabled(url.trimmed().isEmpty()); } void SignatureConfigurator::slotEdit() { QString url = filePath(); // slotEnableEditButton should prevent this assert from being hit: assert(!url.isEmpty()); KRun::RunFlags flags; flags |= KRun::DeleteTemporaryFiles; KRun::runUrl(QUrl::fromLocalFile(url), QStringLiteral("text/plain"), this, flags); } // "use HTML"-checkbox (un)checked void SignatureConfigurator::slotSetHtml() { if (d->mHtmlCheck->checkState() == Qt::Unchecked) { d->mHtmlCheck->setText(i18n("&Use HTML")); #ifndef QT_NO_TOOLBAR d->mEditToolBar->setVisible(false); d->mEditToolBar->setEnabled(false); d->mFormatToolBar->setVisible(false); d->mFormatToolBar->setEnabled(false); #endif d->mTextEdit->switchToPlainText(); d->inlinedHtml = false; } else { d->mHtmlCheck->setText(i18n("&Use HTML (disabling removes formatting)")); d->inlinedHtml = true; #ifndef QT_NO_TOOLBAR d->mEditToolBar->setVisible(true); d->mEditToolBar->setEnabled(true); d->mFormatToolBar->setVisible(true); d->mFormatToolBar->setEnabled(true); #endif d->mTextEdit->activateRichText(); } } void SignatureConfigurator::setImageLocation(const QString &path) { d->imageLocation = path; } void SignatureConfigurator::setImageLocation(const Identity &identity) { const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + QStringLiteral("emailidentities/%1/").arg( QString::number(identity.uoid())); QDir().mkpath(dir); setImageLocation(dir); } }