File indexing completed on 2024-05-26 05:28:14

0001 /* Copyright (C) 2012 Thomas Gahr <thomas.gahr@physik.uni-muenchen.de>
0002    Copyright (C) 2006 - 2016 Jan Kundrát <jkt@kde.org>
0003 
0004    This file is part of the Trojita Qt IMAP e-mail client,
0005    http://trojita.flaska.net/
0006 
0007    This program is free software; you can redistribute it and/or
0008    modify it under the terms of the GNU General Public License as
0009    published by the Free Software Foundation; either version 2 of
0010    the License or (at your option) version 3 or any later version
0011    accepted by the membership of KDE e.V. (or its successor approved
0012    by the membership of KDE e.V.), which shall act as a proxy
0013    defined in Section 14 of version 3 of the license.
0014 
0015    This program is distributed in the hope that it will be useful,
0016    but WITHOUT ANY WARRANTY; without even the implied warranty of
0017    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0018    GNU General Public License for more details.
0019 
0020    You should have received a copy of the GNU General Public License
0021    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0022 */
0023 
0024 #include <QDragMoveEvent>
0025 #include <QDropEvent>
0026 #include <QMenu>
0027 #include <QMimeData>
0028 #include "Common/SettingsNames.h"
0029 #include "Gui/MailBoxTreeView.h"
0030 #include "Gui/Util.h"
0031 #include "Imap/Model/ItemRoles.h"
0032 #include "Imap/Model/MailboxFinder.h"
0033 #include "Imap/Model/DragAndDrop.h"
0034 #include "UiUtils/IconLoader.h"
0035 
0036 namespace Gui {
0037 
0038 MailBoxTreeView::MailBoxTreeView(QWidget *parent, QSettings *settings)
0039     : QTreeView(parent)
0040     , m_mailboxFinder(nullptr), m_settings(settings)
0041 {
0042     setUniformRowHeights(true);
0043     setContextMenuPolicy(Qt::CustomContextMenu);
0044     setSelectionMode(QAbstractItemView::SingleSelection);
0045     setAllColumnsShowFocus(true);
0046     setAcceptDrops(true);
0047     setDropIndicatorShown(true);
0048     setHeaderHidden(true);
0049     // I wonder what's the best value to use here. Unfortunately, the default is to disable auto expanding.
0050     setAutoExpandDelay(800);
0051 
0052     // Track expansion/collapsing so that we remember this state despite the asynchronous nature of mailbox loading
0053     connect(this, &QTreeView::expanded, this, [this](const QModelIndex &what) {
0054         auto name = what.data(Imap::Mailbox::RoleMailboxName).toString();
0055         if (!m_desiredExpansionState.contains(name)) {
0056             m_desiredExpansionState.insert(name);
0057             emit mailboxExpansionChanged(m_desiredExpansionState.values());
0058         }
0059     });
0060     connect(this, &QTreeView::collapsed, this, [this](const QModelIndex &what) {
0061         auto name = what.data(Imap::Mailbox::RoleMailboxName).toString();
0062         if (m_desiredExpansionState.remove(name)) {
0063             emit mailboxExpansionChanged(m_desiredExpansionState.values());
0064         }
0065     });
0066 }
0067 
0068 /** \reimp
0069 
0070 The MailboxFinder has to be kept up-to-speed about these changes.
0071 */
0072 void MailBoxTreeView::setModel(QAbstractItemModel *model)
0073 {
0074     delete m_mailboxFinder;
0075     m_mailboxFinder = nullptr;
0076 
0077     if (model) {
0078         m_mailboxFinder = new Imap::Mailbox::MailboxFinder(this, model);
0079         connect(m_mailboxFinder, &Imap::Mailbox::MailboxFinder::mailboxFound,
0080                 this, [this](const QString &, const QModelIndex &index) {
0081             expand(index);
0082         });
0083         connect(model, &QAbstractItemModel::layoutChanged, this, &MailBoxTreeView::resetWatchedMailboxes);
0084         connect(model, &QAbstractItemModel::rowsRemoved, this, &MailBoxTreeView::resetWatchedMailboxes);
0085         connect(model, &QAbstractItemModel::modelReset, this, &MailBoxTreeView::resetWatchedMailboxes);
0086     }
0087     QTreeView::setModel(model);
0088     resetWatchedMailboxes();
0089 }
0090 /** @short Returns the user's default action from Qt::DropAction or Qt::IgnoreAction if not set */
0091 Qt::DropAction MailBoxTreeView::defaultDropAction()
0092 {
0093     auto mboxDropAction = m_settings->value(Common::SettingsNames::mboxDropAction, QVariant(QStringLiteral("ask"))).toString();
0094     if (mboxDropAction == QStringLiteral("move")) {
0095         return Qt::MoveAction;
0096     } else if (mboxDropAction == QStringLiteral("copy")) {
0097         return Qt::CopyAction;
0098     } else {
0099         return Qt::IgnoreAction;
0100     }
0101 }
0102 
0103 /** @short Reimplemented for more consistent handling of modifiers
0104 
0105   Qt's default behaviour is odd here:
0106   If you selected the move-action by pressing shift and you release the shift
0107   key while moving the mouse, the action does not change back to copy. But if you
0108   then move the mouse over a widget border - i.e. cause dragLeaveEvent, the action
0109   WILL change back to copy.
0110   This implementation immitates KDE's behaviour: react to a change in modifiers
0111   immediately.
0112 */
0113 void MailBoxTreeView::dragMoveEvent(QDragMoveEvent *event)
0114 {
0115     QTreeView::dragMoveEvent(event);
0116     if (!event->isAccepted())
0117         return;
0118     if (event->keyboardModifiers() == Qt::ShiftModifier)
0119         event->setDropAction(Qt::MoveAction);
0120     else
0121         event->setDropAction(Qt::CopyAction);
0122 }
0123 
0124 /** @short Reimplemented to present the user with a menu to choose between copy or move.
0125 
0126   Does not show the menu if either ctrl (copy messages) or shift (move messages)
0127   is pressed
0128 */
0129 void MailBoxTreeView::dropEvent(QDropEvent *event)
0130 {
0131     if (Gui::Util::isFromDistinctImapAccount(event)) {
0132         event->ignore();
0133         return;
0134     }
0135     if (event->keyboardModifiers() == Qt::ControlModifier) {
0136         event->setDropAction(Qt::CopyAction);
0137     } else if (event->keyboardModifiers() == Qt::ShiftModifier) {
0138         event->setDropAction(Qt::MoveAction);
0139     } else if (defaultDropAction() != Qt::IgnoreAction) {
0140         event->setDropAction(defaultDropAction());
0141     } else {
0142         QMenu menu(this);
0143         QAction *moveAction = menu.addAction(UiUtils::loadIcon(QStringLiteral("go-jump")), tr("Move here\tShift"));
0144         menu.addAction(UiUtils::loadIcon(QStringLiteral("edit-copy")), tr("Copy here\tCtrl"));
0145         QAction *cancelAction = menu.addAction(UiUtils::loadIcon(QStringLiteral("process-stop")), tr("Cancel"));
0146 
0147         QAction *selectedAction = menu.exec(mapToGlobal(event->pos()));
0148 
0149         // if user closes the menu or selects cancel, ignore the event
0150         if (!selectedAction || selectedAction == cancelAction) {
0151             event->ignore();
0152             return;
0153         }
0154 
0155         event->setDropAction(selectedAction == moveAction ? Qt::MoveAction : Qt::CopyAction);
0156     }
0157 
0158     QTreeView::dropEvent(event);
0159 }
0160 
0161 void MailBoxTreeView::dragEnterEvent(QDragEnterEvent *event)
0162 {
0163     if (Gui::Util::isFromDistinctImapAccount(event)) {
0164         event->ignore();
0165         return;
0166     }
0167     QTreeView::dragEnterEvent(event);
0168 }
0169 
0170 /** @short Specify which mailboxes should be expanded
0171 
0172 The mailboxes might appear and disappear at any time, so let's make sure that
0173 they are properly expanded/collapsed once they pop in.
0174 */
0175 void MailBoxTreeView::setDesiredExpansion(const QStringList &mailboxNames)
0176 {
0177     m_desiredExpansionState = QSet<QString>(mailboxNames.begin(), mailboxNames.end());
0178     resetWatchedMailboxes();
0179 }
0180 
0181 /** @short Ensure that we watch stuff that we need to watch */
0182 void MailBoxTreeView::resetWatchedMailboxes()
0183 {
0184     if (m_mailboxFinder) {
0185         for (const auto &mailbox: m_desiredExpansionState) {
0186             m_mailboxFinder->addMailbox(mailbox);
0187         }
0188     }
0189 }
0190 
0191 }