diff --git a/src/collectionpage/attributes/expirecollectionattribute.cpp b/src/collectionpage/attributes/expirecollectionattribute.cpp index 90b5112..372169b 100644 --- a/src/collectionpage/attributes/expirecollectionattribute.cpp +++ b/src/collectionpage/attributes/expirecollectionattribute.cpp @@ -1,200 +1,201 @@ /* Copyright (c) 2011-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "expirecollectionattribute.h" #include "folder/foldersettings.h" #include "kernel/mailkernel.h" #include using namespace MailCommon; ExpireCollectionAttribute::ExpireCollectionAttribute() : mExpireMessages(false) , mUnreadExpireAge(28) , mReadExpireAge(14) , mUnreadExpireUnits(ExpireNever) , mReadExpireUnits(ExpireNever) , mExpireAction(ExpireDelete) , mExpireToFolderId(-1) { } QByteArray ExpireCollectionAttribute::type() const { static const QByteArray sType("expirationcollectionattribute"); return sType; } ExpireCollectionAttribute *ExpireCollectionAttribute::clone() const { ExpireCollectionAttribute *expireAttr = new ExpireCollectionAttribute(); expireAttr->setAutoExpire(mExpireMessages); expireAttr->setUnreadExpireAge(mUnreadExpireAge); expireAttr->setUnreadExpireUnits(mUnreadExpireUnits); expireAttr->setReadExpireAge(mReadExpireAge); expireAttr->setReadExpireUnits(mReadExpireUnits); expireAttr->setExpireAction(mExpireAction); expireAttr->setExpireToFolderId(mExpireToFolderId); return expireAttr; } void ExpireCollectionAttribute::setAutoExpire(bool enabled) { mExpireMessages = enabled; } bool ExpireCollectionAttribute::isAutoExpire() const { return mExpireMessages; } void ExpireCollectionAttribute::setUnreadExpireAge(int age) { if (age >= 0 && age != mUnreadExpireAge) { mUnreadExpireAge = age; } } int ExpireCollectionAttribute::unreadExpireAge() const { return mUnreadExpireAge; } void ExpireCollectionAttribute::setUnreadExpireUnits(ExpireUnits units) { if (units >= ExpireNever && units < ExpireMaxUnits) { mUnreadExpireUnits = units; } } void ExpireCollectionAttribute::setReadExpireAge(int age) { if (age >= 0 && age != mReadExpireAge) { mReadExpireAge = age; } } int ExpireCollectionAttribute::readExpireAge() const { return mReadExpireAge; } void ExpireCollectionAttribute::setReadExpireUnits(ExpireUnits units) { if (units >= ExpireNever && units <= ExpireMaxUnits) { mReadExpireUnits = units; } } void ExpireCollectionAttribute::setExpireAction(ExpireAction a) { mExpireAction = a; } ExpireCollectionAttribute::ExpireAction ExpireCollectionAttribute::expireAction() const { return mExpireAction; } void ExpireCollectionAttribute::setExpireToFolderId(Akonadi::Collection::Id id) { mExpireToFolderId = id; } Akonadi::Collection::Id ExpireCollectionAttribute::expireToFolderId() const { return mExpireToFolderId; } ExpireCollectionAttribute::ExpireUnits ExpireCollectionAttribute::unreadExpireUnits() const { return mUnreadExpireUnits; } ExpireCollectionAttribute::ExpireUnits ExpireCollectionAttribute::readExpireUnits() const { return mReadExpireUnits; } bool ExpireCollectionAttribute::operator==(const ExpireCollectionAttribute &other) const { return (mExpireMessages == other.isAutoExpire()) && (mUnreadExpireAge == other.unreadExpireAge()) && (mReadExpireAge == other.readExpireAge()) && (mUnreadExpireUnits == other.unreadExpireUnits()) && (mReadExpireUnits == other.readExpireUnits()) && (mExpireAction == other.expireAction()) && (mExpireToFolderId == other.expireToFolderId()); } int ExpireCollectionAttribute::daysToExpire(int number, ExpireCollectionAttribute::ExpireUnits units) { switch (units) { case ExpireCollectionAttribute::ExpireDays: // Days return number; case ExpireCollectionAttribute::ExpireWeeks: // Weeks return number * 7; case ExpireCollectionAttribute::ExpireMonths: // Months - this could be better // rather than assuming 31day months. return number * 31; default: // this avoids a compiler warning (not handled enumeration values) ; } return -1; } void ExpireCollectionAttribute::daysToExpire(int &unreadDays, int &readDays) { unreadDays = ExpireCollectionAttribute::daysToExpire(unreadExpireAge(), unreadExpireUnits()); readDays = ExpireCollectionAttribute::daysToExpire(readExpireAge(), readExpireUnits()); } QByteArray ExpireCollectionAttribute::serialized() const { QByteArray result; QDataStream s(&result, QIODevice::WriteOnly); s << mExpireToFolderId; s << (int)mExpireAction; s << (int)mReadExpireUnits; s << mReadExpireAge; s << (int)mUnreadExpireUnits; s << mUnreadExpireAge; s << mExpireMessages; return result; } void ExpireCollectionAttribute::deserialize(const QByteArray &data) { QDataStream s(data); s >> mExpireToFolderId; int action; s >> action; mExpireAction = (ExpireCollectionAttribute::ExpireAction)action; int valUnitRead; s >> valUnitRead; mReadExpireUnits = (ExpireCollectionAttribute::ExpireUnits)valUnitRead; s >> mReadExpireAge; int valUnitUread; s >> valUnitUread; mUnreadExpireUnits = (ExpireCollectionAttribute::ExpireUnits)valUnitUread; s >> mUnreadExpireAge; s >> mExpireMessages; } diff --git a/src/collectionpage/attributes/expirecollectionattribute.h b/src/collectionpage/attributes/expirecollectionattribute.h index ae258f1..1ded023 100644 --- a/src/collectionpage/attributes/expirecollectionattribute.h +++ b/src/collectionpage/attributes/expirecollectionattribute.h @@ -1,142 +1,143 @@ /* Copyright (c) 2011-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_EXPIRECOLLECTIONATTRIBUTE_H #define MAILCOMMON_EXPIRECOLLECTIONATTRIBUTE_H #include "mailcommon_export.h" #include #include namespace MailCommon { class MAILCOMMON_EXPORT ExpireCollectionAttribute : public Akonadi::Attribute { public: ExpireCollectionAttribute(); /* * Define the possible units to use for measuring message expiry. * expireNever is used to switch off message expiry, and expireMaxUnits * must always be the last in the list (for bounds checking). */ enum ExpireUnits { ExpireNever, ExpireDays, ExpireWeeks, ExpireMonths, ExpireMaxUnits }; enum ExpireAction { ExpireDelete, ExpireMove }; QByteArray type() const override; ExpireCollectionAttribute *clone() const override; QByteArray serialized() const override; void deserialize(const QByteArray &data) override; void daysToExpire(int &unreadDays, int &readDays); /** * Sets whether this folder automatically expires messages. */ void setAutoExpire(bool enabled); /** * Returns true if this folder automatically expires old messages. */ bool isAutoExpire() const; /** * Sets the maximum age for unread messages in this folder. * Age should not be negative. Units are set using * setUnreadExpireUnits(). */ void setUnreadExpireAge(int age); /** * Sets the units to use for expiry of unread messages. * Values are 1 = days, 2 = weeks, 3 = months. */ void setUnreadExpireUnits(ExpireUnits units); /** * Sets the maximum age for read messages in this folder. * Age should not be negative. Units are set using * setReadExpireUnits(). */ void setReadExpireAge(int age); /** * Sets the units to use for expiry of read messages. * Values are 1 = days, 2 = weeks, 3 = months. */ void setReadExpireUnits(ExpireUnits units); /** * Returns the age at which unread messages are expired. * Units are determined by unreadExpireUnits(). */ int unreadExpireAge() const; /** * Returns the age at which read messages are expired. * Units are determined by readExpireUnits(). */ int readExpireAge() const; /** * What should expiry do? Delete or move to another folder? */ ExpireAction expireAction() const; void setExpireAction(ExpireAction a); /** * If expiry should move to folder, return the ID of that folder */ Akonadi::Collection::Id expireToFolderId() const; void setExpireToFolderId(Akonadi::Collection::Id id); /** * Units getUnreadExpireAge() is returned in. * 1 = days, 2 = weeks, 3 = months. */ ExpireUnits unreadExpireUnits() const; /** * Units getReadExpireAge() is returned in. * 1 = days, 2 = weeks, 3 = months. */ ExpireUnits readExpireUnits() const; bool operator==(const ExpireCollectionAttribute &other) const; private: static int daysToExpire(int number, ExpireCollectionAttribute::ExpireUnits units); bool mExpireMessages; // true if old messages are expired int mUnreadExpireAge; // Given in unreadExpireUnits int mReadExpireAge; // Given in readExpireUnits ExpireCollectionAttribute::ExpireUnits mUnreadExpireUnits; ExpireCollectionAttribute::ExpireUnits mReadExpireUnits; ExpireCollectionAttribute::ExpireAction mExpireAction; Akonadi::Collection::Id mExpireToFolderId; }; } #endif /* EXPIRATIONCOLLECTIONATTRIBUTE_H */ diff --git a/src/collectionpage/collectiongeneralpage.cpp b/src/collectionpage/collectiongeneralpage.cpp index 048d5a8..e2e605b 100644 --- a/src/collectionpage/collectiongeneralpage.cpp +++ b/src/collectionpage/collectiongeneralpage.cpp @@ -1,398 +1,399 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "collectiongeneralpage.h" #include "collectiontypeutil.h" #include "incidencesforwidget.h" #include #include #include "folder/foldersettings.h" #include "kernel/mailkernel.h" #include "util/mailutil_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; using namespace MailCommon; CollectionGeneralPage::CollectionGeneralPage(QWidget *parent) : CollectionPropertiesPage(parent) , mContentsComboBox(nullptr) , mIncidencesForComboBox(nullptr) , mSharedSeenFlagsCheckBox(nullptr) , mNameEdit(nullptr) , mFolderCollection(nullptr) { setObjectName(QStringLiteral("MailCommon::CollectionGeneralPage")); setPageTitle(i18nc("@title:tab General settings for a folder.", "General")); } CollectionGeneralPage::~CollectionGeneralPage() { } void CollectionGeneralPage::addLine(QWidget *parent, QVBoxLayout *layout) { QFrame *line = new QFrame(parent); line->setGeometry(QRect(80, 150, 250, 20)); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); line->setFrameShape(QFrame::HLine); layout->addWidget(line); } void CollectionGeneralPage::init(const Akonadi::Collection &collection) { mIsLocalSystemFolder = CommonKernel->isSystemFolderCollection(collection) || Kernel::folderIsInbox(collection); mIsResourceFolder = (collection.parentCollection() == Akonadi::Collection::root()); QLabel *label = nullptr; QVBoxLayout *topLayout = new QVBoxLayout(this); // Musn't be able to edit details for a non-resource, system folder. if ((!mIsLocalSystemFolder || mIsResourceFolder) && !mFolderCollection->isReadOnly()) { QHBoxLayout *hl = new QHBoxLayout(); topLayout->addItem(hl); label = new QLabel(i18nc("@label:textbox Name of the folder.", "&Name:"), this); hl->addWidget(label); mNameEdit = new KLineEdit(this); mNameEdit->setTrapReturnKey(true); connect(mNameEdit, &KLineEdit::textChanged, this, &CollectionGeneralPage::slotNameChanged); label->setBuddy(mNameEdit); hl->addWidget(mNameEdit); } // should new mail in this folder be ignored? QHBoxLayout *hbl = new QHBoxLayout(); topLayout->addItem(hbl); mNotifyOnNewMailCheckBox = new QCheckBox(i18n("Act on new/unread mail in this folder"), this); mNotifyOnNewMailCheckBox->setWhatsThis( i18n("

If this option is enabled then you will be notified about " "new/unread mail in this folder. Moreover, going to the " "next/previous folder with unread messages will stop at this " "folder.

" "

Uncheck this option if you do not want to be notified about " "new/unread mail in this folder and if you want this folder to " "be skipped when going to the next/previous folder with unread " "messages. This is useful for ignoring any new/unread mail in " "your trash and spam folder.

