File indexing completed on 2024-12-15 04:54:46

0001 /*
0002     SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #pragma once
0008 
0009 #include "messagelist/enums.h"
0010 #include "messagelist/view.h"
0011 #include <QHash>
0012 #include <QList>
0013 #include <QTabWidget>
0014 
0015 #include "messagelist_export.h"
0016 #include <Akonadi/Collection>
0017 #include <Akonadi/Item>
0018 #include <KMime/KMimeMessage>
0019 
0020 class KXMLGUIClient;
0021 class QAbstractItemModel;
0022 class QItemSelectionModel;
0023 class QItemSelection;
0024 
0025 namespace KPIM
0026 {
0027 class MessageStatus;
0028 }
0029 
0030 namespace MessageList
0031 {
0032 class Widget;
0033 class StorageModel;
0034 
0035 /**
0036  * This is the main MessageList panel for Akonadi applications.
0037  * It contains multiple MessageList::Widget tabs
0038  * so it can actually display multiple folder sets at once.
0039  *
0040  * When a KXmlGuiWindow is passed to setXmlGuiClient, the XMLGUI
0041  * defined context menu @c akonadi_messagelist_contextmenu is
0042  * used if available.
0043  *
0044  */
0045 class MESSAGELIST_EXPORT Pane : public QTabWidget
0046 {
0047     Q_OBJECT
0048 
0049 public:
0050     /**
0051      * Create a Pane wrapping the specified model and selection.
0052      */
0053     explicit Pane(bool restoreSession, QAbstractItemModel *model, QItemSelectionModel *selectionModel, QWidget *parent = nullptr);
0054     ~Pane() override;
0055 
0056     virtual MessageList::StorageModel *createStorageModel(QAbstractItemModel *model, QItemSelectionModel *selectionModel, QObject *parent);
0057 
0058     virtual void writeConfig(bool restoreSession);
0059 
0060     /**
0061      * Sets the XML GUI client which the pane is used in.
0062      *
0063      * This is needed if you want to use the built-in context menu.
0064      * Passing 0 is ok and will disable the builtin context menu.
0065      *
0066      * @param xmlGuiClient The KXMLGUIClient the view is used in.
0067      */
0068     void setXmlGuiClient(KXMLGUIClient *xmlGuiClient);
0069 
0070     /**
0071      * Returns the current message for the list as Akonadi::Item.
0072      * May return an invalid Item if there is no current message or no current folder.
0073      */
0074     [[nodiscard]] Akonadi::Item currentItem() const;
0075 
0076     /**
0077      * Returns the current message for the list as KMime::Message::Ptr.
0078      * May return 0 if there is no current message or no current folder.
0079      */
0080     KMime::Message::Ptr currentMessage() const;
0081 
0082     /**
0083      * Returns the currently selected KMime::Message::Ptr (bound to current StorageModel).
0084      * The list may be empty if there are no selected messages or no StorageModel.
0085      *
0086      * If includeCollapsedChildren is true then the children of the selected but
0087      * collapsed items are also added to the list.
0088      *
0089      * The returned list is guaranteed to be valid only until you return control
0090      * to the main even loop. Don't store it for any longer. If you need to reference
0091      * this set of messages at a later stage then take a look at createPersistentSet().
0092      */
0093     [[nodiscard]] QList<KMime::Message::Ptr> selectionAsMessageList(bool includeCollapsedChildren = true) const;
0094 
0095     /**
0096      * Returns the currently selected Items (bound to current StorageModel).
0097      * The list may be empty if there are no selected messages or no StorageModel.
0098      *
0099      * If includeCollapsedChildren is true then the children of the selected but
0100      * collapsed items are also added to the list.
0101      *
0102      * The returned list is guaranteed to be valid only until you return control
0103      * to the main even loop. Don't store it for any longer. If you need to reference
0104      * this set of messages at a later stage then take a look at createPersistentSet().
0105      */
0106     [[nodiscard]] Akonadi::Item::List selectionAsMessageItemList(bool includeCollapsedChildren = true) const;
0107 
0108     /**
0109      * Returns the currently selected Items id(bound to current StorageModel).
0110      * The list may be empty if there are no selected messages or no StorageModel.
0111      *
0112      * If includeCollapsedChildren is true then the children of the selected but
0113      * collapsed items are also added to the list.
0114      *
0115      * The returned list is guaranteed to be valid only until you return control
0116      * to the main even loop. Don't store it for any longer. If you need to reference
0117      * this set of messages at a later stage then take a look at createPersistentSet().
0118      */
0119     [[nodiscard]] QList<qlonglong> selectionAsMessageItemListId(bool includeCollapsedChildren = true) const;
0120 
0121     [[nodiscard]] QList<Akonadi::Item::Id> selectionAsListMessageId(bool includeCollapsedChildren = true) const;
0122 
0123     /**
0124      * Returns the Akonadi::Item bound to the current StorageModel that
0125      * are part of the current thread. The current thread is the thread
0126      * that contains currentMessageItem().
0127      * The list may be empty if there is no currentMessageItem() or no StorageModel.
0128      *
0129      * The returned list is guaranteed to be valid only until you return control
0130      * to the main even loop. Don't store it for any longer. If you need to reference
0131      * this set of messages at a later stage then take a look at createPersistentSet().
0132      */
0133     [[nodiscard]] Akonadi::Item::List currentThreadAsMessageList() const;
0134 
0135     /**
0136      * Selects the next message item in the view.
0137      *
0138      * messageTypeFilter can be used to restrict the selection to only certain message types.
0139      *
0140      * existingSelectionBehaviour specifies how the existing selection
0141      * is manipulated. It may be cleared, expanded or grown/shrunk.
0142      *
0143      * If centerItem is true then the specified item will be positioned
0144      * at the center of the view, if possible.
0145      * If loop is true then the "next" algorithm will restart from the beginning
0146      * of the list if the end is reached, otherwise it will just stop returning false.
0147      */
0148     [[nodiscard]] bool selectNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter,
0149                                              MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour,
0150                                              bool centerItem,
0151                                              bool loop);
0152 
0153     /**
0154      * Selects the previous message item in the view.
0155      * If centerItem is true then the specified item will be positioned
0156      * at the center of the view, if possible.
0157      *
0158      * messageTypeFilter can be used to restrict the selection to only certain message types.
0159      *
0160      * existingSelectionBehaviour specifies how the existing selection
0161      * is manipulated. It may be cleared, expanded or grown/shrunk.
0162      *
0163      * If loop is true then the "previous" algorithm will restart from the end
0164      * of the list if the beginning is reached, otherwise it will just stop returning false.
0165      */
0166     [[nodiscard]] bool selectPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter,
0167                                                  MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour,
0168                                                  bool centerItem,
0169                                                  bool loop);
0170 
0171     /**
0172      * Focuses the next message item in the view without actually selecting it.
0173      *
0174      * messageTypeFilter can be used to restrict the selection to only certain message types.
0175      *
0176      * If centerItem is true then the specified item will be positioned
0177      * at the center of the view, if possible.
0178      * If loop is true then the "next" algorithm will restart from the beginning
0179      * of the list if the end is reached, otherwise it will just stop returning false.
0180      */
0181     [[nodiscard]] bool focusNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop);
0182 
0183     /**
0184      * Focuses the previous message item in the view without actually selecting it.
0185      *
0186      * messageTypeFilter can be used to restrict the selection to only certain message types.
0187      *
0188      * If centerItem is true then the specified item will be positioned
0189      * at the center of the view, if possible.
0190      * If loop is true then the "previous" algorithm will restart from the end
0191      * of the list if the beginning is reached, otherwise it will just stop returning false.
0192      */
0193     [[nodiscard]] bool focusPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop);
0194 
0195     /**
0196      * Selects the currently focused message item. May do nothing if the
0197      * focused message item is already selected (which is very likely).
0198      * If centerItem is true then the specified item will be positioned
0199      * at the center of the view, if possible.
0200      */
0201     void selectFocusedMessageItem(bool centerItem);
0202 
0203     /**
0204      * Selects the first message item in the view that matches the specified Core::MessageTypeFilter.
0205      * If centerItem is true then the specified item will be positioned
0206      * at the center of the view, if possible.
0207      *
0208      * If the current view is already loaded then the request will
0209      * be satisfied immediately (well... if an unread message exists at all).
0210      * If the current view is still loading then the selection of the first
0211      * message will be scheduled to be executed when loading terminates.
0212      *
0213      * So this function doesn't actually guarantee that an unread or new message
0214      * was selected when the call returns. Take care :)
0215      *
0216      * The function returns true if a message was selected and false otherwise.
0217      */
0218     [[nodiscard]] bool selectFirstMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem);
0219 
0220     /**
0221      * Selects the last message item in the view that matches the specified Core::MessageTypeFilter.
0222      * If centerItem is true then the specified item will be positioned
0223      * at the center of the view, if possible.
0224      *
0225      * The function returns true if a message was selected and false otherwise.
0226      */
0227     [[nodiscard]] bool selectLastMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem);
0228 
0229     /**
0230      * If expand is true then it expands the current thread, otherwise
0231      * collapses it.
0232      */
0233     void setCurrentThreadExpanded(bool expand);
0234 
0235     /**
0236      * If expand is true then it expands all the threads, otherwise
0237      * collapses them.
0238      */
0239     void setAllThreadsExpanded(bool expand);
0240 
0241     /**
0242      * If expand is true then it expands all the groups (only the toplevel
0243      * group item: inner threads are NOT expanded). If expand is false
0244      * then it collapses all the groups. If no grouping is in effect
0245      * then this function does nothing.
0246      */
0247     void setAllGroupsExpanded(bool expand);
0248 
0249     /**
0250      * Sets the focus on the quick search line of the currently active tab.
0251      */
0252     void focusQuickSearch(const QString &selectedText = QString());
0253 
0254     /**
0255      * Returns the Akonadi::MessageStatus in the current quicksearch field.
0256      */
0257     [[nodiscard]] QList<Akonadi::MessageStatus> currentFilterStatus() const;
0258 
0259     /**
0260      * Returns the search term in the current quicksearch field.
0261      */
0262     [[nodiscard]] QString currentFilterSearchString() const;
0263 
0264     /**
0265      * Returns true if the current Aggregation is threaded, false otherwise
0266      * (or if there is no current Aggregation).
0267      */
0268     [[nodiscard]] bool isThreaded() const;
0269 
0270     /**
0271      * Fast function that determines if the selection is empty
0272      */
0273     [[nodiscard]] bool selectionEmpty() const;
0274     /**
0275      * Fills the lists of the selected message serial numbers and of the selected+visible ones.
0276      * Returns true if the returned stats are valid (there is a current folder after all)
0277      * and false otherwise. This is called by KMMainWidget in a single place so we optimize by
0278      * making it a single sweep on the selection.
0279      *
0280      * If includeCollapsedChildren is true then the children of the selected but
0281      * collapsed items are also included in the stats
0282      */
0283     [[nodiscard]] bool getSelectionStats(Akonadi::Item::List &selectedItems,
0284                                          Akonadi::Item::List &selectedVisibleItems,
0285                                          bool *allSelectedBelongToSameThread,
0286                                          bool includeCollapsedChildren = true) const;
0287     /**
0288      * Deletes the persistent set pointed by the specified reference.
0289      * If the set does not exist anymore, nothing happens.
0290      */
0291     void deletePersistentSet(MessageList::Core::MessageItemSetReference ref);
0292 
0293     /**
0294      * If bMark is true this function marks the messages as "about to be removed"
0295      * so they appear dimmer and aren't selectable in the view.
0296      * If bMark is false then this function clears the "about to be removed" state
0297      * for the specified MessageItems.
0298      */
0299     void markMessageItemsAsAboutToBeRemoved(MessageList::Core::MessageItemSetReference ref, bool bMark);
0300 
0301     /**
0302      * Return Akonadi::Item from messageItemReference
0303      */
0304     [[nodiscard]] Akonadi::Item::List itemListFromPersistentSet(MessageList::Core::MessageItemSetReference ref);
0305 
0306     /**
0307      * Return a persistent set from current selection
0308      */
0309     [[nodiscard]] MessageList::Core::MessageItemSetReference selectionAsPersistentSet(bool includeCollapsedChildren = true) const;
0310 
0311     /**
0312      * Return a persistent set from current thread
0313      */
0314     [[nodiscard]] MessageList::Core::MessageItemSetReference currentThreadAsPersistentSet() const;
0315     /**
0316      * Sets the focus on the view of the currently active tab.
0317      */
0318     void focusView();
0319 
0320     /**
0321      * Reloads global configuration and eventually reloads all the views.
0322      */
0323     void reloadGlobalConfiguration();
0324 
0325     /**
0326      * Returns the QItemSelectionModel for the currently displayed tab.
0327      */
0328     QItemSelectionModel *currentItemSelectionModel();
0329 
0330     /**
0331      * Sets the current folder to be displayed by this Pane.
0332      * If the specified folder is already open in one of the tabs
0333      * then that tab is made current (and no reloading happens).
0334      * If the specified folder is not open yet then behaviour
0335      * depends on the preferEmptyTab value as follows.
0336      *
0337      * @param etmIndex the index for the collection in the EntityTreeModel (source model)
0338      *
0339      * If preferEmptyTab is set to false then the (new) folder is loaded
0340      * in the current tab. If preferEmptyTab is set to true then the (new) folder is
0341      * loaded in the first empty tab (or a new one if there are no empty ones).
0342      *
0343      * Pre-selection is the action of automatically selecting a message just after the folder
0344      * has finished loading. See Model::setStorageModel() for more information.
0345      *
0346      * If overrideLabel is not empty then it's used as the tab text for the
0347      * specified folder. This is useful to signal a particular folder state
0348      * like "loading..."
0349      */
0350     void setCurrentFolder(const Akonadi::Collection &fld,
0351                           const QModelIndex &etmIndex,
0352                           bool preferEmptyTab = false,
0353                           MessageList::Core::PreSelectionMode preSelectionMode = MessageList::Core::PreSelectLastSelected,
0354                           const QString &overrideLabel = QString());
0355 
0356     void resetModelStorage();
0357 
0358     void setPreferEmptyTab(bool emptyTab);
0359 
0360     void updateTabIconText(const Akonadi::Collection &collection, const QString &label, const QIcon &icon);
0361 
0362     void saveCurrentSelection();
0363 
0364     void updateTagComboBox();
0365 
0366     bool searchEditHasFocus() const;
0367 
0368     void setQuickSearchClickMessage(const QString &msg);
0369 
0370     void populateStatusFilterCombo();
0371 
0372     Core::QuickSearchLine::SearchOptions currentOptions() const;
0373 
0374     [[nodiscard]] Akonadi::Collection currentFolder() const;
0375 
0376 public Q_SLOTS:
0377     /**
0378      * Selects all the items in the current folder.
0379      */
0380     void selectAll();
0381 
0382     /**
0383      * Add a new tab to the Pane and select it.
0384      */
0385     QItemSelectionModel *createNewTab();
0386 
0387     void sortOrderMenuAboutToShow();
0388 
0389     void aggregationMenuAboutToShow();
0390 
0391     void themeMenuAboutToShow();
0392 
0393 Q_SIGNALS:
0394     /**
0395      * Emitted when a message is selected (that is, single clicked and thus made current in the view)
0396      * Note that this message CAN be 0 (when the current item is cleared, for example).
0397      *
0398      * This signal is emitted when a SINGLE message is selected in the view, probably
0399      * by clicking on it or by simple keyboard navigation. When multiple items
0400      * are selected at once (by shift+clicking, for example) then you will get
0401      * this signal only for the last clicked message (or at all, if the last shift+clicked
0402      * thing is a group header...). You should handle selection changed in this case.
0403      */
0404     void messageSelected(const Akonadi::Item &item);
0405 
0406     /**
0407      * Emitted when a message is doubleclicked or activated by other input means
0408      */
0409     void messageActivated(const Akonadi::Item &item);
0410 
0411     /**
0412      * Emitted when the selection in the view changes.
0413      */
0414     void selectionChanged();
0415 
0416     /**
0417      * Emitted when a message wants its status to be changed
0418      */
0419     void messageStatusChangeRequest(const Akonadi::Item &item, const Akonadi::MessageStatus &set, const Akonadi::MessageStatus &clear);
0420 
0421     /**
0422      * Notify the outside when updating the status bar with a message
0423      * could be useful
0424      */
0425     void statusMessage(const QString &message);
0426 
0427     /**
0428      * Emitted when the current tab has changed. Clients using the
0429      *  selection model from currentItemSelectionModel() should
0430      *  ask for it again, as it may be different now.
0431      */
0432     void currentTabChanged();
0433 
0434     void forceLostFocus();
0435 
0436 private:
0437     MESSAGELIST_NO_EXPORT void restoreHeaderSettings(int index, bool restoreSession);
0438     MESSAGELIST_NO_EXPORT void readConfig(bool restoreSession);
0439 
0440     bool eventFilter(QObject *obj, QEvent *event) override;
0441 
0442     class PanePrivate;
0443     std::unique_ptr<PanePrivate> const d;
0444 };
0445 } // namespace MessageList