You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
620 lines
19 KiB
620 lines
19 KiB
/* |
|
|
|
Copyright (c) 2009-2017 Montel Laurent <montel@kde.org> |
|
|
|
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 <CollectionStatistics> |
|
#include <CollectionStatisticsDelegate> |
|
#include <EntityTreeModel> |
|
|
|
#include <KConfigGroup> |
|
#include <KGuiItem> |
|
#include <KLocalizedString> |
|
#include <QMenu> |
|
#include <KMessageBox> |
|
|
|
#include <QHeaderView> |
|
#include <QMouseEvent> |
|
|
|
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<FolderTreeWidget::ToolTipDisplayPolicy>( |
|
myGroup.readEntry("ToolTipDisplayPolicy", |
|
static_cast<int>(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 there is none, jump to the last collection and try again |
|
trySelectNextUnreadFolder(model()->index(0, 0), MailCommon::Util::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 there is none, jump to top and try again |
|
const QModelIndex index = lastChildOf(model(), QModelIndex()); |
|
trySelectNextUnreadFolder(index, MailCommon::Util::BackwardSearch, confirm); |
|
} |
|
} |
|
|
|
bool FolderTreeView::trySelectNextUnreadFolder(const QModelIndex ¤t, |
|
MailCommon::Util::SearchDirection direction, |
|
bool confirm) |
|
{ |
|
QModelIndex index = current; |
|
while (true) { |
|
index = MailCommon::Util::nextUnreadCollection(model(), index, direction); |
|
|
|
if (!index.isValid()) { |
|
return false; |
|
} |
|
|
|
const Akonadi::Collection collection = |
|
index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); |
|
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<FolderTreeView *>(this), |
|
i18n("<qt>Go to the next unread message in folder <b>%1</b>?</qt>", 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<Akonadi::Collection>(); |
|
|
|
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("<qt>Go to the next unread message in folder <b>%1</b>?</qt>", |
|
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<Akonadi::Collection>(); |
|
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. |
|
} |
|
|
|
} |
|
|
|
|