")); hbl->addWidget(mNotifyOnNewMailCheckBox); // should replies to mails in this folder be kept in this same folder? hbl = new QHBoxLayout(); topLayout->addItem(hbl); mKeepRepliesInSameFolderCheckBox = new QCheckBox(i18n("Keep replies in this folder"), this); mKeepRepliesInSameFolderCheckBox->setWhatsThis( i18n("Check this option if you want replies you write " "to mails in this folder to be put in this same folder " "after sending, instead of in the configured sent-mail folder.")); hbl->addWidget(mKeepRepliesInSameFolderCheckBox); hbl->addStretch(1); // should this folder be shown in the folder selection dialog? hbl = new QHBoxLayout(); topLayout->addItem(hbl); mHideInSelectionDialogCheckBox = new QCheckBox(i18n("Hide this folder in the folder selection dialog"), this); mHideInSelectionDialogCheckBox->setWhatsThis( xi18nc("@info:whatsthis", "Check this option if you do not want this folder " "to be shown in folder selection dialogs, such as the " "Jump to Folder dialog.")); hbl->addWidget(mHideInSelectionDialogCheckBox); hbl->addStretch(1); addLine(this, topLayout); // use grid layout for the following combobox settings QGridLayout *gl = new QGridLayout(); topLayout->addItem(gl); gl->setColumnStretch(1, 100); // make the second column use all available space int row = -1; // sender identity ++row; mUseDefaultIdentityCheckBox = new QCheckBox(i18n("Use &default identity"), this); gl->addWidget(mUseDefaultIdentityCheckBox); connect(mUseDefaultIdentityCheckBox, &QCheckBox::stateChanged, this, &CollectionGeneralPage::slotIdentityCheckboxChanged); ++row; label = new QLabel(i18n("&Sender identity:"), this); gl->addWidget(label, row, 0); mIdentityComboBox = new KIdentityManagement::IdentityCombo(KernelIf->identityManager(), this); label->setBuddy(mIdentityComboBox); gl->addWidget(mIdentityComboBox, row, 1); mIdentityComboBox->setWhatsThis( i18n("Select the sender identity to be used when writing new mail " "or replying to mail in this folder. This means that if you are in " "one of your work folders, you can make KMail use the corresponding " "sender email address, signature and signing or encryption keys " "automatically. Identities can be set up in the main configuration " "dialog. (Settings -> Configure KMail)")); // Only do make this settable, if the IMAP resource is enabled // and it's not the personal folders (those must not be changed) const QString collectionResource = collection.resource(); if (CommonKernel->imapResourceManager()->hasAnnotationSupport(collectionResource)) { PimCommon::CollectionTypeUtil::FolderContentsType contentsType = PimCommon::CollectionTypeUtil::ContentsTypeMail; const PimCommon::CollectionAnnotationsAttribute *annotationAttribute = collection.attribute(); const QMap annotations = (annotationAttribute ? annotationAttribute->annotations() : QMap()); const bool sharedSeen = (annotations.value(PimCommon::CollectionTypeUtil::kolabSharedSeen()) == "true"); PimCommon::CollectionTypeUtil collectionUtil; const PimCommon::CollectionTypeUtil::IncidencesFor incidencesFor = collectionUtil.incidencesForFromString(QLatin1String(annotations.value(PimCommon::CollectionTypeUtil::kolabIncidencesFor()))); const PimCommon::CollectionTypeUtil::FolderContentsType folderType = collectionUtil.typeFromKolabName(annotations.value(PimCommon::CollectionTypeUtil::kolabFolderType())); ++row; mContentsComboBox = new PimCommon::ContentTypeWidget(this); gl->addWidget(mContentsComboBox, row, 0, 1, 2); mContentsComboBox->setCurrentIndex(contentsType); connect(mContentsComboBox, &PimCommon::ContentTypeWidget::activated, this, &CollectionGeneralPage::slotFolderContentsSelectionChanged); if (mFolderCollection->isReadOnly() || mIsResourceFolder) { mContentsComboBox->setEnabled(false); } // Kolab incidences-for annotation. // Show incidences-for combobox if the contents type can be changed (new folder), // or if it's set to calendar or task (existing folder) const bool folderTypeComboboxEnabled = (folderType == PimCommon::CollectionTypeUtil::ContentsTypeCalendar || folderType == PimCommon::CollectionTypeUtil::ContentsTypeTask); ++row; mIncidencesForComboBox = new PimCommon::IncidencesForWidget(this); gl->addWidget(mIncidencesForComboBox, row, 0, 1, 2); mIncidencesForComboBox->setCurrentIndex(incidencesFor); mIncidencesForComboBox->setEnabled(folderTypeComboboxEnabled); mSharedSeenFlagsCheckBox = new QCheckBox(this); mSharedSeenFlagsCheckBox->setText(i18n("Share unread state with all users")); mSharedSeenFlagsCheckBox->setChecked(sharedSeen); ++row; gl->addWidget(mSharedSeenFlagsCheckBox, row, 0, 1, 1); mSharedSeenFlagsCheckBox->setWhatsThis( i18n("If enabled, the unread state of messages in this folder will be " "the same for all users having access to this folder. If disabled " "(the default), every user with access to this folder has their " "own unread state.")); } topLayout->addStretch(100); // eat all superfluous space } void CollectionGeneralPage::load(const Akonadi::Collection &collection) { mFolderCollection = FolderSettings::forCollection(collection); init(collection); if (mNameEdit) { const QString displayName = collection.displayName(); if (!mIsLocalSystemFolder || mIsResourceFolder) { mNameEdit->setText(displayName); } } // folder identity mIdentityComboBox->setCurrentIdentity(mFolderCollection->identity()); mUseDefaultIdentityCheckBox->setChecked(mFolderCollection->useDefaultIdentity()); // ignore new mail mNotifyOnNewMailCheckBox->setChecked(!Util::ignoreNewMailInFolder(collection)); const bool keepInFolder = (mFolderCollection->canCreateMessages() && mFolderCollection->putRepliesInSameFolder()); mKeepRepliesInSameFolderCheckBox->setChecked(keepInFolder); mKeepRepliesInSameFolderCheckBox->setEnabled(mFolderCollection->canCreateMessages()); mHideInSelectionDialogCheckBox->setChecked(mFolderCollection->hideInSelectionDialog()); if (mContentsComboBox) { const PimCommon::CollectionAnnotationsAttribute *annotationsAttribute = collection.attribute(); if (annotationsAttribute) { const QMap annotations = annotationsAttribute->annotations(); if (annotations.contains(PimCommon::CollectionTypeUtil::kolabFolderType())) { PimCommon::CollectionTypeUtil collectionUtil; mContentsComboBox->setCurrentItem( collectionUtil.typeNameFromKolabType(annotations[ PimCommon::CollectionTypeUtil::kolabFolderType() ])); } } } } void CollectionGeneralPage::save(Collection &collection) { if (mNameEdit) { if (!mIsLocalSystemFolder) { const QString nameFolder(mNameEdit->text().trimmed()); bool canRenameFolder = !(nameFolder.startsWith(QLatin1Char('.')) || nameFolder.endsWith(QLatin1Char('.')) || nameFolder.contains(QLatin1Char('/')) || nameFolder.isEmpty()); if (mIsResourceFolder && (PimCommon::Util::isImapResource(collection.resource()))) { collection.setName(nameFolder); Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource()); instance.setName(nameFolder); } else if (canRenameFolder) { if (collection.hasAttribute() && !collection.attribute()->displayName().isEmpty()) { collection.attribute()->setDisplayName( nameFolder); } else if (!nameFolder.isEmpty()) { collection.setName(nameFolder); } } } } if (!mNotifyOnNewMailCheckBox->isChecked()) { Akonadi::NewMailNotifierAttribute *newMailNotifierAttr = collection.attribute(Akonadi::Collection::AddIfMissing); newMailNotifierAttr->setIgnoreNewMail(true); } else { collection.removeAttribute(); } PimCommon::CollectionAnnotationsAttribute *annotationsAttribute = collection.attribute(Collection::AddIfMissing); QMap annotations = annotationsAttribute->annotations(); if (mSharedSeenFlagsCheckBox && mSharedSeenFlagsCheckBox->isEnabled()) { annotations[ PimCommon::CollectionTypeUtil::kolabSharedSeen() ] = mSharedSeenFlagsCheckBox->isChecked() ? "true" : "false"; } PimCommon::CollectionTypeUtil collectionUtil; if (mIncidencesForComboBox && mIncidencesForComboBox->isEnabled()) { annotations[ PimCommon::CollectionTypeUtil::kolabIncidencesFor() ] = collectionUtil.incidencesForToString( static_cast(mIncidencesForComboBox->currentIndex())).toLatin1(); } if (mContentsComboBox) { const PimCommon::CollectionTypeUtil::FolderContentsType type = collectionUtil.contentsTypeFromString(mContentsComboBox->currentText()); const QByteArray kolabName = collectionUtil.kolabNameFromType(type); if (!kolabName.isEmpty()) { const QString iconName = collectionUtil.iconNameFromContentsType(type); Akonadi::EntityDisplayAttribute *attribute = collection.attribute(Akonadi::Collection::AddIfMissing); attribute->setIconName(iconName); new Akonadi::CollectionModifyJob(collection); annotations[ PimCommon::CollectionTypeUtil::kolabFolderType() ] = kolabName; } } if (annotations.isEmpty()) { collection.removeAttribute(); } else { annotationsAttribute->setAnnotations(annotations); } if (mFolderCollection) { mFolderCollection->setIdentity(mIdentityComboBox->currentIdentity()); mFolderCollection->setUseDefaultIdentity(mUseDefaultIdentityCheckBox->isChecked()); mFolderCollection->setPutRepliesInSameFolder(mKeepRepliesInSameFolderCheckBox->isChecked()); mFolderCollection->setHideInSelectionDialog(mHideInSelectionDialogCheckBox->isChecked()); mFolderCollection->writeConfig(); } } void CollectionGeneralPage::slotIdentityCheckboxChanged() { mIdentityComboBox->setEnabled(!mUseDefaultIdentityCheckBox->isChecked()); if (mFolderCollection && mUseDefaultIdentityCheckBox->isChecked()) { mIdentityComboBox->setCurrentIdentity(mFolderCollection->fallBackIdentity()); } } void CollectionGeneralPage::slotFolderContentsSelectionChanged(int) { PimCommon::CollectionTypeUtil collectionUtil; const PimCommon::CollectionTypeUtil::FolderContentsType type = collectionUtil.contentsTypeFromString(mContentsComboBox->currentText()); if (type != PimCommon::CollectionTypeUtil::ContentsTypeMail) { const QString message = i18n("You have configured this folder to contain groupware information. " "That means that this folder will disappear once the configuration " "dialog is closed."); KMessageBox::information(this, message); } const bool enable = (type == PimCommon::CollectionTypeUtil::ContentsTypeCalendar || type == PimCommon::CollectionTypeUtil::ContentsTypeTask); if (mIncidencesForComboBox) { mIncidencesForComboBox->setEnabled(enable); } } void CollectionGeneralPage::slotNameChanged(const QString &name) { #ifndef QT_NO_STYLE_STYLESHEET QString styleSheet; if (name.startsWith(QLatin1Char('.')) || name.endsWith(QLatin1Char('.')) || name.contains(QLatin1Char('/')) || name.trimmed().isEmpty()) { if (mColorName.isEmpty()) { const KColorScheme::BackgroundRole bgColorScheme(KColorScheme::NegativeBackground); KStatefulBrush bgBrush(KColorScheme::View, bgColorScheme); mColorName = bgBrush.brush(this).color().name(); } styleSheet = QStringLiteral("QLineEdit{ background-color:%1 }"). arg(mColorName); } setStyleSheet(styleSheet); #endif } diff --git a/src/collectionpage/collectiongeneralpage.h b/src/collectionpage/collectiongeneralpage.h index bae2045..5b46fb4 100644 --- a/src/collectionpage/collectiongeneralpage.h +++ b/src/collectionpage/collectiongeneralpage.h @@ -1,79 +1,80 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_COLLECTIONGENERALPAGE_H #define MAILCOMMON_COLLECTIONGENERALPAGE_H #include "mailcommon_export.h" #include class KLineEdit; class QCheckBox; template class QSharedPointer; namespace PimCommon { class IncidencesForWidget; class ContentTypeWidget; } namespace KIdentityManagement { class IdentityCombo; } class QVBoxLayout; namespace MailCommon { class FolderSettings; class MAILCOMMON_EXPORT CollectionGeneralPage : public Akonadi::CollectionPropertiesPage { Q_OBJECT public: explicit CollectionGeneralPage(QWidget *parent = nullptr); ~CollectionGeneralPage() override; void load(const Akonadi::Collection &collection) override; void save(Akonadi::Collection &collection) override; protected: void init(const Akonadi::Collection &); private Q_SLOTS: void slotIdentityCheckboxChanged(); void slotFolderContentsSelectionChanged(int); void slotNameChanged(const QString &name); private: void addLine(QWidget *parent, QVBoxLayout *layout); QString mColorName; PimCommon::ContentTypeWidget *mContentsComboBox = nullptr; PimCommon::IncidencesForWidget *mIncidencesForComboBox = nullptr; QCheckBox *mSharedSeenFlagsCheckBox = nullptr; QCheckBox *mNotifyOnNewMailCheckBox = nullptr; QCheckBox *mKeepRepliesInSameFolderCheckBox = nullptr; QCheckBox *mHideInSelectionDialogCheckBox = nullptr; QCheckBox *mUseDefaultIdentityCheckBox = nullptr; KLineEdit *mNameEdit = nullptr; KIdentityManagement::IdentityCombo *mIdentityComboBox = nullptr; QSharedPointer mFolderCollection; bool mIsLocalSystemFolder = false; bool mIsResourceFolder = false; }; AKONADI_COLLECTION_PROPERTIES_PAGE_FACTORY(CollectionGeneralPageFactory, CollectionGeneralPage) } #endif diff --git a/src/folder/accountconfigorderdialog.cpp b/src/folder/accountconfigorderdialog.cpp index 8020970..0f1c87e 100644 --- a/src/folder/accountconfigorderdialog.cpp +++ b/src/folder/accountconfigorderdialog.cpp @@ -1,271 +1,272 @@ /* Copyright (c) 2012-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "accountconfigorderdialog.h" #include "mailcommonsettings_base.h" #include #include "util/mailutil.h" #include "mailcommon_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace MailCommon; struct InstanceStruct { QString name; QIcon icon; bool isValid() const { return !name.isEmpty(); } }; class MailCommon::AccountConfigOrderDialogPrivate { public: AccountConfigOrderDialogPrivate() : mListAccount(nullptr) , mUpButton(nullptr) , mDownButton(nullptr) , mEnableAccountOrder(nullptr) { } QListWidget *mListAccount = nullptr; QPushButton *mUpButton = nullptr; QPushButton *mDownButton = nullptr; QCheckBox *mEnableAccountOrder = nullptr; MailCommon::MailCommonSettings *mSettings = nullptr; }; AccountConfigOrderDialog::AccountConfigOrderDialog(MailCommon::MailCommonSettings *settings, QWidget *parent) : QDialog(parent) , d(new MailCommon::AccountConfigOrderDialogPrivate) { d->mSettings = settings; setWindowTitle(i18n("Edit Accounts Order")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QVBoxLayout *mainLayout = new QVBoxLayout(this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::rejected, this, &AccountConfigOrderDialog::reject); QWidget *page = new QWidget(this); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); QVBoxLayout *vbox = new QVBoxLayout; vbox->setMargin(0); page->setLayout(vbox); d->mEnableAccountOrder = new QCheckBox(i18n("Use custom order"), this); connect(d->mEnableAccountOrder, &QCheckBox::clicked, this, &AccountConfigOrderDialog::slotEnableAccountOrder); vbox->addWidget(d->mEnableAccountOrder); QHBoxLayout *vlay = new QHBoxLayout; vbox->addLayout(vlay); d->mListAccount = new QListWidget(this); d->mListAccount->setDragDropMode(QAbstractItemView::InternalMove); vlay->addWidget(d->mListAccount); QWidget *upDownBox = new QWidget(page); QVBoxLayout *upDownBoxVBoxLayout = new QVBoxLayout(upDownBox); upDownBoxVBoxLayout->setMargin(0); d->mUpButton = new QPushButton(upDownBox); upDownBoxVBoxLayout->addWidget(d->mUpButton); d->mUpButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); d->mUpButton->setToolTip(i18nc("Move selected account up.", "Up")); d->mUpButton->setEnabled(false); // b/c no item is selected yet d->mUpButton->setFocusPolicy(Qt::StrongFocus); d->mUpButton->setAutoRepeat(true); d->mDownButton = new QPushButton(upDownBox); upDownBoxVBoxLayout->addWidget(d->mDownButton); d->mDownButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); d->mDownButton->setToolTip(i18nc("Move selected account down.", "Down")); d->mDownButton->setEnabled(false); // b/c no item is selected yet d->mDownButton->setFocusPolicy(Qt::StrongFocus); d->mDownButton->setAutoRepeat(true); QWidget *spacer = new QWidget(upDownBox); upDownBoxVBoxLayout->addWidget(spacer); upDownBoxVBoxLayout->setStretchFactor(spacer, 100); vlay->addWidget(upDownBox); connect(d->mUpButton, &QPushButton::clicked, this, &AccountConfigOrderDialog::slotMoveUp); connect(d->mDownButton, &QPushButton::clicked, this, &AccountConfigOrderDialog::slotMoveDown); connect(d->mListAccount, &QListWidget::itemSelectionChanged, this, &AccountConfigOrderDialog::slotEnableControls); connect(d->mListAccount->model(), &QAbstractItemModel::rowsMoved, this, &AccountConfigOrderDialog::slotEnableControls); connect(okButton, &QPushButton::clicked, this, &AccountConfigOrderDialog::slotOk); readConfig(); init(); } AccountConfigOrderDialog::~AccountConfigOrderDialog() { writeConfig(); delete d; } void AccountConfigOrderDialog::slotEnableAccountOrder(bool state) { d->mListAccount->setEnabled(state); d->mUpButton->setEnabled(state); d->mDownButton->setEnabled(state); if (state) { slotEnableControls(); } } void AccountConfigOrderDialog::slotMoveUp() { if (!d->mListAccount->currentItem()) { return; } const int pos = d->mListAccount->row(d->mListAccount->currentItem()); d->mListAccount->blockSignals(true); QListWidgetItem *item = d->mListAccount->takeItem(pos); // now selected item is at idx(idx-1), so // insert the other item at idx, ie. above(below). d->mListAccount->insertItem(pos - 1, item); d->mListAccount->blockSignals(false); d->mListAccount->setCurrentRow(pos - 1); } void AccountConfigOrderDialog::slotMoveDown() { if (!d->mListAccount->currentItem()) { return; } const int pos = d->mListAccount->row(d->mListAccount->currentItem()); d->mListAccount->blockSignals(true); QListWidgetItem *item = d->mListAccount->takeItem(pos); // now selected item is at idx(idx-1), so // insert the other item at idx, ie. above(below). d->mListAccount->insertItem(pos + 1, item); d->mListAccount->blockSignals(false); d->mListAccount->setCurrentRow(pos + 1); } void AccountConfigOrderDialog::slotEnableControls() { QListWidgetItem *item = d->mListAccount->currentItem(); d->mUpButton->setEnabled(item && d->mListAccount->currentRow() != 0); d->mDownButton->setEnabled(item && d->mListAccount->currentRow() != d->mListAccount->count() - 1); } void AccountConfigOrderDialog::init() { const QStringList listOrderAccount = d->mSettings->order(); QStringList instanceList; QMap mapInstance; const Akonadi::AgentInstance::List lstInstances = Akonadi::AgentManager::self()->instances(); for (const Akonadi::AgentInstance &instance : lstInstances) { const QString identifier = instance.identifier(); if (!MailCommon::Util::isMailAgent(instance) || identifier.contains(POP3_RESOURCE_IDENTIFIER)) { continue; } instanceList << instance.identifier(); InstanceStruct instanceStruct; instanceStruct.name = instance.name(); if (PimCommon::Util::isImapResource(identifier)) { instanceStruct.name += QLatin1String(" (IMAP)"); } else if (identifier.startsWith(QLatin1String("akonadi_maildir_resource"))) { instanceStruct.name += QLatin1String(" (Maildir)"); } else if (identifier.startsWith(QLatin1String("akonadi_mailbox_resource"))) { instanceStruct.name += QLatin1String(" (Mailbox)"); } else if (identifier.startsWith(QLatin1String("akonadi_mixedmaildir_resource"))) { instanceStruct.name += QLatin1String(" (Mixedmaildir)"); } else { qCDebug(MAILCOMMON_LOG) << " Unknown resource type " << identifier; } instanceStruct.icon = instance.type().icon(); mapInstance.insert(instance.identifier(), instanceStruct); } instanceList.sort(Qt::CaseInsensitive); const int numberOfList(listOrderAccount.count()); for (int i = 0; i < numberOfList; ++i) { instanceList.removeOne(listOrderAccount.at(i)); } QStringList finalList(listOrderAccount); finalList += instanceList; const int numberOfElement(finalList.count()); for (int i = 0; i < numberOfElement; ++i) { const QString identifier(finalList.at(i)); const InstanceStruct tmp = mapInstance.value(identifier); if (tmp.isValid()) { QListWidgetItem *item = new QListWidgetItem(tmp.icon, tmp.name, d->mListAccount); item->setData(AccountConfigOrderDialog::IdentifierAccount, identifier); d->mListAccount->addItem(item); } } const bool enabled = d->mSettings->enableAccountOrder(); d->mEnableAccountOrder->setChecked(enabled); slotEnableAccountOrder(enabled); } void AccountConfigOrderDialog::slotOk() { QStringList order; const int numberOfItems(d->mListAccount->count()); order.reserve(numberOfItems); for (int i = 0; i < numberOfItems; ++i) { order << d->mListAccount->item(i)->data(AccountConfigOrderDialog::IdentifierAccount).toString(); } d->mSettings->setOrder(order); d->mSettings->setEnableAccountOrder(d->mEnableAccountOrder->isChecked()); d->mSettings->save(); QDialog::accept(); } void AccountConfigOrderDialog::readConfig() { KConfigGroup accountConfigDialog(d->mSettings->config(), "AccountConfigOrderDialog"); const QSize size = accountConfigDialog.readEntry("Size", QSize(600, 400)); if (size.isValid()) { resize(size); } } void AccountConfigOrderDialog::writeConfig() { KConfigGroup accountConfigDialog(d->mSettings->config(), "AccountConfigOrderDialog"); accountConfigDialog.writeEntry("Size", size()); accountConfigDialog.sync(); } diff --git a/src/folder/accountconfigorderdialog.h b/src/folder/accountconfigorderdialog.h index 19b8a32..93e1b7b 100644 --- a/src/folder/accountconfigorderdialog.h +++ b/src/folder/accountconfigorderdialog.h @@ -1,51 +1,52 @@ /* Copyright (c) 2012-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ACCOUNTCONFIGORDERDIALOG_H #define ACCOUNTCONFIGORDERDIALOG_H #include #include "mailcommon_export.h" namespace MailCommon { class AccountConfigOrderDialogPrivate; class MailCommonSettings; class MAILCOMMON_EXPORT AccountConfigOrderDialog : public QDialog { Q_OBJECT public: explicit AccountConfigOrderDialog(MailCommon::MailCommonSettings *settings, QWidget *parent); ~AccountConfigOrderDialog(); private: void slotOk(); void slotMoveUp(); void slotMoveDown(); void slotEnableControls(); void slotEnableAccountOrder(bool state); enum Type { IdentifierAccount = Qt::UserRole + 1 }; void writeConfig(); void readConfig(); void init(); AccountConfigOrderDialogPrivate *const d; }; } #endif // ACCOUNTCONFIGORDERDIALOG_H diff --git a/src/folder/entitycollectionorderproxymodel.cpp b/src/folder/entitycollectionorderproxymodel.cpp index 7ef2b51..98deffb 100644 --- a/src/folder/entitycollectionorderproxymodel.cpp +++ b/src/folder/entitycollectionorderproxymodel.cpp @@ -1,164 +1,165 @@ /* Copyright (c) 2010-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "entitycollectionorderproxymodel.h" #include "kernel/mailkernel.h" #include "util/mailutil.h" #include "mailcommon_debug.h" #include #include #include #include namespace MailCommon { class Q_DECL_HIDDEN EntityCollectionOrderProxyModel::EntityCollectionOrderProxyModelPrivate { public: EntityCollectionOrderProxyModelPrivate() : manualSortingActive(false) { } int collectionRank(const Akonadi::Collection &collection) { const Akonadi::Collection::Id id = collection.id(); const int cachedRank = collectionRanks.value(id, -1); if (cachedRank != -1) { return cachedRank; } int rank = 100; if (Kernel::folderIsInbox(collection)) { rank = 1; } else if (Kernel::self()->folderIsDraftOrOutbox(collection)) { if (Kernel::self()->folderIsDrafts(collection)) { rank = 5; } else { rank = 2; } } else if (Kernel::self()->folderIsSentMailFolder(collection)) { rank = 3; } else if (Kernel::self()->folderIsTrash(collection)) { rank = 4; } else if (Kernel::self()->folderIsTemplates(collection)) { rank = 6; } else if (MailCommon::Util::isVirtualCollection(collection)) { rank = 200; } else if (collection.parentCollection() == Akonadi::Collection::root() && MailCommon::Util::isUnifiedMailboxesAgent(collection)) { // special treatment for Unified Mailboxes: they are *always* on top rank = 0; } else if (!topLevelOrder.isEmpty()) { if (collection.parentCollection() == Akonadi::Collection::root()) { const QString resource = collection.resource(); if (resource.isEmpty()) { qCDebug(MAILCOMMON_LOG) << " collection has not resource: " << collection; //Don't save in collectionranks because we don't have resource name => pb. return rank; } const int order = topLevelOrder.indexOf(resource); if (order != -1) { rank = order + 1; /* top-level rank "0" belongs to Unified Mailboxes */ } } } collectionRanks.insert(id, rank); return rank; } QMap collectionRanks; QStringList topLevelOrder; bool manualSortingActive; }; EntityCollectionOrderProxyModel::EntityCollectionOrderProxyModel(QObject *parent) : EntityOrderProxyModel(parent) , d(new EntityCollectionOrderProxyModelPrivate()) { setSortCaseSensitivity(Qt::CaseInsensitive); connect(Akonadi::SpecialMailCollections::self(), &Akonadi::SpecialMailCollections::defaultCollectionsChanged, this, &EntityCollectionOrderProxyModel::slotSpecialCollectionsChanged); connect(Akonadi::SpecialMailCollections::self(), &Akonadi::SpecialMailCollections::collectionsChanged, this, &EntityCollectionOrderProxyModel::slotSpecialCollectionsChanged); } EntityCollectionOrderProxyModel::~EntityCollectionOrderProxyModel() { if (d->manualSortingActive) { saveOrder(); } delete d; } void EntityCollectionOrderProxyModel::slotSpecialCollectionsChanged() { if (!d->manualSortingActive) { d->collectionRanks.clear(); invalidate(); } } void EntityCollectionOrderProxyModel::setTopLevelOrder(const QStringList &list) { d->topLevelOrder = list; clearRanks(); } void EntityCollectionOrderProxyModel::clearRanks() { d->collectionRanks.clear(); invalidate(); } bool EntityCollectionOrderProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { const auto leftData = left.data(Akonadi::EntityTreeModel::CollectionRole).value(); const auto rightData = right.data(Akonadi::EntityTreeModel::CollectionRole).value(); if (!d->manualSortingActive) { const int rankLeft = d->collectionRank(leftData); const int rankRight = d->collectionRank(rightData); if (rankLeft < rankRight) { return true; } else if (rankLeft > rankRight) { return false; } return QSortFilterProxyModel::lessThan(left, right); } if (MailCommon::Util::isUnifiedMailboxesAgent(leftData)) { return true; } else { return EntityOrderProxyModel::lessThan(left, right); } } void EntityCollectionOrderProxyModel::setManualSortingActive(bool active) { if (d->manualSortingActive == active) { return; } d->manualSortingActive = active; d->collectionRanks.clear(); invalidate(); } bool EntityCollectionOrderProxyModel::isManualSortingActive() const { return d->manualSortingActive; } } diff --git a/src/folder/entitycollectionorderproxymodel.h b/src/folder/entitycollectionorderproxymodel.h index 77d896d..719249a 100644 --- a/src/folder/entitycollectionorderproxymodel.h +++ b/src/folder/entitycollectionorderproxymodel.h @@ -1,64 +1,65 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_ENTITYCOLLECTIONORDERPROXYMODEL_H #define MAILCOMMON_ENTITYCOLLECTIONORDERPROXYMODEL_H #include #include "mailcommon_export.h" namespace MailCommon { /** * @brief The EntityCollectionOrderProxyModel class implements ordering of mail collections. * It supports two modes: manual sorting and automatic sorting. * * The manual sorting (which has to be activated explicitly by the user) allows the user to * reorder the collections (both toplevel resources and folders within the resource) by drag-n-drop, * and is implemented by the base class EntityOrderProxyModel. * * The automatic sorting is implemented by this class itself, and consists of assigning ranks * to various special folders (outbox, drafts, sent etc.) and then having the other folders sorted * by name (or another column), i.e. the default behaviour from QSortFilterProxyModel. * In that mode, the order of the toplevel folders can be controlled with setTopLevelOrder(). */ class MAILCOMMON_EXPORT EntityCollectionOrderProxyModel : public Akonadi::EntityOrderProxyModel { Q_OBJECT public: explicit EntityCollectionOrderProxyModel(QObject *parent = nullptr); ~EntityCollectionOrderProxyModel() override; Q_REQUIRED_RESULT bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; void setManualSortingActive(bool active); Q_REQUIRED_RESULT bool isManualSortingActive() const; void clearRanks(); void setTopLevelOrder(const QStringList &list); public Q_SLOTS: void slotSpecialCollectionsChanged(); private: class EntityCollectionOrderProxyModelPrivate; EntityCollectionOrderProxyModelPrivate *const d; }; } #endif diff --git a/src/folder/favoritecollectionorderproxymodel.cpp b/src/folder/favoritecollectionorderproxymodel.cpp index ba70231..11fedfd 100644 --- a/src/folder/favoritecollectionorderproxymodel.cpp +++ b/src/folder/favoritecollectionorderproxymodel.cpp @@ -1,49 +1,50 @@ /* Copyright (c) 2017 David Faure - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "favoritecollectionorderproxymodel.h" #include "mailcommon_debug.h" #include #include using namespace MailCommon; class Q_DECL_HIDDEN FavoriteCollectionOrderProxyModel::FavoriteCollectionOrderProxyModelPrivate { public: FavoriteCollectionOrderProxyModelPrivate() { } }; FavoriteCollectionOrderProxyModel::FavoriteCollectionOrderProxyModel(QObject *parent) : EntityOrderProxyModel(parent) , d(nullptr) //, d(new FavoriteCollectionOrderProxyModelPrivate()) { } FavoriteCollectionOrderProxyModel::~FavoriteCollectionOrderProxyModel() { delete d; } Akonadi::Collection FavoriteCollectionOrderProxyModel::parentCollection(const QModelIndex &index) const { Q_UNUSED(index); return {}; } diff --git a/src/folder/favoritecollectionorderproxymodel.h b/src/folder/favoritecollectionorderproxymodel.h index 23eba03..20e4c50 100644 --- a/src/folder/favoritecollectionorderproxymodel.h +++ b/src/folder/favoritecollectionorderproxymodel.h @@ -1,45 +1,46 @@ /* Copyright (c) 2017 David Faure - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_FAVORITECOLLECTIONORDERPROXYMODEL_H #define MAILCOMMON_FAVORITECOLLECTIONORDERPROXYMODEL_H #include #include "mailcommon_export.h" namespace MailCommon { /** * @brief The FavoriteCollectionOrderProxyModel class implements ordering of favorite collections. */ class MAILCOMMON_EXPORT FavoriteCollectionOrderProxyModel : public Akonadi::EntityOrderProxyModel { Q_OBJECT public: explicit FavoriteCollectionOrderProxyModel(QObject *parent = nullptr); ~FavoriteCollectionOrderProxyModel() override; protected: Akonadi::Collection parentCollection(const QModelIndex &index) const override; private: class FavoriteCollectionOrderProxyModelPrivate; FavoriteCollectionOrderProxyModelPrivate *const d; }; } #endif diff --git a/src/folder/foldercollectionmonitor.cpp b/src/folder/foldercollectionmonitor.cpp index 725cedf..be649f8 100644 --- a/src/folder/foldercollectionmonitor.cpp +++ b/src/folder/foldercollectionmonitor.cpp @@ -1,135 +1,136 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "foldercollectionmonitor.h" #include "util/mailutil.h" #include "foldersettings.h" #include "mailcommon_debug.h" #include #include #include #include #include #include #include #include #include "collectionpage/attributes/expirecollectionattribute.h" #include #include #include using namespace MailCommon; class MailCommon::FolderCollectionMonitorPrivate { public: FolderCollectionMonitorPrivate() : mMonitor(nullptr) { } Akonadi::ChangeRecorder *mMonitor = nullptr; }; FolderCollectionMonitor::FolderCollectionMonitor(Akonadi::Session *session, QObject *parent) : QObject(parent) , d(new MailCommon::FolderCollectionMonitorPrivate) { // monitor collection changes d->mMonitor = new Akonadi::ChangeRecorder(this); d->mMonitor->setSession(session); d->mMonitor->setCollectionMonitored(Akonadi::Collection::root()); d->mMonitor->fetchCollectionStatistics(true); d->mMonitor->collectionFetchScope().setIncludeStatistics(true); d->mMonitor->fetchCollection(true); d->mMonitor->setAllMonitored(true); d->mMonitor->setMimeTypeMonitored(KMime::Message::mimeType()); d->mMonitor->setResourceMonitored("akonadi_search_resource", true); d->mMonitor->itemFetchScope().fetchPayloadPart(Akonadi::MessagePart::Envelope); d->mMonitor->itemFetchScope().setFetchModificationTime(false); d->mMonitor->itemFetchScope().setFetchRemoteIdentification(false); d->mMonitor->itemFetchScope().setFetchTags(true); d->mMonitor->itemFetchScope().fetchAttribute(true); } FolderCollectionMonitor::~FolderCollectionMonitor() { delete d; } Akonadi::ChangeRecorder *FolderCollectionMonitor::monitor() const { return d->mMonitor; } void FolderCollectionMonitor::expireAllFolders(bool immediate, QAbstractItemModel *collectionModel) { if (collectionModel) { expireAllCollection(collectionModel, immediate); } } void FolderCollectionMonitor::expireAllCollection(const QAbstractItemModel *model, bool immediate, const QModelIndex &parentIndex) { const int rowCount = model->rowCount(parentIndex); for (int row = 0; row < rowCount; ++row) { const QModelIndex index = model->index(row, 0, parentIndex); const Akonadi::Collection collection = model->data( index, Akonadi::EntityTreeModel::CollectionRole).value(); if (!collection.isValid() || Util::isVirtualCollection(collection)) { continue; } bool mustDeleteExpirationAttribute = false; MailCommon::ExpireCollectionAttribute *attr = MailCommon::Util::expirationCollectionAttribute( collection, mustDeleteExpirationAttribute); if (attr->isAutoExpire()) { MailCommon::Util::expireOldMessages(collection, immediate); } if (model->rowCount(index) > 0) { expireAllCollection(model, immediate, index); } if (mustDeleteExpirationAttribute) { delete attr; } } } void FolderCollectionMonitor::expunge(const Akonadi::Collection &col, bool sync) { if (col.isValid()) { Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(col, this); connect(job, &Akonadi::ItemDeleteJob::result, this, &FolderCollectionMonitor::slotDeleteJob); if (sync) { job->exec(); } } else { qCDebug(MAILCOMMON_LOG) << " Try to expunge an invalid collection :" << col; } } void FolderCollectionMonitor::slotDeleteJob(KJob *job) { Util::showJobErrorMessage(job); } diff --git a/src/folder/foldercollectionmonitor.h b/src/folder/foldercollectionmonitor.h index b5e5372..f0b3eb2 100644 --- a/src/folder/foldercollectionmonitor.h +++ b/src/folder/foldercollectionmonitor.h @@ -1,60 +1,61 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_FOLDERCOLLECTIONMONITOR_H #define MAILCOMMON_FOLDERCOLLECTIONMONITOR_H #include "mailcommon_export.h" #include #include #include #include class QAbstractItemModel; namespace Akonadi { class ChangeRecorder; class Collection; class Session; } namespace MailCommon { class FolderCollectionMonitorPrivate; class MAILCOMMON_EXPORT FolderCollectionMonitor : public QObject { Q_OBJECT public: explicit FolderCollectionMonitor(Akonadi::Session *session, QObject *parent = nullptr); ~FolderCollectionMonitor(); Q_REQUIRED_RESULT Akonadi::ChangeRecorder *monitor() const; void expireAllFolders(bool immediate, QAbstractItemModel *collectionModel); void expunge(const Akonadi::Collection &, bool sync = false); protected: void expireAllCollection(const QAbstractItemModel *model, bool immediate, const QModelIndex &parentIndex = QModelIndex()); private: void slotDeleteJob(KJob *job); FolderCollectionMonitorPrivate *const d; }; } #endif diff --git a/src/folder/folderselectiondialog.cpp b/src/folder/folderselectiondialog.cpp index 5d9ea30..b94900b 100644 --- a/src/folder/folderselectiondialog.cpp +++ b/src/folder/folderselectiondialog.cpp @@ -1,307 +1,308 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "folderselectiondialog.h" #include "foldersettings.h" #include "foldertreeview.h" #include "foldertreewidget.h" #include "foldertreewidgetproxymodel.h" #include "kernel/mailkernel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace MailCommon { class Q_DECL_HIDDEN FolderSelectionDialog::FolderSelectionDialogPrivate { public: FolderSelectionDialogPrivate() : folderTreeWidget(nullptr) , mUser1Button(nullptr) , mOkButton(nullptr) , mNotAllowToCreateNewFolder(false) , mUseGlobalSettings(true) { } FolderTreeWidget *folderTreeWidget = nullptr; QPushButton *mUser1Button = nullptr; QPushButton *mOkButton = nullptr; bool mNotAllowToCreateNewFolder = false; bool mUseGlobalSettings = false; }; FolderSelectionDialog::FolderSelectionDialog(QWidget *parent, SelectionFolderOptions options) : QDialog(parent) , d(new FolderSelectionDialogPrivate()) { setObjectName(QStringLiteral("folder dialog")); d->mNotAllowToCreateNewFolder = (options & FolderSelectionDialog::NotAllowToCreateNewFolder); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QVBoxLayout *mainLayout = new QVBoxLayout(this); d->mOkButton = buttonBox->button(QDialogButtonBox::Ok); d->mOkButton->setDefault(true); d->mOkButton->setAutoDefault(true); d->mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &FolderSelectionDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &FolderSelectionDialog::reject); if (!d->mNotAllowToCreateNewFolder) { d->mUser1Button = new QPushButton; d->mUser1Button->setDefault(false); d->mUser1Button->setAutoDefault(false); buttonBox->addButton(d->mUser1Button, QDialogButtonBox::ActionRole); KGuiItem::assign(d->mUser1Button, KGuiItem(i18n("&New Subfolder..."), QStringLiteral("folder-new"), i18n("Create a new subfolder under the currently selected folder"))); } FolderTreeWidget::TreeViewOptions opt = FolderTreeWidget::None; if (options & FolderSelectionDialog::ShowUnreadCount) { opt |= FolderTreeWidget::ShowUnreadCount; } opt |= FolderTreeWidget::UseDistinctSelectionModel; FolderTreeWidgetProxyModel::FolderTreeWidgetProxyModelOptions optReadableProxy = FolderTreeWidgetProxyModel::None; if (options & FolderSelectionDialog::HideVirtualFolder) { optReadableProxy |= FolderTreeWidgetProxyModel::HideVirtualFolder; } optReadableProxy |= FolderTreeWidgetProxyModel::HideSpecificFolder; if (options & FolderSelectionDialog::HideOutboxFolder) { optReadableProxy |= FolderTreeWidgetProxyModel::HideOutboxFolder; } d->folderTreeWidget = new FolderTreeWidget(this, nullptr, opt, optReadableProxy); d->folderTreeWidget->readConfig(); d->folderTreeWidget->disableContextMenuAndExtraColumn(); d->folderTreeWidget->folderTreeWidgetProxyModel()->setEnabledCheck((options & EnableCheck)); //Necessary otherwise we overwrite tooltip config for all application d->folderTreeWidget->folderTreeView()->disableSaveConfig(); d->folderTreeWidget->folderTreeView()->setTooltipsPolicy(FolderTreeWidget::DisplayNever); #ifndef QT_NO_DRAGANDDROP d->folderTreeWidget->folderTreeView()->setDragDropMode(QAbstractItemView::NoDragDrop); #endif mainLayout->addWidget(d->folderTreeWidget); mainLayout->addWidget(buttonBox); d->mOkButton->setEnabled(false); if (!d->mNotAllowToCreateNewFolder) { d->mUser1Button->setEnabled(false); connect(d->mUser1Button, &QPushButton::clicked, this, &FolderSelectionDialog::slotAddChildFolder); d->folderTreeWidget->folderTreeView()->setContextMenuPolicy(Qt::CustomContextMenu); connect(d->folderTreeWidget->folderTreeView(), &QWidget::customContextMenuRequested, this, &FolderSelectionDialog::slotFolderTreeWidgetContextMenuRequested); } connect(d->folderTreeWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FolderSelectionDialog::slotSelectionChanged); connect(d->folderTreeWidget->folderTreeWidgetProxyModel(), &QAbstractItemModel::rowsInserted, this, &FolderSelectionDialog::rowsInserted); connect(d->folderTreeWidget->folderTreeView(), QOverload::of(&QAbstractItemView::doubleClicked), this, &FolderSelectionDialog::slotDoubleClick); d->mUseGlobalSettings = !(options & NotUseGlobalSettings); readConfig(); } FolderSelectionDialog::~FolderSelectionDialog() { writeConfig(); delete d; } void FolderSelectionDialog::slotFolderTreeWidgetContextMenuRequested(const QPoint &pos) { if (d->mUser1Button && d->mUser1Button->isEnabled() && d->folderTreeWidget->folderTreeView()->indexAt(pos).isValid()) { QMenu menu(this); menu.addAction(i18n("&New Subfolder..."), this, &FolderSelectionDialog::slotAddChildFolder); menu.exec(QCursor::pos()); } } void FolderSelectionDialog::slotDoubleClick(const QModelIndex &index) { Q_UNUSED(index); const bool hasSelectedCollection = (!d->folderTreeWidget->selectionModel()->selectedIndexes().isEmpty()); if (hasSelectedCollection) { accept(); } } void FolderSelectionDialog::focusTreeView() { d->folderTreeWidget->folderTreeView()->expandAll(); d->folderTreeWidget->folderTreeView()->setFocus(); } void FolderSelectionDialog::showEvent(QShowEvent *event) { if (!event->spontaneous()) { focusTreeView(); FolderTreeView *view = d->folderTreeWidget->folderTreeView(); view->scrollTo(view->currentIndex()); } QDialog::showEvent(event); } void FolderSelectionDialog::rowsInserted(const QModelIndex &, int, int) { d->folderTreeWidget->folderTreeView()->expandAll(); } bool FolderSelectionDialog::canCreateCollection(Akonadi::Collection &parentCol) { parentCol = selectedCollection(); if (!parentCol.isValid()) { return false; } if ((parentCol.rights() & Akonadi::Collection::CanCreateCollection) && parentCol.contentMimeTypes().contains(Akonadi::Collection::mimeType())) { return true; } return false; } void FolderSelectionDialog::slotAddChildFolder() { Akonadi::Collection parentCol; if (canCreateCollection(parentCol)) { const QString name = QInputDialog::getText(this, i18nc("@title:window", "New Folder"), i18nc("@label:textbox, name of a thing", "Name")); if (name.isEmpty()) { return; } Akonadi::Collection col; col.setName(name); col.parentCollection().setId(parentCol.id()); Akonadi::CollectionCreateJob *job = new Akonadi::CollectionCreateJob(col); connect(job, &Akonadi::CollectionCreateJob::result, this, &FolderSelectionDialog::collectionCreationResult); } } void FolderSelectionDialog::collectionCreationResult(KJob *job) { if (job->error()) { KMessageBox::error( this, i18n("Could not create folder: %1", job->errorString()), i18n("Folder creation failed")); } } void FolderSelectionDialog::slotSelectionChanged() { const bool enablebuttons = (!d->folderTreeWidget->selectionModel()->selectedIndexes().isEmpty()); d->mOkButton->setEnabled(enablebuttons); if (!d->mNotAllowToCreateNewFolder) { Akonadi::Collection parent; d->mUser1Button->setEnabled(canCreateCollection(parent)); if (parent.isValid()) { const QSharedPointer fd(FolderSettings::forCollection(parent, false)); d->mOkButton->setEnabled(fd->canCreateMessages()); } } } void FolderSelectionDialog::setSelectionMode(QAbstractItemView::SelectionMode mode) { d->folderTreeWidget->setSelectionMode(mode); } QAbstractItemView::SelectionMode FolderSelectionDialog::selectionMode() const { return d->folderTreeWidget->selectionMode(); } Akonadi::Collection FolderSelectionDialog::selectedCollection() const { return d->folderTreeWidget->selectedCollection(); } void FolderSelectionDialog::setSelectedCollection(const Akonadi::Collection &collection) { d->folderTreeWidget->selectCollectionFolder(collection); } Akonadi::Collection::List FolderSelectionDialog::selectedCollections() const { return d->folderTreeWidget->selectedCollections(); } static const char myConfigGroupName[] = "FolderSelectionDialog"; void FolderSelectionDialog::readConfig() { KConfigGroup group(KernelIf->config(), myConfigGroupName); const QSize size = group.readEntry("Size", QSize(500, 300)); if (size.isValid()) { resize(size); } if (d->mUseGlobalSettings) { const Akonadi::Collection::Id id = SettingsIf->lastSelectedFolder(); if (id > -1) { const Akonadi::Collection col = Kernel::self()->collectionFromId(id); d->folderTreeWidget->selectCollectionFolder(col); } } } void FolderSelectionDialog::writeConfig() { KConfigGroup group(KernelIf->config(), myConfigGroupName); group.writeEntry("Size", size()); if (d->mUseGlobalSettings) { Akonadi::Collection col = selectedCollection(); if (col.isValid()) { SettingsIf->setLastSelectedFolder(col.id()); } } } void FolderSelectionDialog::hideEvent(QHideEvent *) { d->folderTreeWidget->clearFilter(); } } diff --git a/src/folder/folderselectiondialog.h b/src/folder/folderselectiondialog.h index ce5b081..93587c6 100644 --- a/src/folder/folderselectiondialog.h +++ b/src/folder/folderselectiondialog.h @@ -1,88 +1,89 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_FOLDERSELECTIONDIALOG_H #define MAILCOMMON_FOLDERSELECTIONDIALOG_H #include "mailcommon_export.h" #include #include #include class KJob; namespace MailCommon { /** * A dialog that lets the user select a folder. * TODO: Move most of this to Akonadi::CollectionDialog */ class MAILCOMMON_EXPORT FolderSelectionDialog : public QDialog { Q_OBJECT public: enum SelectionFolderOption { None = 0, EnableCheck = 1, ShowUnreadCount = 2, HideVirtualFolder = 4, NotAllowToCreateNewFolder = 8, HideOutboxFolder = 16, NotUseGlobalSettings = 64 }; Q_DECLARE_FLAGS(SelectionFolderOptions, SelectionFolderOption) FolderSelectionDialog(QWidget *parent, FolderSelectionDialog::SelectionFolderOptions options); ~FolderSelectionDialog() override; void setSelectionMode(QAbstractItemView::SelectionMode mode); Q_REQUIRED_RESULT QAbstractItemView::SelectionMode selectionMode() const; Q_REQUIRED_RESULT Akonadi::Collection selectedCollection() const; void setSelectedCollection(const Akonadi::Collection &collection); Q_REQUIRED_RESULT Akonadi::Collection::List selectedCollections() const; private Q_SLOTS: void slotSelectionChanged(); void slotAddChildFolder(); void collectionCreationResult(KJob *); void rowsInserted(const QModelIndex &col, int, int); void slotDoubleClick(const QModelIndex &); void slotFolderTreeWidgetContextMenuRequested(const QPoint &); protected: void focusTreeView(); void readConfig(); void writeConfig(); bool canCreateCollection(Akonadi::Collection &parentCol); void hideEvent(QHideEvent *) override; void showEvent(QShowEvent *) override; private: class FolderSelectionDialogPrivate; FolderSelectionDialogPrivate *const d; }; } #endif diff --git a/src/folder/foldersettings.cpp b/src/folder/foldersettings.cpp index e6675a0..dffd9b8 100644 --- a/src/folder/foldersettings.cpp +++ b/src/folder/foldersettings.cpp @@ -1,428 +1,429 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "foldersettings.h" #include "kernel/mailkernel.h" #include "util/mailutil.h" #include "util/resourcereadconfigfile.h" #include #include #include "mailcommon_debug.h" #include #include #include using namespace Akonadi; #include #include #include #include #include namespace MailCommon { static QMutex mapMutex; static QMap > fcMap; QSharedPointer FolderSettings::forCollection( const Akonadi::Collection &coll, bool writeConfig) { QMutexLocker lock(&mapMutex); QSharedPointer sptr = fcMap.value(coll.id()); if (!sptr) { sptr = QSharedPointer(new FolderSettings(coll, writeConfig)); fcMap.insert(coll.id(), sptr); } else { sptr->setCollection(coll); if (!sptr->isWriteConfig() && writeConfig) { sptr->setWriteConfig(true); } } return sptr; } FolderSettings::FolderSettings(const Akonadi::Collection &col, bool writeconfig) : mCollection(col) , mFormatMessage(MessageViewer::Viewer::Unknown) , mPutRepliesInSameFolder(false) , mHideInSelectionDialog(false) , mWriteConfig(writeconfig) { Q_ASSERT(col.isValid()); mIdentity = KernelIf->identityManager()->defaultIdentity().uoid(); readConfig(); connect(KernelIf->identityManager(), QOverload<>::of(&KIdentityManagement::IdentityManager::changed), this, &FolderSettings::slotIdentitiesChanged); } FolderSettings::~FolderSettings() { //qCDebug(MAILCOMMON_LOG)<<" FolderCollection::~FolderCollection"< >::const_iterator i = fcMap.constBegin(); while (i != fcMap.constEnd()) { i.value()->setFormatMessage(MessageViewer::Viewer::UseGlobalSetting); ++i; } } bool FolderSettings::isWriteConfig() const { return mWriteConfig; } void FolderSettings::setWriteConfig(bool writeConfig) { mWriteConfig = writeConfig; } QString FolderSettings::name() const { return mCollection.name(); } bool FolderSettings::isSystemFolder() const { return Kernel::self()->isSystemFolderCollection(mCollection); } bool FolderSettings::isStructural() const { return mCollection.contentMimeTypes().isEmpty(); } bool FolderSettings::isReadOnly() const { return mCollection.rights() & Akonadi::Collection::ReadOnly; } bool FolderSettings::canDeleteMessages() const { return mCollection.rights() & Akonadi::Collection::CanDeleteItem; } bool FolderSettings::canCreateMessages() const { return mCollection.rights() & Akonadi::Collection::CanCreateItem; } qint64 FolderSettings::count() const { return mCollection.statistics().count(); } Akonadi::Collection::Rights FolderSettings::rights() const { return mCollection.rights(); } Akonadi::CollectionStatistics FolderSettings::statistics() const { return mCollection.statistics(); } void FolderSettings::setCollection(const Akonadi::Collection &collection) { mCollection = collection; } void FolderSettings::slotIdentitiesChanged() { uint defaultIdentity = KernelIf->identityManager()->defaultIdentity().uoid(); // The default identity may have changed, therefore set it again if necessary if (mUseDefaultIdentity) { mIdentity = defaultIdentity; } // Fall back to the default identity if the one used currently is invalid if (KernelIf->identityManager()->identityForUoid(mIdentity).isNull()) { mIdentity = defaultIdentity; mUseDefaultIdentity = true; } } QString FolderSettings::configGroupName(const Akonadi::Collection &col) { return QStringLiteral("Folder-%1").arg(QString::number(col.id())); } void FolderSettings::readConfig() { KConfigGroup configGroup(KernelIf->config(), configGroupName(mCollection)); mMailingListEnabled = configGroup.readEntry("MailingListEnabled", false); mMailingList.readConfig(configGroup); mUseDefaultIdentity = configGroup.readEntry("UseDefaultIdentity", true); uint defaultIdentity = KernelIf->identityManager()->defaultIdentity().uoid(); mIdentity = configGroup.readEntry("Identity", defaultIdentity); slotIdentitiesChanged(); mPutRepliesInSameFolder = configGroup.readEntry("PutRepliesInSameFolder", false); mHideInSelectionDialog = configGroup.readEntry("HideInSelectionDialog", false); if (configGroup.hasKey(QStringLiteral("IgnoreNewMail"))) { if (configGroup.readEntry(QStringLiteral("IgnoreNewMail"), false)) { //migrate config. Akonadi::NewMailNotifierAttribute *newMailNotifierAttr = mCollection.attribute(Akonadi::Collection::AddIfMissing); newMailNotifierAttr->setIgnoreNewMail(true); new Akonadi::CollectionModifyJob(mCollection, this); //TODO verify if it works; } configGroup.deleteEntry("IgnoreNewMail"); } const QString shortcut(configGroup.readEntry("Shortcut")); if (!shortcut.isEmpty()) { QKeySequence sc(shortcut); setShortcut(sc); } mFormatMessage = static_cast(configGroup.readEntry("displayFormatOverride", static_cast(MessageViewer::Viewer::UseGlobalSetting))); } bool FolderSettings::isValid() const { return mCollection.isValid(); } void FolderSettings::writeConfig() const { const QString res = resource(); KConfigGroup configGroup(KernelIf->config(), configGroupName(mCollection)); configGroup.writeEntry("MailingListEnabled", mMailingListEnabled); mMailingList.writeConfig(configGroup); configGroup.writeEntry("UseDefaultIdentity", mUseDefaultIdentity); if (!mUseDefaultIdentity) { uint defaultIdentityId = -1; if (PimCommon::Util::isImapResource(res)) { MailCommon::ResourceReadConfigFile resourceFile(res); KConfigGroup grp = resourceFile.group(QStringLiteral("cache")); if (grp.isValid()) { defaultIdentityId = grp.readEntry(QStringLiteral("AccountIdentity"), -1); } } else { defaultIdentityId = KernelIf->identityManager()->defaultIdentity().uoid(); } if (mIdentity != defaultIdentityId) { configGroup.writeEntry("Identity", mIdentity); } else { configGroup.deleteEntry("Identity"); } } else { configGroup.deleteEntry("Identity"); } configGroup.writeEntry("PutRepliesInSameFolder", mPutRepliesInSameFolder); if (mHideInSelectionDialog) { configGroup.writeEntry("HideInSelectionDialog", mHideInSelectionDialog); } else { configGroup.deleteEntry("HideInSelectionDialog"); } if (!mShortcut.isEmpty()) { configGroup.writeEntry("Shortcut", mShortcut.toString()); } else { configGroup.deleteEntry("Shortcut"); } if (mFormatMessage != MessageViewer::Viewer::Unknown) { if (mFormatMessage == MessageViewer::Viewer::UseGlobalSetting) { configGroup.deleteEntry("displayFormatOverride"); } else { configGroup.writeEntry("displayFormatOverride", static_cast(mFormatMessage)); } } } void FolderSettings::setShortcut(const QKeySequence &sc) { if (mShortcut != sc) { mShortcut = sc; } } const QKeySequence &FolderSettings::shortcut() const { return mShortcut; } void FolderSettings::setUseDefaultIdentity(bool useDefaultIdentity) { if (mUseDefaultIdentity != useDefaultIdentity) { mUseDefaultIdentity = useDefaultIdentity; if (mUseDefaultIdentity) { mIdentity = KernelIf->identityManager()->defaultIdentity().uoid(); } KernelIf->syncConfig(); } } bool FolderSettings::useDefaultIdentity() const { return mUseDefaultIdentity; } void FolderSettings::setIdentity(uint identity) { if (mIdentity != identity) { mIdentity = identity; KernelIf->syncConfig(); } } QString FolderSettings::resource() const { const QString resource = mCollection.resource(); if (resource.isEmpty()) { const Collection col = CommonKernel->collectionFromId(mCollection.id()); Q_ASSERT(col.isValid()); Q_ASSERT(!col.resource().isEmpty()); return col.resource(); } return resource; } uint FolderSettings::fallBackIdentity() const { int identityId = -1; MailCommon::ResourceReadConfigFile resourceFile(resource()); KConfigGroup grp = resourceFile.group(QStringLiteral("cache")); if (grp.isValid()) { const bool useDefault = grp.readEntry(QStringLiteral("UseDefaultIdentity"), true); if (useDefault) { return mIdentity; } const int remoteAccountIdent = grp.readEntry(QStringLiteral("AccountIdentity"), -1); if (remoteAccountIdent > 0) { identityId = remoteAccountIdent; } } if (identityId != -1 && !KernelIf->identityManager()->identityForUoid(identityId).isNull()) { return identityId; } return mIdentity; } uint FolderSettings::identity() const { if (mUseDefaultIdentity) { return fallBackIdentity(); } return mIdentity; } QString FolderSettings::mailingListPostAddress() const { if (mMailingList.features() & MailingList::Post) { QList post = mMailingList.postUrls(); QList::const_iterator end(post.constEnd()); for (QList::const_iterator it = post.constBegin(); it != end; ++it) { // We check for isEmpty because before 3.3 postAddress was just an // email@kde.org and that leaves protocol() field in the qurl class const QString protocol = (*it).scheme(); if (protocol == QLatin1String("mailto") || protocol.isEmpty()) { return (*it).path(); } } } return QString(); } void FolderSettings::setMailingListEnabled(bool enabled) { if (mMailingListEnabled != enabled) { mMailingListEnabled = enabled; writeConfig(); } } bool FolderSettings::isMailingListEnabled() const { return mMailingListEnabled; } void FolderSettings::setMailingList(const MailingList &mlist) { if (mMailingList == mlist) { return; } mMailingList = mlist; writeConfig(); } MessageCore::MailingList FolderSettings::mailingList() const { return mMailingList; } bool FolderSettings::putRepliesInSameFolder() const { return mPutRepliesInSameFolder; } void FolderSettings::setPutRepliesInSameFolder(bool b) { mPutRepliesInSameFolder = b; } bool FolderSettings::hideInSelectionDialog() const { return mHideInSelectionDialog; } void FolderSettings::setHideInSelectionDialog(bool hide) { mHideInSelectionDialog = hide; } } diff --git a/src/folder/foldersettings.h b/src/folder/foldersettings.h index eea122b..fcdbe0a 100644 --- a/src/folder/foldersettings.h +++ b/src/folder/foldersettings.h @@ -1,148 +1,149 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_FOLDERSETTINGS_H #define MAILCOMMON_FOLDERSETTINGS_H #include "mailcommon_export.h" #include #include using MessageCore::MailingList; #include #include #include #include #include namespace MailCommon { class MAILCOMMON_EXPORT FolderSettings : public QObject { Q_OBJECT public: static QSharedPointer forCollection( const Akonadi::Collection &coll, bool writeConfig = true); ~FolderSettings(); void setCollection(const Akonadi::Collection &collection); static QString configGroupName(const Akonadi::Collection &col); static void clearCache(); static void resetHtmlFormat(); Q_REQUIRED_RESULT bool isWriteConfig() const; void setWriteConfig(bool writeConfig); void writeConfig() const; void readConfig(); Q_REQUIRED_RESULT QString name() const; Q_REQUIRED_RESULT bool isReadOnly() const; Q_REQUIRED_RESULT bool isStructural() const; Q_REQUIRED_RESULT bool isSystemFolder() const; Q_REQUIRED_RESULT qint64 count() const; Q_REQUIRED_RESULT bool canDeleteMessages() const; Q_REQUIRED_RESULT bool canCreateMessages() const; Q_REQUIRED_RESULT bool isValid() const; Q_REQUIRED_RESULT Akonadi::Collection::Rights rights() const; Q_REQUIRED_RESULT Akonadi::CollectionStatistics statistics() const; void setShortcut(const QKeySequence &); const QKeySequence &shortcut() const; /** * Get / set whether the default identity should be used instead of the * identity specified by setIdentity(). */ void setUseDefaultIdentity(bool useDefaultIdentity); Q_REQUIRED_RESULT bool useDefaultIdentity() const; void setIdentity(uint identity); Q_REQUIRED_RESULT uint identity() const; /** * Returns true if this folder is associated with a mailing-list. */ void setMailingListEnabled(bool enabled); Q_REQUIRED_RESULT bool isMailingListEnabled() const; void setMailingList(const MailingList &mlist); MailingList mailingList() const; /** * Returns true if the replies to mails from this folder should be * put in the same folder. */ Q_REQUIRED_RESULT bool putRepliesInSameFolder() const; void setPutRepliesInSameFolder(bool b); /** * Returns true if this folder should be hidden from all folder selection dialogs */ Q_REQUIRED_RESULT bool hideInSelectionDialog() const; void setHideInSelectionDialog(bool hide); Q_REQUIRED_RESULT QString mailingListPostAddress() const; Q_REQUIRED_RESULT uint fallBackIdentity() const; Q_REQUIRED_RESULT MessageViewer::Viewer::DisplayFormatMessage formatMessage() const; void setFormatMessage(MessageViewer::Viewer::DisplayFormatMessage formatMessage); protected Q_SLOTS: void slotIdentitiesChanged(); private: explicit FolderSettings(const Akonadi::Collection &col, bool writeconfig); QString resource() const; Akonadi::Collection mCollection; /** Mailing list attributes */ bool mMailingListEnabled; MailingList mMailingList; bool mUseDefaultIdentity; uint mIdentity; MessageViewer::Viewer::DisplayFormatMessage mFormatMessage; /** Should replies to messages in this folder be put in here? */ bool mPutRepliesInSameFolder; /** Should this folder be hidden in the folder selection dialog? */ bool mHideInSelectionDialog; /** shortcut associated with this folder or null, if none is configured. */ QKeySequence mShortcut; bool mWriteConfig; }; } #endif diff --git a/src/folder/foldertreeview.cpp b/src/folder/foldertreeview.cpp index ba418d4..4478dbd 100644 --- a/src/folder/foldertreeview.cpp +++ b/src/folder/foldertreeview.cpp @@ -1,712 +1,713 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "foldertreeview.h" #include "util/mailutil_p.h" #include "kernel/mailkernel.h" #include #include #include #include #include #include #include #include #include #include using namespace MailCommon; FolderTreeView::FolderTreeView(QWidget *parent, bool showUnreadCount) : Akonadi::EntityTreeView(parent) , mbDisableContextMenuAndExtraColumn(false) , mbDisableSaveConfig(false) { init(showUnreadCount); } FolderTreeView::FolderTreeView(KXMLGUIClient *xmlGuiClient, QWidget *parent, bool showUnreadCount) : Akonadi::EntityTreeView(xmlGuiClient, parent) , mbDisableContextMenuAndExtraColumn(false) , mbDisableSaveConfig(false) { init(showUnreadCount); } FolderTreeView::~FolderTreeView() { } void FolderTreeView::disableSaveConfig() { mbDisableSaveConfig = true; } void FolderTreeView::setTooltipsPolicy(FolderTreeWidget::ToolTipDisplayPolicy policy) { if (mToolTipDisplayPolicy == policy) { return; } mToolTipDisplayPolicy = policy; Q_EMIT changeTooltipsPolicy(mToolTipDisplayPolicy); writeConfig(); } void FolderTreeView::disableContextMenuAndExtraColumn() { mbDisableContextMenuAndExtraColumn = true; const int nbColumn = header()->count(); for (int i = 1; i < nbColumn; ++i) { setColumnHidden(i, true); } } void FolderTreeView::init(bool showUnreadCount) { setIconSize(QSize(22, 22)); setUniformRowHeights(true); mSortingPolicy = FolderTreeWidget::SortByCurrentColumn; mToolTipDisplayPolicy = FolderTreeWidget::DisplayAlways; header()->setContextMenuPolicy(Qt::CustomContextMenu); connect(header(), &QWidget::customContextMenuRequested, this, &FolderTreeView::slotHeaderContextMenuRequested); mCollectionStatisticsDelegate = new Akonadi::CollectionStatisticsDelegate(this); mCollectionStatisticsDelegate->setProgressAnimationEnabled(true); setItemDelegate(mCollectionStatisticsDelegate); mCollectionStatisticsDelegate->setUnreadCountShown( showUnreadCount && !header()->isSectionHidden(1)); } void FolderTreeView::showStatisticAnimation(bool anim) { mCollectionStatisticsDelegate->setProgressAnimationEnabled(anim); } void FolderTreeView::writeConfig() { if (mbDisableSaveConfig) { return; } KConfigGroup myGroup(KernelIf->config(), "MainFolderView"); myGroup.writeEntry("IconSize", iconSize().width()); myGroup.writeEntry("ToolTipDisplayPolicy", (int)mToolTipDisplayPolicy); myGroup.writeEntry("SortingPolicy", (int)mSortingPolicy); } void FolderTreeView::readConfig() { KConfigGroup myGroup(KernelIf->config(), "MainFolderView"); int iIconSize = myGroup.readEntry("IconSize", iconSize().width()); if (iIconSize < 16 || iIconSize > 32) { iIconSize = 22; } setIconSize(QSize(iIconSize, iIconSize)); mToolTipDisplayPolicy = static_cast( myGroup.readEntry("ToolTipDisplayPolicy", static_cast(FolderTreeWidget::DisplayAlways))); Q_EMIT changeTooltipsPolicy(mToolTipDisplayPolicy); setSortingPolicy( (FolderTreeWidget::SortingPolicy)myGroup.readEntry( "SortingPolicy", (int)FolderTreeWidget::SortByCurrentColumn), false); } void FolderTreeView::slotHeaderContextMenuRequested(const QPoint &pnt) { if (mbDisableContextMenuAndExtraColumn) { readConfig(); return; } // the menu for the columns QMenu menu; QAction *act = nullptr; const int nbColumn = header()->count(); if (nbColumn > 1) { menu.addSection(i18n("View Columns")); for (int i = 1; i < nbColumn; ++i) { act = menu.addAction(model()->headerData(i, Qt::Horizontal).toString()); act->setCheckable(true); act->setChecked(!header()->isSectionHidden(i)); act->setData(QVariant(i)); connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeHeader); } } menu.addSection(i18n("Icon Size")); static const int icon_sizes[] = { 16, 22, 32}; QActionGroup *grp = new QActionGroup(&menu); const int nbElement((int)(sizeof(icon_sizes) / sizeof(int))); for (int i = 0; i < nbElement; ++i) { act = menu.addAction(QStringLiteral("%1x%2").arg(icon_sizes[ i ]).arg(icon_sizes[ i ])); act->setCheckable(true); grp->addAction(act); if (iconSize().width() == icon_sizes[ i ]) { act->setChecked(true); } act->setData(QVariant(icon_sizes[ i ])); connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeIconSize); } menu.addSection(i18n("Display Tooltips")); grp = new QActionGroup(&menu); act = menu.addAction(i18nc("@action:inmenu Always display tooltips", "Always")); act->setCheckable(true); grp->addAction(act); act->setChecked(mToolTipDisplayPolicy == FolderTreeWidget::DisplayAlways); act->setData(QVariant((int)FolderTreeWidget::DisplayAlways)); connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeToolTipDisplayPolicy); act = menu.addAction(i18nc("@action:inmenu Never display tooltips.", "Never")); act->setCheckable(true); grp->addAction(act); act->setChecked(mToolTipDisplayPolicy == FolderTreeWidget::DisplayNever); act->setData(QVariant((int)FolderTreeWidget::DisplayNever)); connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeToolTipDisplayPolicy); menu.addSection(i18nc("@action:inmenu", "Sort Items")); grp = new QActionGroup(&menu); act = menu.addAction(i18nc("@action:inmenu", "Automatically, by Current Column")); act->setCheckable(true); grp->addAction(act); act->setChecked(mSortingPolicy == FolderTreeWidget::SortByCurrentColumn); act->setData(QVariant((int)FolderTreeWidget::SortByCurrentColumn)); connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeSortingPolicy); act = menu.addAction(i18nc("@action:inmenu", "Manually, by Drag And Drop")); act->setCheckable(true); grp->addAction(act); act->setChecked(mSortingPolicy == FolderTreeWidget::SortByDragAndDropKey); act->setData(QVariant((int)FolderTreeWidget::SortByDragAndDropKey)); connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeSortingPolicy); menu.exec(header()->mapToGlobal(pnt)); } void FolderTreeView::slotHeaderContextMenuChangeSortingPolicy(bool) { QAction *act = qobject_cast< QAction * >(sender()); if (!act) { return; } QVariant data = act->data(); bool ok; int policy = data.toInt(&ok); if (!ok) { return; } setSortingPolicy((FolderTreeWidget::SortingPolicy)policy, true); } void FolderTreeView::setSortingPolicy(FolderTreeWidget::SortingPolicy policy, bool writeInConfig) { if (mSortingPolicy == policy) { return; } mSortingPolicy = policy; switch (mSortingPolicy) { case FolderTreeWidget::SortByCurrentColumn: header()->setSectionsClickable(true); header()->setSortIndicatorShown(true); setSortingEnabled(true); Q_EMIT manualSortingChanged(false); break; case FolderTreeWidget::SortByDragAndDropKey: header()->setSectionsClickable(false); header()->setSortIndicatorShown(false); setSortingEnabled(false); // hack for qutie bug: this call shouldn't be here at all Q_EMIT manualSortingChanged(true); break; default: // should never happen break; } if (writeInConfig) { writeConfig(); } } void FolderTreeView::slotHeaderContextMenuChangeToolTipDisplayPolicy(bool) { QAction *act = qobject_cast< QAction * >(sender()); if (!act) { return; } QVariant data = act->data(); bool ok; const int id = data.toInt(&ok); if (!ok) { return; } Q_EMIT changeTooltipsPolicy((FolderTreeWidget::ToolTipDisplayPolicy)id); } void FolderTreeView::slotHeaderContextMenuChangeHeader(bool) { QAction *act = qobject_cast< QAction * >(sender()); if (!act) { return; } QVariant data = act->data(); bool ok; const int id = data.toInt(&ok); if (!ok) { return; } if (id > header()->count()) { return; } if (id == 1) { mCollectionStatisticsDelegate->setUnreadCountShown(!act->isChecked()); } setColumnHidden(id, !act->isChecked()); } void FolderTreeView::slotHeaderContextMenuChangeIconSize(bool) { QAction *act = qobject_cast< QAction * >(sender()); if (!act) { return; } QVariant data = act->data(); bool ok; const int size = data.toInt(&ok); if (!ok) { return; } const QSize newIconSize(QSize(size, size)); if (newIconSize == iconSize()) { return; } setIconSize(newIconSize); writeConfig(); } void FolderTreeView::setCurrentModelIndex(const QModelIndex &index) { if (index.isValid()) { clearSelection(); scrollTo(index); selectionModel()->setCurrentIndex(index, QItemSelectionModel::Rows); } } void FolderTreeView::selectModelIndex(const QModelIndex &index) { if (index.isValid()) { scrollTo(index); selectionModel()->select( index, QItemSelectionModel::Rows | QItemSelectionModel::Select |QItemSelectionModel::Current | QItemSelectionModel::Clear); } } void FolderTreeView::slotSelectFocusFolder() { const QModelIndex index = currentIndex(); if (index.isValid()) { setCurrentIndex(index); } } void FolderTreeView::slotFocusNextFolder() { const QModelIndex nextFolder = selectNextFolder(currentIndex()); if (nextFolder.isValid()) { expand(nextFolder); setCurrentModelIndex(nextFolder); } } QModelIndex FolderTreeView::selectNextFolder(const QModelIndex ¤t) { QModelIndex below; if (current.isValid()) { model()->fetchMore(current); if (model()->hasChildren(current)) { expand(current); below = indexBelow(current); } else if (current.row() < model()->rowCount(model()->parent(current)) - 1) { below = model()->index(current.row() + 1, current.column(), model()->parent(current)); } else { below = indexBelow(current); } } return below; } void FolderTreeView::slotFocusPrevFolder() { const QModelIndex current = currentIndex(); if (current.isValid()) { QModelIndex above = indexAbove(current); setCurrentModelIndex(above); } } void FolderTreeView::slotFocusFirstFolder() { const QModelIndex first = moveCursor(QAbstractItemView::MoveHome, nullptr); if (first.isValid()) { setCurrentModelIndex(first); } } void FolderTreeView::slotFocusLastFolder() { const QModelIndex last = moveCursor(QAbstractItemView::MoveEnd, nullptr); if (last.isValid()) { setCurrentModelIndex(last); } } void FolderTreeView::selectNextUnreadFolder(bool confirm) { // find next unread collection starting from current position if (!trySelectNextUnreadFolder(currentIndex(), ForwardSearch, confirm)) { // if there is none, jump to the last collection and try again trySelectNextUnreadFolder(model()->index(0, 0), ForwardSearch, confirm); } } // helper method to find last item in the model tree static QModelIndex lastChildOf(QAbstractItemModel *model, const QModelIndex ¤t) { if (model->rowCount(current) == 0) { return current; } return lastChildOf(model, model->index(model->rowCount(current) - 1, 0, current)); } void FolderTreeView::selectPrevUnreadFolder(bool confirm) { // find next unread collection starting from current position if (!trySelectNextUnreadFolder(currentIndex(), BackwardSearch, confirm)) { // if there is none, jump to top and try again const QModelIndex index = lastChildOf(model(), QModelIndex()); trySelectNextUnreadFolder(index, BackwardSearch, confirm); } } bool FolderTreeView::trySelectNextUnreadFolder(const QModelIndex ¤t, SearchDirection direction, bool confirm) { QModelIndex index = current; while (true) { index = nextUnreadCollection(index, direction); if (!index.isValid()) { return false; } const Akonadi::Collection collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value(); if (collection == Kernel::self()->trashCollectionFolder() || collection == Kernel::self()->outboxCollectionFolder()) { continue; } if (ignoreUnreadFolder(collection, confirm)) { continue; } if (allowedToEnterFolder(collection, confirm)) { expand(index); setCurrentIndex(index); selectModelIndex(index); return true; } else { return false; } } return false; } bool FolderTreeView::ignoreUnreadFolder(const Akonadi::Collection &collection, bool confirm) const { if (!confirm) { return false; } // Skip drafts, sent mail and templates as well, when reading mail with the // space bar - but not when changing into the next folder with unread mail // via ctrl+ or ctrl- so we do this only if (confirm == true), which means // we are doing readOn. return collection == Kernel::self()->draftsCollectionFolder() || collection == Kernel::self()->templatesCollectionFolder() || collection == Kernel::self()->sentCollectionFolder(); } bool FolderTreeView::allowedToEnterFolder(const Akonadi::Collection &collection, bool confirm) const { if (!confirm) { return true; } // warn user that going to next folder - but keep track of // whether he wishes to be notified again in "AskNextFolder" // parameter (kept in the config file for kmail) const int result = KMessageBox::questionYesNo( const_cast(this), i18n("Go to the next unread message in folder %1?", collection.name()), i18n("Go to Next Unread Message"), KGuiItem(i18n("Go To")), KGuiItem(i18n("Do Not Go To")), // defaults QStringLiteral(":kmail_AskNextFolder"), nullptr); return result == KMessageBox::Yes; } bool FolderTreeView::isUnreadFolder(const QModelIndex ¤t, QModelIndex &index, FolderTreeView::Move move, bool confirm) { if (current.isValid()) { if (move == FolderTreeView::Next) { index = selectNextFolder(current); } else if (move == FolderTreeView::Previous) { index = indexAbove(current); } if (index.isValid()) { const Akonadi::Collection collection = index.model()->data( current, Akonadi::EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { if (collection.statistics().unreadCount() > 0) { if (!confirm) { selectModelIndex(current); return true; } else { // Skip drafts, sent mail and templates as well, when reading mail with the // space bar - but not when changing into the next folder with unread mail // via ctrl+ or ctrl- so we do this only if (confirm == true), which means // we are doing readOn. if (collection == Kernel::self()->draftsCollectionFolder() || collection == Kernel::self()->templatesCollectionFolder() || collection == Kernel::self()->sentCollectionFolder()) { return false; } // warn user that going to next folder - but keep track of // whether he wishes to be notified again in "AskNextFolder" // parameter (kept in the config file for kmail) if (KMessageBox::questionYesNo( this, i18n("Go to the next unread message in folder %1?", collection.name()), i18n("Go to Next Unread Message"), KGuiItem(i18n("Go To")), KGuiItem(i18n("Do Not Go To")), // defaults QStringLiteral(":kmail_AskNextFolder"), nullptr) == KMessageBox::No) { return true; // assume selected (do not continue looping) } selectModelIndex(current); return true; } } } } } return false; } Akonadi::Collection FolderTreeView::currentFolder() const { const QModelIndex current = currentIndex(); if (current.isValid()) { const Akonadi::Collection collection = current.model()->data( current, Akonadi::EntityTreeModel::CollectionRole).value(); return collection; } return Akonadi::Collection(); } void FolderTreeView::mousePressEvent(QMouseEvent *e) { const bool buttonPressedIsMiddle = (e->button() == Qt::MidButton); Q_EMIT newTabRequested(buttonPressedIsMiddle); EntityTreeView::mousePressEvent(e); } void FolderTreeView::restoreHeaderState(const QByteArray &data) { if (data.isEmpty()) { const int nbColumn = header()->count(); for (int i = 1; i < nbColumn; ++i) { setColumnHidden(i, true); } } else { header()->restoreState(data); } mCollectionStatisticsDelegate->setUnreadCountShown(header()->isSectionHidden(1)); } void FolderTreeView::updatePalette() { mCollectionStatisticsDelegate->updatePalette(); } void FolderTreeView::keyboardSearch(const QString &) { // Disable keyboardSearch: it interfers with filtering in the // FolderSelectionDialog. We don't want it in KMail main window // either because KMail has one-letter keyboard shortcuts. } QModelIndex FolderTreeView::indexBelow(const QModelIndex ¤t) const { // if we have children, return first child if (model()->rowCount(current) > 0) { return model()->index(0, 0, current); } // if we have siblings, return next sibling const QModelIndex parent = model()->parent(current); const QModelIndex sibling = model()->index(current.row() + 1, 0, parent); if (sibling.isValid()) { // found valid sibling return sibling; } if (!parent.isValid()) { // our parent is the tree root and we have no siblings return QModelIndex(); // we reached the bottom of the tree } // We are the last child, the next index to check is our uncle, parent's first sibling const QModelIndex parentsSibling = parent.sibling(parent.row() + 1, 0); if (parentsSibling.isValid()) { return parentsSibling; } // iterate over our parents back to root until we find a parent with a valid sibling QModelIndex currentParent = parent; QModelIndex grandParent = model()->parent(currentParent); while (currentParent.isValid()) { // check if the parent has children except from us if (model()->rowCount(grandParent) > currentParent.row() + 1) { const auto index = indexBelow(model()->index(currentParent.row() + 1, 0, grandParent)); if (index.isValid()) { return index; } } currentParent = grandParent; grandParent = model()->parent(currentParent); } return QModelIndex(); // nothing found -> end of tree } QModelIndex FolderTreeView::lastChild(const QModelIndex ¤t) const { if (model()->rowCount(current) == 0) { return current; } return lastChild(model()->index(model()->rowCount(current) - 1, 0, current)); } QModelIndex FolderTreeView::indexAbove(const QModelIndex ¤t) const { const QModelIndex parent = model()->parent(current); if (current.row() == 0) { // we have no previous siblings -> our parent is the next item above us return parent; } // find previous sibling const QModelIndex previousSibling = model()->index(current.row() - 1, 0, parent); // the item above us is the last child (or grandchild, or grandgrandchild... etc) // of our previous sibling return lastChild(previousSibling); } QModelIndex FolderTreeView::nextUnreadCollection(const QModelIndex ¤t, SearchDirection direction) const { QModelIndex index = current; while (true) { if (direction == ForwardSearch) { index = indexBelow(index); } else if (direction == BackwardSearch) { index = indexAbove(index); } if (!index.isValid()) { // reach end or top of the model return QModelIndex(); } // check if the index is a collection const auto collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { // check if it is unread if (collection.statistics().unreadCount() > 0) { if (!MailCommon::Util::ignoreNewMailInFolder(collection)) { return index; // we found the next unread collection } } } } return QModelIndex(); // no unread collection found } diff --git a/src/folder/foldertreeview.h b/src/folder/foldertreeview.h index b993119..b15433a 100644 --- a/src/folder/foldertreeview.h +++ b/src/folder/foldertreeview.h @@ -1,129 +1,130 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_FOLDERTREEVIEW_H #define MAILCOMMON_FOLDERTREEVIEW_H #include "mailcommon_export.h" #include "foldertreewidget.h" #include "mailcommon/mailutil.h" #include #include class QMouseEvent; namespace Akonadi { class CollectionStatisticsDelegate; } namespace MailCommon { /** * This is an enhanced EntityTreeView specially suited for the folders in KMail's * main folder widget. */ class MAILCOMMON_EXPORT FolderTreeView : public Akonadi::EntityTreeView { Q_OBJECT public: explicit FolderTreeView(QWidget *parent = nullptr, bool showUnreadCount = true); explicit FolderTreeView(KXMLGUIClient *xmlGuiClient, QWidget *parent = nullptr, bool showUnreadCount = true); ~FolderTreeView() override; void selectNextUnreadFolder(bool confirm = false); void selectPrevUnreadFolder(bool confirm = false); void showStatisticAnimation(bool anim); void disableContextMenuAndExtraColumn(); void setTooltipsPolicy(FolderTreeWidget::ToolTipDisplayPolicy); void restoreHeaderState(const QByteArray &data); Q_REQUIRED_RESULT Akonadi::Collection currentFolder() const; void disableSaveConfig(); void readConfig(); void updatePalette(); void keyboardSearch(const QString &) override; protected: enum Move { Next = 0, Previous = 1 }; void init(bool showUnreadCount); void selectModelIndex(const QModelIndex &); void setCurrentModelIndex(const QModelIndex &); QModelIndex selectNextFolder(const QModelIndex ¤t); bool isUnreadFolder(const QModelIndex ¤t, QModelIndex &nextIndex, FolderTreeView::Move move, bool confirm); void writeConfig(); void setSortingPolicy(FolderTreeWidget::SortingPolicy policy, bool writeInConfig = false); void mousePressEvent(QMouseEvent *e) override; public Q_SLOTS: void slotFocusNextFolder(); void slotFocusPrevFolder(); void slotSelectFocusFolder(); void slotFocusFirstFolder(); void slotFocusLastFolder(); protected Q_SLOTS: void slotHeaderContextMenuRequested(const QPoint &); void slotHeaderContextMenuChangeIconSize(bool); void slotHeaderContextMenuChangeHeader(bool); void slotHeaderContextMenuChangeToolTipDisplayPolicy(bool); void slotHeaderContextMenuChangeSortingPolicy(bool); Q_SIGNALS: void changeTooltipsPolicy(FolderTreeWidget::ToolTipDisplayPolicy); void manualSortingChanged(bool actif); void newTabRequested(bool); private: enum SearchDirection { ForwardSearch, BackwardSearch }; QModelIndex indexAbove(const QModelIndex ¤t) const; QModelIndex indexBelow(const QModelIndex ¤t) const; QModelIndex lastChild(const QModelIndex ¤t) const; QModelIndex nextUnreadCollection(const QModelIndex ¤t, SearchDirection direction) const; bool ignoreUnreadFolder(const Akonadi::Collection &, bool) const; bool allowedToEnterFolder(const Akonadi::Collection &, bool) const; bool trySelectNextUnreadFolder(const QModelIndex &, SearchDirection, bool); FolderTreeWidget::ToolTipDisplayPolicy mToolTipDisplayPolicy; FolderTreeWidget::SortingPolicy mSortingPolicy; Akonadi::CollectionStatisticsDelegate *mCollectionStatisticsDelegate = nullptr; bool mbDisableContextMenuAndExtraColumn = false; bool mbDisableSaveConfig = false; }; } #endif diff --git a/src/folder/foldertreewidget.cpp b/src/folder/foldertreewidget.cpp index 921cb5c..1395636 100644 --- a/src/folder/foldertreewidget.cpp +++ b/src/folder/foldertreewidget.cpp @@ -1,433 +1,434 @@ /* Copyright (c) 2009-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "foldertreewidget.h" #include "entitycollectionorderproxymodel.h" #include "foldertreeview.h" #include "foldertreewidgetproxymodel.h" #include "kernel/mailkernel.h" #include "util/mailutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace MailCommon { class Q_DECL_HIDDEN FolderTreeWidget::FolderTreeWidgetPrivate { public: FolderTreeWidgetPrivate() : filterModel(nullptr) , folderTreeView(nullptr) , quotaModel(nullptr) , readableproxy(nullptr) , entityOrderProxy(nullptr) , filterFolderLineEdit(nullptr) , saver(nullptr) , label(nullptr) , dontKeyFilter(false) { } QString filter; QString oldFilterStr; Akonadi::StatisticsProxyModel *filterModel = nullptr; FolderTreeView *folderTreeView = nullptr; Akonadi::QuotaColorProxyModel *quotaModel = nullptr; FolderTreeWidgetProxyModel *readableproxy = nullptr; EntityCollectionOrderProxyModel *entityOrderProxy = nullptr; QLineEdit *filterFolderLineEdit = nullptr; QPointer saver; QStringList expandedItems; QString currentItem; QLabel *label = nullptr; bool dontKeyFilter = false; }; FolderTreeWidget::FolderTreeWidget( QWidget *parent, KXMLGUIClient *xmlGuiClient, FolderTreeWidget::TreeViewOptions options, FolderTreeWidgetProxyModel::FolderTreeWidgetProxyModelOptions optReadableProxy) : QWidget(parent) , d(new FolderTreeWidgetPrivate()) { Akonadi::AttributeFactory::registerAttribute(); d->folderTreeView = new FolderTreeView(xmlGuiClient, this, options & ShowUnreadCount); d->folderTreeView->showStatisticAnimation(options & ShowCollectionStatisticAnimation); connect(d->folderTreeView, &FolderTreeView::manualSortingChanged, this, &FolderTreeWidget::slotManualSortingChanged); QVBoxLayout *lay = new QVBoxLayout(this); lay->setMargin(0); d->label = new QLabel(i18n("You can start typing to filter the list of folders."), this); lay->addWidget(d->label); d->filterFolderLineEdit = new QLineEdit(this); d->filterFolderLineEdit->setClearButtonEnabled(true); d->filterFolderLineEdit->setPlaceholderText( i18nc("@info Displayed grayed-out inside the textbox, verb to search", "Search")); lay->addWidget(d->filterFolderLineEdit); // ... with statistics... d->quotaModel = new Akonadi::QuotaColorProxyModel(this); d->quotaModel->setSourceModel(KernelIf->collectionModel()); if (!(options & HideStatistics)) { d->filterModel = new Akonadi::StatisticsProxyModel(this); d->filterModel->setSourceModel(d->quotaModel); } d->readableproxy = new FolderTreeWidgetProxyModel(this, optReadableProxy); d->readableproxy->setSourceModel((options & HideStatistics) ? static_cast(d->quotaModel) : static_cast(d->filterModel)); d->readableproxy->addContentMimeTypeInclusionFilter(KMime::Message::mimeType()); connect(d->folderTreeView, &FolderTreeView::changeTooltipsPolicy, this, &FolderTreeWidget::slotChangeTooltipsPolicy); d->folderTreeView->setSelectionMode(QAbstractItemView::SingleSelection); d->folderTreeView->setEditTriggers(QAbstractItemView::NoEditTriggers); d->folderTreeView->installEventFilter(this); //Order proxy d->entityOrderProxy = new EntityCollectionOrderProxyModel(this); d->entityOrderProxy->setSourceModel(d->readableproxy); d->entityOrderProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); KConfigGroup grp(KernelIf->config(), "CollectionTreeOrder"); d->entityOrderProxy->setOrderConfig(grp); d->folderTreeView->setModel(d->entityOrderProxy); if (options & UseDistinctSelectionModel) { d->folderTreeView->setSelectionModel(new QItemSelectionModel(d->entityOrderProxy, this)); } lay->addWidget(d->folderTreeView); d->dontKeyFilter = (options & DontKeyFilter); if ((options & UseLineEditForFiltering)) { connect(d->filterFolderLineEdit, &QLineEdit::textChanged, this, &FolderTreeWidget::slotFilterFixedString); d->label->hide(); } else { d->filterFolderLineEdit->hide(); } } FolderTreeWidget::~FolderTreeWidget() { delete d; } void FolderTreeWidget::slotFilterFixedString(const QString &text) { delete d->saver; if (d->oldFilterStr.isEmpty()) { //Save it. Akonadi::ETMViewStateSaver saver; saver.setView(folderTreeView()); d->expandedItems = saver.expansionKeys(); d->currentItem = saver.currentIndexKey(); } else if (text.isEmpty()) { d->saver = new Akonadi::ETMViewStateSaver; d->saver->setView(folderTreeView()); QString currentIndex = d->saver->currentIndexKey(); if (d->saver->selectionKeys().isEmpty()) { currentIndex = d->currentItem; } else if (!currentIndex.isEmpty()) { d->expandedItems << currentIndex; } d->saver->restoreExpanded(d->expandedItems); d->saver->restoreCurrentItem(currentIndex); } else { d->folderTreeView->expandAll(); } d->oldFilterStr = text; d->entityOrderProxy->setFilterWildcard(text); } void FolderTreeWidget::disableContextMenuAndExtraColumn() { d->folderTreeView->disableContextMenuAndExtraColumn(); } void FolderTreeWidget::selectCollectionFolder(const Akonadi::Collection &collection) { const QModelIndex index = Akonadi::EntityTreeModel::modelIndexForCollection(d->folderTreeView->model(), collection); d->folderTreeView->setCurrentIndex(index); d->folderTreeView->setExpanded(index, true); d->folderTreeView->scrollTo(index); } void FolderTreeWidget::setSelectionMode(QAbstractItemView::SelectionMode mode) { d->folderTreeView->setSelectionMode(mode); } QAbstractItemView::SelectionMode FolderTreeWidget::selectionMode() const { return d->folderTreeView->selectionMode(); } QItemSelectionModel *FolderTreeWidget::selectionModel() const { return d->folderTreeView->selectionModel(); } QModelIndex FolderTreeWidget::currentIndex() const { return d->folderTreeView->currentIndex(); } Akonadi::Collection FolderTreeWidget::selectedCollection() const { if (d->folderTreeView->selectionMode() == QAbstractItemView::SingleSelection) { Akonadi::Collection::List lstCollection = selectedCollections(); if (lstCollection.isEmpty()) { return Akonadi::Collection(); } else { return lstCollection.at(0); } } return Akonadi::Collection(); } Akonadi::Collection::List FolderTreeWidget::selectedCollections() const { Akonadi::Collection::List collections; const QItemSelectionModel *selectionModel = d->folderTreeView->selectionModel(); const QModelIndexList selectedIndexes = selectionModel->selectedIndexes(); for (const QModelIndex &index : selectedIndexes) { if (index.isValid()) { const Akonadi::Collection collection = index.model()->data( index, Akonadi::EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { collections.append(collection); } } } return collections; } FolderTreeView *FolderTreeWidget::folderTreeView() const { return d->folderTreeView; } void FolderTreeWidget::slotGeneralFontChanged() { // Custom/System font support if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) { setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); } } void FolderTreeWidget::slotGeneralPaletteChanged() { d->readableproxy->updatePalette(); d->folderTreeView->updatePalette(); } void FolderTreeWidget::readConfig() { setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); d->folderTreeView->readConfig(); d->folderTreeView->setDropActionMenuEnabled(SettingsIf->showPopupAfterDnD()); d->readableproxy->readConfig(); KConfigGroup readerConfig(KernelIf->config(), "AccountOrder"); QStringList listOrder; if (readerConfig.readEntry("EnableAccountOrder", true)) { listOrder = readerConfig.readEntry("order", QStringList()); } d->entityOrderProxy->setTopLevelOrder(listOrder); readQuotaConfig(); } void FolderTreeWidget::restoreHeaderState(const QByteArray &data) { d->folderTreeView->restoreHeaderState(data); } void FolderTreeWidget::slotChangeTooltipsPolicy(FolderTreeWidget::ToolTipDisplayPolicy policy) { changeToolTipsPolicyConfig(policy); } void FolderTreeWidget::changeToolTipsPolicyConfig(ToolTipDisplayPolicy policy) { switch (policy) { case DisplayAlways: case DisplayWhenTextElided: //Need to implement in the future if (d->filterModel) { d->filterModel->setToolTipEnabled(true); } break; case DisplayNever: if (d->filterModel) { d->filterModel->setToolTipEnabled(false); } } d->folderTreeView->setTooltipsPolicy(policy); } void FolderTreeWidget::quotaWarningParameters(const QColor &color, qreal threshold) { d->quotaModel->setWarningThreshold(threshold); d->quotaModel->setWarningColor(color); } void FolderTreeWidget::readQuotaConfig() { QColor quotaColor = MailCommon::Util::defaultQuotaColor(); qreal threshold = 100; if (!MessageCore::MessageCoreSettings::self()->useDefaultColors()) { KConfigGroup readerConfig(KernelIf->config(), "Reader"); quotaColor = readerConfig.readEntry("CloseToQuotaColor", quotaColor); } threshold = SettingsIf->closeToQuotaThreshold(); quotaWarningParameters(quotaColor, threshold); } Akonadi::StatisticsProxyModel *FolderTreeWidget::statisticsProxyModel() const { return d->filterModel; } FolderTreeWidgetProxyModel *FolderTreeWidget::folderTreeWidgetProxyModel() const { return d->readableproxy; } EntityCollectionOrderProxyModel *FolderTreeWidget::entityOrderProxy() const { return d->entityOrderProxy; } QLineEdit *FolderTreeWidget::filterFolderLineEdit() const { return d->filterFolderLineEdit; } void FolderTreeWidget::applyFilter(const QString &filter) { d->label->setText( filter.isEmpty() ? i18n("You can start typing to filter the list of folders.") : i18n("Path: (%1)", filter)); d->entityOrderProxy->setFilterWildcard(filter); d->folderTreeView->expandAll(); QAbstractItemModel *model = d->folderTreeView->model(); QModelIndex current = d->folderTreeView->currentIndex(); QModelIndex start = current.isValid() ? current : model->index(0, 0); QModelIndexList list = model->match(start, Qt::DisplayRole, d->filter, 1 /* stop at first hit */, Qt::MatchContains | Qt::MatchWrap | Qt::MatchRecursive); if (!list.isEmpty()) { current = list.first(); d->folderTreeView->setCurrentIndex(current); d->folderTreeView->scrollTo(current); } } void FolderTreeWidget::clearFilter() { d->filter.clear(); applyFilter(d->filter); const QModelIndexList lst = d->folderTreeView->selectionModel()->selectedIndexes(); if (!lst.isEmpty()) { d->folderTreeView->scrollTo(lst.first()); } } void FolderTreeWidget::slotManualSortingChanged(bool active) { d->entityOrderProxy->setManualSortingActive(active); d->folderTreeView->setManualSortingActive(active); } bool FolderTreeWidget::eventFilter(QObject *o, QEvent *e) { Q_UNUSED(o); if (d->dontKeyFilter) { return false; } if (e->type() == QEvent::KeyPress) { const QKeyEvent *const ke = static_cast(e); switch (ke->key()) { case Qt::Key_Backspace: { const int filterLength(d->filter.length()); if (filterLength > 0) { d->filter.truncate(filterLength - 1); } applyFilter(d->filter); return false; } case Qt::Key_Delete: d->filter.clear(); applyFilter(d->filter); return false; default: { const QString s = ke->text(); if (!s.isEmpty() && s.at(0).isPrint()) { d->filter += s; applyFilter(d->filter); return false; } break; } } } return false; } } diff --git a/src/folder/foldertreewidget.h b/src/folder/foldertreewidget.h index a88783b..048bf46 100644 --- a/src/folder/foldertreewidget.h +++ b/src/folder/foldertreewidget.h @@ -1,140 +1,141 @@ /* Copyright (c) 2009-2018 Montel Laurent - The program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_FOLDERTREEWIDGET_H #define MAILCOMMON_FOLDERTREEWIDGET_H #include "mailcommon_export.h" #include "foldertreewidgetproxymodel.h" #include #include #include namespace Akonadi { class StatisticsProxyModel; } class QLineEdit; class KXMLGUIClient; class QItemSelectionModel; namespace MailCommon { class EntityCollectionOrderProxyModel; class FolderTreeView; /** * This is the widget that shows the main folder tree. * * It consists of the view (FolderTreeView) and a search line. * Internally, several proxy models are used on top of a entity tree model. */ class MAILCOMMON_EXPORT FolderTreeWidget : public QWidget { Q_OBJECT public: enum TreeViewOption { None = 0, ShowUnreadCount = 1, UseLineEditForFiltering = 2, UseDistinctSelectionModel = 4, ShowCollectionStatisticAnimation = 8, DontKeyFilter = 16, HideStatistics = 32 }; Q_DECLARE_FLAGS(TreeViewOptions, TreeViewOption) explicit FolderTreeWidget( QWidget *parent = nullptr, KXMLGUIClient *xmlGuiClient = nullptr, TreeViewOptions options = (TreeViewOptions)(ShowUnreadCount |ShowCollectionStatisticAnimation), FolderTreeWidgetProxyModel::FolderTreeWidgetProxyModelOptions optReadableProxy = FolderTreeWidgetProxyModel::None); ~FolderTreeWidget(); /** * The possible tooltip display policies. */ enum ToolTipDisplayPolicy { DisplayAlways, ///< Always display a tooltip when hovering over an item DisplayWhenTextElided, ///< Display the tooltip if the item text is actually elided DisplayNever ///< Nevery display tooltips }; /** * The available sorting policies. */ enum SortingPolicy { SortByCurrentColumn, ///< Columns are clickable, sorting is by the current column SortByDragAndDropKey ///< Columns are NOT clickable, sorting is done by drag and drop }; void selectCollectionFolder(const Akonadi::Collection &col); void setSelectionMode(QAbstractItemView::SelectionMode mode); Q_REQUIRED_RESULT QAbstractItemView::SelectionMode selectionMode() const; Q_REQUIRED_RESULT QItemSelectionModel *selectionModel() const; Q_REQUIRED_RESULT QModelIndex currentIndex() const; Q_REQUIRED_RESULT Akonadi::Collection selectedCollection() const; Q_REQUIRED_RESULT Akonadi::Collection::List selectedCollections() const; Q_REQUIRED_RESULT FolderTreeView *folderTreeView() const; Q_REQUIRED_RESULT Akonadi::StatisticsProxyModel *statisticsProxyModel() const; Q_REQUIRED_RESULT FolderTreeWidgetProxyModel *folderTreeWidgetProxyModel() const; Q_REQUIRED_RESULT EntityCollectionOrderProxyModel *entityOrderProxy() const; void quotaWarningParameters(const QColor &color, qreal threshold); void readQuotaConfig(); Q_REQUIRED_RESULT QLineEdit *filterFolderLineEdit() const; void applyFilter(const QString &); void clearFilter(); void disableContextMenuAndExtraColumn(); void readConfig(); void restoreHeaderState(const QByteArray &data); protected: void changeToolTipsPolicyConfig(ToolTipDisplayPolicy); protected Q_SLOTS: void slotChangeTooltipsPolicy(FolderTreeWidget::ToolTipDisplayPolicy); void slotManualSortingChanged(bool); void slotFilterFixedString(const QString &); void slotGeneralFontChanged(); void slotGeneralPaletteChanged(); private: bool eventFilter(QObject *o, QEvent *e) override; class FolderTreeWidgetPrivate; FolderTreeWidgetPrivate *const d; }; } #endif diff --git a/src/interfaces/mailinterfaces.h b/src/interfaces/mailinterfaces.h index 28b281c..e39cc6f 100644 --- a/src/interfaces/mailinterfaces.h +++ b/src/interfaces/mailinterfaces.h @@ -1,104 +1,105 @@ /* Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_MAILINTERFACES_H #define MAILCOMMON_MAILINTERFACES_H #include #include namespace MessageComposer { class MessageSender; } namespace Akonadi { class ChangeRecorder; class EntityMimeTypeFilterModel; } namespace KIdentityManagement { class IdentityManager; } namespace MailCommon { class FilterDialog; class JobScheduler; /** Generic interface for mail kernels.*/ class IKernel { public: /** * Returns a model of all folders in KMail. * This is basically the same as entityTreeModel(), but with items * filtered out, the model contains only collections. */ virtual Akonadi::EntityMimeTypeFilterModel *collectionModel() const = 0; /** * Return the pointer to the identity manager. */ virtual KIdentityManagement::IdentityManager *identityManager() = 0; virtual KSharedConfig::Ptr config() = 0; virtual void syncConfig() = 0; virtual JobScheduler *jobScheduler() const = 0; virtual Akonadi::ChangeRecorder *folderCollectionMonitor() const = 0; virtual void updateSystemTray() = 0; virtual MessageComposer::MessageSender *msgSender() = 0; virtual void expunge(Akonadi::Collection::Id col, bool sync) = 0; virtual ~IKernel() { } }; /** Filter related interface */ class IFilter { public: virtual void openFilterDialog(bool createDummyFilter = true) = 0; virtual void createFilter(const QByteArray &field, const QString &value) = 0; virtual ~IFilter() { } }; /** Interface to access some settings. */ class ISettings { public: virtual bool showPopupAfterDnD() = 0; virtual bool excludeImportantMailFromExpiry() = 0; virtual qreal closeToQuotaThreshold() = 0; virtual Akonadi::Collection::Id lastSelectedFolder() = 0; virtual void setLastSelectedFolder(Akonadi::Collection::Id col) = 0; virtual QStringList customTemplates() = 0; virtual ~ISettings() { } }; } #endif diff --git a/src/job/expirejob.cpp b/src/job/expirejob.cpp index 08cf17f..068ced3 100644 --- a/src/job/expirejob.cpp +++ b/src/job/expirejob.cpp @@ -1,305 +1,306 @@ /** * Copyright (c) 2004 David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "expirejob.h" #include "collectionpage/attributes/expirecollectionattribute.h" #include "kernel/mailkernel.h" #include "util/mailutil.h" #include using KPIM::BroadcastStatus; #include "mailcommon_debug.h" #include #include #include #include #include #include #include #include #include #include /* Testcases for folder expiry: Automatic expiry: - normal case (ensure folder has old mails and expiry settings, wait for auto-expiry) - having the folder selected when the expiry job would run (gets delayed) - selecting a folder while an expiry job is running for it (should interrupt) - exiting kmail while an expiry job is running (should abort & delete things cleanly) Manual expiry: - RMB / expire (for one folder) [KMMainWidget::slotExpireFolder()] - RMB on Local Folders / Expire All Folders [KMFolderMgr::expireAll()] - Expire All Folders [KMMainWidget::slotExpireAll()] */ namespace MailCommon { ExpireJob::ExpireJob(const Akonadi::Collection &folder, bool immediate) : ScheduledJob(folder, immediate) , mMaxUnreadTime(0) , mMaxReadTime(0) , mMoveToFolder(0) { } ExpireJob::~ExpireJob() { qCDebug(MAILCOMMON_LOG); } void ExpireJob::kill() { ScheduledJob::kill(); } void ExpireJob::execute() { mMaxUnreadTime = 0; mMaxReadTime = 0; int unreadDays, readDays; bool mustDeleteExpirationAttribute = false; MailCommon::ExpireCollectionAttribute *expirationAttribute = MailCommon::Util::expirationCollectionAttribute( mSrcFolder, mustDeleteExpirationAttribute); expirationAttribute->daysToExpire(unreadDays, readDays); if (mustDeleteExpirationAttribute) { delete expirationAttribute; } if (unreadDays > 0) { qCDebug(MAILCOMMON_LOG) << "ExpireJob: deleting unread older than" << unreadDays << "days"; mMaxUnreadTime = QDateTime::currentDateTime().toSecsSinceEpoch() - unreadDays * 3600 * 24; } if (readDays > 0) { qCDebug(MAILCOMMON_LOG) << "ExpireJob: deleting read older than" << readDays << "days"; mMaxReadTime = QDateTime::currentDateTime().toSecsSinceEpoch() - readDays * 3600 * 24; } if ((mMaxUnreadTime == 0) && (mMaxReadTime == 0)) { qCDebug(MAILCOMMON_LOG) << "ExpireJob: nothing to do"; deleteLater(); return; } qCDebug(MAILCOMMON_LOG) << "ExpireJob: starting to expire in folder" << mSrcFolder.name(); slotDoWork(); // do nothing here, we might be deleted! } void ExpireJob::slotDoWork() { Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(mSrcFolder, this); job->fetchScope().fetchPayloadPart(Akonadi::MessagePart::Envelope); connect(job, &Akonadi::ItemFetchJob::result, this, &ExpireJob::itemFetchResult); } void ExpireJob::itemFetchResult(KJob *job) { if (job->error()) { qCWarning(MAILCOMMON_LOG) << job->errorString(); deleteLater(); return; } const Akonadi::Item::List items = qobject_cast(job)->items(); for (const Akonadi::Item &item : items) { if (!item.hasPayload()) { continue; } const KMime::Message::Ptr mb = item.payload(); Akonadi::MessageStatus status; status.setStatusFromFlags(item.flags()); if ((status.isImportant() || status.isToAct() || status.isWatched()) && SettingsIf->excludeImportantMailFromExpiry()) { continue; } time_t maxTime = status.isRead() ? mMaxReadTime : mMaxUnreadTime; if (!mb->date(false)) { continue; } if (mb->date()->dateTime().toSecsSinceEpoch() < maxTime) { mRemovedMsgs.append(item); } } done(); } void ExpireJob::done() { QString str; bool moving = false; if (!mRemovedMsgs.isEmpty()) { int count = mRemovedMsgs.count(); // The command shouldn't kill us because it opens the folder mCancellable = false; bool mustDeleteExpirationAttribute = false; MailCommon::ExpireCollectionAttribute *expirationAttribute = MailCommon::Util::expirationCollectionAttribute( mSrcFolder, mustDeleteExpirationAttribute); if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) { // Expire by deletion, i.e. move to null target folder qCDebug(MAILCOMMON_LOG) << "ExpireJob: finished expiring in folder" << mSrcFolder.name() << count << "messages to remove."; Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob(mRemovedMsgs, this); connect(job, &Akonadi::ItemDeleteJob::result, this, &ExpireJob::slotExpireDone); moving = true; str = i18np("Removing 1 old message from folder %2...", "Removing %1 old messages from folder %2...", count, mSrcFolder.name()); } else { // Expire by moving mMoveToFolder = Kernel::self()->collectionFromId(expirationAttribute->expireToFolderId()); if (!mMoveToFolder.isValid()) { str = i18n("Cannot expire messages from folder %1: destination " "folder %2 not found", mSrcFolder.name(), expirationAttribute->expireToFolderId()); qCWarning(MAILCOMMON_LOG) << str; } else { qCDebug(MAILCOMMON_LOG) << "ExpireJob: finished expiring in folder" << mSrcFolder.name() << mRemovedMsgs.count() << "messages to move to" << mMoveToFolder.name(); Akonadi::ItemMoveJob *job = new Akonadi::ItemMoveJob(mRemovedMsgs, mMoveToFolder, this); connect(job, &Akonadi::ItemMoveJob::result, this, &ExpireJob::slotMoveDone); moving = true; str = i18np("Moving 1 old message from folder %2 to folder %3...", "Moving %1 old messages from folder %2 to folder %3...", count, mSrcFolder.name(), mMoveToFolder.name()); } } if (mustDeleteExpirationAttribute) { delete expirationAttribute; } } if (!str.isEmpty()) { BroadcastStatus::instance()->setStatusMsg(str); } if (!moving) { deleteLater(); } } void ExpireJob::slotMoveDone(KJob *job) { if (job->error()) { qCCritical(MAILCOMMON_LOG) << job->error() << job->errorString(); } Akonadi::ItemMoveJob *itemjob = qobject_cast(job); if (itemjob) { const Akonadi::Item::List lst = itemjob->items(); if (!lst.isEmpty()) { Akonadi::Item::List newLst; for (Akonadi::Item item : lst) { if (!item.hasFlag(Akonadi::MessageFlags::Seen)) { item.setFlag(Akonadi::MessageFlags::Seen); newLst << item; } } if (!newLst.isEmpty()) { Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(newLst, this); modifyJob->disableRevisionCheck(); connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &ExpireJob::slotExpireDone); } else { slotExpireDone(job); } } } else { slotExpireDone(job); } } void ExpireJob::slotExpireDone(KJob *job) { if (job->error()) { qCCritical(MAILCOMMON_LOG) << job->error() << job->errorString(); } QString msg; const int error = job->error(); bool mustDeleteExpirationAttribute = false; MailCommon::ExpireCollectionAttribute *expirationAttribute = MailCommon::Util::expirationCollectionAttribute( mSrcFolder, mustDeleteExpirationAttribute); switch (error) { case KJob::NoError: if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) { msg = i18np("Removed 1 old message from folder %2.", "Removed %1 old messages from folder %2.", mRemovedMsgs.count(), mSrcFolder.name()); } else { msg = i18np("Moved 1 old message from folder %2 to folder %3.", "Moved %1 old messages from folder %2 to folder %3.", mRemovedMsgs.count(), mSrcFolder.name(), mMoveToFolder.name()); } break; case Akonadi::Job::UserCanceled: if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) { msg = i18n("Removing old messages from folder %1 was canceled.", mSrcFolder.name()); } else { msg = i18n("Moving old messages from folder %1 to folder %2 was " "canceled.", mSrcFolder.name(), mMoveToFolder.name()); } break; default: //any other error if (expirationAttribute->expireAction() == MailCommon::ExpireCollectionAttribute::ExpireDelete) { msg = i18n("Removing old messages from folder %1 failed.", mSrcFolder.name()); } else { msg = i18n("Moving old messages from folder %1 to folder %2 failed.", mSrcFolder.name(), mMoveToFolder.name()); } break; } BroadcastStatus::instance()->setStatusMsg(msg); if (mustDeleteExpirationAttribute) { delete expirationAttribute; } deleteLater(); } } diff --git a/src/job/expirejob.h b/src/job/expirejob.h index 6022ba7..3de294e 100644 --- a/src/job/expirejob.h +++ b/src/job/expirejob.h @@ -1,90 +1,91 @@ /* -*- mode: C++ -*- * Copyright (c) 2004 David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #ifndef MAILCOMMON_EXPIREJOB_H #define MAILCOMMON_EXPIREJOB_H #include "jobscheduler.h" #include #include class KJob; namespace MailCommon { class ExpireJob : public ScheduledJob { Q_OBJECT public: explicit ExpireJob(const Akonadi::Collection &folder, bool immediate); ~ExpireJob() override; void execute() override; void kill() override; private: void slotDoWork(); void slotExpireDone(KJob *job); void slotMoveDone(KJob *job); void itemFetchResult(KJob *job); void done(); private: Akonadi::Item::List mRemovedMsgs; qint64 mMaxUnreadTime; qint64 mMaxReadTime; Akonadi::Collection mMoveToFolder; }; /// A scheduled "expire mails in this folder" task. class ScheduledExpireTask : public ScheduledTask { public: /// If immediate is set, the job will execute synchronously. This is used when /// the user requests explicitly that the operation should happen immediately. ScheduledExpireTask(const Akonadi::Collection &folder, bool immediate) : ScheduledTask(folder, immediate) { } ~ScheduledExpireTask() override { } ScheduledJob *run() override { return folder().isValid() ? new ExpireJob(folder(), isImmediate()) : nullptr; } int taskTypeId() const override { return 1; } }; } // namespace #endif diff --git a/src/job/folderjob.cpp b/src/job/folderjob.cpp index 5b1ba1e..741489f 100644 --- a/src/job/folderjob.cpp +++ b/src/job/folderjob.cpp @@ -1,80 +1,81 @@ /* * * Copyright (c) 2003 Zack Rusin * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License, version 2, as - * published by the Free Software Foundation. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * * This program 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "folderjob.h" #include //krazy:exclude=camelcase as there is no such using namespace MailCommon; //---------------------------------------------------------------------------- FolderJob::FolderJob() : mErrorCode(0) , mStarted(false) , mCancellable(false) { } //---------------------------------------------------------------------------- FolderJob::~FolderJob() { Q_EMIT result(this); Q_EMIT finished(); } //---------------------------------------------------------------------------- void FolderJob::start() { if (!mStarted) { mStarted = true; execute(); } } //---------------------------------------------------------------------------- void FolderJob::kill() { mErrorCode = KIO::ERR_USER_CANCELED; delete this; } int FolderJob::error() const { return mErrorCode; } bool FolderJob::isCancellable() const { return mCancellable; } void FolderJob::setCancellable(bool b) { mCancellable = b; } diff --git a/src/job/folderjob.h b/src/job/folderjob.h index 1b0d8a2..ed49377 100644 --- a/src/job/folderjob.h +++ b/src/job/folderjob.h @@ -1,106 +1,107 @@ /* * * Copyright (c) 2003 Zack Rusin * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License, version 2, as - * published by the Free Software Foundation. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * * This program 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #ifndef MAILCOMMON_FOLDERJOB_H #define MAILCOMMON_FOLDERJOB_H #include #include "mailcommon_export.h" namespace MailCommon { class MAILCOMMON_EXPORT FolderJob : public QObject { Q_OBJECT public: FolderJob(); virtual ~FolderJob(); /** * Start the job */ void start(); /** * Interrupt the job. Note that the finished() and result() signal * will be emitted, unless you called setPassiveDestructor(true) before. * This kills the job, don't use it afterwards. */ virtual void kill(); /** * @return the error code of the job. This must only be called from * the slot connected to the finished() signal. */ Q_REQUIRED_RESULT int error() const; /** * @return true if this job can be canceled, e.g. to exit the application */ Q_REQUIRED_RESULT bool isCancellable() const; /** * Call this to change the "cancellable" property of this job. * By default, tListMessages, tGetMessage, tGetFolder and tCheckUidValidity * are cancellable, the others are not. But when copying, a non-cancellable * tGetMessage is needed. */ void setCancellable(bool b); Q_SIGNALS: /** * Emitted when the job finishes all processing. */ void finished(); /** * Emitted when the job finishes all processing. * More convenient signal than finished(), since it provides a pointer to the job. * This signal is emitted by the FolderJob destructor => do NOT downcast * the job to a subclass! */ void result(FolderJob *job); protected: /** * Has to be reimplemented. It's called by the start() method. Should * start the processing of the specified job function. */ virtual void execute() = 0; Akonadi::Collection mSrcFolder; int mErrorCode; bool mStarted; bool mCancellable; }; } #endif diff --git a/src/job/jobscheduler.cpp b/src/job/jobscheduler.cpp index 637aa28..8b618bc 100644 --- a/src/job/jobscheduler.cpp +++ b/src/job/jobscheduler.cpp @@ -1,250 +1,251 @@ /** * Copyright (c) 2004 David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "jobscheduler.h" #include "mailcommon_debug.h" namespace MailCommon { ScheduledTask::ScheduledTask(const Akonadi::Collection &folder, bool immediate) : mCurrentFolder(folder) , mImmediate(immediate) { } ScheduledTask::~ScheduledTask() { } JobScheduler::JobScheduler(QObject *parent) : QObject(parent) , mTimer(this) , mPendingImmediateTasks(0) , mCurrentTask(nullptr) , mCurrentJob(nullptr) { connect(&mTimer, &QTimer::timeout, this, &JobScheduler::slotRunNextJob); // No need to start the internal timer yet, we wait for a task to be scheduled } JobScheduler::~JobScheduler() { qDeleteAll(mTaskList); mTaskList.clear(); delete mCurrentTask; mCurrentTask = nullptr; delete mCurrentJob; } void JobScheduler::registerTask(ScheduledTask *task) { bool immediate = task->isImmediate(); int typeId = task->taskTypeId(); if (typeId) { const Akonadi::Collection folder = task->folder(); // Search for an identical task already scheduled TaskList::Iterator end(mTaskList.end()); for (TaskList::Iterator it = mTaskList.begin(); it != end; ++it) { if ((*it)->taskTypeId() == typeId && (*it)->folder() == folder) { #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: already having task type" << typeId << "for folder" << folder->label(); #endif delete task; if (!mCurrentTask && immediate) { ScheduledTask *task = *it; removeTask(it); runTaskNow(task); } return; } } // Note that scheduling an identical task as the one currently running is allowed. } if (!mCurrentTask && immediate) { runTaskNow(task); } else { #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: adding task" << task << "(type" << task->taskTypeId() << ") for folder" << task->folder() << task->folder().name(); #endif mTaskList.append(task); if (immediate) { ++mPendingImmediateTasks; } if (!mCurrentTask && !mTimer.isActive()) { restartTimer(); } } } void JobScheduler::removeTask(TaskList::Iterator &it) { if ((*it)->isImmediate()) { --mPendingImmediateTasks; } mTaskList.erase(it); } void JobScheduler::interruptCurrentTask() { Q_ASSERT(mCurrentTask); #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: interrupting job" << mCurrentJob << "for folder" << mCurrentTask->folder()->label(); #endif // File it again. This will either delete it or put it in mTaskList. registerTask(mCurrentTask); mCurrentTask = nullptr; mCurrentJob->kill(); // This deletes the job and calls slotJobFinished! } void JobScheduler::slotRunNextJob() { while (!mCurrentJob) { #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: slotRunNextJob"; #endif Q_ASSERT(mCurrentTask == nullptr); ScheduledTask *task = nullptr; // Find a task suitable for being run TaskList::Iterator end(mTaskList.end()); for (TaskList::Iterator it = mTaskList.begin(); it != end; ++it) { // Remove if folder died const Akonadi::Collection folder = (*it)->folder(); if (!folder.isValid()) { #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << " folder for task" << (*it) << "was deleted"; #endif removeTask(it); if (!mTaskList.isEmpty()) { slotRunNextJob(); // to avoid the mess with invalid iterators :) } else { mTimer.stop(); } return; } #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << " looking at folder" << folder.name(); #endif task = *it; removeTask(it); break; } if (!task) { // found nothing to run, i.e. folder was opened return; // Timer keeps running, i.e. try again in 1 minute } runTaskNow(task); } // If nothing to do for that task, loop and find another one to run } void JobScheduler::restartTimer() { if (mPendingImmediateTasks > 0) { slotRunNextJob(); } else { #ifdef DEBUG_SCHEDULER mTimer.start(10000); // 10 seconds #else mTimer.start(1 * 60000); // 1 minute #endif } } void JobScheduler::runTaskNow(ScheduledTask *task) { Q_ASSERT(mCurrentTask == nullptr); if (mCurrentTask) { interruptCurrentTask(); } mCurrentTask = task; mTimer.stop(); mCurrentJob = mCurrentTask->run(); #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: task" << mCurrentTask << "(type" << mCurrentTask->taskTypeId() << ")" << "for folder" << mCurrentTask->folder()->label() << "returned job" << mCurrentJob << (mCurrentJob ? mCurrentJob->className() : 0); #endif if (!mCurrentJob) { // nothing to do, e.g. folder deleted delete mCurrentTask; mCurrentTask = nullptr; if (!mTaskList.isEmpty()) { restartTimer(); } return; } // Register the job in the folder. This makes it autodeleted if the folder is deleted. #if 0 mCurrentTask->folder()->storage()->addJob(mCurrentJob); #endif connect(mCurrentJob, &ScheduledJob::finished, this, &JobScheduler::slotJobFinished); mCurrentJob->start(); } void JobScheduler::slotJobFinished() { // Do we need to test for mCurrentJob->error()? What do we do then? #ifdef DEBUG_SCHEDULER qCDebug(MAILCOMMON_LOG) << "JobScheduler: slotJobFinished"; #endif delete mCurrentTask; mCurrentTask = nullptr; mCurrentJob = nullptr; if (!mTaskList.isEmpty()) { restartTimer(); } } // D-Bus call to pause any background jobs void JobScheduler::pause() { mPendingImmediateTasks = 0; if (mCurrentJob && mCurrentJob->isCancellable()) { interruptCurrentTask(); } mTimer.stop(); } void JobScheduler::resume() { restartTimer(); } //// ScheduledJob::ScheduledJob(const Akonadi::Collection &folder, bool immediate) : mImmediate(immediate) { mCancellable = true; mSrcFolder = folder; } ScheduledJob::~ScheduledJob() { } } diff --git a/src/job/jobscheduler.h b/src/job/jobscheduler.h index 6620074..3ce67c0 100644 --- a/src/job/jobscheduler.h +++ b/src/job/jobscheduler.h @@ -1,152 +1,153 @@ /* * Copyright (c) 2004 David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #ifndef MAILCOMMON_JOBSCHEDULER_H #define MAILCOMMON_JOBSCHEDULER_H #include "mailcommon_export.h" #include #include #include "folderjob.h" #include // If this define is set, JobScheduler will show debug output, and related kmkernel timers will be shortened // This is for debugging purposes only, don't commit with it. //#define DEBUG_SCHEDULER namespace MailCommon { class FolderJob; class ScheduledJob; /** * A scheduled task is some information about a folder job that should be run later. * As long as it's not running, it's called a "task", i.e. something that needs to be done. * Tasks are held in the JobScheduler. */ class MAILCOMMON_EXPORT ScheduledTask { public: /// Create a scheduled task for a given folder /// If @p immediate is true, the scheduler will run this task as soon /// as possible (but won't interrupt a currently running job for it) ScheduledTask(const Akonadi::Collection &folder, bool immediate); virtual ~ScheduledTask(); /// Run this task, i.e. create a job for it. /// Important: the job's execute() method must either call open() on the /// folder or storage immediately, or abort (deleting itself). /// Usually, that job should also be cancellable. /// Otherwise (if the open() is delayed) an unrelated open() could happen first /// and mess things up. /// If for some reason (e.g. folder deleted) nothing should be done, return 0. virtual ScheduledJob *run() = 0; /// An identifier for the type of task (a bit like QListViewItem::rtti) /// This allows to automatically prevent two identical tasks from being scheduled /// for the same folder. To circumvent this feature and make every task /// unique, return 0 here. virtual int taskTypeId() const = 0; /// The folder which this task is about, 0 if it was deleted meanwhile. Q_REQUIRED_RESULT Akonadi::Collection folder() const { return mCurrentFolder; } Q_REQUIRED_RESULT bool isImmediate() const { return mImmediate; } private: Akonadi::Collection mCurrentFolder; bool mImmediate; }; /** * The unique JobScheduler instance (owned by kmkernel) implements "background processing" * of folder operations (like expiration and compaction). Tasks (things to be done) * are registered with the JobScheduler, and it will execute them one at a time, * separated with a 1-minute timer. The jobs themselves should use timers to avoid * using too much CPU for too long. Tasks for opened folders are not executed until * the folder is closed. */ class MAILCOMMON_EXPORT JobScheduler : public QObject { Q_OBJECT public: explicit JobScheduler(QObject *parent); ~JobScheduler(); /// Register a task to be done for a given folder /// The ownership of the task is transferred to the JobScheduler void registerTask(ScheduledTask *task); // D-Bus calls, called from KMKernel void pause(); void resume(); private: /// Called by a timer to run the next job void slotRunNextJob(); /// Called when the current job terminates void slotJobFinished(); void restartTimer(); void interruptCurrentTask(); void runTaskNow(ScheduledTask *task); typedef QList TaskList; void removeTask(TaskList::Iterator &it); private: TaskList mTaskList; // FIFO of tasks to be run QTimer mTimer; int mPendingImmediateTasks; /// Information about the currently running job, if any ScheduledTask *mCurrentTask = nullptr; ScheduledJob *mCurrentJob = nullptr; }; /** * Base class for scheduled jobs */ class MAILCOMMON_EXPORT ScheduledJob : public FolderJob { public: ScheduledJob(const Akonadi::Collection &folder, bool immediate); ~ScheduledJob(); protected: bool mImmediate; }; } #endif diff --git a/src/kernel/mailkernel.cpp b/src/kernel/mailkernel.cpp index a418d2b..fcda03f 100644 --- a/src/kernel/mailkernel.cpp +++ b/src/kernel/mailkernel.cpp @@ -1,410 +1,411 @@ /* Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mailkernel.h" #include "util/mailutil.h" #include "mailcommon_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace MailCommon { class KernelPrivate { public: KernelPrivate() : kernel(new Kernel) { } ~KernelPrivate() { qCDebug(MAILCOMMON_LOG); delete kernel; } Kernel *kernel; }; Q_GLOBAL_STATIC(KernelPrivate, sInstance) Kernel::Kernel(QObject *parent) : QObject(parent) { mKernelIf = nullptr; mSettingsIf = nullptr; mFilterIf = nullptr; mImapResourceManager = new PimCommon::ImapResourceCapabilitiesManager(this); } Kernel::~Kernel() { qCDebug(MAILCOMMON_LOG); } Kernel *Kernel::self() { return sInstance->kernel; //will create it } void Kernel::registerKernelIf(IKernel *kernelIf) { mKernelIf = kernelIf; } bool Kernel::kernelIsRegistered() const { return mKernelIf != nullptr; } IKernel *Kernel::kernelIf() const { Q_ASSERT(mKernelIf); return mKernelIf; } void Kernel::registerSettingsIf(ISettings *settingsIf) { mSettingsIf = settingsIf; } ISettings *Kernel::settingsIf() const { Q_ASSERT(mSettingsIf); return mSettingsIf; } void Kernel::registerFilterIf(IFilter *filterIf) { mFilterIf = filterIf; } IFilter *Kernel::filterIf() const { Q_ASSERT(mFilterIf); return mFilterIf; } PimCommon::ImapResourceCapabilitiesManager *Kernel::imapResourceManager() const { return mImapResourceManager; } Akonadi::Collection Kernel::collectionFromId(Akonadi::Collection::Id id) const { return Akonadi::EntityTreeModel::updatedCollection(kernelIf()->collectionModel(), id); } Akonadi::Collection Kernel::trashCollectionFolder() { return Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::Trash); } Akonadi::Collection Kernel::inboxCollectionFolder() { return Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::Inbox); } Akonadi::Collection Kernel::outboxCollectionFolder() { return Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::Outbox); } Akonadi::Collection Kernel::sentCollectionFolder() { return Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::SentMail); } Akonadi::Collection Kernel::draftsCollectionFolder() { return Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::Drafts); } Akonadi::Collection Kernel::templatesCollectionFolder() { return Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::Templates); } bool Kernel::isSystemFolderCollection(const Akonadi::Collection &col) { return col == inboxCollectionFolder() || col == outboxCollectionFolder() || col == sentCollectionFolder() || col == trashCollectionFolder() || col == draftsCollectionFolder() || col == templatesCollectionFolder(); } bool Kernel::isMainFolderCollection(const Akonadi::Collection &col) { return col == inboxCollectionFolder(); } //----------------------------------------------------------------------------- void Kernel::initFolders() { qCDebug(MAILCOMMON_LOG) << "Initialized and looking for specialcollection folders."; findCreateDefaultCollection(Akonadi::SpecialMailCollections::Inbox); findCreateDefaultCollection(Akonadi::SpecialMailCollections::Outbox); findCreateDefaultCollection(Akonadi::SpecialMailCollections::SentMail); findCreateDefaultCollection(Akonadi::SpecialMailCollections::Drafts); findCreateDefaultCollection(Akonadi::SpecialMailCollections::Trash); findCreateDefaultCollection(Akonadi::SpecialMailCollections::Templates); Akonadi::SpecialMailCollectionsDiscoveryJob *job = new Akonadi::SpecialMailCollectionsDiscoveryJob(this); job->start(); // we don't connect to the job directly: it will register stuff into SpecialMailCollections, which will Q_EMIT signals. } void Kernel::findCreateDefaultCollection(Akonadi::SpecialMailCollections::Type type) { if (Akonadi::SpecialMailCollections::self()->hasDefaultCollection(type)) { const Akonadi::Collection col = Akonadi::SpecialMailCollections::self()->defaultCollection(type); if (!(col.rights() & Akonadi::Collection::AllRights)) { emergencyExit(i18n("You do not have read/write permission to your inbox folder.")); } } else { Akonadi::SpecialMailCollectionsRequestJob *job = new Akonadi::SpecialMailCollectionsRequestJob(this); connect(job, &Akonadi::SpecialMailCollectionsRequestJob::result, this, &Kernel::createDefaultCollectionDone); job->requestDefaultCollection(type); } } void Kernel::createDefaultCollectionDone(KJob *job) { if (job->error()) { emergencyExit(job->errorText()); return; } Akonadi::SpecialMailCollectionsRequestJob *requestJob = qobject_cast(job); const Akonadi::Collection col = requestJob->collection(); if (!(col.rights() & Akonadi::Collection::AllRights)) { emergencyExit(i18n("You do not have read/write permission to your inbox folder.")); } Akonadi::SpecialMailCollections::self()->verifyI18nDefaultCollection(Akonadi::SpecialMailCollections::Inbox); Akonadi::SpecialMailCollections::self()->verifyI18nDefaultCollection(Akonadi::SpecialMailCollections::Outbox); Akonadi::SpecialMailCollections::self()->verifyI18nDefaultCollection(Akonadi::SpecialMailCollections::SentMail); Akonadi::SpecialMailCollections::self()->verifyI18nDefaultCollection(Akonadi::SpecialMailCollections::Drafts); Akonadi::SpecialMailCollections::self()->verifyI18nDefaultCollection(Akonadi::SpecialMailCollections::Trash); Akonadi::SpecialMailCollections::self()->verifyI18nDefaultCollection(Akonadi::SpecialMailCollections::Templates); connect(Akonadi::SpecialMailCollections::self(), &Akonadi::SpecialMailCollections::defaultCollectionsChanged, this, &Kernel::slotDefaultCollectionsChanged, Qt::UniqueConnection); } void Kernel::slotDefaultCollectionsChanged() { disconnect(Akonadi::SpecialMailCollections::self(), &Akonadi::SpecialMailCollections::defaultCollectionsChanged, this, &Kernel::slotDefaultCollectionsChanged); initFolders(); } void Kernel::emergencyExit(const QString &reason) { QString mesg; if (reason.isEmpty()) { mesg = i18n("The Email program encountered a fatal error and will terminate now"); } else { mesg = i18n("The Email program encountered a fatal error and will terminate now.\n" "The error was:\n%1", reason); } qCWarning(MAILCOMMON_LOG) << mesg; // Show error box for the first error that caused emergencyExit. static bool s_showingErrorBox = false; if (!s_showingErrorBox) { s_showingErrorBox = true; if (qApp) { //see bug 313104 KMessageBox::error(nullptr, mesg); } ::exit(1); } } bool Kernel::folderIsDraftOrOutbox(const Akonadi::Collection &col) { if (col == Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Outbox)) { return true; } return folderIsDrafts(col); } bool Kernel::folderIsDrafts(const Akonadi::Collection &col) { if (col == Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Drafts)) { return true; } const QString idString = QString::number(col.id()); if (idString.isEmpty()) { return false; } // search the identities if the folder matches the drafts-folder const KIdentityManagement::IdentityManager *im = KernelIf->identityManager(); KIdentityManagement::IdentityManager::ConstIterator end(im->end()); for (KIdentityManagement::IdentityManager::ConstIterator it = im->begin(); it != end; ++it) { if ((*it).drafts() == idString) { return true; } } return false; } bool Kernel::folderIsTemplates(const Akonadi::Collection &col) { if (col == Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Templates)) { return true; } const QString idString = QString::number(col.id()); if (idString.isEmpty()) { return false; } // search the identities if the folder matches the templates-folder const KIdentityManagement::IdentityManager *im = KernelIf->identityManager(); KIdentityManagement::IdentityManager::ConstIterator end(im->end()); for (KIdentityManagement::IdentityManager::ConstIterator it = im->begin(); it != end; ++it) { if ((*it).templates() == idString) { return true; } } return false; } Akonadi::Collection Kernel::trashCollectionFromResource(const Akonadi::Collection &col) { Akonadi::Collection trashCol; if (col.isValid()) { const Akonadi::AgentInstance agent = Akonadi::AgentManager::self()->instance(col.resource()); trashCol = Akonadi::SpecialMailCollections::self()->collection(Akonadi::SpecialMailCollections::Trash, agent); } return trashCol; } bool Kernel::folderIsTrash(const Akonadi::Collection &col) { if (col == Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Trash)) { return true; } const Akonadi::AgentInstance::List lst = MailCommon::Util::agentInstances(); for (const Akonadi::AgentInstance &agent : lst) { const Akonadi::Collection trash = Akonadi::SpecialMailCollections::self()->collection(Akonadi::SpecialMailCollections::Trash, agent); if (col == trash) { return true; } } return false; } bool Kernel::folderIsSentMailFolder(const Akonadi::Collection &col) { if (col == Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::SentMail)) { return true; } const QString idString = QString::number(col.id()); if (idString.isEmpty()) { return false; } // search the identities if the folder matches the sent-folder const KIdentityManagement::IdentityManager *im = KernelIf->identityManager(); KIdentityManagement::IdentityManager::ConstIterator end(im->end()); for (KIdentityManagement::IdentityManager::ConstIterator it = im->begin(); it != end; ++it) { if ((*it).fcc() == idString) { return true; } } return false; } bool Kernel::folderIsInbox(const Akonadi::Collection &collection) { const QString collectionRemoteIdLower = collection.remoteId().toLower(); if (collectionRemoteIdLower == QLatin1String("inbox") || collectionRemoteIdLower == QLatin1String("/inbox") || collectionRemoteIdLower == QLatin1String(".inbox") || collectionRemoteIdLower == QLatin1String("|inbox")) { return true; } //Fix order. Remoteid is not "inbox" when translated if (Akonadi::SpecialMailCollections::self()->specialCollectionType(collection) == Akonadi::SpecialMailCollections::Inbox) { return true; } //MBOX is one folder only, treat as inbox if (collection.resource().contains(MBOX_RESOURCE_IDENTIFIER)) { return true; } return false; } QMap Kernel::pop3ResourceTargetCollection() { QMap mapIdentifierCollectionId; const Akonadi::AgentInstance::List lst = MailCommon::Util::agentInstances(); for (const Akonadi::AgentInstance &type : lst) { if (type.status() == Akonadi::AgentInstance::Broken) { continue; } const QString typeIdentifier = type.identifier(); if (typeIdentifier.contains(POP3_RESOURCE_IDENTIFIER)) { MailCommon::ResourceReadConfigFile resourceFile(typeIdentifier); const KConfigGroup grp = resourceFile.group(QStringLiteral("General")); if (grp.isValid()) { const Akonadi::Collection::Id targetCollection = grp.readEntry(QStringLiteral("targetCollection"), -1); mapIdentifierCollectionId.insert(typeIdentifier, targetCollection); } } } return mapIdentifierCollectionId; } } diff --git a/src/kernel/mailkernel.h b/src/kernel/mailkernel.h index 7b79bca..7ba6568 100644 --- a/src/kernel/mailkernel.h +++ b/src/kernel/mailkernel.h @@ -1,171 +1,172 @@ /* Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_MAILKERNEL_H #define MAILCOMMON_MAILKERNEL_H #include "mailcommon_export.h" #include "mailcommon/mailinterfaces.h" #include #include #include #include #include namespace PimCommon { class ImapResourceCapabilitiesManager; } namespace MailCommon { /** * Deals with common mail application related operations. The required interfaces * MUST be registered before using it! * Be careful when using in multi-threaded applications, as Kernel is a QObject * singleton, created in the main thread, thus event handling for Kernel::self() * will happen in the main thread. */ class MAILCOMMON_EXPORT Kernel : public QObject { Q_OBJECT public: virtual ~Kernel(); static Kernel *self(); /** * Registers the interface dealing with main mail functionality. This function * MUST be called with a valid interface pointer, before any Kernel::self() * method is used. The pointer ownership will not be transferred to Kernel. */ void registerKernelIf(IKernel *kernelIf); bool kernelIsRegistered() const; IKernel *kernelIf() const; /** * Registers the interface dealing with mail settings. This function * MUST be called with a valid interface pointer, before any Kernel::self() * method is used. The pointer ownership will not be transferred to Kernel. */ void registerSettingsIf(ISettings *settingsIf); ISettings *settingsIf() const; /** * Registers the interface dealing with mail settings. This function * MUST be called with a valid interface pointer, before any Kernel::self() * method is used. The pointer ownership will not be transferred to Kernel. */ void registerFilterIf(IFilter *filterIf); IFilter *filterIf() const; /** * Returns the collection associated with the given @p id, or an invalid * collection if not found. The EntityTreeModel of the kernel is searched for * the collection. Since the ETM is loaded async, this method will not find * the collection right after startup, when the ETM is not yet fully loaded. */ Akonadi::Collection collectionFromId(Akonadi::Collection::Id id) const; Akonadi::Collection inboxCollectionFolder(); Akonadi::Collection outboxCollectionFolder(); Akonadi::Collection sentCollectionFolder(); Akonadi::Collection trashCollectionFolder(); Akonadi::Collection draftsCollectionFolder(); Akonadi::Collection templatesCollectionFolder(); bool isSystemFolderCollection(const Akonadi::Collection &col); /** * Returns true if this folder is the inbox on the local disk */ bool isMainFolderCollection(const Akonadi::Collection &col); /** * Returns true if the folder is either the outbox or one of the drafts-folders. */ bool folderIsDraftOrOutbox(const Akonadi::Collection &collection); bool folderIsDrafts(const Akonadi::Collection &); bool folderIsTemplates(const Akonadi::Collection &collection); /** * Returns true if the folder is a trash folder. * * When calling this too early (before the SpecialMailCollectionsDiscoveryJob from initFolders finishes), * it will say false erroneously. However you can connect to SpecialMailCollections::collectionsChanged * to react on dynamic changes and call this again. */ bool folderIsTrash(const Akonadi::Collection &collection); /** * Returns the trash folder for the resource which @p col belongs to. * * When calling this too early (before the SpecialMailCollectionsDiscoveryJob from initFolders finishes), * it will return an invalid collection erroneously. However you can connect to SpecialMailCollections::collectionsChanged * to react on dynamic changes and call this again. */ Akonadi::Collection trashCollectionFromResource(const Akonadi::Collection &col); /** * Returns true if the folder is one of the sent-mail folders. */ bool folderIsSentMailFolder(const Akonadi::Collection &); static bool folderIsInbox(const Akonadi::Collection &); void initFolders(); void emergencyExit(const QString &reason); PimCommon::ImapResourceCapabilitiesManager *imapResourceManager() const; static QMap pop3ResourceTargetCollection(); private: void findCreateDefaultCollection(Akonadi::SpecialMailCollections::Type); private Q_SLOTS: void createDefaultCollectionDone(KJob *job); void slotDefaultCollectionsChanged(); Q_SIGNALS: void requestConfigSync(); void requestSystemTrayUpdate(); private: Kernel(QObject *parent = nullptr); friend class KernelPrivate; IKernel *mKernelIf; IFilter *mFilterIf; ISettings *mSettingsIf; PimCommon::ImapResourceCapabilitiesManager *mImapResourceManager; }; } #define KernelIf MailCommon::Kernel::self()->kernelIf() #define FilterIf MailCommon::Kernel::self()->filterIf() #define SettingsIf MailCommon::Kernel::self()->settingsIf() #define CommonKernel MailCommon::Kernel::self() #endif diff --git a/src/search/searchrule/searchrule.cpp b/src/search/searchrule/searchrule.cpp index 34a3719..7029f90 100644 --- a/src/search/searchrule/searchrule.cpp +++ b/src/search/searchrule/searchrule.cpp @@ -1,593 +1,594 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "searchrule.h" #include "searchrule/searchrulenumerical.h" #include "searchrule/searchruledate.h" #include "searchrule/searchrulestring.h" #include "searchrule/searchrulestatus.h" #include "searchrule/searchruleencryption.h" #include "mailcommon_debug.h" #include #include #include #include #include using namespace MailCommon; static const char *const funcConfigNames[] = { "contains", "contains-not", "equals", "not-equal", "regexp", "not-regexp", "greater", "less-or-equal", "less", "greater-or-equal", "is-in-addressbook", "is-not-in-addressbook", "is-in-category", "is-not-in-category", "has-attachment", "has-no-attachment", "start-with", "not-start-with", "end-with", "not-end-with" }; static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames; //================================================== // // class SearchRule (was: KMFilterRule) // //================================================== SearchRule::SearchRule(const QByteArray &field, Function func, const QString &contents) : mField(field) , mFunction(func) , mContents(contents) { } SearchRule::SearchRule(const SearchRule &other) : mField(other.mField) , mFunction(other.mFunction) , mContents(other.mContents) { } const SearchRule &SearchRule::operator=(const SearchRule &other) { if (this == &other) { return *this; } mField = other.mField; mFunction = other.mFunction; mContents = other.mContents; return *this; } SearchRule::Ptr SearchRule::createInstance(const QByteArray &field, Function func, const QString &contents) { SearchRule::Ptr ret; if (field == "") { ret = SearchRule::Ptr(new SearchRuleStatus(field, func, contents)); } else if (field == "" || field == "") { ret = SearchRule::Ptr(new SearchRuleNumerical(field, func, contents)); } else if (field == "") { ret = SearchRule::Ptr(new SearchRuleDate(field, func, contents)); } else if (field == "") { ret = SearchRule::Ptr(new SearchRuleEncryption(field, func, contents)); } else { ret = SearchRule::Ptr(new SearchRuleString(field, func, contents)); } return ret; } SearchRule::Ptr SearchRule::createInstance(const QByteArray &field, const char *func, const QString &contents) { return createInstance(field, configValueToFunc(func), contents); } SearchRule::Ptr SearchRule::createInstance(const SearchRule &other) { return createInstance(other.field(), other.function(), other.contents()); } SearchRule::Ptr SearchRule::createInstanceFromConfig(const KConfigGroup &config, int aIdx) { const char cIdx = char(int('A') + aIdx); static const QString field = QStringLiteral("field"); static const QString func = QStringLiteral("func"); static const QString contents = QStringLiteral("contents"); const QByteArray &field2 = config.readEntry(field + cIdx, QString()).toLatin1(); Function func2 = configValueToFunc(config.readEntry(func + cIdx, QString()).toLatin1().constData()); const QString &contents2 = config.readEntry(contents + cIdx, QString()); if (field2 == "") { // backwards compat return SearchRule::createInstance("", func2, contents2); } else { return SearchRule::createInstance(field2, func2, contents2); } } SearchRule::Ptr SearchRule::createInstance(QDataStream &s) { QByteArray field; s >> field; QString function; s >> function; Function func = configValueToFunc(function.toUtf8().constData()); QString contents; s >> contents; return createInstance(field, func, contents); } SearchRule::~SearchRule() { } SearchRule::Function SearchRule::configValueToFunc(const char *str) { if (!str) { return FuncNone; } for (int i = 0; i < numFuncConfigNames; ++i) { if (qstricmp(funcConfigNames[i], str) == 0) { return (Function)i; } } return FuncNone; } QString SearchRule::functionToString(Function function) { if (function != FuncNone) { return funcConfigNames[int(function)]; } else { return QStringLiteral("invalid"); } } void SearchRule::writeConfig(KConfigGroup &config, int aIdx) const { const char cIdx = char('A' + aIdx); static const QString field = QStringLiteral("field"); static const QString func = QStringLiteral("func"); static const QString contents = QStringLiteral("contents"); config.writeEntry(field + cIdx, /*QString*/ (mField)); config.writeEntry(func + cIdx, functionToString(mFunction)); config.writeEntry(contents + cIdx, mContents); } QString SearchRule::conditionToString(Function function) { QString str; switch (function) { case FuncEquals: str = i18n("equal"); break; case FuncNotEqual: str = i18n("not equal"); break; case FuncIsGreater: str = i18n("is greater"); break; case FuncIsLessOrEqual: str = i18n("is less or equal"); break; case FuncIsLess: str = i18n("is less"); break; case FuncIsGreaterOrEqual: str = i18n("is greater or equal"); break; case FuncIsInAddressbook: str = i18n("is in addressbook"); break; case FuncIsNotInAddressbook: str = i18n("is not in addressbook"); break; case FuncIsInCategory: str = i18n("is in category"); break; case FuncIsNotInCategory: str = i18n("is in category"); break; case FuncHasAttachment: str = i18n("has an attachment"); break; case FuncHasNoAttachment: str = i18n("has not an attachment"); break; case FuncStartWith: str = i18n("start with"); break; case FuncNotStartWith: str = i18n("not start with"); break; case FuncEndWith: str = i18n("end with"); break; case FuncNotEndWith: str = i18n("not end with"); break; case FuncNone: str = i18n("none"); break; case FuncContains: str = i18n("contains"); break; case FuncContainsNot: str = i18n("not contains"); break; case FuncRegExp: str = i18n("has regexp"); break; case FuncNotRegExp: str = i18n("not regexp"); break; } return str; } void SearchRule::generateSieveScript(QStringList &requireModules, QString &code) { QString contentStr = mContents; if (mField == "") { QString comparaison; int offset = 0; switch (mFunction) { case FuncEquals: comparaison = QLatin1Char('"') + i18n("size equals not supported") + QLatin1Char('"'); break; case FuncNotEqual: comparaison = QLatin1Char('"') + i18n("size not equals not supported") + QLatin1Char('"'); break; case FuncIsGreater: comparaison = QStringLiteral(":over"); break; case FuncIsLessOrEqual: comparaison = QStringLiteral(":under"); offset = 1; break; case FuncIsLess: comparaison = QStringLiteral(":under"); break; case FuncIsGreaterOrEqual: comparaison = QStringLiteral(":over"); offset = -1; break; case FuncIsInAddressbook: case FuncIsNotInAddressbook: case FuncIsInCategory: case FuncIsNotInCategory: case FuncHasAttachment: case FuncHasNoAttachment: case FuncStartWith: case FuncNotStartWith: case FuncEndWith: case FuncNotEndWith: case FuncNone: case FuncContains: case FuncContainsNot: case FuncRegExp: case FuncNotRegExp: code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1String(mField), conditionToString(mFunction)) + QLatin1Char('"'); return; } code += QStringLiteral("size %1 %2K").arg(comparaison).arg(QString::number(mContents.toInt() + offset)); } else if (mField == "") { //TODO ? code += QLatin1Char('"') + i18n(" not implemented/supported") + QLatin1Char('"'); } else if (mField == "") { //TODO ? code += QLatin1Char('"') + i18n(" not implemented/supported") + QLatin1Char('"'); } else if (mField == "contents") { //TODO ? code += QLatin1Char('"') + i18n(" not implemented/supported") + QLatin1Char('"'); } else if (mField == "") { //TODO ? code += QLatin1Char('"') + i18n(" not implemented/supported") + QLatin1Char('"'); } else if (mField == "") { //TODO ? code += QLatin1Char('"') + i18n(" not implemented/supported") + QLatin1Char('"'); } else if (mField == "") { //TODO ? code += QLatin1Char('"') + i18n(" not implemented/supported") + QLatin1Char('"'); } else if (mField == "") { code += QLatin1Char('"') + i18n(" is not supported") + QLatin1Char('"'); } else if (mField == "") { //TODO ? code += i18n(" not implemented/supported"); } else if (mField == "") { if (!requireModules.contains(QLatin1String("body"))) { requireModules << QStringLiteral("body"); } QString comparaison; bool negative = false; switch (mFunction) { case FuncNone: break; case FuncContains: comparaison = QStringLiteral(":contains"); break; case FuncContainsNot: negative = true; comparaison = QStringLiteral(":contains"); break; case FuncEquals: comparaison = QStringLiteral(":is"); break; case FuncNotEqual: comparaison = QStringLiteral(":is"); negative = true; break; case FuncRegExp: comparaison = QStringLiteral(":regex"); if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } break; case FuncNotRegExp: if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } comparaison = QStringLiteral(":regex"); negative = true; break; case FuncStartWith: comparaison = QStringLiteral(":regex"); if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } contentStr = QLatin1Char('^') + contentStr; break; case FuncNotStartWith: comparaison = QStringLiteral(":regex"); if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } comparaison = QStringLiteral(":regex"); contentStr = QLatin1Char('^') + contentStr; negative = true; break; case FuncEndWith: comparaison = QStringLiteral(":regex"); if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } comparaison = QStringLiteral(":regex"); contentStr = contentStr + QLatin1Char('$'); break; case FuncNotEndWith: comparaison = QStringLiteral(":regex"); if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } comparaison = QStringLiteral(":regex"); contentStr = contentStr + QLatin1Char('$'); negative = true; break; case FuncIsGreater: case FuncIsLessOrEqual: case FuncIsLess: case FuncIsGreaterOrEqual: case FuncIsInAddressbook: case FuncIsNotInAddressbook: case FuncIsInCategory: case FuncIsNotInCategory: case FuncHasAttachment: case FuncHasNoAttachment: code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1String(mField), conditionToString(mFunction)) + QLatin1Char('"'); return; } code += (negative ? QStringLiteral("not ") : QString()) + QStringLiteral("body :text %1 \"%2\"").arg(comparaison).arg(contentStr); } else { QString comparaison; bool negative = false; switch (mFunction) { case FuncNone: break; case FuncContains: comparaison = QStringLiteral(":contains"); break; case FuncContainsNot: negative = true; comparaison = QStringLiteral(":contains"); break; case FuncEquals: comparaison = QStringLiteral(":is"); break; case FuncNotEqual: comparaison = QStringLiteral(":is"); negative = true; break; case FuncRegExp: comparaison = QStringLiteral(":regex"); if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } break; case FuncNotRegExp: if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } comparaison = QStringLiteral(":regex"); negative = true; break; case FuncStartWith: comparaison = QStringLiteral(":regex"); if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } contentStr = QLatin1Char('^') + contentStr; break; case FuncNotStartWith: comparaison = QStringLiteral(":regex"); if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } comparaison = QStringLiteral(":regex"); contentStr = QLatin1Char('^') + contentStr; negative = true; break; case FuncEndWith: comparaison = QStringLiteral(":regex"); if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } comparaison = QStringLiteral(":regex"); contentStr = contentStr + QLatin1Char('$'); break; case FuncNotEndWith: comparaison = QStringLiteral(":regex"); if (!requireModules.contains(QLatin1String("regex"))) { requireModules << QStringLiteral("regex"); } comparaison = QStringLiteral(":regex"); contentStr = contentStr + QLatin1Char('$'); negative = true; break; case FuncIsGreater: case FuncIsLessOrEqual: case FuncIsLess: case FuncIsGreaterOrEqual: case FuncIsInAddressbook: case FuncIsNotInAddressbook: case FuncIsInCategory: case FuncIsNotInCategory: case FuncHasAttachment: case FuncHasNoAttachment: code += QLatin1Char('"') + i18n("\"%1\" is not supported with condition \"%2\"", QLatin1String(mField), conditionToString(mFunction)) + QLatin1Char('"'); return; } code += (negative ? QStringLiteral("not ") : QString()) + QStringLiteral("header %1 \"%2\" \"%3\"").arg(comparaison).arg(QLatin1String(mField)).arg(contentStr); } } void SearchRule::setFunction(Function function) { mFunction = function; } SearchRule::Function SearchRule::function() const { return mFunction; } void SearchRule::setField(const QByteArray &field) { mField = field; } QByteArray SearchRule::field() const { return mField; } void SearchRule::setContents(const QString &contents) { mContents = contents; } QString SearchRule::contents() const { return mContents; } const QString SearchRule::asString() const { QString result = QLatin1String("\"") + QString::fromLatin1(mField) + QLatin1String("\" <"); result += functionToString(mFunction); result += QStringLiteral("> \"") + mContents + QStringLiteral("\""); return result; } Akonadi::SearchTerm::Condition SearchRule::akonadiComparator() const { switch (function()) { case SearchRule::FuncContains: case SearchRule::FuncContainsNot: return Akonadi::SearchTerm::CondContains; case SearchRule::FuncEquals: case SearchRule::FuncNotEqual: return Akonadi::SearchTerm::CondEqual; case SearchRule::FuncIsGreater: return Akonadi::SearchTerm::CondGreaterThan; case SearchRule::FuncIsGreaterOrEqual: return Akonadi::SearchTerm::CondGreaterOrEqual; case SearchRule::FuncIsLess: return Akonadi::SearchTerm::CondLessThan; case SearchRule::FuncIsLessOrEqual: return Akonadi::SearchTerm::CondLessOrEqual; case SearchRule::FuncRegExp: case SearchRule::FuncNotRegExp: //TODO is this sufficient? return Akonadi::SearchTerm::CondContains; case SearchRule::FuncStartWith: case SearchRule::FuncNotStartWith: case SearchRule::FuncEndWith: case SearchRule::FuncNotEndWith: //TODO is this sufficient? return Akonadi::SearchTerm::CondContains; default: qCDebug(MAILCOMMON_LOG) << "Unhandled function type: " << function(); } return Akonadi::SearchTerm::CondEqual; } bool SearchRule::isNegated() const { bool negate = false; switch (function()) { case SearchRule::FuncContainsNot: case SearchRule::FuncNotEqual: case SearchRule::FuncNotRegExp: case SearchRule::FuncHasNoAttachment: case SearchRule::FuncIsNotInCategory: case SearchRule::FuncIsNotInAddressbook: case SearchRule::FuncNotStartWith: case SearchRule::FuncNotEndWith: negate = true; default: break; } return negate; } QDataStream &SearchRule::operator >>(QDataStream &s) const { s << mField << functionToString(mFunction) << mContents; return s; } diff --git a/src/search/searchrule/searchrule.h b/src/search/searchrule/searchrule.h index f1c6aac..5922b09 100644 --- a/src/search/searchrule/searchrule.h +++ b/src/search/searchrule/searchrule.h @@ -1,273 +1,274 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef SEARCHRULE_H #define SEARCHRULE_H #include "mailcommon_export.h" #include #include class KConfigGroup; namespace MailCommon { /** * @short This class represents one search pattern rule. * Incoming mail is sent through the list of mail filter * rules before it is placed in the associated mail folder (usually "inbox"). * This class represents one mail filter rule. It is also used to represent * a search rule as used by the search dialog and folders. */ class MAILCOMMON_EXPORT SearchRule { public: /** * Defines a pointer to a search rule. */ typedef std::shared_ptr Ptr; /** * Describes operators for comparison of field and contents. * * If you change the order or contents of the enum: do not forget * to change funcConfigNames[], sFilterFuncList and matches() * in SearchRule, too. * Also, it is assumed that these functions come in pairs of logical * opposites (ie. "=" <-> "!=", ">" <-> "<=", etc.). */ enum Function { FuncNone = -1, FuncContains = 0, FuncContainsNot, FuncEquals, FuncNotEqual, FuncRegExp, FuncNotRegExp, FuncIsGreater, FuncIsLessOrEqual, FuncIsLess, FuncIsGreaterOrEqual, FuncIsInAddressbook, FuncIsNotInAddressbook, FuncIsInCategory, FuncIsNotInCategory, FuncHasAttachment, FuncHasNoAttachment, FuncStartWith, FuncNotStartWith, FuncEndWith, FuncNotEndWith }; enum RequiredPart { Envelope = 0, Header, CompleteMessage }; /** * Creates new new search rule. * * @param field The field to search in. * @param function The function to use for searching. * @param contents The contents to search for. */ explicit SearchRule(const QByteArray &field = QByteArray(), Function function = FuncContains, const QString &contents = QString()); /** * Creates a new search rule from an @p other rule. */ SearchRule(const SearchRule &other); /** * Initializes this rule with an @p other rule. */ const SearchRule &operator=(const SearchRule &other); /** * Creates a new search rule of a certain type by instantiating the * appropriate subclass depending on the @p field. * * @param field The field to search in. * @param function The function to use for searching. * @param contents The contents to search for. */ static SearchRule::Ptr createInstance(const QByteArray &field = QByteArray(), Function function = FuncContains, const QString &contents = QString()); /** * Creates a new search rule of a certain type by instantiating the * appropriate subclass depending on the @p field. * * @param field The field to search in. * @param function The name of the function to use for searching. * @param contents The contents to search for. */ static SearchRule::Ptr createInstance(const QByteArray &field, const char *function, const QString &contents); /** * Creates a new search rule by cloning an @p other rule. */ static SearchRule::Ptr createInstance(const SearchRule &other); /** * Creates a new search rule by deseralizing its structure from a data @p stream. */ static SearchRule::Ptr createInstance(QDataStream &stream); /** * Creates a new search rule from a given config @p group. * * @param group The config group to read the structure from. * @param index The identifier that is used to distinguish * rules within a single config group. * * @note This function does no validation of the data obtained * from the config file. You should call isEmpty yourself * if you need valid rules. */ static SearchRule::Ptr createInstanceFromConfig(const KConfigGroup &group, int index); /** * Destroys the search rule. */ virtual ~SearchRule(); /** * Tries to match the rule against the KMime::Message in the * given @p item. * * @return true if the rule matched, false otherwise. * * @note Must be implemented by subclasses. */ virtual bool matches(const Akonadi::Item &item) const = 0; /** * Determines whether the rule is worth considering. * It isn't if either the field is not set or the contents is empty. * The calling code should make sure that it's rule list contains * only non-empty rules, as matches doesn't check this. */ virtual bool isEmpty() const = 0; /** * Returns the required part from the item that is needed for the search to * operate. See @ref RequiredPart */ virtual SearchRule::RequiredPart requiredPart() const = 0; /** * Saves the object into a given config @p group. * * @param index The identifier that is used to distinguish * rules within a single config group. * * @note This function will happily write itself even when it's * not valid, assuming higher layers to Do The Right Thing(TM). */ void writeConfig(KConfigGroup &group, int index) const; void generateSieveScript(QStringList &requireModules, QString &code); /** * Sets the filter @p function of the rule. */ void setFunction(Function function); /** * Returns the filter function of the rule. */ Function function() const; /** * Sets the message header field @p name. * * @note Make sure the name contains no trailing ':'. */ void setField(const QByteArray &name); /** * Returns the message header field name (without the trailing ':'). * * There are also six pseudo-headers: * @li \: Try to match against the whole message. * @li \: Try to match against the body of the message. * @li \: Try to match against any header field. * @li \: Try to match against both To: and Cc: header fields. * @li \: Try to match against size of message (numerical). * @li \: Try to match against age of message (numerical). * @li \: Try to match against status of message (status). * @li \: Try to match against message tags. */ QByteArray field() const; /** * Set the @p contents of the rule. * * This can be either a substring to search for in * or a regexp pattern to match against the header. */ void setContents(const QString &contents); /** * Returns the contents of the rule. */ QString contents() const; /** * Returns the rule as string for debugging purpose */ const QString asString() const; /** * Adds query terms to the given term group. */ virtual void addQueryTerms(Akonadi::SearchTerm &groupTerm, bool &emptyIsNotAnError) const { Q_UNUSED(groupTerm); Q_UNUSED(emptyIsNotAnError); } QDataStream &operator>>(QDataStream &) const; virtual QString informationAboutNotValidRules() const { return QString(); } protected: /** * Helper that returns whether the rule has a negated function. */ bool isNegated() const; /** * Converts the rule function into the corresponding Akonadi query operator. */ Akonadi::SearchTerm::Condition akonadiComparator() const; protected: QString quote(const QString &content) const; private: static Function configValueToFunc(const char *); static QString functionToString(Function); QString conditionToString(Function function); QByteArray mField; Function mFunction; QString mContents; }; } #endif // SEARCHRULE_H diff --git a/src/search/searchrule/searchruledate.cpp b/src/search/searchrule/searchruledate.cpp index dc2014c..8f3eb5c 100644 --- a/src/search/searchrule/searchruledate.cpp +++ b/src/search/searchrule/searchruledate.cpp @@ -1,103 +1,104 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "searchruledate.h" #include "filter/filterlog.h" using MailCommon::FilterLog; #include #include using namespace MailCommon; SearchRuleDate::SearchRuleDate(const QByteArray &field, Function func, const QString &contents) : SearchRule(field, func, contents) { } QString SearchRuleDate::informationAboutNotValidRules() const { return i18n("Date is not valid."); } bool SearchRuleDate::isEmpty() const { return !QDate::fromString(contents(), Qt::ISODate).isValid(); } bool SearchRuleDate::matches(const Akonadi::Item &item) const { if (!item.hasPayload()) { return false; } const KMime::Message::Ptr msg = item.payload(); const QDate msgDate = msg->date()->dateTime().date(); const QDate dateValue = QDate::fromString(contents(), Qt::ISODate); bool rc = matchesInternal(dateValue, msgDate); if (FilterLog::instance()->isLogging()) { QString msg = (rc ? QStringLiteral("1 = ") : QStringLiteral("0 = ")); msg += FilterLog::recode(asString()); msg += QStringLiteral(" ( ") + contents() + QStringLiteral(" )"); //TODO change with locale? FilterLog::instance()->add(msg, FilterLog::RuleResult); } return rc; } bool SearchRuleDate::matchesInternal(const QDate &dateValue, const QDate &msgDate) const { switch (function()) { case SearchRule::FuncEquals: return dateValue == msgDate; case SearchRule::FuncNotEqual: return dateValue != msgDate; case FuncIsGreater: return msgDate > dateValue; case FuncIsLessOrEqual: return msgDate <= dateValue; case FuncIsLess: return msgDate < dateValue; case FuncIsGreaterOrEqual: return msgDate >= dateValue; default: ; } return false; } SearchRule::RequiredPart SearchRuleDate::requiredPart() const { return SearchRule::Envelope; } void SearchRuleDate::addQueryTerms(Akonadi::SearchTerm &groupTerm, bool &emptyIsNotAnError) const { using namespace Akonadi; emptyIsNotAnError = false; const QDate date = QDate::fromString(contents(), Qt::ISODate); EmailSearchTerm term(EmailSearchTerm::HeaderOnlyDate, date, akonadiComparator()); term.setIsNegated(isNegated()); groupTerm.addSubTerm(term); } diff --git a/src/search/searchrule/searchruledate.h b/src/search/searchrule/searchruledate.h index 7e94bfe..62734d6 100644 --- a/src/search/searchrule/searchruledate.h +++ b/src/search/searchrule/searchruledate.h @@ -1,68 +1,69 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef SEARCHRULEDATE_H #define SEARCHRULEDATE_H #include "searchpattern.h" #include namespace MailCommon { class SearchRuleDate : public SearchRule { public: /** * Creates new date search rule. * * @param field The field to search in. * @param function The function to use for searching. * @param contents The contents to search for. */ explicit SearchRuleDate(const QByteArray &field = QByteArray(), Function function = FuncContains, const QString &contents = QString()); /** * @copydoc SearchRule::isEmpty() */ bool isEmpty() const override; /** * @copydoc SearchRule::matches() */ bool matches(const Akonadi::Item &item) const override; /** * @copydoc SearchRule::requiredPart() */ RequiredPart requiredPart() const override; // Optimized matching not implemented, will use the unoptimized matching // from SearchRule using SearchRule::matches; /** * A helper method for the main matches() method. * Does the actual comparing. */ bool matchesInternal(const QDate &dateValue, const QDate &msgDate) const; /** * @copydoc SearchRule::addQueryTerms() */ void addQueryTerms(Akonadi::SearchTerm &groupTerm, bool &emptyIsNotAnError) const override; QString informationAboutNotValidRules() const override; }; } #endif // SEARCHRULEDATE_H diff --git a/src/search/searchrule/searchruleencryption.cpp b/src/search/searchrule/searchruleencryption.cpp index d0ffb0f..48fea00 100644 --- a/src/search/searchrule/searchruleencryption.cpp +++ b/src/search/searchrule/searchruleencryption.cpp @@ -1,66 +1,67 @@ /* Copyright (c) 2017 Daniel Vrátil - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "searchruleencryption.h" #include "util/cryptoutils.h" #include "filter/filterlog.h" using MailCommon::FilterLog; #include using namespace MailCommon; SearchRuleEncryption::SearchRuleEncryption(const QByteArray &field, Function func, const QString &contents) : SearchRule(field, func, contents) { } SearchRuleEncryption::~SearchRuleEncryption() { } bool SearchRuleEncryption::isEmpty() const { // It's true or false, so it can't be empty return false; } bool SearchRuleEncryption::matches(const Akonadi::Item &item) const { const bool shouldBeEncrypted = (function() == FuncEquals); if (!item.hasPayload()) { return false; } const auto msg = item.payload(); bool rc = (shouldBeEncrypted == CryptoUtils::isEncrypted(msg.data())); if (FilterLog::instance()->isLogging()) { QString msg = (rc ? QStringLiteral("1 = ") : QStringLiteral("0 = ")); msg += FilterLog::recode(asString()); msg += QStringLiteral(" ( ") + contents() + QStringLiteral(" )"); //TODO change with locale? FilterLog::instance()->add(msg, FilterLog::RuleResult); } return rc; } SearchRule::RequiredPart SearchRuleEncryption::requiredPart() const { // We can't detect inline signatures just from headers, we need to inspect // the entire body. return SearchRule::CompleteMessage; } diff --git a/src/search/searchrule/searchruleencryption.h b/src/search/searchrule/searchruleencryption.h index 6e9f6eb..b225252 100644 --- a/src/search/searchrule/searchruleencryption.h +++ b/src/search/searchrule/searchruleencryption.h @@ -1,38 +1,39 @@ /* Copyright (c) 2017 Daniel Vrátil - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_SEARCH_SEARCHRULEENCRYPTION_H_ #define MAILCOMMON_SEARCH_SEARCHRULEENCRYPTION_H_ #include "searchpattern.h" namespace MailCommon { class SearchRuleEncryption : public SearchRule { public: explicit SearchRuleEncryption(const QByteArray &field = { }, Function func = FuncEquals, const QString &contents = {}); ~SearchRuleEncryption() override; bool isEmpty() const override; bool matches(const Akonadi::Item &item) const override; SearchRule::RequiredPart requiredPart() const override; }; } #endif diff --git a/src/search/searchrule/searchrulenumerical.cpp b/src/search/searchrule/searchrulenumerical.cpp index fe514a7..9ab4158 100644 --- a/src/search/searchrule/searchrulenumerical.cpp +++ b/src/search/searchrule/searchrulenumerical.cpp @@ -1,157 +1,158 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "searchrulenumerical.h" #include "filter/filterlog.h" using MailCommon::FilterLog; #include #include #include #include using namespace MailCommon; SearchRuleNumerical::SearchRuleNumerical(const QByteArray &field, Function func, const QString &contents) : SearchRule(field, func, contents) { } bool SearchRuleNumerical::isEmpty() const { bool ok = false; contents().toLongLong(&ok); return !ok; } bool SearchRuleNumerical::matches(const Akonadi::Item &item) const { if (!item.hasPayload()) { return false; } const KMime::Message::Ptr msg = item.payload(); QString msgContents; qint64 numericalMsgContents = 0; qint64 numericalValue = 0; if (qstricmp(field().constData(), "") == 0) { numericalMsgContents = item.size(); numericalValue = contents().toLongLong(); msgContents.setNum(numericalMsgContents); } else if (qstricmp(field().constData(), "") == 0) { QDateTime msgDateTime = msg->date()->dateTime(); numericalMsgContents = msgDateTime.daysTo(QDateTime::currentDateTime()); numericalValue = contents().toInt(); msgContents.setNum(numericalMsgContents); } else { return false; } bool rc = matchesInternal(numericalValue, numericalMsgContents, msgContents); if (FilterLog::instance()->isLogging()) { QString msg = (rc ? QStringLiteral("1 = ") : QStringLiteral("0 = ")); msg += FilterLog::recode(asString()); msg += QStringLiteral(" ( ") + QString::number(numericalMsgContents) + QStringLiteral(" )"); FilterLog::instance()->add(msg, FilterLog::RuleResult); } return rc; } SearchRule::RequiredPart SearchRuleNumerical::requiredPart() const { return SearchRule::Envelope; } bool SearchRuleNumerical::matchesInternal(long numericalValue, long numericalMsgContents, const QString &msgContents) const { switch (function()) { case SearchRule::FuncEquals: return numericalValue == numericalMsgContents; case SearchRule::FuncNotEqual: return numericalValue != numericalMsgContents; case SearchRule::FuncContains: return msgContents.contains(contents(), Qt::CaseInsensitive); case SearchRule::FuncContainsNot: return !msgContents.contains(contents(), Qt::CaseInsensitive); case SearchRule::FuncRegExp: { QRegExp regexp(contents(), Qt::CaseInsensitive); return regexp.indexIn(msgContents) >= 0; } case SearchRule::FuncNotRegExp: { QRegExp regexp(contents(), Qt::CaseInsensitive); return regexp.indexIn(msgContents) < 0; } case FuncIsGreater: return numericalMsgContents > numericalValue; case FuncIsLessOrEqual: return numericalMsgContents <= numericalValue; case FuncIsLess: return numericalMsgContents < numericalValue; case FuncIsGreaterOrEqual: return numericalMsgContents >= numericalValue; case FuncIsInAddressbook: // since email-addresses are not numerical, I settle for false here return false; case FuncIsNotInAddressbook: return false; default: ; } return false; } void SearchRuleNumerical::addQueryTerms(Akonadi::SearchTerm &groupTerm, bool &emptyIsNotAnError) const { using namespace Akonadi; emptyIsNotAnError = false; if (qstricmp(field().constData(), "") == 0) { EmailSearchTerm term(EmailSearchTerm::ByteSize, contents().toInt(), akonadiComparator()); term.setIsNegated(isNegated()); groupTerm.addSubTerm(term); } else if (qstricmp(field().constData(), "") == 0) { QDate date(QDate::currentDate()); date = date.addDays(contents().toInt()); EmailSearchTerm term(EmailSearchTerm::HeaderOnlyDate, date, akonadiComparator()); term.setIsNegated(isNegated()); groupTerm.addSubTerm(term); } } QString SearchRuleNumerical::informationAboutNotValidRules() const { return i18n("Content is not a number."); } diff --git a/src/search/searchrule/searchrulenumerical.h b/src/search/searchrule/searchrulenumerical.h index 282067f..8c335ac 100644 --- a/src/search/searchrule/searchrulenumerical.h +++ b/src/search/searchrule/searchrulenumerical.h @@ -1,74 +1,75 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef SEARCHRULENUMERICAL_H #define SEARCHRULENUMERICAL_H #include "searchpattern.h" #include namespace MailCommon { /** * @short This class represents a search pattern rule operating on numerical values. * * This class represents a search to be performed against a numerical value, * such as the age of the message in days or its size. */ class SearchRuleNumerical : public SearchRule { public: /** * Creates new numerical search rule. * * @param field The field to search in. * @param function The function to use for searching. * @param contents The contents to search for. */ explicit SearchRuleNumerical(const QByteArray &field = QByteArray(), Function function = FuncContains, const QString &contents = QString()); /** * @copydoc SearchRule::isEmpty() */ bool isEmpty() const override; /** * @copydoc SearchRule::matches() */ bool matches(const Akonadi::Item &item) const override; /** * @copydoc SearchRule::requiredPart() */ RequiredPart requiredPart() const override; // Optimized matching not implemented, will use the unoptimized matching // from SearchRule using SearchRule::matches; /** * A helper method for the main matches() method. * Does the actual comparing. */ bool matchesInternal(long numericalValue, long numericalContents, const QString &contents) const; /** * @copydoc SearchRule::addQueryTerms() */ void addQueryTerms(Akonadi::SearchTerm &groupTerm, bool &emptyIsNotAnError) const override; QString informationAboutNotValidRules() const override; }; } #endif // SEARCHRULENUMERICAL_H diff --git a/src/search/searchrule/searchrulestatus.cpp b/src/search/searchrule/searchrulestatus.cpp index 6095eb7..354edad 100644 --- a/src/search/searchrule/searchrulestatus.cpp +++ b/src/search/searchrule/searchrulestatus.cpp @@ -1,178 +1,179 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "searchrulestatus.h" #include "filter/filterlog.h" using MailCommon::FilterLog; #include using namespace MailCommon; struct _statusNames { const char *name; Akonadi::MessageStatus status; }; static struct _statusNames statusNames[] = { { "Important", Akonadi::MessageStatus::statusImportant() }, { "Unread", Akonadi::MessageStatus::statusUnread() }, { "Read", Akonadi::MessageStatus::statusRead() }, { "Deleted", Akonadi::MessageStatus::statusDeleted() }, { "Replied", Akonadi::MessageStatus::statusReplied() }, { "Forwarded", Akonadi::MessageStatus::statusForwarded() }, { "Queued", Akonadi::MessageStatus::statusQueued() }, { "Sent", Akonadi::MessageStatus::statusSent() }, { "Watched", Akonadi::MessageStatus::statusWatched() }, { "Ignored", Akonadi::MessageStatus::statusIgnored() }, { "Action Item", Akonadi::MessageStatus::statusToAct() }, { "Spam", Akonadi::MessageStatus::statusSpam() }, { "Ham", Akonadi::MessageStatus::statusHam() }, { "Has Attachment", Akonadi::MessageStatus::statusHasAttachment() } }; static const int numStatusNames = sizeof statusNames / sizeof(struct _statusNames); QString englishNameForStatus(Akonadi::MessageStatus status) { for (int i = 0; i < numStatusNames; ++i) { if (statusNames[i].status == status) { return QString::fromLatin1(statusNames[i].name); } } return QString(); } SearchRuleStatus::SearchRuleStatus(const QByteArray &field, Function func, const QString &aContents) : SearchRule(field, func, aContents) { // the values are always in english, both from the conf file as well as // the patternedit gui mStatus = statusFromEnglishName(aContents); } SearchRuleStatus::SearchRuleStatus(Akonadi::MessageStatus status, Function func) : SearchRule("", func, englishNameForStatus(status)) { mStatus = status; } Akonadi::MessageStatus SearchRuleStatus::statusFromEnglishName(const QString &aStatusString) { for (int i = 0; i < numStatusNames; ++i) { if (!aStatusString.compare(QString::fromLatin1(statusNames[i].name))) { return statusNames[i].status; } } Akonadi::MessageStatus unknown; return unknown; } QString SearchRuleStatus::informationAboutNotValidRules() const { //TODO return QStringLiteral(""); } bool SearchRuleStatus::isEmpty() const { return field().trimmed().isEmpty() || contents().isEmpty(); } bool SearchRuleStatus::matches(const Akonadi::Item &item) const { Akonadi::MessageStatus status; status.setStatusFromFlags(item.flags()); bool rc = false; switch (function()) { case FuncEquals: // fallthrough. So that " 'is' 'read'" works case FuncContains: if (status & mStatus) { rc = true; } break; case FuncNotEqual: // fallthrough. So that " 'is not' 'read'" works case FuncContainsNot: if (!(status & mStatus)) { rc = true; } break; // FIXME what about the remaining funcs, how can they make sense for // stati? default: break; } if (FilterLog::instance()->isLogging()) { QString msg = (rc ? QStringLiteral("1 = ") : QStringLiteral("0 = ")); msg += FilterLog::recode(asString()); FilterLog::instance()->add(msg, FilterLog::RuleResult); } return rc; } SearchRule::RequiredPart SearchRuleStatus::requiredPart() const { return SearchRule::Envelope; } void SearchRuleStatus::addQueryTerms(Akonadi::SearchTerm &groupTerm, bool &emptyIsNotAnError) const { using namespace Akonadi; emptyIsNotAnError = true; //TODO double check that isRead also works if (!mStatus.statusFlags().isEmpty()) { EmailSearchTerm term(EmailSearchTerm::MessageStatus, mStatus.statusFlags().toList().first(), akonadiComparator()); term.setIsNegated(isNegated()); groupTerm.addSubTerm(term); } else { //Special case Unread Akonadi::MessageStatus status; status.setRead(true); EmailSearchTerm term(EmailSearchTerm::MessageStatus, status.statusFlags().toList().first(), akonadiComparator()); term.setIsNegated(!isNegated()); groupTerm.addSubTerm(term); } } diff --git a/src/search/searchrule/searchrulestatus.h b/src/search/searchrule/searchrulestatus.h index 58daba0..fbfdeae 100644 --- a/src/search/searchrule/searchrulestatus.h +++ b/src/search/searchrule/searchrulestatus.h @@ -1,94 +1,95 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef SEARCHRULESTATUS_H #define SEARCHRULESTATUS_H #include #include "mailcommon/searchpattern.h" #include namespace MailCommon { //TODO: Check if the below one is needed or not! // The below are used in several places and here so they are accessible. struct MessageStatusInfo { const char *text; const char *icon; }; // If you change the ordering here; also do it in the enum below static const MessageStatusInfo StatusValues[] = { { I18N_NOOP2("message status", "Important"), "emblem-important" }, { I18N_NOOP2("message status", "Action Item"), "mail-task" }, { I18N_NOOP2("message status", "Unread"), "mail-unread" }, { I18N_NOOP2("message status", "Read"), "mail-read" }, { I18N_NOOP2("message status", "Deleted"), "mail-deleted" }, { I18N_NOOP2("message status", "Replied"), "mail-replied" }, { I18N_NOOP2("message status", "Forwarded"), "mail-forwarded" }, { I18N_NOOP2("message status", "Queued"), "mail-queued" }, { I18N_NOOP2("message status", "Sent"), "mail-sent" }, { I18N_NOOP2("message status", "Watched"), "mail-thread-watch" }, { I18N_NOOP2("message status", "Ignored"), "mail-thread-ignored" }, { I18N_NOOP2("message status", "Spam"), "mail-mark-junk" }, { I18N_NOOP2("message status", "Ham"), "mail-mark-notjunk" }, { I18N_NOOP2("message status", "Has Attachment"), "mail-attachment" } //must be last }; static const int StatusValueCount = sizeof(StatusValues) / sizeof(MessageStatusInfo); // we want to show all status entries in the quick search bar, but only the // ones up to attachment in the search/filter dialog, because there the // attachment case is handled separately. static const int StatusValueCountWithoutHidden = StatusValueCount - 1; /** * This class represents a search to be performed against the status of a * messsage. The status is represented by a bitfield. * * @short This class represents a search pattern rule operating on message * status. */ class MAILCOMMON_EXPORT SearchRuleStatus : public SearchRule { public: explicit SearchRuleStatus(const QByteArray &field = QByteArray(), Function function = FuncContains, const QString &contents = QString()); explicit SearchRuleStatus(Akonadi::MessageStatus status, Function function = FuncContains); bool isEmpty() const override; bool matches(const Akonadi::Item &item) const override; /** * @copydoc SearchRule::requiredPart() */ RequiredPart requiredPart() const override; void addQueryTerms(Akonadi::SearchTerm &groupTerm, bool &emptyIsNotAnError) const override; //Not possible to implement optimized form for status searching using SearchRule::matches; static Akonadi::MessageStatus statusFromEnglishName(const QString &); QString informationAboutNotValidRules() const override; private: Akonadi::MessageStatus mStatus; }; } #endif // SEARCHRULESTATUS_H diff --git a/src/search/searchrule/searchrulestring.cpp b/src/search/searchrule/searchrulestring.cpp index 56915ee..86bb9b8 100644 --- a/src/search/searchrule/searchrulestring.cpp +++ b/src/search/searchrule/searchrulestring.cpp @@ -1,400 +1,401 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "searchrulestring.h" #include "filter/filterlog.h" using MailCommon::FilterLog; #include #include #include #include #include #include #include #include using namespace MailCommon; SearchRuleString::SearchRuleString(const QByteArray &field, Function func, const QString &contents) : SearchRule(field, func, contents) { } SearchRuleString::SearchRuleString(const SearchRuleString &other) : SearchRule(other) { } const SearchRuleString &SearchRuleString::operator=(const SearchRuleString &other) { if (this == &other) { return *this; } setField(other.field()); setFunction(other.function()); setContents(other.contents()); return *this; } SearchRuleString::~SearchRuleString() { } bool SearchRuleString::isEmpty() const { return field().trimmed().isEmpty() || contents().isEmpty(); } SearchRule::RequiredPart SearchRuleString::requiredPart() const { const QByteArray f = field(); SearchRule::RequiredPart part = Header; if (qstricmp(f.constData(), "") == 0 || qstricmp(f.constData(), "") == 0 || qstricmp(f.constData(), "") == 0 || qstricmp(f.constData(), "subject") == 0 || qstricmp(f.constData(), "from") == 0 || qstricmp(f.constData(), "sender") == 0 || qstricmp(f.constData(), "reply-to") == 0 || qstricmp(f.constData(), "to") == 0 || qstricmp(f.constData(), "cc") == 0 || qstricmp(f.constData(), "bcc") == 0 || qstricmp(f.constData(), "in-reply-to") == 0 || qstricmp(f.constData(), "message-id") == 0 || qstricmp(f.constData(), "references") == 0) { // these fields are directly provided by KMime::Message, no need to fetch the whole Header part part = Envelope; } else if (qstricmp(f.constData(), "") == 0 || qstricmp(f.constData(), "") == 0) { part = CompleteMessage; } return part; } bool SearchRuleString::matches(const Akonadi::Item &item) const { if (isEmpty()) { return false; } if (!item.hasPayload()) { return false; } const KMime::Message::Ptr msg = item.payload(); Q_ASSERT(msg.data()); if (!msg->hasHeader("From")) { msg->parse(); // probably not parsed yet: make sure we can access all headers } QString msgContents; // Show the value used to compare the rules against in the log. // Overwrite the value for complete messages and all headers! bool logContents = true; if (qstricmp(field().constData(), "") == 0) { msgContents = QString::fromUtf8(msg->encodedContent()); logContents = false; } else if (qstricmp(field().constData(), "") == 0) { msgContents = QString::fromUtf8(msg->body()); logContents = false; } else if (qstricmp(field().constData(), "") == 0) { msgContents = QString::fromUtf8(msg->head()); logContents = false; } else if (qstricmp(field().constData(), "") == 0) { // (mmutz 2001-11-05) hack to fix " !contains foo" to // meet user's expectations. See FAQ entry in KDE 2.2.2's KMail // handbook if (function() == FuncEquals || function() == FuncNotEqual) { // do we need to treat this case specially? Ie.: What shall // "equality" mean for recipients. return matchesInternal(msg->to()->asUnicodeString()) || matchesInternal(msg->cc()->asUnicodeString()) || matchesInternal(msg->bcc()->asUnicodeString()); } msgContents = msg->to()->asUnicodeString(); msgContents += QStringLiteral(", ") + msg->cc()->asUnicodeString(); msgContents += QStringLiteral(", ") + msg->bcc()->asUnicodeString(); } else if (qstricmp(field().constData(), "") == 0) { //port? // const Nepomuk2::Resource res( item.url() ); // foreach ( const Nepomuk2::Tag &tag, res.tags() ) { // msgContents += tag.label(); // } logContents = false; } else { // make sure to treat messages with multiple header lines for // the same header correctly msgContents = QStringLiteral(""); if (auto hrd = msg->headerByType(field().constData())) { msgContents = hrd->asUnicodeString(); } } if (function() == FuncIsInAddressbook || function() == FuncIsNotInAddressbook) { // I think only the "from"-field makes sense. msgContents = QStringLiteral(""); if (auto hrd = msg->headerByType(field().constData())) { msgContents = hrd->asUnicodeString(); } if (msgContents.isEmpty()) { return (function() == FuncIsInAddressbook) ? false : true; } } // these two functions need the kmmessage therefore they don't call matchesInternal if (function() == FuncHasAttachment) { return KMime::hasAttachment(msg.data()); } else if (function() == FuncHasNoAttachment) { return !KMime::hasAttachment(msg.data()); } bool rc = matchesInternal(msgContents); if (FilterLog::instance()->isLogging()) { QString msg = (rc ? QStringLiteral("1 = ") : QStringLiteral("0 = ")); msg += FilterLog::recode(asString()); // only log headers bcause messages and bodies can be pretty large if (logContents) { msg += QStringLiteral(" (") + FilterLog::recode(msgContents) + QStringLiteral(")"); } FilterLog::instance()->add(msg, FilterLog::RuleResult); } return rc; } void SearchRuleString::addQueryTerms(Akonadi::SearchTerm &groupTerm, bool &emptyIsNotAnError) const { using namespace Akonadi; emptyIsNotAnError = false; SearchTerm termGroup(SearchTerm::RelOr); if (qstricmp(field().constData(), "subject") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Subject, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "reply-to") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderReplyTo, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Message, contents(), akonadiComparator())); } else if (field() == "") { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Body, contents(), akonadiComparator())); termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Attachment, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderTo, contents(), akonadiComparator())); termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderCC, contents(), akonadiComparator())); termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderBCC, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Headers, contents(), akonadiComparator())); termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::Subject, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "to") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderTo, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "cc") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderCC, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "bcc") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderBCC, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "from") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderFrom, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "list-id") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderListId, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "resent-from") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderResentFrom, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "x-loop") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderXLoop, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "x-mailing-list") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderXMailingList, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "x-spam-flag") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderXSpamFlag, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "organization") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::HeaderOrganization, contents(), akonadiComparator())); } else if (qstricmp(field().constData(), "") == 0) { termGroup.addSubTerm(EmailSearchTerm(EmailSearchTerm::MessageTag, contents(), akonadiComparator())); } // TODO complete for other headers, generic headers if (!termGroup.subTerms().isEmpty()) { termGroup.setIsNegated(isNegated()); groupTerm.addSubTerm(termGroup); } } QString SearchRuleString::informationAboutNotValidRules() const { return i18n("String is empty."); } // helper, does the actual comparing bool SearchRuleString::matchesInternal(const QString &msgContents) const { if (msgContents.isEmpty()) { return false; } switch (function()) { case SearchRule::FuncEquals: return QString::compare(msgContents.toLower(), contents().toLower()) == 0; case SearchRule::FuncNotEqual: return QString::compare(msgContents.toLower(), contents().toLower()) != 0; case SearchRule::FuncContains: return msgContents.contains(contents(), Qt::CaseInsensitive); case SearchRule::FuncContainsNot: return !msgContents.contains(contents(), Qt::CaseInsensitive); case SearchRule::FuncRegExp: { QRegExp regexp(contents(), Qt::CaseInsensitive); return regexp.indexIn(msgContents) >= 0; } case SearchRule::FuncNotRegExp: { QRegExp regexp(contents(), Qt::CaseInsensitive); return regexp.indexIn(msgContents) < 0; } case SearchRule::FuncStartWith: return msgContents.startsWith(contents()); case SearchRule::FuncNotStartWith: return !msgContents.startsWith(contents()); case SearchRule::FuncEndWith: return msgContents.endsWith(contents()); case SearchRule::FuncNotEndWith: return !msgContents.endsWith(contents()); case FuncIsGreater: return QString::compare(msgContents.toLower(), contents().toLower()) > 0; case FuncIsLessOrEqual: return QString::compare(msgContents.toLower(), contents().toLower()) <= 0; case FuncIsLess: return QString::compare(msgContents.toLower(), contents().toLower()) < 0; case FuncIsGreaterOrEqual: return QString::compare(msgContents.toLower(), contents().toLower()) >= 0; case FuncIsInAddressbook: { const QStringList addressList = KEmailAddress::splitAddressList(msgContents.toLower()); QStringList::ConstIterator end(addressList.constEnd()); for (QStringList::ConstIterator it = addressList.constBegin(); (it != end); ++it) { const QString email(KEmailAddress::extractEmailAddress(*it).toLower()); if (!email.isEmpty()) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(); job->setLimit(1); job->setQuery(Akonadi::ContactSearchJob::Email, email); job->exec(); if (!job->contacts().isEmpty()) { return true; } } } return false; } case FuncIsNotInAddressbook: { const QStringList addressList = KEmailAddress::splitAddressList(msgContents.toLower()); QStringList::ConstIterator end(addressList.constEnd()); for (QStringList::ConstIterator it = addressList.constBegin(); (it != end); ++it) { const QString email(KEmailAddress::extractEmailAddress(*it).toLower()); if (!email.isEmpty()) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(); job->setLimit(1); job->setQuery(Akonadi::ContactSearchJob::Email, email); job->exec(); if (job->contacts().isEmpty()) { return true; } } } return false; } case FuncIsInCategory: { QString category = contents(); const QStringList addressList = KEmailAddress::splitAddressList(msgContents.toLower()); QStringList::ConstIterator end(addressList.constEnd()); for (QStringList::ConstIterator it = addressList.constBegin(); it != end; ++it) { const QString email(KEmailAddress::extractEmailAddress(*it).toLower()); if (!email.isEmpty()) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(); job->setQuery(Akonadi::ContactSearchJob::Email, email); job->exec(); const KContacts::Addressee::List contacts = job->contacts(); for (const KContacts::Addressee &contact : contacts) { if (contact.hasCategory(category)) { return true; } } } } return false; } case FuncIsNotInCategory: { QString category = contents(); const QStringList addressList = KEmailAddress::splitAddressList(msgContents.toLower()); QStringList::ConstIterator end(addressList.constEnd()); for (QStringList::ConstIterator it = addressList.constBegin(); it != end; ++it) { const QString email(KEmailAddress::extractEmailAddress(*it).toLower()); if (!email.isEmpty()) { Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(); job->setQuery(Akonadi::ContactSearchJob::Email, email); job->exec(); const KContacts::Addressee::List contacts = job->contacts(); for (const KContacts::Addressee &contact : contacts) { if (contact.hasCategory(category)) { return false; } } } } return true; } default: ; } return false; } diff --git a/src/search/searchrule/searchrulestring.h b/src/search/searchrule/searchrulestring.h index 02c160d..ffe0a00 100644 --- a/src/search/searchrule/searchrulestring.h +++ b/src/search/searchrule/searchrulestring.h @@ -1,87 +1,88 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef SEARCHRULESTRING_H #define SEARCHRULESTRING_H #include "searchpattern.h" #include /** * @short This class represents a search pattern rule operating on a string. * * This class represents a search to be performed against a string. * The string can be either a message header, or a pseudo header, such * as \ */ namespace MailCommon { class SearchRuleString : public SearchRule { public: /** * Creates new new string search rule. * * @param field The field to search in. * @param function The function to use for searching. * @param contents The contents to search for. */ explicit SearchRuleString(const QByteArray &field = QByteArray(), Function function = FuncContains, const QString &contents = QString()); /** * Creates a new string search rule from an @p other rule. */ SearchRuleString(const SearchRuleString &other); /** * Initializes this rule with an @p other rule. */ const SearchRuleString &operator=(const SearchRuleString &other); /** * Destroys the string search rule. */ ~SearchRuleString() override; /** * @copydoc SearchRule::isEmpty() */ bool isEmpty() const override; /** * @copydoc SearchRule::requiredPart() */ RequiredPart requiredPart() const override; /** * @copydoc SearchRule::matches() */ bool matches(const Akonadi::Item &item) const override; /** * A helper method for the main matches() method. * Does the actual comparing. */ bool matchesInternal(const QString &contents) const; /** * @copydoc SearchRule::addQueryTerms() */ void addQueryTerms(Akonadi::SearchTerm &groupTerm, bool &emptyIsNotAnError) const override; QString informationAboutNotValidRules() const override; }; } #endif // SEARCHRULESTRING_H diff --git a/src/search/widgethandler/daterulewidgethandler.cpp b/src/search/widgethandler/daterulewidgethandler.cpp index cff54b9..aee6cb3 100644 --- a/src/search/widgethandler/daterulewidgethandler.cpp +++ b/src/search/widgethandler/daterulewidgethandler.cpp @@ -1,238 +1,239 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "daterulewidgethandler.h" #include "search/searchpattern.h" #include #include #include #include #include #include using namespace MailCommon; static const struct { SearchRule::Function id; const char *displayName; } DateFunctions[] = { { SearchRule::FuncEquals, I18N_NOOP("is equal to") }, { SearchRule::FuncNotEqual, I18N_NOOP("is not equal to") }, { SearchRule::FuncIsGreater, I18N_NOOP("is after") }, { SearchRule::FuncIsLessOrEqual, I18N_NOOP("is before or equal to") }, { SearchRule::FuncIsLess, I18N_NOOP("is before") }, { SearchRule::FuncIsGreaterOrEqual, I18N_NOOP("is after or equal to") } }; static const int DateFunctionCount = sizeof(DateFunctions) / sizeof(*DateFunctions); //--------------------------------------------------------------------------- QWidget *DateRuleWidgetHandler::createFunctionWidget( int number, QStackedWidget *functionStack, const QObject *receiver, bool /*isBalooSearch*/) const { if (number != 0) { return nullptr; } PimCommon::MinimumComboBox *funcCombo = new PimCommon::MinimumComboBox(functionStack); funcCombo->setObjectName(QStringLiteral("dateRuleFuncCombo")); for (int i = 0; i < DateFunctionCount; ++i) { funcCombo->addItem(i18n(DateFunctions[i].displayName)); } funcCombo->adjustSize(); QObject::connect(funcCombo, SIGNAL(activated(int)), receiver, SLOT(slotFunctionChanged())); return funcCombo; } //--------------------------------------------------------------------------- QWidget *DateRuleWidgetHandler::createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const { if (number != 0) { return nullptr; } KDateComboBox *dateCombo = new KDateComboBox(valueStack); dateCombo->setObjectName(QStringLiteral("KDateComboBox")); dateCombo->setOptions(KDateComboBox::SelectDate | KDateComboBox::DatePicker | KDateComboBox::DateKeywords); QObject::connect(dateCombo, SIGNAL(dateChanged(QDate)), receiver, SLOT(slotValueChanged())); return dateCombo; } //--------------------------------------------------------------------------- SearchRule::Function DateRuleWidgetHandler::currentFunction( const QStackedWidget *functionStack) const { const PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("dateRuleFuncCombo")); if (funcCombo && funcCombo->currentIndex() >= 0) { return DateFunctions[funcCombo->currentIndex()].id; } return SearchRule::FuncNone; } //--------------------------------------------------------------------------- SearchRule::Function DateRuleWidgetHandler::function(const QByteArray &field, const QStackedWidget *functionStack) const { if (!handlesField(field)) { return SearchRule::FuncNone; } return currentFunction(functionStack); } //--------------------------------------------------------------------------- QString DateRuleWidgetHandler::currentValue(const QStackedWidget *valueStack) const { const KDateComboBox *dateInput = valueStack->findChild(QStringLiteral("KDateComboBox")); if (dateInput) { return dateInput->date().toString(Qt::ISODate); } return QString(); } //--------------------------------------------------------------------------- QString DateRuleWidgetHandler::value(const QByteArray &field, const QStackedWidget *, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } return currentValue(valueStack); } //--------------------------------------------------------------------------- QString DateRuleWidgetHandler::prettyValue(const QByteArray &field, const QStackedWidget *, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } return currentValue(valueStack); } //--------------------------------------------------------------------------- bool DateRuleWidgetHandler::handlesField(const QByteArray &field) const { return field == ""; } //--------------------------------------------------------------------------- void DateRuleWidgetHandler::reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const { // reset the function combo box PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("dateRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); funcCombo->setCurrentIndex(0); funcCombo->blockSignals(false); } // reset the value widget KDateComboBox *dateInput = valueStack->findChild(QStringLiteral("KDateComboBox")); if (dateInput) { dateInput->blockSignals(true); dateInput->setDate(QDate::currentDate()); dateInput->blockSignals(false); } } //--------------------------------------------------------------------------- bool DateRuleWidgetHandler::setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool /*isBalooSearch*/) const { if (!rule || !handlesField(rule->field())) { reset(functionStack, valueStack); return false; } // set the function const SearchRule::Function func = rule->function(); int funcIndex = 0; for (; funcIndex < DateFunctionCount; ++funcIndex) { if (func == DateFunctions[funcIndex].id) { break; } } PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("dateRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); if (funcIndex < DateFunctionCount) { funcCombo->setCurrentIndex(funcIndex); } else { funcCombo->setCurrentIndex(0); } funcCombo->blockSignals(false); functionStack->setCurrentWidget(funcCombo); } // set the value const QString value = rule->contents(); KDateComboBox *dateInput = valueStack->findChild(QStringLiteral("KDateComboBox")); if (dateInput) { dateInput->blockSignals(true); dateInput->setDate(QDate::fromString(value, Qt::ISODate)); dateInput->blockSignals(false); valueStack->setCurrentWidget(dateInput); } return true; } //--------------------------------------------------------------------------- bool DateRuleWidgetHandler::update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const { if (!handlesField(field)) { return false; } // raise the correct function widget functionStack->setCurrentWidget(functionStack->findChild(QStringLiteral("dateRuleFuncCombo"))); // raise the correct value widget KDateComboBox *dateInput = valueStack->findChild(QStringLiteral("KDateComboBox")); if (dateInput) { valueStack->setCurrentWidget(dateInput); } return true; } diff --git a/src/search/widgethandler/daterulewidgethandler.h b/src/search/widgethandler/daterulewidgethandler.h index f178b00..f8ba43d 100644 --- a/src/search/widgethandler/daterulewidgethandler.h +++ b/src/search/widgethandler/daterulewidgethandler.h @@ -1,58 +1,59 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef DATERULEWIDGETHANDLER_H #define DATERULEWIDGETHANDLER_H #include "interfaces/rulewidgethandler.h" namespace MailCommon { class DateRuleWidgetHandler : public MailCommon::RuleWidgetHandler { public: DateRuleWidgetHandler() : MailCommon::RuleWidgetHandler() { } ~DateRuleWidgetHandler() override { } QWidget *createFunctionWidget(int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const override; QWidget *createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const override; SearchRule::Function function(const QByteArray &field, const QStackedWidget *functionStack) const override; QString value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; QString prettyValue(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; bool handlesField(const QByteArray &field) const override; void reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const override; bool setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool isBalooSearch) const override; bool update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const override; private: SearchRule::Function currentFunction(const QStackedWidget *functionStack) const; QString currentValue(const QStackedWidget *valueStack) const; }; } #endif // DATERULEWIDGETHANDLER_H diff --git a/src/search/widgethandler/encryptionwidgethandler.cpp b/src/search/widgethandler/encryptionwidgethandler.cpp index 71e9c68..e55769d 100644 --- a/src/search/widgethandler/encryptionwidgethandler.cpp +++ b/src/search/widgethandler/encryptionwidgethandler.cpp @@ -1,165 +1,166 @@ /* Copyright (c) 2017 Daniel Vrátil - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "encryptionwidgethandler.h" #include #include #include using namespace MailCommon; static const struct { SearchRule::Function id; const char *displayName; } EncryptionFunctions[] = { { SearchRule::FuncEquals, I18N_NOOP("is") }, { SearchRule::FuncNotEqual, I18N_NOOP("is not") } }; static const int EncryptionFunctionCount = sizeof(EncryptionFunctions) / sizeof(*EncryptionFunctions); EncryptionWidgetHandler::EncryptionWidgetHandler() : RuleWidgetHandler() { } EncryptionWidgetHandler::~EncryptionWidgetHandler() { } QWidget *EncryptionWidgetHandler::createFunctionWidget(int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const { Q_UNUSED(isBalooSearch); if (number != 0) { return nullptr; } auto combo = new PimCommon::MinimumComboBox(functionStack); combo->setObjectName(QStringLiteral("encryptionRuleFuncCombo")); for (int i = 0; i < EncryptionFunctionCount; ++i) { combo->addItem(i18n(EncryptionFunctions[i].displayName)); } combo->adjustSize(); QObject::connect(combo, SIGNAL(activated(int)), receiver, SLOT(slotFunctionChanged())); return combo; } QWidget *EncryptionWidgetHandler::createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const { Q_UNUSED(receiver); if (number != 0) { return nullptr; } auto lbl = new QLabel(i18n("encrypted"), valueStack); lbl->setObjectName(QStringLiteral("encryptionRuleValueLabel")); return lbl; } SearchRule::Function EncryptionWidgetHandler::function(const QByteArray &field, const QStackedWidget *functionStack) const { if (!handlesField(field)) { return SearchRule::FuncNone; } const auto combo = functionStack->findChild(QStringLiteral("encryptionRuleFuncCombo")); if (combo && combo->currentIndex() >= 0) { return EncryptionFunctions[combo->currentIndex()].id; } return SearchRule::FuncNone; } QString EncryptionWidgetHandler::value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const { Q_UNUSED(functionStack); Q_UNUSED(valueStack); if (!handlesField(field)) { return QString(); } return QStringLiteral("is encrypted"); // dummy value } QString EncryptionWidgetHandler::prettyValue(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const { Q_UNUSED(functionStack); Q_UNUSED(valueStack); if (!handlesField(field)) { return QString(); } return i18n("is encrypted"); } bool EncryptionWidgetHandler::handlesField(const QByteArray &field) const { return field == ""; } void EncryptionWidgetHandler::reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const { const auto combo = functionStack->findChild(QStringLiteral("encryptionRuleFuncCombo")); if (combo) { const bool blocked = combo->blockSignals(true); combo->setCurrentIndex(0); combo->blockSignals(blocked); } Q_UNUSED(valueStack); } bool EncryptionWidgetHandler::setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool isBalooSearch) const { Q_UNUSED(isBalooSearch); if (!rule || !handlesField(rule->field())) { reset(functionStack, valueStack); return false; } update("", functionStack, valueStack); const auto combo = functionStack->findChild(QStringLiteral("encryptionRuleFuncCombo")); if (combo) { const bool blocked = combo->blockSignals(true); for (int i = 0; i < EncryptionFunctionCount; ++i) { if (EncryptionFunctions[i].id == rule->function()) { combo->setCurrentIndex(i); break; } } combo->blockSignals(blocked); return true; } return true; } bool EncryptionWidgetHandler::update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const { if (!handlesField(field)) { return false; } functionStack->setCurrentWidget(functionStack->findChild(QStringLiteral("encryptionRuleFuncCombo"))); valueStack->setCurrentWidget(valueStack->findChild(QStringLiteral("encryptionRuleValueLabel"))); return true; } diff --git a/src/search/widgethandler/encryptionwidgethandler.h b/src/search/widgethandler/encryptionwidgethandler.h index afe8c3c..729ab31 100644 --- a/src/search/widgethandler/encryptionwidgethandler.h +++ b/src/search/widgethandler/encryptionwidgethandler.h @@ -1,42 +1,43 @@ /* Copyright (c) 2017 Daniel Vrátil - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_SEARCH_ENCRYPTIONWIDGETHANDLER_H_ #define MAILCOMMON_SEARCH_ENCRYPTIONWIDGETHANDLER_H_ #include "interfaces/rulewidgethandler.h" namespace MailCommon { class EncryptionWidgetHandler : public RuleWidgetHandler { public: explicit EncryptionWidgetHandler(); ~EncryptionWidgetHandler() override; QWidget *createFunctionWidget(int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const override; QWidget *createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const override; MailCommon::SearchRule::Function function(const QByteArray &field, const QStackedWidget *functionStack) const override; bool handlesField(const QByteArray &field) const override; QString prettyValue(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; bool setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const MailCommon::SearchRule::Ptr rule, bool isBalooSearch) const override; void reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const override; bool update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const override; QString value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; }; } #endif diff --git a/src/search/widgethandler/headersrulerwidgethandler.cpp b/src/search/widgethandler/headersrulerwidgethandler.cpp index c072f58..4963a35 100644 --- a/src/search/widgethandler/headersrulerwidgethandler.cpp +++ b/src/search/widgethandler/headersrulerwidgethandler.cpp @@ -1,292 +1,293 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "headersrulerwidgethandler.h" #include #include "search/searchpattern.h" #include #include #include #include using namespace MailCommon; // also see SearchRule::matches() and SearchRule::Function // if you change the following strings! static const struct { SearchRule::Function id; const char *displayName; } HeaderFunctions[] = { { SearchRule::FuncContains, I18N_NOOP("contains") }, { SearchRule::FuncContainsNot, I18N_NOOP("does not contain") }, { SearchRule::FuncEquals, I18N_NOOP("equals") }, { SearchRule::FuncNotEqual, I18N_NOOP("does not equal") }, { SearchRule::FuncStartWith, I18N_NOOP("starts with") }, { SearchRule::FuncNotStartWith, I18N_NOOP("does not start with")}, { SearchRule::FuncEndWith, I18N_NOOP("ends with") }, { SearchRule::FuncNotEndWith, I18N_NOOP("does not end with") }, { SearchRule::FuncRegExp, I18N_NOOP("matches regular expr.") }, { SearchRule::FuncNotRegExp, I18N_NOOP("does not match reg. expr.") }, { SearchRule::FuncIsInAddressbook, I18N_NOOP("is in address book") }, { SearchRule::FuncIsNotInAddressbook, I18N_NOOP("is not in address book") } }; static const int HeadersFunctionCount = sizeof(HeaderFunctions) / sizeof(*HeaderFunctions); //--------------------------------------------------------------------------- QWidget *HeadersRuleWidgetHandler::createFunctionWidget( int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const { if (number != 0) { return nullptr; } PimCommon::MinimumComboBox *funcCombo = new PimCommon::MinimumComboBox(functionStack); funcCombo->setObjectName(QStringLiteral("headerRuleFuncCombo")); for (int i = 0; i < HeadersFunctionCount; ++i) { if (!(isBalooSearch && (HeaderFunctions[i].id == SearchRule::FuncIsInAddressbook || HeaderFunctions[i].id == SearchRule::FuncIsNotInAddressbook))) { funcCombo->addItem(i18n(HeaderFunctions[i].displayName)); } } funcCombo->adjustSize(); QObject::connect(funcCombo, SIGNAL(activated(int)), receiver, SLOT(slotFunctionChanged())); return funcCombo; } //--------------------------------------------------------------------------- QWidget *HeadersRuleWidgetHandler::createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const { if (number == 0) { KLineEdit *lineEdit = new KLineEdit(valueStack); lineEdit->setClearButtonEnabled(true); lineEdit->setTrapReturnKey(true); lineEdit->setObjectName(QStringLiteral("regExpLineEdit")); QObject::connect(lineEdit, SIGNAL(textChanged(QString)), receiver, SLOT(slotValueChanged())); QObject::connect(lineEdit, SIGNAL(returnPressed()), receiver, SLOT(slotReturnPressed())); return lineEdit; } // blank QLabel to hide value widget for in-address-book rule if (number == 1) { QLabel *label = new QLabel(valueStack); label->setObjectName(QStringLiteral("headerRuleValueHider")); label->setBuddy(valueStack); return label; } return nullptr; } //--------------------------------------------------------------------------- SearchRule::Function HeadersRuleWidgetHandler::currentFunction( const QStackedWidget *functionStack) const { const PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("headerRuleFuncCombo")); if (funcCombo && funcCombo->currentIndex() >= 0) { return HeaderFunctions[funcCombo->currentIndex()].id; } return SearchRule::FuncNone; } //--------------------------------------------------------------------------- SearchRule::Function HeadersRuleWidgetHandler::function(const QByteArray &field, const QStackedWidget *functionStack) const { if (!handlesField(field)) { return SearchRule::FuncNone; } return currentFunction(functionStack); } //--------------------------------------------------------------------------- QString HeadersRuleWidgetHandler::currentValue(const QStackedWidget *valueStack, SearchRule::Function func) const { Q_UNUSED(func); const KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { return lineEdit->text(); } // or anything else, like addressbook return QString(); } //--------------------------------------------------------------------------- QString HeadersRuleWidgetHandler::value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } SearchRule::Function func = currentFunction(functionStack); if (func == SearchRule::FuncIsInAddressbook) { return QStringLiteral("is in address book"); // just a non-empty dummy value } else if (func == SearchRule::FuncIsNotInAddressbook) { return QStringLiteral("is not in address book"); // just a non-empty dummy value } else { return currentValue(valueStack, func); } } //--------------------------------------------------------------------------- QString HeadersRuleWidgetHandler::prettyValue(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } SearchRule::Function func = currentFunction(functionStack); if (func == SearchRule::FuncIsInAddressbook) { return i18n("is in address book"); } else if (func == SearchRule::FuncIsNotInAddressbook) { return i18n("is not in address book"); } else { return currentValue(valueStack, func); } } //--------------------------------------------------------------------------- bool HeadersRuleWidgetHandler::handlesField(const QByteArray &field) const { return field == "To" || field == "From" || field == "CC" || field == ""; } //--------------------------------------------------------------------------- void HeadersRuleWidgetHandler::reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const { // reset the function combo box PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("headerRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); funcCombo->setCurrentIndex(0); funcCombo->blockSignals(false); } // reset the value widget KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { lineEdit->blockSignals(true); lineEdit->clear(); lineEdit->blockSignals(false); lineEdit->setClearButtonEnabled(false); lineEdit->setClearButtonEnabled(true); valueStack->setCurrentWidget(lineEdit); } } //--------------------------------------------------------------------------- bool HeadersRuleWidgetHandler::setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool isBalooSearch) const { if (!rule || !handlesField(rule->field())) { reset(functionStack, valueStack); return false; } const SearchRule::Function func = rule->function(); if ((isBalooSearch && (func == SearchRule::FuncIsInAddressbook || func == SearchRule::FuncIsNotInAddressbook))) { reset(functionStack, valueStack); return false; } int i = 0; for (; i < HeadersFunctionCount; ++i) { if (func == HeaderFunctions[i].id) { break; } } PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("headerRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); if (i < HeadersFunctionCount) { funcCombo->setCurrentIndex(i); } else { funcCombo->setCurrentIndex(0); } funcCombo->blockSignals(false); functionStack->setCurrentWidget(funcCombo); } if (func == SearchRule::FuncIsInAddressbook || func == SearchRule::FuncIsNotInAddressbook) { QWidget *w = valueStack->findChild(QStringLiteral("headerRuleValueHider")); valueStack->setCurrentWidget(w); } else { KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { lineEdit->blockSignals(true); lineEdit->setText(rule->contents()); lineEdit->blockSignals(false); lineEdit->setClearButtonEnabled(false); lineEdit->setClearButtonEnabled(true); valueStack->setCurrentWidget(lineEdit); } } return true; } //--------------------------------------------------------------------------- bool HeadersRuleWidgetHandler::update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const { if (!handlesField(field)) { return false; } // raise the correct function widget functionStack->setCurrentWidget(functionStack->findChild(QStringLiteral("headerRuleFuncCombo"))); // raise the correct value widget SearchRule::Function func = currentFunction(functionStack); if (func == SearchRule::FuncIsInAddressbook || func == SearchRule::FuncIsNotInAddressbook) { valueStack->setCurrentWidget(valueStack->findChild(QStringLiteral("headerRuleValueHider"))); } else { KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { valueStack->setCurrentWidget(lineEdit); } } return true; } diff --git a/src/search/widgethandler/headersrulerwidgethandler.h b/src/search/widgethandler/headersrulerwidgethandler.h index 0d0347c..5c958b7 100644 --- a/src/search/widgethandler/headersrulerwidgethandler.h +++ b/src/search/widgethandler/headersrulerwidgethandler.h @@ -1,58 +1,59 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef HEADERSRULERWIDGETHANDLER_H #define HEADERSRULERWIDGETHANDLER_H #include "interfaces/rulewidgethandler.h" namespace MailCommon { class HeadersRuleWidgetHandler : public MailCommon::RuleWidgetHandler { public: HeadersRuleWidgetHandler() : MailCommon::RuleWidgetHandler() { } ~HeadersRuleWidgetHandler() override { } QWidget *createFunctionWidget(int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const override; QWidget *createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const override; SearchRule::Function function(const QByteArray &field, const QStackedWidget *functionStack) const override; QString value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; QString prettyValue(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; bool handlesField(const QByteArray &field) const override; void reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const override; bool setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool isBalooSearch) const override; bool update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const override; private: SearchRule::Function currentFunction(const QStackedWidget *functionStack) const; QString currentValue(const QStackedWidget *valueStack, SearchRule::Function func) const; }; } #endif // HEADERSRULERWIDGETHANDLER_H diff --git a/src/search/widgethandler/messagerulewidgethandler.cpp b/src/search/widgethandler/messagerulewidgethandler.cpp index d83802d..4474982 100644 --- a/src/search/widgethandler/messagerulewidgethandler.cpp +++ b/src/search/widgethandler/messagerulewidgethandler.cpp @@ -1,282 +1,283 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "messagerulewidgethandler.h" #include "search/searchpattern.h" #include #include #include #include #include using namespace MailCommon; // also see SearchRule::matches() and SearchRule::Function // if you change the following strings! static const struct { SearchRule::Function id; const char *displayName; } MessageFunctions[] = { { SearchRule::FuncContains, I18N_NOOP("contains") }, { SearchRule::FuncContainsNot, I18N_NOOP("does not contain") }, { SearchRule::FuncRegExp, I18N_NOOP("matches regular expr.") }, { SearchRule::FuncNotRegExp, I18N_NOOP("does not match reg. expr.") }, { SearchRule::FuncHasAttachment, I18N_NOOP("has an attachment") }, { SearchRule::FuncHasNoAttachment, I18N_NOOP("has no attachment") }, }; static const int MessageFunctionCount = sizeof(MessageFunctions) / sizeof(*MessageFunctions); //--------------------------------------------------------------------------- QWidget *MessageRuleWidgetHandler::createFunctionWidget( int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const { if (number != 0) { return nullptr; } PimCommon::MinimumComboBox *funcCombo = new PimCommon::MinimumComboBox(functionStack); funcCombo->setObjectName(QStringLiteral("messageRuleFuncCombo")); for (int i = 0; i < MessageFunctionCount; ++i) { if (!(isBalooSearch && (MessageFunctions[i].id == SearchRule::FuncHasAttachment || MessageFunctions[i].id == SearchRule::FuncHasNoAttachment))) { funcCombo->addItem(i18n(MessageFunctions[i].displayName)); } } funcCombo->adjustSize(); QObject::connect(funcCombo, SIGNAL(activated(int)), receiver, SLOT(slotFunctionChanged())); return funcCombo; } //--------------------------------------------------------------------------- QWidget *MessageRuleWidgetHandler::createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const { if (number == 0) { KLineEdit *lineEdit = new KLineEdit(valueStack); lineEdit->setClearButtonEnabled(true); lineEdit->setTrapReturnKey(true); lineEdit->setObjectName(QStringLiteral("regExpLineEdit")); QObject::connect(lineEdit, SIGNAL(textChanged(QString)), receiver, SLOT(slotValueChanged())); QObject::connect(lineEdit, SIGNAL(returnPressed()), receiver, SLOT(slotReturnPressed())); return lineEdit; } // blank QLabel to hide value widget for has-attachment rule if (number == 1) { QLabel *label = new QLabel(valueStack); label->setObjectName(QStringLiteral("textRuleValueHider")); label->setBuddy(valueStack); return label; } return nullptr; } //--------------------------------------------------------------------------- SearchRule::Function MessageRuleWidgetHandler::currentFunction( const QStackedWidget *functionStack) const { const PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("messageRuleFuncCombo")); if (funcCombo && funcCombo->currentIndex() >= 0) { return MessageFunctions[funcCombo->currentIndex()].id; } return SearchRule::FuncNone; } //--------------------------------------------------------------------------- SearchRule::Function MessageRuleWidgetHandler::function(const QByteArray &field, const QStackedWidget *functionStack) const { if (!handlesField(field)) { return SearchRule::FuncNone; } return currentFunction(functionStack); } //--------------------------------------------------------------------------- QString MessageRuleWidgetHandler::currentValue(const QStackedWidget *valueStack, SearchRule::Function) const { const KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { return lineEdit->text(); } return QString(); } //--------------------------------------------------------------------------- QString MessageRuleWidgetHandler::value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } SearchRule::Function func = currentFunction(functionStack); if (func == SearchRule::FuncHasAttachment) { return QStringLiteral("has an attachment"); // just a non-empty dummy value } else if (func == SearchRule::FuncHasNoAttachment) { return QStringLiteral("has no attachment"); // just a non-empty dummy value } else { return currentValue(valueStack, func); } } //--------------------------------------------------------------------------- QString MessageRuleWidgetHandler::prettyValue(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } SearchRule::Function func = currentFunction(functionStack); if (func == SearchRule::FuncHasAttachment) { return i18n("has an attachment"); } else if (func == SearchRule::FuncHasNoAttachment) { return i18n("has no attachment"); } else { return currentValue(valueStack, func); } } //--------------------------------------------------------------------------- bool MessageRuleWidgetHandler::handlesField(const QByteArray &field) const { return field == ""; } //--------------------------------------------------------------------------- void MessageRuleWidgetHandler::reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const { // reset the function combo box PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("messageRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); funcCombo->setCurrentIndex(0); funcCombo->blockSignals(false); } // reset the value widget KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { lineEdit->blockSignals(true); lineEdit->clear(); lineEdit->blockSignals(false); lineEdit->setClearButtonEnabled(false); lineEdit->setClearButtonEnabled(true); valueStack->setCurrentWidget(lineEdit); } } //--------------------------------------------------------------------------- bool MessageRuleWidgetHandler::setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool isBalooSearch) const { if (!rule || !handlesField(rule->field())) { reset(functionStack, valueStack); return false; } const SearchRule::Function func = rule->function(); if ((isBalooSearch && (func == SearchRule::FuncHasAttachment || func == SearchRule::FuncHasNoAttachment))) { reset(functionStack, valueStack); return false; } int i = 0; for (; i < MessageFunctionCount; ++i) { if (func == MessageFunctions[i].id) { break; } } PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("messageRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); if (i < MessageFunctionCount) { funcCombo->setCurrentIndex(i); } else { funcCombo->setCurrentIndex(0); } funcCombo->blockSignals(false); functionStack->setCurrentWidget(funcCombo); } if (func == SearchRule::FuncHasAttachment || func == SearchRule::FuncHasNoAttachment) { QWidget *w = valueStack->findChild(QStringLiteral("textRuleValueHider")); valueStack->setCurrentWidget(w); } else { KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { lineEdit->blockSignals(true); lineEdit->setText(rule->contents()); lineEdit->blockSignals(false); lineEdit->setClearButtonEnabled(false); lineEdit->setClearButtonEnabled(true); valueStack->setCurrentWidget(lineEdit); } } return true; } //--------------------------------------------------------------------------- bool MessageRuleWidgetHandler::update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const { if (!handlesField(field)) { return false; } // raise the correct function widget functionStack->setCurrentWidget(functionStack->findChild(QStringLiteral("messageRuleFuncCombo"))); // raise the correct value widget SearchRule::Function func = currentFunction(functionStack); if (func == SearchRule::FuncHasAttachment || func == SearchRule::FuncHasNoAttachment) { QWidget *w = valueStack->findChild(QStringLiteral("textRuleValueHider")); valueStack->setCurrentWidget(w); } else { KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { valueStack->setCurrentWidget(lineEdit); } } return true; } diff --git a/src/search/widgethandler/messagerulewidgethandler.h b/src/search/widgethandler/messagerulewidgethandler.h index 3c2e3a1..7b18bbc 100644 --- a/src/search/widgethandler/messagerulewidgethandler.h +++ b/src/search/widgethandler/messagerulewidgethandler.h @@ -1,59 +1,60 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MESSAGERULEWIDGETHANDLER_H #define MESSAGERULEWIDGETHANDLER_H #include "interfaces/rulewidgethandler.h" namespace MailCommon { class MessageRuleWidgetHandler : public MailCommon::RuleWidgetHandler { public: MessageRuleWidgetHandler() : MailCommon::RuleWidgetHandler() { } ~MessageRuleWidgetHandler() { } QWidget *createFunctionWidget(int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const override; QWidget *createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const override; SearchRule::Function function(const QByteArray &field, const QStackedWidget *functionStack) const override; QString value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; QString prettyValue(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; bool handlesField(const QByteArray &field) const override; void reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const override; bool setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool isBalooSearch) const override; bool update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const override; private: SearchRule::Function currentFunction(const QStackedWidget *functionStack) const; QString currentValue(const QStackedWidget *valueStack, SearchRule::Function func) const; }; } #endif // MESSAGERULEWIDGETHANDLER_H diff --git a/src/search/widgethandler/numericdoublerulewidgethandler.cpp b/src/search/widgethandler/numericdoublerulewidgethandler.cpp index 548a250..7eb4c1d 100644 --- a/src/search/widgethandler/numericdoublerulewidgethandler.cpp +++ b/src/search/widgethandler/numericdoublerulewidgethandler.cpp @@ -1,250 +1,251 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "numericdoublerulewidgethandler.h" #include #include "search/searchpattern.h" #include #include #include using namespace MailCommon; static const struct { SearchRule::Function id; const char *displayName; } NumericFunctions[] = { { SearchRule::FuncEquals, I18N_NOOP("is equal to") }, { SearchRule::FuncNotEqual, I18N_NOOP("is not equal to") }, { SearchRule::FuncIsGreater, I18N_NOOP("is greater than") }, { SearchRule::FuncIsLessOrEqual, I18N_NOOP("is less than or equal to") }, { SearchRule::FuncIsLess, I18N_NOOP("is less than") }, { SearchRule::FuncIsGreaterOrEqual, I18N_NOOP("is greater than or equal to") } }; static const int NumericFunctionCount = sizeof(NumericFunctions) / sizeof(*NumericFunctions); QWidget *NumericDoubleRuleWidgetHandler::createFunctionWidget( int number, QStackedWidget *functionStack, const QObject *receiver, bool /*isBalooSearch*/) const { if (number != 0) { return nullptr; } PimCommon::MinimumComboBox *funcCombo = new PimCommon::MinimumComboBox(functionStack); funcCombo->setObjectName(QStringLiteral("numericDoubleRuleFuncCombo")); for (int i = 0; i < NumericFunctionCount; ++i) { funcCombo->addItem(i18n(NumericFunctions[i].displayName)); } funcCombo->adjustSize(); QObject::connect(funcCombo, SIGNAL(activated(int)), receiver, SLOT(slotFunctionChanged())); return funcCombo; } //--------------------------------------------------------------------------- QWidget *NumericDoubleRuleWidgetHandler::createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const { if (number != 0) { return nullptr; } QDoubleSpinBox *numInput = new QDoubleSpinBox(valueStack); numInput->setObjectName(QStringLiteral("QDoubleSpinBox")); QObject::connect(numInput, SIGNAL(valueChanged(double)), receiver, SLOT(slotValueChanged())); return numInput; } //--------------------------------------------------------------------------- SearchRule::Function NumericDoubleRuleWidgetHandler::currentFunction( const QStackedWidget *functionStack) const { const PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("numericDoubleRuleFuncCombo")); if (funcCombo && funcCombo->currentIndex() >= 0) { return NumericFunctions[funcCombo->currentIndex()].id; } return SearchRule::FuncNone; } //--------------------------------------------------------------------------- SearchRule::Function NumericDoubleRuleWidgetHandler::function(const QByteArray &field, const QStackedWidget *functionStack) const { if (!handlesField(field)) { return SearchRule::FuncNone; } return currentFunction(functionStack); } //--------------------------------------------------------------------------- QString NumericDoubleRuleWidgetHandler::currentValue(const QStackedWidget *valueStack) const { const QDoubleSpinBox *numInput = valueStack->findChild(QStringLiteral("QDoubleSpinBox")); if (numInput) { return QString::number(int(numInput->value() * 1024)); } return QString(); } //--------------------------------------------------------------------------- QString NumericDoubleRuleWidgetHandler::value(const QByteArray &field, const QStackedWidget *, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } return currentValue(valueStack); } //--------------------------------------------------------------------------- QString NumericDoubleRuleWidgetHandler::prettyValue(const QByteArray &field, const QStackedWidget *, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } return currentValue(valueStack); } //--------------------------------------------------------------------------- bool NumericDoubleRuleWidgetHandler::handlesField(const QByteArray &field) const { return field == ""; } //--------------------------------------------------------------------------- void NumericDoubleRuleWidgetHandler::reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const { // reset the function combo box PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("numericDoubleRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); funcCombo->setCurrentIndex(0); funcCombo->blockSignals(false); } // reset the value widget QDoubleSpinBox *numInput = valueStack->findChild(QStringLiteral("QDoubleSpinBox")); if (numInput) { numInput->blockSignals(true); numInput->setValue(0.0); numInput->blockSignals(false); } } //--------------------------------------------------------------------------- void initDoubleNumInput(QDoubleSpinBox *numInput, const QByteArray &field) { if (field == "") { numInput->setMinimum(0); numInput->setSingleStep(1); numInput->setMaximum(10000000); numInput->setSuffix(i18nc("spinbox suffix: unit for kilobyte", " kB")); } } //--------------------------------------------------------------------------- bool NumericDoubleRuleWidgetHandler::setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool /*isBalooSearch*/) const { if (!rule || !handlesField(rule->field())) { reset(functionStack, valueStack); return false; } // set the function const SearchRule::Function func = rule->function(); int funcIndex = 0; for (; funcIndex < NumericFunctionCount; ++funcIndex) { if (func == NumericFunctions[funcIndex].id) { break; } } PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("numericDoubleRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); if (funcIndex < NumericFunctionCount) { funcCombo->setCurrentIndex(funcIndex); } else { funcCombo->setCurrentIndex(0); } funcCombo->blockSignals(false); functionStack->setCurrentWidget(funcCombo); } // set the value bool ok; int value = rule->contents().toInt(&ok); if (!ok) { value = 0; } QDoubleSpinBox *numInput = valueStack->findChild(QStringLiteral("QDoubleSpinBox")); if (numInput) { initDoubleNumInput(numInput, rule->field()); numInput->blockSignals(true); numInput->setValue(value / (1024.0)); numInput->blockSignals(false); valueStack->setCurrentWidget(numInput); } return true; } //--------------------------------------------------------------------------- bool NumericDoubleRuleWidgetHandler::update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const { if (!handlesField(field)) { return false; } // raise the correct function widget functionStack->setCurrentWidget(functionStack->findChild(QStringLiteral("numericDoubleRuleFuncCombo"))); // raise the correct value widget QDoubleSpinBox *numInput = valueStack->findChild(QStringLiteral("QDoubleSpinBox")); if (numInput) { initDoubleNumInput(numInput, field); valueStack->setCurrentWidget(numInput); } return true; } diff --git a/src/search/widgethandler/numericdoublerulewidgethandler.h b/src/search/widgethandler/numericdoublerulewidgethandler.h index 9d207e0..8d4a9bd 100644 --- a/src/search/widgethandler/numericdoublerulewidgethandler.h +++ b/src/search/widgethandler/numericdoublerulewidgethandler.h @@ -1,59 +1,60 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef NUMERICDOUBLERULEWIDGETHANDLER_H #define NUMERICDOUBLERULEWIDGETHANDLER_H #include "interfaces/rulewidgethandler.h" namespace MailCommon { class NumericDoubleRuleWidgetHandler : public MailCommon::RuleWidgetHandler { public: NumericDoubleRuleWidgetHandler() : MailCommon::RuleWidgetHandler() { } ~NumericDoubleRuleWidgetHandler() override { } QWidget *createFunctionWidget(int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const override; QWidget *createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const override; SearchRule::Function function(const QByteArray &field, const QStackedWidget *functionStack) const override; QString value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; QString prettyValue(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; bool handlesField(const QByteArray &field) const override; void reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const override; bool setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool isBalooSearch) const override; bool update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const override; private: SearchRule::Function currentFunction(const QStackedWidget *functionStack) const; QString currentValue(const QStackedWidget *valueStack) const; }; } #endif // NUMERICDOUBLERULEWIDGETHANDLER_H diff --git a/src/search/widgethandler/numericrulewidgethandler.cpp b/src/search/widgethandler/numericrulewidgethandler.cpp index 8972b5b..17e135a 100644 --- a/src/search/widgethandler/numericrulewidgethandler.cpp +++ b/src/search/widgethandler/numericrulewidgethandler.cpp @@ -1,251 +1,252 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "numericrulewidgethandler.h" #include "search/searchpattern.h" #include #include #include #include using namespace MailCommon; static const struct { SearchRule::Function id; const char *displayName; } NumericFunctions[] = { { SearchRule::FuncEquals, I18N_NOOP("is equal to") }, { SearchRule::FuncNotEqual, I18N_NOOP("is not equal to") }, { SearchRule::FuncIsGreater, I18N_NOOP("is greater than") }, { SearchRule::FuncIsLessOrEqual, I18N_NOOP("is less than or equal to") }, { SearchRule::FuncIsLess, I18N_NOOP("is less than") }, { SearchRule::FuncIsGreaterOrEqual, I18N_NOOP("is greater than or equal to") } }; static const int NumericFunctionCount = sizeof(NumericFunctions) / sizeof(*NumericFunctions); //--------------------------------------------------------------------------- QWidget *NumericRuleWidgetHandler::createFunctionWidget( int number, QStackedWidget *functionStack, const QObject *receiver, bool /*isBalooSearch*/) const { if (number != 0) { return nullptr; } PimCommon::MinimumComboBox *funcCombo = new PimCommon::MinimumComboBox(functionStack); funcCombo->setObjectName(QStringLiteral("numericRuleFuncCombo")); for (int i = 0; i < NumericFunctionCount; ++i) { funcCombo->addItem(i18n(NumericFunctions[i].displayName)); } funcCombo->adjustSize(); QObject::connect(funcCombo, SIGNAL(activated(int)), receiver, SLOT(slotFunctionChanged())); return funcCombo; } //--------------------------------------------------------------------------- QWidget *NumericRuleWidgetHandler::createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const { if (number != 0) { return nullptr; } KPluralHandlingSpinBox *numInput = new KPluralHandlingSpinBox(valueStack); numInput->setObjectName(QStringLiteral("KPluralHandlingSpinBox")); QObject::connect(numInput, SIGNAL(valueChanged(int)), receiver, SLOT(slotValueChanged())); return numInput; } //--------------------------------------------------------------------------- SearchRule::Function NumericRuleWidgetHandler::currentFunction( const QStackedWidget *functionStack) const { const PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("numericRuleFuncCombo")); if (funcCombo && funcCombo->currentIndex() >= 0) { return NumericFunctions[funcCombo->currentIndex()].id; } return SearchRule::FuncNone; } //--------------------------------------------------------------------------- SearchRule::Function NumericRuleWidgetHandler::function(const QByteArray &field, const QStackedWidget *functionStack) const { if (!handlesField(field)) { return SearchRule::FuncNone; } return currentFunction(functionStack); } //--------------------------------------------------------------------------- QString NumericRuleWidgetHandler::currentValue(const QStackedWidget *valueStack) const { const KPluralHandlingSpinBox *numInput = valueStack->findChild(QStringLiteral("KPluralHandlingSpinBox")); if (numInput) { return QString::number(numInput->value()); } return QString(); } //--------------------------------------------------------------------------- QString NumericRuleWidgetHandler::value(const QByteArray &field, const QStackedWidget *, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } return currentValue(valueStack); } //--------------------------------------------------------------------------- QString NumericRuleWidgetHandler::prettyValue(const QByteArray &field, const QStackedWidget *, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } return currentValue(valueStack); } //--------------------------------------------------------------------------- bool NumericRuleWidgetHandler::handlesField(const QByteArray &field) const { return field == ""; } //--------------------------------------------------------------------------- void NumericRuleWidgetHandler::reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const { // reset the function combo box PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("numericRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); funcCombo->setCurrentIndex(0); funcCombo->blockSignals(false); } // reset the value widget KPluralHandlingSpinBox *numInput = valueStack->findChild(QStringLiteral("KPluralHandlingSpinBox")); if (numInput) { numInput->blockSignals(true); numInput->setValue(0); numInput->blockSignals(false); } } //--------------------------------------------------------------------------- void initNumInput(KPluralHandlingSpinBox *numInput, const QByteArray &field) { if (field == "") { numInput->setMinimum(-10000); numInput->setSuffix(ki18ncp("Unit suffix where units are days.", " day", " days")); } } //--------------------------------------------------------------------------- bool NumericRuleWidgetHandler::setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool /*isBalooSearch*/) const { if (!rule || !handlesField(rule->field())) { reset(functionStack, valueStack); return false; } // set the function const SearchRule::Function func = rule->function(); int funcIndex = 0; for (; funcIndex < NumericFunctionCount; ++funcIndex) { if (func == NumericFunctions[funcIndex].id) { break; } } PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("numericRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); if (funcIndex < NumericFunctionCount) { funcCombo->setCurrentIndex(funcIndex); } else { funcCombo->setCurrentIndex(0); } funcCombo->blockSignals(false); functionStack->setCurrentWidget(funcCombo); } // set the value bool ok; int value = rule->contents().toInt(&ok); if (!ok) { value = 0; } KPluralHandlingSpinBox *numInput = valueStack->findChild(QStringLiteral("KPluralHandlingSpinBox")); if (numInput) { initNumInput(numInput, rule->field()); numInput->blockSignals(true); numInput->setValue(value); numInput->blockSignals(false); valueStack->setCurrentWidget(numInput); } return true; } //--------------------------------------------------------------------------- bool NumericRuleWidgetHandler::update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const { if (!handlesField(field)) { return false; } // raise the correct function widget functionStack->setCurrentWidget(functionStack->findChild(QStringLiteral("numericRuleFuncCombo"))); // raise the correct value widget KPluralHandlingSpinBox *numInput = valueStack->findChild(QStringLiteral("KPluralHandlingSpinBox")); if (numInput) { initNumInput(numInput, field); valueStack->setCurrentWidget(numInput); } return true; } diff --git a/src/search/widgethandler/statusrulewidgethandler.cpp b/src/search/widgethandler/statusrulewidgethandler.cpp index bf22327..09f8ca6 100644 --- a/src/search/widgethandler/statusrulewidgethandler.cpp +++ b/src/search/widgethandler/statusrulewidgethandler.cpp @@ -1,260 +1,261 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "statusrulewidgethandler.h" #include "search/searchrule/searchrulestatus.h" #include #include #include using namespace MailCommon; static const struct { SearchRule::Function id; const char *displayName; } StatusFunctions[] = { { SearchRule::FuncContains, I18N_NOOP("is") }, { SearchRule::FuncContainsNot, I18N_NOOP("is not") } }; static const int StatusFunctionCount = sizeof(StatusFunctions) / sizeof(*StatusFunctions); //--------------------------------------------------------------------------- QWidget *StatusRuleWidgetHandler::createFunctionWidget( int number, QStackedWidget *functionStack, const QObject *receiver, bool /*isBalooSearch*/) const { if (number != 0) { return nullptr; } PimCommon::MinimumComboBox *funcCombo = new PimCommon::MinimumComboBox(functionStack); funcCombo->setObjectName(QStringLiteral("statusRuleFuncCombo")); for (int i = 0; i < StatusFunctionCount; ++i) { funcCombo->addItem(i18n(StatusFunctions[i].displayName)); } funcCombo->adjustSize(); QObject::connect(funcCombo, SIGNAL(activated(int)), receiver, SLOT(slotFunctionChanged())); return funcCombo; } //--------------------------------------------------------------------------- QWidget *StatusRuleWidgetHandler::createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const { if (number != 0) { return nullptr; } PimCommon::MinimumComboBox *statusCombo = new PimCommon::MinimumComboBox(valueStack); statusCombo->setObjectName(QStringLiteral("statusRuleValueCombo")); for (int i = 0; i < MailCommon::StatusValueCountWithoutHidden; ++i) { if (MailCommon::StatusValues[ i ].icon != nullptr) { statusCombo->addItem( SmallIcon(QLatin1String(MailCommon::StatusValues[ i ].icon)), i18nc("message status", MailCommon::StatusValues[ i ].text)); } else { statusCombo->addItem( i18nc("message status", MailCommon::StatusValues[ i ].text)); } } statusCombo->adjustSize(); QObject::connect(statusCombo, SIGNAL(activated(int)), receiver, SLOT(slotValueChanged())); return statusCombo; } //--------------------------------------------------------------------------- SearchRule::Function StatusRuleWidgetHandler::currentFunction( const QStackedWidget *functionStack) const { const PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("statusRuleFuncCombo")); if (funcCombo && funcCombo->currentIndex() >= 0) { return StatusFunctions[funcCombo->currentIndex()].id; } return SearchRule::FuncNone; } //--------------------------------------------------------------------------- SearchRule::Function StatusRuleWidgetHandler::function(const QByteArray &field, const QStackedWidget *functionStack) const { if (!handlesField(field)) { return SearchRule::FuncNone; } return currentFunction(functionStack); } //--------------------------------------------------------------------------- int StatusRuleWidgetHandler::currentStatusValue(const QStackedWidget *valueStack) const { const PimCommon::MinimumComboBox *statusCombo = valueStack->findChild(QStringLiteral("statusRuleValueCombo")); if (statusCombo) { return statusCombo->currentIndex(); } return -1; } //--------------------------------------------------------------------------- QString StatusRuleWidgetHandler::value(const QByteArray &field, const QStackedWidget *, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } const int status = currentStatusValue(valueStack); if (status != -1) { return QString::fromLatin1(MailCommon::StatusValues[ status ].text); } else { return QString(); } } //--------------------------------------------------------------------------- QString StatusRuleWidgetHandler::prettyValue(const QByteArray &field, const QStackedWidget *, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } const int status = currentStatusValue(valueStack); if (status != -1) { return i18nc("message status", MailCommon::StatusValues[ status ].text); } else { return QString(); } } //--------------------------------------------------------------------------- bool StatusRuleWidgetHandler::handlesField(const QByteArray &field) const { return field == ""; } //--------------------------------------------------------------------------- void StatusRuleWidgetHandler::reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const { // reset the function combo box PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("statusRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); funcCombo->setCurrentIndex(0); funcCombo->blockSignals(false); } // reset the status value combo box PimCommon::MinimumComboBox *statusCombo = valueStack->findChild(QStringLiteral("statusRuleValueCombo")); if (statusCombo) { statusCombo->blockSignals(true); statusCombo->setCurrentIndex(0); statusCombo->blockSignals(false); } } //--------------------------------------------------------------------------- bool StatusRuleWidgetHandler::setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool /*isBalooSearch*/) const { if (!rule || !handlesField(rule->field())) { reset(functionStack, valueStack); return false; } // set the function const SearchRule::Function func = rule->function(); int funcIndex = 0; for (; funcIndex < StatusFunctionCount; ++funcIndex) { if (func == StatusFunctions[funcIndex].id) { break; } } PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("statusRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); if (funcIndex < StatusFunctionCount) { funcCombo->setCurrentIndex(funcIndex); } else { funcCombo->setCurrentIndex(0); } funcCombo->blockSignals(false); functionStack->setCurrentWidget(funcCombo); } // set the value const QString value = rule->contents(); int valueIndex = 0; for (; valueIndex < MailCommon::StatusValueCountWithoutHidden; ++valueIndex) { if (value == QString::fromLatin1(MailCommon::StatusValues[ valueIndex ].text)) { break; } } PimCommon::MinimumComboBox *statusCombo = valueStack->findChild(QStringLiteral("statusRuleValueCombo")); if (statusCombo) { statusCombo->blockSignals(true); if (valueIndex < MailCommon::StatusValueCountWithoutHidden) { statusCombo->setCurrentIndex(valueIndex); } else { statusCombo->setCurrentIndex(0); } statusCombo->blockSignals(false); valueStack->setCurrentWidget(statusCombo); } return true; } //--------------------------------------------------------------------------- bool StatusRuleWidgetHandler::update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const { if (!handlesField(field)) { return false; } // raise the correct function widget functionStack->setCurrentWidget(functionStack->findChild(QStringLiteral("statusRuleFuncCombo"))); // raise the correct value widget valueStack->setCurrentWidget(valueStack->findChild(QStringLiteral("statusRuleValueCombo"))); return true; } diff --git a/src/search/widgethandler/statusrulewidgethandler.h b/src/search/widgethandler/statusrulewidgethandler.h index dca3b55..a399dd6 100644 --- a/src/search/widgethandler/statusrulewidgethandler.h +++ b/src/search/widgethandler/statusrulewidgethandler.h @@ -1,59 +1,60 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef STATUSRULEWIDGETHANDLER_H #define STATUSRULEWIDGETHANDLER_H #include "interfaces/rulewidgethandler.h" namespace MailCommon { class StatusRuleWidgetHandler : public MailCommon::RuleWidgetHandler { public: StatusRuleWidgetHandler() : MailCommon::RuleWidgetHandler() { } ~StatusRuleWidgetHandler() override { } QWidget *createFunctionWidget(int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const override; QWidget *createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const override; SearchRule::Function function(const QByteArray &field, const QStackedWidget *functionStack) const override; QString value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; QString prettyValue(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; bool handlesField(const QByteArray &field) const override; void reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const override; bool setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool isBalooSearch) const override; bool update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const override; private: SearchRule::Function currentFunction(const QStackedWidget *functionStack) const; int currentStatusValue(const QStackedWidget *valueStack) const; }; } #endif // STATUSRULEWIDGETHANDLER_H diff --git a/src/search/widgethandler/tagrulewidgethandler.cpp b/src/search/widgethandler/tagrulewidgethandler.cpp index aa8175e..602163a 100644 --- a/src/search/widgethandler/tagrulewidgethandler.cpp +++ b/src/search/widgethandler/tagrulewidgethandler.cpp @@ -1,378 +1,379 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "tagrulewidgethandler.h" #include "search/searchpattern.h" #include "mailcommon_debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace MailCommon; class FillTagComboJob : public KJob { Q_OBJECT public: explicit FillTagComboJob(KComboBox *combo, QObject *parent = nullptr); void start() override; private Q_SLOTS: void onDestroyed(); void onTagsFetched(KJob *); private: KComboBox *mComboBox = nullptr; }; FillTagComboJob::FillTagComboJob(KComboBox *combo, QObject *parent) : KJob(parent) , mComboBox(combo) { connect(combo, &QObject::destroyed, this, &FillTagComboJob::onDestroyed); } void FillTagComboJob::onDestroyed() { mComboBox = nullptr; setError(KJob::UserDefinedError); qCDebug(MAILCOMMON_LOG) << "Combobox destroyed"; emitResult(); } void FillTagComboJob::start() { Akonadi::TagFetchJob *fetchJob = new Akonadi::TagFetchJob(this); fetchJob->fetchScope().fetchAttribute(); connect(fetchJob, &Akonadi::TagFetchJob::result, this, &FillTagComboJob::onTagsFetched); } void FillTagComboJob::onTagsFetched(KJob *job) { if (job->error()) { qCWarning(MAILCOMMON_LOG) << job->errorString(); setError(KJob::UserDefinedError); emitResult(); } if (!mComboBox) { qCDebug(MAILCOMMON_LOG) << "combobox already destroyed"; emitResult(); return; } Akonadi::TagFetchJob *fetchJob = static_cast(job); foreach (const Akonadi::Tag &tag, fetchJob->tags()) { QString iconName = QStringLiteral("mail-tagged"); Akonadi::TagAttribute *attr = tag.attribute(); if (attr) { if (!attr->iconName().isEmpty()) { iconName = attr->iconName(); } } mComboBox->addItem(QIcon::fromTheme(iconName), tag.name(), tag.url().url()); } emitResult(); } static const struct { SearchRule::Function id; const char *displayName; } TagFunctions[] = { { SearchRule::FuncContains, I18N_NOOP("contains") }, { SearchRule::FuncContainsNot, I18N_NOOP("does not contain") }, { SearchRule::FuncEquals, I18N_NOOP("equals") }, { SearchRule::FuncNotEqual, I18N_NOOP("does not equal") }, { SearchRule::FuncRegExp, I18N_NOOP("matches regular expr.") }, { SearchRule::FuncNotRegExp, I18N_NOOP("does not match reg. expr.") } }; static const int TagFunctionCount = sizeof(TagFunctions) / sizeof(*TagFunctions); //--------------------------------------------------------------------------- QWidget *TagRuleWidgetHandler::createFunctionWidget( int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const { if (number != 0) { return nullptr; } PimCommon::MinimumComboBox *funcCombo = new PimCommon::MinimumComboBox(functionStack); funcCombo->setObjectName(QStringLiteral("tagRuleFuncCombo")); for (int i = 0; i < TagFunctionCount; ++i) { if (isBalooSearch) { if (TagFunctions[i].id == SearchRule::FuncContains || TagFunctions[i].id == SearchRule::FuncContainsNot) { funcCombo->addItem(i18n(TagFunctions[i].displayName)); } } else { funcCombo->addItem(i18n(TagFunctions[i].displayName)); } } funcCombo->adjustSize(); QObject::connect(funcCombo, SIGNAL(activated(int)), receiver, SLOT(slotFunctionChanged())); return funcCombo; } //--------------------------------------------------------------------------- QWidget *TagRuleWidgetHandler::createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const { if (number == 0) { KLineEdit *lineEdit = new KLineEdit(valueStack); lineEdit->setClearButtonEnabled(true); lineEdit->setTrapReturnKey(true); lineEdit->setObjectName(QStringLiteral("tagRuleRegExpLineEdit")); QObject::connect(lineEdit, SIGNAL(textChanged(QString)), receiver, SLOT(slotValueChanged())); QObject::connect(lineEdit, SIGNAL(returnPressed()), receiver, SLOT(slotReturnPressed())); return lineEdit; } if (number == 1) { PimCommon::MinimumComboBox *valueCombo = new PimCommon::MinimumComboBox(valueStack); valueCombo->setObjectName(QStringLiteral("tagRuleValueCombo")); valueCombo->setEditable(true); valueCombo->addItem(QString()); // empty entry for user input FillTagComboJob *job = new FillTagComboJob(valueCombo); job->start(); valueCombo->adjustSize(); QObject::connect(valueCombo, SIGNAL(activated(int)), receiver, SLOT(slotValueChanged())); return valueCombo; } return nullptr; } //--------------------------------------------------------------------------- SearchRule::Function TagRuleWidgetHandler::function(const QByteArray &field, const QStackedWidget *functionStack) const { if (!handlesField(field)) { return SearchRule::FuncNone; } const PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("tagRuleFuncCombo")); if (funcCombo && funcCombo->currentIndex() >= 0) { return TagFunctions[funcCombo->currentIndex()].id; } return SearchRule::FuncNone; } //--------------------------------------------------------------------------- QString TagRuleWidgetHandler::value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const { if (!handlesField(field)) { return QString(); } SearchRule::Function func = function(field, functionStack); if (func == SearchRule::FuncRegExp || func == SearchRule::FuncNotRegExp) { // Use regexp line edit const KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("tagRuleRegExpLineEdit")); if (lineEdit) { return lineEdit->text(); } else { return QString(); } } // Use combo box const PimCommon::MinimumComboBox *tagCombo = valueStack->findChild(QStringLiteral("tagRuleValueCombo")); if (tagCombo) { return tagCombo->itemData(tagCombo->currentIndex()).toString(); } else { return QString(); } } //--------------------------------------------------------------------------- QString TagRuleWidgetHandler::prettyValue(const QByteArray &field, const QStackedWidget *funcStack, const QStackedWidget *valueStack) const { return value(field, funcStack, valueStack); } //--------------------------------------------------------------------------- bool TagRuleWidgetHandler::handlesField(const QByteArray &field) const { return field == ""; } //--------------------------------------------------------------------------- void TagRuleWidgetHandler::reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const { // reset the function combo box PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("tagRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); funcCombo->setCurrentIndex(0); funcCombo->blockSignals(false); } // reset the status value combo box and reg exp line edit KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("tagRuleRegExpLineEdit")); if (lineEdit) { lineEdit->blockSignals(true); lineEdit->clear(); lineEdit->blockSignals(false); lineEdit->setClearButtonEnabled(false); lineEdit->setClearButtonEnabled(true); valueStack->setCurrentWidget(lineEdit); } PimCommon::MinimumComboBox *tagCombo = valueStack->findChild(QStringLiteral("tagRuleValueCombo")); if (tagCombo) { tagCombo->blockSignals(true); tagCombo->setCurrentIndex(0); tagCombo->blockSignals(false); } } //--------------------------------------------------------------------------- bool TagRuleWidgetHandler::setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool isBalooSearch) const { if (!rule || !handlesField(rule->field())) { reset(functionStack, valueStack); return false; } // set the function const SearchRule::Function func = rule->function(); if (isBalooSearch) { if (func != SearchRule::FuncContains && func != SearchRule::FuncContainsNot) { reset(functionStack, valueStack); return false; } } int funcIndex = 0; for (; funcIndex < TagFunctionCount; ++funcIndex) { if (func == TagFunctions[funcIndex].id) { break; } } PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("tagRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); if (funcIndex < TagFunctionCount) { funcCombo->setCurrentIndex(funcIndex); } else { funcCombo->setCurrentIndex(0); } funcCombo->blockSignals(false); functionStack->setCurrentWidget(funcCombo); } // set the value if (func == SearchRule::FuncRegExp || func == SearchRule::FuncNotRegExp) { // set reg exp value KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("tagRuleRegExpLineEdit")); if (lineEdit) { lineEdit->blockSignals(true); lineEdit->setText(rule->contents()); lineEdit->blockSignals(false); lineEdit->setClearButtonEnabled(false); lineEdit->setClearButtonEnabled(true); valueStack->setCurrentWidget(lineEdit); } } else { // set combo box value PimCommon::MinimumComboBox *tagCombo = valueStack->findChild(QStringLiteral("tagRuleValueCombo")); if (tagCombo) { tagCombo->blockSignals(true); bool found = false; // Existing tags numbered from 1 for (int i = 1; i < tagCombo->count(); i++) { if (rule->contents() == tagCombo->itemData(i).toString()) { tagCombo->setCurrentIndex(i); found = true; break; } } if (!found) { tagCombo->setCurrentIndex(0); // Still show tag if it was deleted from MsgTagMgr QLineEdit *lineEdit = tagCombo->lineEdit(); // krazy:exclude=qclasses Q_ASSERT(lineEdit); lineEdit->setText(rule->contents()); } tagCombo->blockSignals(false); valueStack->setCurrentWidget(tagCombo); } } return true; } //--------------------------------------------------------------------------- bool TagRuleWidgetHandler::update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const { if (!handlesField(field)) { return false; } // raise the correct function widget functionStack->setCurrentWidget(functionStack->findChild(QStringLiteral("tagRuleFuncCombo"))); // raise the correct value widget SearchRule::Function func = function(field, functionStack); if (func == SearchRule::FuncRegExp || func == SearchRule::FuncNotRegExp) { valueStack->setCurrentWidget(valueStack->findChild(QStringLiteral("tagRuleRegExpLineEdit"))); } else { valueStack->setCurrentWidget(valueStack->findChild(QStringLiteral("tagRuleValueCombo"))); } return true; } #include "tagrulewidgethandler.moc" diff --git a/src/search/widgethandler/tagrulewidgethandler.h b/src/search/widgethandler/tagrulewidgethandler.h index 542d037..857f5e5 100644 --- a/src/search/widgethandler/tagrulewidgethandler.h +++ b/src/search/widgethandler/tagrulewidgethandler.h @@ -1,55 +1,56 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef TAGRULEWIDGETHANDLER_H #define TAGRULEWIDGETHANDLER_H #include "interfaces/rulewidgethandler.h" namespace MailCommon { class TagRuleWidgetHandler : public MailCommon::RuleWidgetHandler { public: TagRuleWidgetHandler() : MailCommon::RuleWidgetHandler() { } ~TagRuleWidgetHandler() override { } QWidget *createFunctionWidget(int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const override; QWidget *createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const override; SearchRule::Function function(const QByteArray &field, const QStackedWidget *functionStack) const override; QString value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; QString prettyValue(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; bool handlesField(const QByteArray &field) const override; void reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const override; bool setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool isBalooSearch) const override; bool update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const override; }; } #endif // TAGRULEWIDGETHANDLER_H diff --git a/src/search/widgethandler/textrulerwidgethandler.cpp b/src/search/widgethandler/textrulerwidgethandler.cpp index 21a3669..bd885bd 100644 --- a/src/search/widgethandler/textrulerwidgethandler.cpp +++ b/src/search/widgethandler/textrulerwidgethandler.cpp @@ -1,247 +1,248 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "textrulerwidgethandler.h" #include #include "search/searchpattern.h" #include #include #include using namespace MailCommon; #include // also see SearchRule::matches() and SearchRule::Function // if you change the following strings! static const struct { SearchRule::Function id; const char *displayName; } TextFunctions[] = { { SearchRule::FuncContains, I18N_NOOP("contains") }, { SearchRule::FuncContainsNot, I18N_NOOP("does not contain") }, { SearchRule::FuncEquals, I18N_NOOP("equals") }, { SearchRule::FuncNotEqual, I18N_NOOP("does not equal") }, { SearchRule::FuncStartWith, I18N_NOOP("starts with") }, { SearchRule::FuncNotStartWith, I18N_NOOP("does not start with")}, { SearchRule::FuncEndWith, I18N_NOOP("ends with") }, { SearchRule::FuncNotEndWith, I18N_NOOP("does not end with") }, { SearchRule::FuncRegExp, I18N_NOOP("matches regular expr.") }, { SearchRule::FuncNotRegExp, I18N_NOOP("does not match reg. expr.") } }; static const int TextFunctionCount = sizeof(TextFunctions) / sizeof(*TextFunctions); //--------------------------------------------------------------------------- QWidget *TextRuleWidgetHandler::createFunctionWidget( int number, QStackedWidget *functionStack, const QObject *receiver, bool /*isBalooSearch*/) const { if (number != 0) { return nullptr; } PimCommon::MinimumComboBox *funcCombo = new PimCommon::MinimumComboBox(functionStack); funcCombo->setObjectName(QStringLiteral("textRuleFuncCombo")); for (int i = 0; i < TextFunctionCount; ++i) { funcCombo->addItem(i18n(TextFunctions[i].displayName)); } funcCombo->adjustSize(); QObject::connect(funcCombo, SIGNAL(activated(int)), receiver, SLOT(slotFunctionChanged())); return funcCombo; } //--------------------------------------------------------------------------- QWidget *TextRuleWidgetHandler::createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const { if (number == 0) { KLineEdit *lineEdit = new KLineEdit(valueStack); lineEdit->setClearButtonEnabled(true); lineEdit->setTrapReturnKey(true); lineEdit->setObjectName(QStringLiteral("regExpLineEdit")); QObject::connect(lineEdit, SIGNAL(textChanged(QString)), receiver, SLOT(slotValueChanged())); QObject::connect(lineEdit, SIGNAL(returnPressed()), receiver, SLOT(slotReturnPressed())); return lineEdit; } // blank QLabel to hide value widget for in-address-book rule if (number == 1) { QLabel *label = new QLabel(valueStack); label->setObjectName(QStringLiteral("textRuleValueHider")); label->setBuddy(valueStack); return label; } return nullptr; } //--------------------------------------------------------------------------- SearchRule::Function TextRuleWidgetHandler::currentFunction( const QStackedWidget *functionStack) const { const PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("textRuleFuncCombo")); if (funcCombo && funcCombo->currentIndex() >= 0) { return TextFunctions[funcCombo->currentIndex()].id; } return SearchRule::FuncNone; } //--------------------------------------------------------------------------- SearchRule::Function TextRuleWidgetHandler::function(const QByteArray &, const QStackedWidget *functionStack) const { return currentFunction(functionStack); } //--------------------------------------------------------------------------- QString TextRuleWidgetHandler::currentValue(const QStackedWidget *valueStack, SearchRule::Function) const { //in other cases of func it is a lineedit const KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { return lineEdit->text(); } // or anything else, like addressbook return QString(); } //--------------------------------------------------------------------------- QString TextRuleWidgetHandler::value(const QByteArray &, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const { SearchRule::Function func = currentFunction(functionStack); return currentValue(valueStack, func); } //--------------------------------------------------------------------------- QString TextRuleWidgetHandler::prettyValue(const QByteArray &, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const { SearchRule::Function func = currentFunction(functionStack); return currentValue(valueStack, func); } //--------------------------------------------------------------------------- bool TextRuleWidgetHandler::handlesField(const QByteArray &) const { return true; // we handle all fields (as fallback) } //--------------------------------------------------------------------------- void TextRuleWidgetHandler::reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const { // reset the function combo box PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("textRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); funcCombo->setCurrentIndex(0); funcCombo->blockSignals(false); } // reset the value widget KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { lineEdit->blockSignals(true); lineEdit->clear(); lineEdit->blockSignals(false); lineEdit->setClearButtonEnabled(false); lineEdit->setClearButtonEnabled(true); valueStack->setCurrentWidget(lineEdit); } } //--------------------------------------------------------------------------- bool TextRuleWidgetHandler::setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool /*isBalooSearch*/) const { if (!rule) { reset(functionStack, valueStack); return false; } const SearchRule::Function func = rule->function(); int i = 0; for (; i < TextFunctionCount; ++i) { if (func == TextFunctions[i].id) { break; } } PimCommon::MinimumComboBox *funcCombo = functionStack->findChild(QStringLiteral("textRuleFuncCombo")); if (funcCombo) { funcCombo->blockSignals(true); if (i < TextFunctionCount) { funcCombo->setCurrentIndex(i); } else { funcCombo->setCurrentIndex(0); } funcCombo->blockSignals(false); functionStack->setCurrentWidget(funcCombo); } KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { lineEdit->blockSignals(true); lineEdit->setText(rule->contents()); lineEdit->blockSignals(false); lineEdit->setClearButtonEnabled(false); lineEdit->setClearButtonEnabled(true); valueStack->setCurrentWidget(lineEdit); } return true; } //--------------------------------------------------------------------------- bool TextRuleWidgetHandler::update(const QByteArray &, QStackedWidget *functionStack, QStackedWidget *valueStack) const { // raise the correct function widget functionStack->setCurrentWidget(functionStack->findChild(QStringLiteral("textRuleFuncCombo"))); // raise the correct value widget SearchRule::Function func = currentFunction(functionStack); if (func == SearchRule::FuncIsInAddressbook || func == SearchRule::FuncIsNotInAddressbook) { valueStack->setCurrentWidget(valueStack->findChild(QStringLiteral("textRuleValueHider"))); } else { KLineEdit *lineEdit = valueStack->findChild(QStringLiteral("regExpLineEdit")); if (lineEdit) { valueStack->setCurrentWidget(lineEdit); } } return true; } diff --git a/src/search/widgethandler/textrulerwidgethandler.h b/src/search/widgethandler/textrulerwidgethandler.h index cc715a9..c534135 100644 --- a/src/search/widgethandler/textrulerwidgethandler.h +++ b/src/search/widgethandler/textrulerwidgethandler.h @@ -1,58 +1,59 @@ /* Copyright (c) 2013-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef TEXTRULERWIDGETHANDLER_H #define TEXTRULERWIDGETHANDLER_H #include "interfaces/rulewidgethandler.h" namespace MailCommon { class TextRuleWidgetHandler : public MailCommon::RuleWidgetHandler { public: TextRuleWidgetHandler() : MailCommon::RuleWidgetHandler() { } ~TextRuleWidgetHandler() override { } QWidget *createFunctionWidget(int number, QStackedWidget *functionStack, const QObject *receiver, bool isBalooSearch) const override; QWidget *createValueWidget(int number, QStackedWidget *valueStack, const QObject *receiver) const override; SearchRule::Function function(const QByteArray &field, const QStackedWidget *functionStack) const override; QString value(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; QString prettyValue(const QByteArray &field, const QStackedWidget *functionStack, const QStackedWidget *valueStack) const override; bool handlesField(const QByteArray &field) const override; void reset(QStackedWidget *functionStack, QStackedWidget *valueStack) const override; bool setRule(QStackedWidget *functionStack, QStackedWidget *valueStack, const SearchRule::Ptr rule, bool isBalooSearch) const override; bool update(const QByteArray &field, QStackedWidget *functionStack, QStackedWidget *valueStack) const override; private: SearchRule::Function currentFunction(const QStackedWidget *functionStack) const; QString currentValue(const QStackedWidget *valueStack, SearchRule::Function func) const; }; } #endif // TEXTRULERWIDGETHANDLER_H diff --git a/src/tag/addtagdialog.cpp b/src/tag/addtagdialog.cpp index 81ab70c..4010fee 100644 --- a/src/tag/addtagdialog.cpp +++ b/src/tag/addtagdialog.cpp @@ -1,130 +1,131 @@ /* Copyright (c) 2012-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "addtagdialog.h" #include "tag/tagwidget.h" #include "mailcommon_debug.h" #include #include #include #include #include #include #include using namespace MailCommon; class MailCommon::AddTagDialogPrivate { public: AddTagDialogPrivate() : mTagWidget(nullptr) , mOkButton(nullptr) { } QString mLabel; QString mGid; MailCommon::TagWidget *mTagWidget = nullptr; QList mTags; Akonadi::Tag mTag; QPushButton *mOkButton = nullptr; }; AddTagDialog::AddTagDialog(const QList &actions, QWidget *parent) : QDialog(parent) , d(new MailCommon::AddTagDialogPrivate) { setModal(true); setWindowTitle(i18n("Add Tag")); QVBoxLayout *mainLayout = new QVBoxLayout(this); d->mTagWidget = new MailCommon::TagWidget(actions, this); mainLayout->addWidget(d->mTagWidget); connect(d->mTagWidget->tagNameLineEdit(), &KLineEdit::textChanged, this, &AddTagDialog::slotTagNameChanged); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); d->mOkButton = buttonBox->button(QDialogButtonBox::Ok); d->mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &AddTagDialog::slotSave); connect(buttonBox, &QDialogButtonBox::rejected, this, &AddTagDialog::reject); d->mOkButton->setDefault(true); d->mOkButton->setEnabled(false); mainLayout->addWidget(buttonBox); } AddTagDialog::~AddTagDialog() { delete d; } void AddTagDialog::setTags(const QList &tags) { d->mTags = tags; } void AddTagDialog::slotTagNameChanged(const QString &text) { d->mOkButton->setEnabled(!text.trimmed().isEmpty()); } void AddTagDialog::slotSave() { const QString name(d->mTagWidget->tagNameLineEdit()->text()); for (const MailCommon::Tag::Ptr &tag : qAsConst(d->mTags)) { if (tag->name() == name) { KMessageBox::error(this, i18n("Tag %1 already exists", name)); d->mTagWidget->tagNameLineEdit()->setFocus(); d->mTagWidget->tagNameLineEdit()->selectAll(); return; } } MailCommon::Tag::Ptr tag(Tag::createDefaultTag(name)); d->mTagWidget->recordTagSettings(tag); const Akonadi::Tag akonadiTag = tag->saveToAkonadi(); Akonadi::TagCreateJob *createJob = new Akonadi::TagCreateJob(akonadiTag, this); connect(createJob, &Akonadi::TagCreateJob::result, this, &AddTagDialog::onTagCreated); d->mLabel = name; } void AddTagDialog::onTagCreated(KJob *job) { if (job->error()) { qCWarning(MAILCOMMON_LOG) << "Failed to create tag: " << job->errorString(); reject(); return; } Akonadi::TagCreateJob *createJob = static_cast(job); d->mTag = createJob->tag(); accept(); } QString AddTagDialog::label() const { return d->mLabel; } Akonadi::Tag AddTagDialog::tag() const { return d->mTag; } diff --git a/src/tag/addtagdialog.h b/src/tag/addtagdialog.h index 8d26f1e..68c1f1e 100644 --- a/src/tag/addtagdialog.h +++ b/src/tag/addtagdialog.h @@ -1,49 +1,50 @@ /* Copyright (c) 2012-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef ADDTAGDIALOG_H #define ADDTAGDIALOG_H #include "mailcommon_export.h" #include "tag.h" #include #include #include class KActionCollection; namespace MailCommon { class AddTagDialogPrivate; class MAILCOMMON_EXPORT AddTagDialog : public QDialog { Q_OBJECT public: explicit AddTagDialog(const QList &actions, QWidget *parent = nullptr); ~AddTagDialog(); void setTags(const QList &tags); Q_REQUIRED_RESULT QString label() const; Q_REQUIRED_RESULT Akonadi::Tag tag() const; private: void slotSave(); void slotTagNameChanged(const QString &text); void onTagCreated(KJob *job); AddTagDialogPrivate *const d; }; } #endif // ADDTAGDIALOG_H diff --git a/src/tag/tagwidget.cpp b/src/tag/tagwidget.cpp index 38f6051..10676ff 100644 --- a/src/tag/tagwidget.cpp +++ b/src/tag/tagwidget.cpp @@ -1,305 +1,306 @@ /* Copyright (c) 2012-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "tagwidget.h" #include #include #include #include #include #include #include #include #include #include #include using namespace MailCommon; class MailCommon::TagWidgetPrivate { public: TagWidgetPrivate() : mTagNameLineEdit(nullptr) , mTextColorCheck(nullptr) , mBackgroundColorCheck(nullptr) , mTextFontCheck(nullptr) , mInToolbarCheck(nullptr) , mTextColorCombo(nullptr) , mBackgroundColorCombo(nullptr) , mBoldCheckBox(nullptr) , mItalicCheckBox(nullptr) , mIconButton(nullptr) , mKeySequenceWidget(nullptr) { } KLineEdit *mTagNameLineEdit = nullptr; QCheckBox *mTextColorCheck = nullptr; QCheckBox *mBackgroundColorCheck = nullptr; QCheckBox *mTextFontCheck = nullptr; QCheckBox *mInToolbarCheck = nullptr; KColorCombo *mTextColorCombo = nullptr; KColorCombo *mBackgroundColorCombo = nullptr; QCheckBox *mBoldCheckBox = nullptr; QCheckBox *mItalicCheckBox = nullptr; KIconButton *mIconButton = nullptr; KKeySequenceWidget *mKeySequenceWidget = nullptr; }; TagWidget::TagWidget(const QList &actionCollections, QWidget *parent) : QWidget(parent) , d(new MailCommon::TagWidgetPrivate) { QGridLayout *settings = new QGridLayout(this); settings->setMargin(0); //Stretcher layout for adding some space after the label QVBoxLayout *spacer = new QVBoxLayout(); settings->addLayout(spacer, 0, 0, 1, 2); //First row for renaming d->mTagNameLineEdit = new KLineEdit(this); d->mTagNameLineEdit->setClearButtonEnabled(true); d->mTagNameLineEdit->setTrapReturnKey(true); settings->addWidget(d->mTagNameLineEdit, 1, 1); QLabel *namelabel = new QLabel(i18nc("@label:listbox Name of the tag", "Name:"), this); namelabel->setBuddy(d->mTagNameLineEdit); settings->addWidget(namelabel, 1, 0); connect(d->mTagNameLineEdit, &KLineEdit::textChanged, this, &TagWidget::slotEmitChangeCheck); //Second row for text color d->mTextColorCheck = new QCheckBox(i18n("Change te&xt color:"), this); settings->addWidget(d->mTextColorCheck, 2, 0); d->mTextColorCombo = new KColorCombo(this); settings->addWidget(d->mTextColorCombo, 2, 1); d->mTextColorCombo->setEnabled(false); connect(d->mTextColorCheck, &QCheckBox::toggled, d->mTextColorCombo, &KColorCombo::setEnabled); connect(d->mTextColorCheck, &QCheckBox::stateChanged, this, &TagWidget::slotEmitChangeCheck); connect(d->mTextColorCombo, &KColorCombo::activated, this, &TagWidget::slotEmitChangeCheck); //Third row for text background color d->mBackgroundColorCheck = new QCheckBox(i18n("Change &background color:"), this); settings->addWidget(d->mBackgroundColorCheck, 3, 0); d->mBackgroundColorCombo = new KColorCombo(this); settings->addWidget(d->mBackgroundColorCombo, 3, 1); d->mBackgroundColorCombo->setEnabled(false); connect(d->mBackgroundColorCheck, &QAbstractButton::toggled, d->mBackgroundColorCombo, &QWidget::setEnabled); connect(d->mBackgroundColorCheck, &QCheckBox::stateChanged, this, &TagWidget::slotEmitChangeCheck); connect(d->mBackgroundColorCombo, &KColorCombo::activated, this, &TagWidget::slotEmitChangeCheck); //Fourth for font selection d->mTextFontCheck = new QCheckBox(i18n("Change fo&nt:"), this); settings->addWidget(d->mTextFontCheck, 4, 0); QVBoxLayout *fontLayout = new QVBoxLayout; settings->addLayout(fontLayout, 4, 1); d->mBoldCheckBox = new QCheckBox(i18n("&Bold")); d->mBoldCheckBox->setEnabled(false); fontLayout->addWidget(d->mBoldCheckBox); d->mItalicCheckBox = new QCheckBox(i18n("&Italics")); d->mItalicCheckBox->setEnabled(false); fontLayout->addWidget(d->mItalicCheckBox); connect(d->mTextFontCheck, &QAbstractButton::toggled, d->mBoldCheckBox, &QWidget::setEnabled); connect(d->mTextFontCheck, &QAbstractButton::toggled, d->mItalicCheckBox, &QWidget::setEnabled); connect(d->mTextFontCheck, &QCheckBox::stateChanged, this, &TagWidget::slotEmitChangeCheck); connect(d->mBoldCheckBox, &QAbstractButton::toggled, this, &TagWidget::slotEmitChangeCheck); connect(d->mItalicCheckBox, &QAbstractButton::toggled, this, &TagWidget::slotEmitChangeCheck); //Fifth for toolbar icon d->mIconButton = new KIconButton(this); d->mIconButton->setIconSize(16); d->mIconButton->setIconType(KIconLoader::NoGroup, KIconLoader::Action); d->mIconButton->setIcon(QIcon::fromTheme(QStringLiteral("mail-tagged"))); settings->addWidget(d->mIconButton, 5, 1); connect(d->mIconButton, &KIconButton::iconChanged, this, &TagWidget::iconNameChanged); QLabel *iconlabel = new QLabel(i18n("Message tag &icon:"), this); iconlabel->setBuddy(d->mIconButton); settings->addWidget(iconlabel, 5, 0); //We do not connect the checkbox to icon selector since icons are used in the //menus as well connect(d->mIconButton, &KIconButton::iconChanged, this, &TagWidget::slotEmitChangeCheck); //Sixth for shortcut d->mKeySequenceWidget = new KKeySequenceWidget(this); settings->addWidget(d->mKeySequenceWidget, 6, 1); QLabel *sclabel = new QLabel(i18n("Shortc&ut:"), this); sclabel->setBuddy(d->mKeySequenceWidget); settings->addWidget(sclabel, 6, 0); if (!actionCollections.isEmpty()) { d->mKeySequenceWidget->setCheckActionCollections(actionCollections); connect(d->mKeySequenceWidget, &KKeySequenceWidget::keySequenceChanged, this, &TagWidget::slotEmitChangeCheck); } else { d->mKeySequenceWidget->setEnabled(false); } //Seventh for Toolbar checkbox d->mInToolbarCheck = new QCheckBox(i18n("Enable &toolbar button"), this); settings->addWidget(d->mInToolbarCheck, 7, 0); connect(d->mInToolbarCheck, &QCheckBox::stateChanged, this, &TagWidget::slotEmitChangeCheck); } TagWidget::~TagWidget() { delete d; } void TagWidget::slotEmitChangeCheck() { Q_EMIT changed(); } void TagWidget::setTagTextColor(const QColor &color) { d->mTextColorCheck->setEnabled(true); if (color.isValid()) { d->mTextColorCheck->setChecked(true); d->mTextColorCombo->setColor(color); } else { d->mTextColorCheck->setChecked(false); d->mTextColorCombo->setColor(Qt::white); } d->mTextColorCombo->setEnabled(d->mTextColorCheck->isChecked()); } void TagWidget::setTagBackgroundColor(const QColor &color) { d->mBackgroundColorCheck->setEnabled(true); if (color.isValid()) { d->mBackgroundColorCheck->setChecked(true); d->mBackgroundColorCombo->setColor(color); } else { d->mBackgroundColorCheck->setChecked(false); d->mBackgroundColorCombo->setColor(Qt::white); } d->mBackgroundColorCombo->setEnabled(d->mBackgroundColorCheck->isChecked()); } void TagWidget::setTagTextFormat(bool isBold, bool isItalic) { d->mTextFontCheck->setEnabled(true); d->mTextFontCheck->setChecked(isBold || isItalic); d->mBoldCheckBox->setChecked(isBold); d->mItalicCheckBox->setChecked(isItalic); } void TagWidget::recordTagSettings(MailCommon::Tag::Ptr tag) { tag->textColor = d->mTextColorCheck->isChecked() ? d->mTextColorCombo->color() : QColor(); tag->backgroundColor = d->mBackgroundColorCheck->isChecked() ? d->mBackgroundColorCombo->color() : QColor(); tag->isBold = d->mTextFontCheck->isChecked() ? d->mBoldCheckBox->isChecked() : false; tag->isItalic = d->mTextFontCheck->isChecked() ? d->mItalicCheckBox->isChecked() : false; tag->iconName = iconButton()->icon(); if (d->mKeySequenceWidget->isEnabled()) { d->mKeySequenceWidget->applyStealShortcut(); tag->shortcut = QKeySequence(d->mKeySequenceWidget->keySequence()); } tag->inToolbar = d->mInToolbarCheck->isChecked(); } KLineEdit *TagWidget::tagNameLineEdit() const { return d->mTagNameLineEdit; } QCheckBox *TagWidget::textColorCheck() const { return d->mTextColorCheck; } QCheckBox *TagWidget::textFontCheck() const { return d->mTextFontCheck; } QCheckBox *TagWidget::backgroundColorCheck() const { return d->mBackgroundColorCheck; } QCheckBox *TagWidget::inToolBarCheck() const { return d->mInToolbarCheck; } KColorCombo *TagWidget::textColorCombo() const { return d->mTextColorCombo; } KColorCombo *TagWidget::backgroundColorCombo() const { return d->mBackgroundColorCombo; } QCheckBox *TagWidget::textBoldCheck() const { return d->mBoldCheckBox; } QCheckBox *TagWidget::textItalicCheck() const { return d->mItalicCheckBox; } KIconButton *TagWidget::iconButton() const { return d->mIconButton; } KKeySequenceWidget *TagWidget::keySequenceWidget() const { return d->mKeySequenceWidget; } diff --git a/src/tag/tagwidget.h b/src/tag/tagwidget.h index 60a4e2b..4d8693d 100644 --- a/src/tag/tagwidget.h +++ b/src/tag/tagwidget.h @@ -1,75 +1,76 @@ /* Copyright (c) 2012-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_TAGWIDGET_H #define MAILCOMMON_TAGWIDGET_H #include "mailcommon_export.h" #include "tag.h" #include class KLineEdit; class KColorCombo; class KIconButton; class KKeySequenceWidget; class QCheckBox; class KActionCollection; namespace MailCommon { class TagWidgetPrivate; class MAILCOMMON_EXPORT TagWidget : public QWidget { Q_OBJECT public: explicit TagWidget(const QList &actionCollections, QWidget *parent = nullptr); ~TagWidget(); void recordTagSettings(MailCommon::Tag::Ptr tag); Q_REQUIRED_RESULT KLineEdit *tagNameLineEdit() const; Q_REQUIRED_RESULT QCheckBox *textColorCheck() const; Q_REQUIRED_RESULT QCheckBox *textFontCheck() const; Q_REQUIRED_RESULT QCheckBox *backgroundColorCheck() const; Q_REQUIRED_RESULT QCheckBox *inToolBarCheck() const; Q_REQUIRED_RESULT KColorCombo *textColorCombo() const; Q_REQUIRED_RESULT KColorCombo *backgroundColorCombo() const; Q_REQUIRED_RESULT QCheckBox *textBoldCheck() const; Q_REQUIRED_RESULT QCheckBox *textItalicCheck() const; Q_REQUIRED_RESULT KIconButton *iconButton() const; Q_REQUIRED_RESULT KKeySequenceWidget *keySequenceWidget() const; void setTagTextColor(const QColor &color); void setTagBackgroundColor(const QColor &color); void setTagTextFormat(bool bold, bool italic); Q_SIGNALS: void changed(); void iconNameChanged(const QString &); private: void slotEmitChangeCheck(); TagWidgetPrivate *const d; }; } #endif // MAILCOMMON_TAGWIDGET_H diff --git a/src/util/resourcereadconfigfile.cpp b/src/util/resourcereadconfigfile.cpp index 146026e..b0467ec 100644 --- a/src/util/resourcereadconfigfile.cpp +++ b/src/util/resourcereadconfigfile.cpp @@ -1,57 +1,58 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "resourcereadconfigfile.h" #include using namespace MailCommon; class MailCommon::ResourceReadConfigFilePrivate { public: ResourceReadConfigFilePrivate() : mConfig(nullptr) { } ~ResourceReadConfigFilePrivate() { delete mConfig; } KConfig *mConfig = nullptr; }; ResourceReadConfigFile::ResourceReadConfigFile(const QString &resourceName) : d(new MailCommon::ResourceReadConfigFilePrivate) { d->mConfig = new KConfig(resourceName + QStringLiteral("rc")); } ResourceReadConfigFile::~ResourceReadConfigFile() { delete d; } KConfigGroup ResourceReadConfigFile::group(const QString &name) const { if (d->mConfig) { return d->mConfig->group(name); } return KConfigGroup(); } diff --git a/src/util/resourcereadconfigfile.h b/src/util/resourcereadconfigfile.h index 7c7ba27..dc6cf9c 100644 --- a/src/util/resourcereadconfigfile.h +++ b/src/util/resourcereadconfigfile.h @@ -1,39 +1,40 @@ /* Copyright (c) 2015-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef RESOURCEREADCONFIGFILE_H #define RESOURCEREADCONFIGFILE_H #include #include #include "mailcommon_export.h" namespace MailCommon { class ResourceReadConfigFilePrivate; class MAILCOMMON_EXPORT ResourceReadConfigFile { public: ResourceReadConfigFile(const QString &resourceName); ~ResourceReadConfigFile(); KConfigGroup group(const QString &name) const; private: ResourceReadConfigFilePrivate *const d; }; } #endif // RESOURCEREADCONFIGFILE_H diff --git a/src/widgets/favoritecollectionwidget.cpp b/src/widgets/favoritecollectionwidget.cpp index 20191f1..f91c398 100644 --- a/src/widgets/favoritecollectionwidget.cpp +++ b/src/widgets/favoritecollectionwidget.cpp @@ -1,325 +1,326 @@ /* Copyright (c) 2012-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "favoritecollectionwidget.h" #include "kernel/mailkernel.h" #include "mailcommonsettings_base.h" #include #include #include #include #include #include #include #include #include #include using namespace MailCommon; class Q_DECL_HIDDEN FavoriteCollectionWidget::Private { public: Private() { } QColor textColor; QAction *listMode = nullptr; QAction *iconMode = nullptr; MailCommonSettings *settings = nullptr; }; FavoriteCollectionWidget::FavoriteCollectionWidget(MailCommon::MailCommonSettings *settings, KXMLGUIClient *xmlGuiClient, QWidget *parent) : Akonadi::EntityListView(xmlGuiClient, parent) , d(new Private) { d->settings = settings; setFocusPolicy(Qt::NoFocus); Akonadi::CollectionStatisticsDelegate *delegate = new Akonadi::CollectionStatisticsDelegate(this); delegate->setProgressAnimationEnabled(true); setItemDelegate(delegate); delegate->setUnreadCountShown(true); readConfig(); createMenu(xmlGuiClient->actionCollection()); } FavoriteCollectionWidget::~FavoriteCollectionWidget() { delete d; } void FavoriteCollectionWidget::mousePressEvent(QMouseEvent *e) { const bool buttonPressedIsMiddle = (e->button() == Qt::MidButton); Q_EMIT newTabRequested(buttonPressedIsMiddle); Akonadi::EntityListView::mousePressEvent(e); } void FavoriteCollectionWidget::updateMode() { switch (viewMode()) { case ListMode: d->listMode->setChecked(true); d->iconMode->setChecked(false); break; case IconMode: d->listMode->setChecked(false); d->iconMode->setChecked(true); break; } } void FavoriteCollectionWidget::createMenu(KActionCollection *ac) { KActionMenu *iconSizeMenu = new KActionMenu(i18n("Icon size"), this); ac->addAction(QStringLiteral("favorite_icon_size"), iconSizeMenu); static const int icon_sizes[] = { 16, 22, 32 /*, 48, 64, 128 */ }; QActionGroup *grp = new QActionGroup(iconSizeMenu); const int nbElement((int)(sizeof(icon_sizes) / sizeof(int))); QAction *act = nullptr; for (int i = 0; i < nbElement; ++i) { act = new QAction(QStringLiteral("%1x%2").arg(icon_sizes[ i ]).arg(icon_sizes[ i ]), iconSizeMenu); iconSizeMenu->addAction(act); act->setCheckable(true); grp->addAction(act); if (iconSize().width() == icon_sizes[ i ]) { act->setChecked(true); } act->setData(QVariant(icon_sizes[ i ])); connect(act, &QAction::triggered, this, &FavoriteCollectionWidget::slotChangeIconSize); } KActionMenu *modeFavoriteMenu = new KActionMenu(i18n("Mode"), this); ac->addAction(QStringLiteral("favorite_mode"), modeFavoriteMenu); grp = new QActionGroup(modeFavoriteMenu); d->listMode = new QAction(i18n("List Mode"), modeFavoriteMenu); modeFavoriteMenu->addAction(d->listMode); d->listMode->setCheckable(true); grp->addAction(d->listMode); if (viewMode() == ListMode) { d->listMode->setChecked(true); } d->listMode->setData(QVariant(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::ListMode)); connect(d->listMode, &QAction::triggered, this, &FavoriteCollectionWidget::slotChangeMode); d->iconMode = new QAction(i18n("Icon Mode"), modeFavoriteMenu); modeFavoriteMenu->addAction(d->iconMode); grp->addAction(d->iconMode); d->iconMode->setCheckable(true); if (viewMode() == IconMode) { d->iconMode->setChecked(true); } d->iconMode->setData(QVariant(MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::IconMode)); connect(d->iconMode, &QAction::triggered, this, &FavoriteCollectionWidget::slotChangeMode); } void FavoriteCollectionWidget::slotChangeMode(bool) { QAction *act = qobject_cast< QAction * >(sender()); if (!act) { return; } QVariant data = act->data(); bool ok; const int mode = data.toInt(&ok); if (!ok) { return; } switch (mode) { case MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::IconMode: changeViewMode(IconMode); break; case MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::ListMode: changeViewMode(ListMode); break; } d->settings->setFavoriteCollectionViewMode(mode); d->settings->save(); } void FavoriteCollectionWidget::changeViewMode(QListView::ViewMode mode) { setViewMode(mode); setDragEnabled(true); setAcceptDrops(true); } void FavoriteCollectionWidget::slotChangeIconSize(bool) { QAction *act = qobject_cast< QAction * >(sender()); if (!act) { return; } QVariant data = act->data(); bool ok; const int size = data.toInt(&ok); if (!ok) { return; } const QSize newIconSize(QSize(size, size)); if (newIconSize == iconSize()) { return; } setIconSize(newIconSize); d->settings->setIconSize(iconSize().width()); d->settings->save(); } void FavoriteCollectionWidget::slotGeneralPaletteChanged() { const QPalette palette = viewport()->palette(); QColor color = palette.text().color(); color.setAlpha(128); d->textColor = color; } void FavoriteCollectionWidget::slotGeneralFontChanged() { // Custom/System font support if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) { setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); } } void FavoriteCollectionWidget::readConfig() { setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); int iIconSize = d->settings->iconSize(); if (iIconSize < 16 || iIconSize > 32) { iIconSize = 22; } setIconSize(QSize(iIconSize, iIconSize)); } void FavoriteCollectionWidget::paintEvent(QPaintEvent *event) { if (!model() || model()->rowCount() == 0) { QPainter p(viewport()); QFont font = p.font(); font.setItalic(true); p.setFont(font); if (!d->textColor.isValid()) { slotGeneralPaletteChanged(); } p.setPen(d->textColor); p.drawText(QRect(0, 0, width(), height()), Qt::AlignCenter, i18n("Drop your favorite folders here...")); } else { Akonadi::EntityListView::paintEvent(event); } } static bool isCollection(const QMimeData *mimeData) { const QList urls = mimeData->urls(); for (const QUrl &url : urls) { const Akonadi::Collection collection = Akonadi::Collection::fromUrl(url); if (collection.isValid()) { return true; } } return false; } bool FavoriteCollectionWidget::acceptEvent(QDropEvent *event) const { const bool draggingCollection = isCollection(event->mimeData()); const bool droppingOnCollection = dropIndicatorPosition() == QAbstractItemView::OnItem; if (event->source() == this) { if (draggingCollection && !droppingOnCollection) { // Re-ordering favorites return true; } } else { if ((draggingCollection && !droppingOnCollection) // Adding a new favorite collection || (!draggingCollection && droppingOnCollection)) { // Dropping emails onto a favorite collection return true; } } event->ignore(); return false; } void FavoriteCollectionWidget::dragEnterEvent(QDragEnterEvent *event) { if (event->source() == this) { QListView::dragEnterEvent(event); // Re-ordering favourites } else { Akonadi::EntityListView::dragEnterEvent(event); // Dropping emails onto a favorite collection } } void FavoriteCollectionWidget::dragMoveEvent(QDragMoveEvent *event) { // We need to ask QListView to update dropIndicatorPosition() first... QListView::dragMoveEvent(event); if (event->source() == this) { if (acceptEvent(event)) { event->setDropAction(Qt::MoveAction); event->accept(); // Re-ordering favourites } } else { if (acceptEvent(event)) { Akonadi::EntityListView::dragMoveEvent(event); // Dropping emails onto a favorite collection } } } void FavoriteCollectionWidget::dropEvent(QDropEvent *event) { if (event->source() == this) { if (acceptEvent(event)) { QListView::dropEvent(event); // Re-ordering favourites } } else { if (acceptEvent(event)) { if (dropIndicatorPosition() == QAbstractItemView::OnItem) { Akonadi::EntityListView::dropEvent(event); // Dropping emails onto a favorite collection } else { QListView::dropEvent(event); // Add new favorite } } } } void FavoriteCollectionWidget::startDrag(Qt::DropActions supportedActions) { // skip EntityListView logic (we want to reorder favorites, not trigger moving/copying of actual folders) QListView::startDrag(supportedActions); } diff --git a/src/widgets/favoritecollectionwidget.h b/src/widgets/favoritecollectionwidget.h index 1e0e677..c151db8 100644 --- a/src/widgets/favoritecollectionwidget.h +++ b/src/widgets/favoritecollectionwidget.h @@ -1,69 +1,70 @@ /* Copyright (c) 2012-2018 Montel Laurent - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License, version 2, as - published by the Free Software Foundation. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MAILCOMMON_FAVORITECOLLECTIONWIDGET_H #define MAILCOMMON_FAVORITECOLLECTIONWIDGET_H #include "mailcommon_export.h" #include class KXMLGUIClient; class KActionCollection; namespace MailCommon { class MailCommonSettings; class MAILCOMMON_EXPORT FavoriteCollectionWidget : public Akonadi::EntityListView { Q_OBJECT public: explicit FavoriteCollectionWidget(MailCommon::MailCommonSettings *settings, KXMLGUIClient *xmlGuiClient, QWidget *parent = nullptr); ~FavoriteCollectionWidget() override; void readConfig(); void updateMode(); void changeViewMode(QListView::ViewMode mode); protected Q_SLOTS: void slotGeneralFontChanged(); void slotGeneralPaletteChanged(); void slotChangeIconSize(bool); void slotChangeMode(bool); protected: void paintEvent(QPaintEvent *) override; void dragEnterEvent(QDragEnterEvent *event) override; void dragMoveEvent(QDragMoveEvent *event) override; void dropEvent(QDropEvent *event) override; void startDrag(Qt::DropActions) override; void mousePressEvent(QMouseEvent *e) override; Q_SIGNALS: void newTabRequested(bool); private: bool acceptEvent(QDropEvent *event) const; void createMenu(KActionCollection *ac); class Private; Private *const d; }; } #endif /* MAILCOMMON_FAVORITECOLLECTIONWIDGET_H */