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

/*
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 &current)
{
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 &current)
{
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 &current,
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 &current,
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.
}
}