From 7179c38fbff9c5e881112e381731bdc29b0530d5 Mon Sep 17 00:00:00 2001 From: Roland Pallai Date: Sat, 18 Mar 2017 21:57:52 +0100 Subject: [PATCH] Move cursor to the next message after current one disappears A big inconvience for keyboard usability if the cursor forgets last position when the current message disappeared from message list. It also prevents next/previous message actions to function. This patch using an unobtrusive approach to this problem, moves the cursor to the next message but does not activate it. The user can activate with return key or can navigate towards. This way it does not interfere with automatic read marking nor habits of users. CCBUG: 360091 CCBUG: 321387 Change-Id: Ic823c2c9d0bba9de65d01fb0e72b91093f233f7e --- src/Gui/MsgListView.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++++++++ src/Gui/MsgListView.h | 6 +++++ 2 files changed, 72 insertions(+) diff --git a/src/Gui/MsgListView.cpp b/src/Gui/MsgListView.cpp index b7c31ce5..ed5c1b44 100644 --- a/src/Gui/MsgListView.cpp +++ b/src/Gui/MsgListView.cpp @@ -34,6 +34,7 @@ #include "ColoredItemDelegate.h" #include "Imap/Model/MsgListModel.h" #include "Imap/Model/PrettyMsgListModel.h" +#include "Imap/Model/ThreadingMsgListModel.h" namespace Gui { @@ -400,12 +401,77 @@ void MsgListView::setModel(QAbstractItemModel *model) if (Imap::Mailbox::PrettyMsgListModel *prettyModel = findPrettyMsgListModel(this->model())) { disconnect(prettyModel, &Imap::Mailbox::PrettyMsgListModel::sortingPreferenceChanged, this, &MsgListView::slotHandleSortCriteriaChanged); + disconnect(qobject_cast(prettyModel->sourceModel())->sourceModel(), + &QAbstractItemModel::rowsAboutToBeRemoved, + this, &MsgListView::slotMsgListModelRowsAboutToBeRemoved); } } QTreeView::setModel(model); if (Imap::Mailbox::PrettyMsgListModel *prettyModel = findPrettyMsgListModel(model)) { connect(prettyModel, &Imap::Mailbox::PrettyMsgListModel::sortingPreferenceChanged, this, &MsgListView::slotHandleSortCriteriaChanged); + connect(qobject_cast(prettyModel->sourceModel())->sourceModel(), + &QAbstractItemModel::rowsAboutToBeRemoved, + this, &MsgListView::slotMsgListModelRowsAboutToBeRemoved); + } +} + +/** @short Get ThreadingMsgListModel index and call the next handler */ +void MsgListView::slotMsgListModelRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_ASSERT(!parent.isValid()); + + auto threadingModel = qobject_cast(findPrettyMsgListModel(model())->sourceModel()); + for (int i = start; i <= end; ++i) { + QModelIndex index = threadingModel->sourceModel()->index(i, 0, parent); + Q_ASSERT(index.isValid()); + QModelIndex translated = threadingModel->mapFromSource(index); + + if (translated.isValid()) + slotThreadingMsgListModelRowAboutToBeRemoved(translated); + } +} + +/** @short Keep the cursor in place for better keyboard usability + +In the worst case this is an O(log n). Such a worst case is when messages are removed in descending +order starting from last one of the view. But in practice, there are many cases when it performs +well better. + +A performance-wise approach could be to hook signal layoutAboutToBeChanged, but the underlying model +has removed the rows by then and it makes everything complicated. +*/ +void MsgListView::slotThreadingMsgListModelRowAboutToBeRemoved(const QModelIndex &index) +{ + Imap::Mailbox::PrettyMsgListModel *prettyModel = findPrettyMsgListModel(model()); + Q_ASSERT(!index.isValid() || index.model() == qobject_cast(prettyModel->sourceModel())); + QModelIndex current = currentIndex(); + if (current.isValid() && prettyModel->mapFromSource(index) == current) { + setCurrentIndexToNextValid(current); + } +} + +/** @short Try to move the cursor to next message + +Used when the current message disappearing. +*/ +void MsgListView::setCurrentIndexToNextValid(const QModelIndex ¤t) +{ + Q_ASSERT(current.isValid()); + Imap::Mailbox::PrettyMsgListModel *prettyModel = findPrettyMsgListModel(model()); + Q_ASSERT(current.model() == prettyModel); + for (bool forward : {true,false}) { + QModelIndex walker = forward ? indexBelow(current) : indexAbove(current); + while (walker.isValid()) { + // Queued for pruning..? + if (prettyModel->data(walker, Imap::Mailbox::RoleMessageUid).isValid()) { + // Do not activate, just keep the cursor in place. + selectionModel()->setCurrentIndex(walker, QItemSelectionModel::NoUpdate); + // It has won. For now. + return; + } + walker = forward ? indexBelow(walker) : indexAbove(walker); + } } } diff --git a/src/Gui/MsgListView.h b/src/Gui/MsgListView.h index 067d8ca7..567b7653 100644 --- a/src/Gui/MsgListView.h +++ b/src/Gui/MsgListView.h @@ -65,12 +65,18 @@ private slots: void slotUpdateHeaderActions(); /** @short Show/hide a corresponding column */ void slotHeaderSectionVisibilityToggled(int section); + /** @short Get ThreadingMsgListModel index and call the next handler */ + void slotMsgListModelRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + /** @short Keep the cursor in place for better keyboard usability */ + void slotThreadingMsgListModelRowAboutToBeRemoved(const QModelIndex &index); /** @short Pick up the change of the sort critera */ void slotHandleSortCriteriaChanged(int column, Qt::SortOrder order); /** @short conditionally emits activated(currentIndex()) for keyboard events */ void slotCurrentActivated(); void slotHandleNewColumns(int oldCount, int newCount); private: + /** @short Try to move the cursor to next message */ + void setCurrentIndexToNextValid(const QModelIndex ¤t); static Imap::Mailbox::PrettyMsgListModel *findPrettyMsgListModel(QAbstractItemModel *model); QSignalMapper *headerFieldsMapper; -- 2.11.4.GIT