diff --git a/src/folder/foldertreeview.cpp b/src/folder/foldertreeview.cpp index 3f91923..7472882 100644 --- a/src/folder/foldertreeview.cpp +++ b/src/folder/foldertreeview.cpp @@ -1,610 +1,710 @@ /* Copyright (c) 2009-2017 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 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 -namespace MailCommon { +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; menu.addSection(i18n("View Columns")); const int nbColumn = header()->count(); 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 /*, 48, 64, 128 */ }; 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(), MailCommon::Util::ForwardSearch, confirm)) { + if (!trySelectNextUnreadFolder(currentIndex(), ForwardSearch, confirm)) { // if there is none, jump to the last collection and try again - trySelectNextUnreadFolder(model()->index(0, 0), MailCommon::Util::ForwardSearch, confirm); + 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(), MailCommon::Util::BackwardSearch, confirm)) { + if (!trySelectNextUnreadFolder(currentIndex(), BackwardSearch, confirm)) { // if there is none, jump to top and try again const QModelIndex index = lastChildOf(model(), QModelIndex()); - trySelectNextUnreadFolder(index, MailCommon::Util::BackwardSearch, confirm); + trySelectNextUnreadFolder(index, BackwardSearch, confirm); } } -bool FolderTreeView::trySelectNextUnreadFolder(const QModelIndex ¤t, MailCommon::Util::SearchDirection direction, bool confirm) +bool FolderTreeView::trySelectNextUnreadFolder(const QModelIndex ¤t, SearchDirection direction, bool confirm) { QModelIndex index = current; while (true) { - index = MailCommon::Util::nextUnreadCollection(model(), index, direction); + 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 4f89cc2..066202e 100644 --- a/src/folder/foldertreeview.h +++ b/src/folder/foldertreeview.h @@ -1,119 +1,129 @@ /* Copyright (c) 2009-2017 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 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); virtual ~FolderTreeView(); 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); 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 &, MailCommon::Util::SearchDirection, bool); + bool trySelectNextUnreadFolder(const QModelIndex &, SearchDirection, bool); FolderTreeWidget::ToolTipDisplayPolicy mToolTipDisplayPolicy; FolderTreeWidget::SortingPolicy mSortingPolicy; Akonadi::CollectionStatisticsDelegate *mCollectionStatisticsDelegate; bool mbDisableContextMenuAndExtraColumn; bool mbDisableSaveConfig; }; } #endif diff --git a/src/util/mailutil.cpp b/src/util/mailutil.cpp index b32a2ff..5e1a3fe 100644 --- a/src/util/mailutil.cpp +++ b/src/util/mailutil.cpp @@ -1,403 +1,297 @@ /******************************************************************************* ** ** Filename : util ** Created on : 03 April, 2005 ** Copyright : (c) 2005 Till Adam ** Email : ** *******************************************************************************/ /******************************************************************************* ** ** 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. ** ** It 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 "mailutil.h" #include "mailutil_p.h" #include "mailcommon_debug.h" #include "calendarinterface.h" #include "job/expirejob.h" #include "folder/foldersettings.h" #include "pop3settings.h" #include "kernel/mailkernel.h" #include "filter/dialog/filteractionmissingargumentdialog.h" #include "mailimporter/filterbalsa.h" #include "mailimporter/filterevolution.h" #include "mailimporter/filterevolution_v2.h" #include "mailimporter/filterevolution_v3.h" #include "mailimporter/filterclawsmail.h" #include "mailimporter/filtersylpheed.h" #include "mailimporter/filterthunderbird.h" #include "mailimporter/filteropera.h" #include "mailimporter/filtericedove.h" #include "mailimporter/othermailerutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include OrgKdeAkonadiPOP3SettingsInterface *MailCommon::Util::createPop3SettingsInterface( const QString &ident) { return new OrgKdeAkonadiPOP3SettingsInterface( QLatin1String("org.freedesktop.Akonadi.Resource.") + ident, QStringLiteral("/Settings"), QDBusConnection::sessionBus()); } bool MailCommon::Util::isVirtualCollection(const Akonadi::Collection &collection) { return MailCommon::Util::isVirtualCollection(collection.resource()); } bool MailCommon::Util::isVirtualCollection(const QString &resource) { return resource == QLatin1String("akonadi_search_resource"); } bool MailCommon::Util::isLocalCollection(const QString &resource) { return resource.contains(QStringLiteral("akonadi_mbox_resource")) || resource.contains(QStringLiteral("akonadi_maildir_resource")) || resource.contains(QStringLiteral("akonadi_mixedmaildir_resource")); } QString MailCommon::Util::fullCollectionPath(const Akonadi::Collection &collection) { QString fullPath; QModelIndex idx = Akonadi::EntityTreeModel::modelIndexForCollection(KernelIf->collectionModel(), collection); if (!idx.isValid()) { return fullPath; } fullPath = idx.data().toString(); idx = idx.parent(); while (idx != QModelIndex()) { fullPath = idx.data().toString() + QLatin1Char('/') + fullPath; idx = idx.parent(); } return fullPath; } bool MailCommon::Util::showJobErrorMessage(KJob *job) { if (job->error()) { if (auto uidelegate = static_cast(job)->ui()) { uidelegate->showErrorMessage(); } else { qCDebug(MAILCOMMON_LOG) << " job->errorString() :" << job->errorString(); } return true; } return false; } Akonadi::AgentInstance::List MailCommon::Util::agentInstances(bool excludeMailDispacher) { Akonadi::AgentInstance::List relevantInstances; const Akonadi::AgentInstance::List agentList = Akonadi::AgentManager::self()->instances(); for (const Akonadi::AgentInstance &instance : agentList) { const QStringList capabilities(instance.type().capabilities()); if (instance.type().mimeTypes().contains(KMime::Message::mimeType())) { if (capabilities.contains(QStringLiteral("Resource")) && !capabilities.contains(QStringLiteral("Virtual")) && !capabilities.contains(QStringLiteral("MailTransport"))) { relevantInstances << instance; } else if (!excludeMailDispacher && instance.identifier() == QLatin1String("akonadi_maildispatcher_agent")) { relevantInstances << instance; } } } return relevantInstances; } uint MailCommon::Util::folderIdentity(const Akonadi::Item &item) { uint id = 0; if (item.isValid() && item.parentCollection().isValid()) { Akonadi::Collection col = item.parentCollection(); if (col.resource().isEmpty()) { col = parentCollectionFromItem(item); } const QSharedPointer fd = FolderSettings::forCollection(col, false); id = fd->identity(); } return id; } -static QModelIndex indexBelow(QAbstractItemModel *model, const QModelIndex ¤t) -{ - // 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 QModelIndex index - = indexBelow(model, 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 -} - -static QModelIndex lastChildOfModel(QAbstractItemModel *model, const QModelIndex ¤t) -{ - if (model->rowCount(current) == 0) { - return current; - } - - return lastChildOfModel(model, model->index(model->rowCount(current) - 1, 0, current)); -} - -static QModelIndex indexAbove(QAbstractItemModel *model, const QModelIndex ¤t) -{ - 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 lastChildOfModel(model, previousSibling); -} - -QModelIndex MailCommon::Util::nextUnreadCollection(QAbstractItemModel *model, const QModelIndex ¤t, SearchDirection direction, bool (*ignoreCollectionCallback)( - const Akonadi::Collection &collection)) -{ - QModelIndex index = current; - while (true) { - if (direction == MailCommon::Util::ForwardSearch) { - index = indexBelow(model, index); - } else if (direction == MailCommon::Util::BackwardSearch) { - index = indexAbove(model, index); - } - - if (!index.isValid()) { // reach end or top of the model - return QModelIndex(); - } - - // check if the index is a collection - const Akonadi::Collection collection - = index.data(Akonadi::EntityTreeModel::CollectionRole).value(); - - if (collection.isValid()) { - // check if it is unread - if (collection.statistics().unreadCount() > 0) { - if (ignoreCollectionCallback && ignoreCollectionCallback(collection)) { - continue; - } - if (!ignoreNewMailInFolder(collection)) { - return index; // we found the next unread collection - } - } - } - } - - return QModelIndex(); // no unread collection found -} - bool MailCommon::Util::ignoreNewMailInFolder(const Akonadi::Collection &collection) { if (collection.hasAttribute()) { if (collection.attribute()->ignoreNewMail()) { return true; } } return false; } Akonadi::Collection MailCommon::Util::parentCollectionFromItem(const Akonadi::Item &item) { return updatedCollection(item.parentCollection()); } QString MailCommon::Util::realFolderPath(const QString &path) { QString realPath(path); realPath.remove(QStringLiteral(".directory")); realPath.replace(QLatin1String("/."), QStringLiteral("/")); if (!realPath.isEmpty() && (realPath.at(0) == QLatin1Char('.'))) { realPath.remove(0, 1); //remove first "." } return realPath; } QColor MailCommon::Util::defaultQuotaColor() { KColorScheme scheme(QPalette::Active, KColorScheme::View); return scheme.foreground(KColorScheme::NegativeText).color(); } void MailCommon::Util::expireOldMessages(const Akonadi::Collection &collection, bool immediate) { ScheduledExpireTask *task = new ScheduledExpireTask(collection, immediate); KernelIf->jobScheduler()->registerTask(task); } Akonadi::Collection MailCommon::Util::updatedCollection(const Akonadi::Collection &col) { const QModelIndex idx = Akonadi::EntityTreeModel::modelIndexForCollection(KernelIf->collectionModel(), col); const Akonadi::Collection collection = idx.data(Akonadi::EntityTreeModel::CollectionRole).value(); return collection; } Akonadi::Collection::Id MailCommon::Util::convertFolderPathToCollectionId(const QString &folder) { Akonadi::Collection::Id newFolderId = -1; bool exactPath = false; Akonadi::Collection::List lst = FilterActionMissingCollectionDialog::potentialCorrectFolders(folder, exactPath); if (lst.count() == 1 && exactPath) { newFolderId = lst.at(0).id(); } else { QPointer dlg = new FilterActionMissingCollectionDialog(lst, QString(), folder); if (dlg->exec()) { newFolderId = dlg->selectedCollection().id(); } delete dlg; } return newFolderId; } QString MailCommon::Util::convertFolderPathToCollectionStr(const QString &folder) { Akonadi::Collection::Id newFolderId = MailCommon::Util::convertFolderPathToCollectionId(folder); if (newFolderId == -1) { return QString(); } return QString::number(newFolderId); } void MailCommon::Util::foundMailer(QStringList &lst, const QString &name) { if (!name.isEmpty()) { lst.append(name); } } QStringList MailCommon::Util::foundMailer() { QStringList lst; MailCommon::Util::foundMailer(lst, MailImporter::FilterEvolution::isMailerFound()); MailCommon::Util::foundMailer(lst, MailImporter::FilterEvolution_v2::isMailerFound()); MailCommon::Util::foundMailer(lst, MailImporter::FilterEvolution_v3::isMailerFound()); MailCommon::Util::foundMailer(lst, MailImporter::FilterBalsa::isMailerFound()); MailCommon::Util::foundMailer(lst, MailImporter::FilterClawsMail::isMailerFound()); MailCommon::Util::foundMailer(lst, MailImporter::FilterOpera::isMailerFound()); MailCommon::Util::foundMailer(lst, MailImporter::FilterSylpheed::isMailerFound()); MailCommon::Util::foundMailer(lst, MailImporter::FilterThunderbird::isMailerFound()); MailCommon::Util::foundMailer(lst, MailImporter::FilterIcedove::isMailerFound()); const QStringList otherMailer = MailImporter::OtherMailerUtil::isMailerFound(); if (!otherMailer.isEmpty()) { lst << otherMailer; } return lst; } MailCommon::ExpireCollectionAttribute *MailCommon::Util::expirationCollectionAttribute(const Akonadi::Collection &collection, bool &mustDeleteExpirationAttribute) { MailCommon::ExpireCollectionAttribute *attr = nullptr; if (collection.hasAttribute()) { attr = collection.attribute(); mustDeleteExpirationAttribute = false; } else { attr = new MailCommon::ExpireCollectionAttribute(); KConfigGroup configGroup(KernelIf->config(), MailCommon::FolderSettings::configGroupName(collection)); if (configGroup.hasKey("ExpireMessages")) { attr->setAutoExpire(configGroup.readEntry("ExpireMessages", false)); attr->setReadExpireAge(configGroup.readEntry("ReadExpireAge", 3)); attr->setReadExpireUnits((MailCommon::ExpireCollectionAttribute::ExpireUnits)configGroup.readEntry("ReadExpireUnits", (int)MailCommon::ExpireCollectionAttribute::ExpireMonths)); attr->setUnreadExpireAge(configGroup.readEntry("UnreadExpireAge", 12)); attr->setUnreadExpireUnits((MailCommon::ExpireCollectionAttribute::ExpireUnits)configGroup.readEntry("UnreadExpireUnits", (int)MailCommon::ExpireCollectionAttribute::ExpireNever)); attr->setExpireAction(configGroup.readEntry("ExpireAction", "Delete") == QLatin1String("Move") ? MailCommon::ExpireCollectionAttribute::ExpireMove : MailCommon::ExpireCollectionAttribute::ExpireDelete); attr->setExpireToFolderId(configGroup.readEntry("ExpireToFolder", -1)); } mustDeleteExpirationAttribute = true; } return attr; } diff --git a/src/util/mailutil.h b/src/util/mailutil.h index ff7325b..bf33cc9 100644 --- a/src/util/mailutil.h +++ b/src/util/mailutil.h @@ -1,110 +1,102 @@ /******************************************************************************* ** ** Filename : util ** Created on : 03 April, 2005 ** Copyright : (c) 2005 Till Adam ** Email : ** *******************************************************************************/ /******************************************************************************* ** ** 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. ** ** It 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_MAILUTIL_H #define MAILCOMMON_MAILUTIL_H #include "mailcommon_export.h" #include #include class OrgKdeAkonadiPOP3SettingsInterface; namespace Akonadi { class Item; } class KJob; class QAbstractItemModel; class QModelIndex; class QString; namespace MailCommon { class ExpireCollectionAttribute; /** * The Util namespace contains a collection of helper functions use in * various places. */ namespace Util { MAILCOMMON_EXPORT OrgKdeAkonadiPOP3SettingsInterface *createPop3SettingsInterface( const QString &ident); MAILCOMMON_EXPORT bool isVirtualCollection(const Akonadi::Collection &col); MAILCOMMON_EXPORT bool isVirtualCollection(const QString &resource); MAILCOMMON_EXPORT QString fullCollectionPath(const Akonadi::Collection &collection); MAILCOMMON_EXPORT bool showJobErrorMessage(KJob *job); MAILCOMMON_EXPORT Akonadi::AgentInstance::List agentInstances(bool excludeMailTransport = true); /** * Returns the identity of the folder that contains the given Akonadi::Item. */ MAILCOMMON_EXPORT uint folderIdentity(const Akonadi::Item &item); -/** - * Describes the direction for searching next unread collection. - */ -enum SearchDirection { - ForwardSearch, - BackwardSearch -}; - MAILCOMMON_EXPORT Akonadi::Collection parentCollectionFromItem(const Akonadi::Item &item); MAILCOMMON_EXPORT QString realFolderPath(const QString &path); MAILCOMMON_EXPORT QColor defaultQuotaColor(); MAILCOMMON_EXPORT void expireOldMessages(const Akonadi::Collection &collection, bool immediate); MAILCOMMON_EXPORT Akonadi::Collection updatedCollection(const Akonadi::Collection &col); MAILCOMMON_EXPORT Akonadi::Collection::Id convertFolderPathToCollectionId(const QString &folder); MAILCOMMON_EXPORT QString convertFolderPathToCollectionStr(const QString &folder); MAILCOMMON_EXPORT void foundMailer(QStringList &lst, const QString &name); MAILCOMMON_EXPORT QStringList foundMailer(); MAILCOMMON_EXPORT bool isLocalCollection(const QString &resource); MAILCOMMON_EXPORT MailCommon::ExpireCollectionAttribute *expirationCollectionAttribute(const Akonadi::Collection &collection, bool &mustDeleteExpirationAttribute); } } #endif diff --git a/src/util/mailutil_p.h b/src/util/mailutil_p.h index 073af20..618d886 100644 --- a/src/util/mailutil_p.h +++ b/src/util/mailutil_p.h @@ -1,76 +1,67 @@ /******************************************************************************* ** ** Filename : util ** Created on : 03 April, 2005 ** Copyright : (c) 2005 Till Adam ** Email : ** *******************************************************************************/ /******************************************************************************* ** ** 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. ** ** It 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_MAILUTIL_P_H #define MAILCOMMON_MAILUTIL_P_H #include "mailcommon_export.h" #include "mailutil.h" #include #include namespace Akonadi { class Item; } class QAbstractItemModel; class QModelIndex; class QString; namespace MailCommon { /** * The Util namespace contains a collection of helper functions use in * various places. */ namespace Util { -/** - * Returns the index of the next unread collection following a given index. - * - * @param model The item model to search in. - * @param current The index of the collection where the search will start. - * @param direction The direction of search. - * @param ignoreCollectionCallback A callback method to ignore certain - * collections by returning @c true. - */ -QModelIndex nextUnreadCollection(QAbstractItemModel *model, const QModelIndex ¤t, SearchDirection direction, bool (*ignoreCollectionCallback)(const Akonadi::Collection &collection) = 0); bool ignoreNewMailInFolder(const Akonadi::Collection &collection); + } } #endif