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

0001 /******************************************************************************
0002  *
0003  *  SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  *
0007  *******************************************************************************/
0008 
0009 #pragma once
0010 
0011 #include <QList>
0012 #include <QPoint>
0013 #include <QTreeView>
0014 
0015 #include "messagelist/enums.h"
0016 #include "messagelist/quicksearchline.h"
0017 
0018 class QMenu;
0019 
0020 namespace Akonadi
0021 {
0022 class MessageStatus;
0023 }
0024 
0025 namespace MessageList
0026 {
0027 namespace Core
0028 {
0029 using MessageItemSetReference = long;
0030 
0031 class Aggregation;
0032 class Delegate;
0033 class Item;
0034 class MessageItem;
0035 class Model;
0036 class Theme;
0037 class SortOrder;
0038 class StorageModel;
0039 class Widget;
0040 
0041 /**
0042  * The MessageList::View is the real display of the message list. It is
0043  * based on QTreeView, has a Model that manipulates the underlying message storage
0044  * and a Delegate that is responsible of painting the items.
0045  */
0046 class View : public QTreeView
0047 {
0048     friend class Model;
0049     friend class ModelPrivate;
0050     Q_OBJECT
0051 public:
0052     explicit View(Widget *parent);
0053     ~View() override;
0054 
0055     /**
0056      * Returns the Model attached to this View. You probably never need to manipulate
0057      * it directly.
0058      */
0059     Model *model() const;
0060 
0061     /**
0062      * Returns the Delegate attached to this View. You probably never need to manipulate
0063      * it directly. Model uses it to obtain size hints.
0064      */
0065     Delegate *delegate() const;
0066 
0067     /**
0068      * Sets the StorageModel to be displayed in this view. The StorageModel may be 0 (so no content is displayed).
0069      * Setting the StorageModel will obviously trigger a view reload.
0070      * Be sure to set the Aggregation and the Theme BEFORE calling this function.
0071      *
0072      * Pre-selection is the action of automatically selecting a message just after the folder
0073      * has finished loading. See Model::setStorageModel() for more information.
0074      */
0075     void setStorageModel(StorageModel *storageModel, PreSelectionMode preSelectionMode = PreSelectLastSelected);
0076 
0077     /**
0078      * Returns the currently displayed StorageModel. May be 0.
0079      */
0080     StorageModel *storageModel() const;
0081 
0082     /**
0083      * Sets the aggregation for this view.
0084      * Does not trigger a reload of the view: you *MUST* trigger it manually.
0085      */
0086     void setAggregation(const Aggregation *aggregation);
0087 
0088     /**
0089      * Sets the specified theme for this view.
0090      * Does not trigger a reload of the view: you *MUST* trigger it manually.
0091      */
0092     void setTheme(Theme *theme);
0093 
0094     /**
0095      * Sets the specified sort order.
0096      * Does not trigger a reload of the view: you *MUST* trigger it manually.
0097      */
0098     void setSortOrder(const SortOrder *sortOrder);
0099 
0100     /**
0101      * Triggers a reload of the view in order to re-display the current folder.
0102      * Call this function after changing the Aggregation or the Theme.
0103      */
0104     void reload();
0105 
0106     /**
0107      * Returns the current MessageItem (that is bound to current StorageModel).
0108      * May return 0 if there is no current message or no current StorageModel.
0109      * If the current message item isn't currently selected (so is only focused)
0110      * then it's selected when this function is called, unless selectIfNeeded is false.
0111      */
0112     MessageItem *currentMessageItem(bool selectIfNeeded = true) const;
0113 
0114     /**
0115      * Returns the current Item (that is bound to current StorageModel).
0116      * May return 0 if there is no current item or no current StorageModel.
0117      * If the current item isn't currently selected (so is only focused)
0118      * then it's selected when this function is called.
0119      */
0120     Item *currentItem() const;
0121 
0122     /**
0123      * Sets the current message item.
0124      */
0125     void setCurrentMessageItem(MessageItem *it, bool center = false);
0126 
0127     /**
0128      * Returns true if the specified item is currently displayed in the tree
0129      * and has all the parents expanded. This means that the user can
0130      * see the message (by eventually scrolling the view).
0131      */
0132     bool isDisplayedWithParentsExpanded(Item *it) const;
0133 
0134     /**
0135      * Makes sure that the specified is currently viewable by the user.
0136      * This means that the user can see the message (by eventually scrolling the view).
0137      */
0138     void ensureDisplayedWithParentsExpanded(Item *it);
0139 
0140     /**
0141      * Returns the currently selected MessageItems (bound to current StorageModel).
0142      * The list may be empty if there are no selected messages or no StorageModel.
0143      *
0144      * If includeCollapsedChildren is true then the children of the selected but
0145      * collapsed items are also added to the list.
0146      *
0147      * The returned list is guaranteed to be valid only until you return control
0148      * to the main even loop. Don't store it for any longer. If you need to reference
0149      * this set of messages at a later stage then take a look at createPersistentSet().
0150      */
0151     QList<MessageItem *> selectionAsMessageItemList(bool includeCollapsedChildren = true) const;
0152 
0153     /**
0154      * Returns the MessageItems bound to the current StorageModel that
0155      * are part of the current thread. The current thread is the thread
0156      * that contains currentMessageItem().
0157      * The list may be empty if there is no currentMessageItem() or no StorageModel.
0158      *
0159      * The returned list is guaranteed to be valid only until you return control
0160      * to the main even loop. Don't store it for any longer. If you need to reference
0161      * this set of messages at a later stage then take a look at createPersistentSet().
0162      */
0163     QList<MessageItem *> currentThreadAsMessageItemList() const;
0164 
0165     /**
0166      * Fast function that determines if the selection is empty
0167      */
0168     bool selectionEmpty() const;
0169 
0170     /**
0171      * Selects the specified MessageItems. The current selection is NOT removed.
0172      * Use clearSelection() for that purpose.
0173      */
0174     void selectMessageItems(const QList<MessageItem *> &list);
0175 
0176     /**
0177      * Creates a persistent set for the specified MessageItems and
0178      * returns its reference. Later you can use this reference
0179      * to retrieve the list of MessageItems that are still valid.
0180      * See persistentSetCurrentMessageList() for that.
0181      *
0182      * Persistent sets consume resources (both memory and CPU time
0183      * while manipulating the view) so be sure to call deletePersistentSet()
0184      * when you no longer need it.
0185      */
0186     MessageItemSetReference createPersistentSet(const QList<MessageItem *> &items);
0187 
0188     /**
0189      * Returns the list of MessageItems that are still existing in the
0190      * set pointed by the specified reference. This list will contain
0191      * at most the messages that you have passed to createPersistentSet()
0192      * but may contain less (even 0) if these MessageItem object were removed
0193      * from the view for some reason.
0194      */
0195     [[nodiscard]] QList<MessageItem *> persistentSetCurrentMessageItemList(MessageItemSetReference ref);
0196 
0197     /**
0198      * Deletes the persistent set pointed by the specified reference.
0199      * If the set does not exist anymore, nothing happens.
0200      */
0201     void deletePersistentSet(MessageItemSetReference ref);
0202 
0203     /**
0204      * If bMark is true this function marks the messages as "about to be removed"
0205      * so they appear dimmer and aren't selectable in the view.
0206      * If bMark is false then this function clears the "about to be removed" state
0207      * for the specified MessageItems.
0208      */
0209     void markMessageItemsAsAboutToBeRemoved(const QList<MessageItem *> &items, bool bMark);
0210 
0211     /**
0212      * Returns true if the current Aggregation is threaded, false otherwise
0213      * (or if there is no current Aggregation).
0214      */
0215     bool isThreaded() const;
0216 
0217     /**
0218      * If expand is true then it expands the current thread, otherwise
0219      * collapses it.
0220      */
0221     void setCurrentThreadExpanded(bool expand);
0222 
0223     /**
0224      * If expand is true then it expands all the threads, otherwise
0225      * collapses them.
0226      */
0227     void setAllThreadsExpanded(bool expand);
0228 
0229     /**
0230      * If expand is true then it expands all the groups (only the toplevel
0231      * group item: inner threads are NOT expanded). If expand is false
0232      * then it collapses all the groups. If no grouping is in effect
0233      * then this function does nothing.
0234      */
0235     void setAllGroupsExpanded(bool expand);
0236 
0237     /**
0238      * Selects the next message item in the view.
0239      *
0240      * messageTypeFilter can be used to limit the selection to
0241      * a certain category of messages.
0242      *
0243      * existingSelectionBehaviour specifies how the existing selection
0244      * is manipulated. It may be cleared, expanded or grown/shrunk.
0245      *
0246      * If centerItem is true then the specified item will be positioned
0247      * at the center of the view, if possible.
0248      * If loop is true then the "next" algorithm will restart from the beginning
0249      * of the list if the end is reached, otherwise it will just stop returning false.
0250      *
0251      * \sa MessageList::Core::MessageTypeFilter
0252      * \sa MessageList::Core::ExistingSelectionBehaviour
0253      */
0254     bool selectNextMessageItem(MessageTypeFilter messageTypeFilter, ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop);
0255 
0256     /**
0257      * Selects the previous message item in the view.
0258      *
0259      * messageTypeFilter can be used to limit the selection to
0260      * a certain category of messages.
0261      *
0262      * existingSelectionBehaviour specifies how the existing selection
0263      * is manipulated. It may be cleared, expanded or grown/shrunk.
0264      *
0265      * If centerItem is true then the specified item will be positioned
0266      * at the center of the view, if possible.
0267      * If loop is true then the "previous" algorithm will restart from the end
0268      * of the list if the beginning is reached, otherwise it will just stop returning false.
0269      *
0270      * \sa MessageList::Core::MessageTypeFilter
0271      * \sa MessageList::Core::ExistingSelectionBehaviour
0272      */
0273     bool selectPreviousMessageItem(MessageTypeFilter messageTypeFilter, ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop);
0274 
0275     /**
0276      * Focuses the next message item in the view without actually selecting it.
0277      *
0278      * messageTypeFilter can be used to limit the selection to
0279      * a certain category of messages.
0280      *
0281      * If centerItem is true then the specified item will be positioned
0282      * at the center of the view, if possible.
0283      * If loop is true then the "next" algorithm will restart from the beginning
0284      * of the list if the end is reached, otherwise it will just stop returning false.
0285      */
0286     bool focusNextMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem, bool loop);
0287 
0288     /**
0289      * Focuses the previous message item in the view without actually selecting it.
0290      * If unread is true then focuses the previous unread message item.
0291      * If centerItem is true then the specified item will be positioned
0292      * at the center of the view, if possible.
0293      * If loop is true then the "previous" algorithm will restart from the end
0294      * of the list if the beginning is reached, otherwise it will just stop returning false.
0295      */
0296     bool focusPreviousMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem, bool loop);
0297 
0298     /**
0299      * Selects the currently focused message item. If the currently focused
0300      * message is already selected (which is very likely) nothing happens.
0301      * If centerItem is true then the specified item will be positioned
0302      * at the center of the view, if possible.
0303      */
0304     void selectFocusedMessageItem(bool centerItem);
0305 
0306     /**
0307      * Selects the first message item in the view that matches messageTypeFilter.
0308      * If centerItem is true then the specified item will be positioned
0309      * at the center of the view, if possible.
0310      */
0311     bool selectFirstMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem);
0312 
0313     /**
0314      * Selects the last message item in the view that matches messageTypeFilter.
0315      * If centerItem is true then the specified item will be positioned
0316      * at the center of the view, if possible.
0317      */
0318     bool selectLastMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem);
0319 
0320     /**
0321      * Sets the focus on the quick search line of the currently active tab.
0322      */
0323     void focusQuickSearch(const QString &selectedText);
0324 
0325     /**
0326      * Returns the Akonadi::MessageStatus in the current quicksearch field.
0327      */
0328     QList<Akonadi::MessageStatus> currentFilterStatus() const;
0329 
0330     /**
0331      * Returns the search term in the current quicksearch field.
0332      */
0333     QString currentFilterSearchString() const;
0334 
0335     /**
0336      * Called to hide or show the specified row from the view.
0337      * @reimp
0338      */
0339     virtual void setRowHidden(int row, const QModelIndex &parent, bool hide);
0340 
0341     void sortOrderMenuAboutToShow(QMenu *menu);
0342 
0343     void aggregationMenuAboutToShow(QMenu *menu);
0344 
0345     void themeMenuAboutToShow(QMenu *menu);
0346 
0347     void setCollapseItem(const QModelIndex &index);
0348     void setExpandItem(const QModelIndex &index);
0349 
0350     void setQuickSearchClickMessage(const QString &msg);
0351 
0352     MessageList::Core::QuickSearchLine::SearchOptions currentOptions() const;
0353 
0354 protected:
0355     /**
0356      * Reimplemented in order to catch QHelpEvent
0357      */
0358     bool event(QEvent *e) override;
0359 
0360     /**
0361      * Reimplemented in order to catch palette, font and style changes
0362      */
0363     void changeEvent(QEvent *e) override;
0364 
0365     /**
0366      * Reimplemented in order to handle clicks with sub-item precision.
0367      */
0368     void mousePressEvent(QMouseEvent *e) override;
0369 
0370     /**
0371      * Reimplemented in order to handle double clicks with sub-item precision.
0372      */
0373     void mouseDoubleClickEvent(QMouseEvent *e) override;
0374 
0375     /**
0376      * Reimplemented in order to handle DnD
0377      */
0378     void mouseMoveEvent(QMouseEvent *e) override;
0379 
0380     /**
0381      * Reimplemented in order to handle message DnD
0382      */
0383     void dragEnterEvent(QDragEnterEvent *e) override;
0384 
0385     /**
0386      * Reimplemented in order to handle message DnD
0387      */
0388     void dragMoveEvent(QDragMoveEvent *e) override;
0389 
0390     /**
0391      * Reimplemented in order to handle message DnD
0392      */
0393     void dropEvent(QDropEvent *e) override;
0394 
0395     /**
0396      * Reimplemented in order to resize columns when header is not visible
0397      */
0398     void resizeEvent(QResizeEvent *e) override;
0399 
0400     void paintEvent(QPaintEvent *event) override;
0401     /**
0402      * Reimplemented in order to kill the QTreeView column auto-resizing
0403      */
0404     int sizeHintForColumn(int logicalColumnIndex) const override;
0405 
0406     /**
0407      * Reimplemented in order to disable update of the geometries
0408      * while a job step is running (as it takes a very long time and it's called for every item insertion...)
0409      * TODO: not true anymore, it's called after a delay.
0410      */
0411     void updateGeometries() override;
0412 
0413     /**
0414      * Returns true if the vertical scrollbar should keep to the top or bottom
0415      * while inserting items.
0416      */
0417     bool isScrollingLocked() const;
0418 
0419     /**
0420      *  Used to enable/disable the ignoring of updateGeometries() calls.
0421      */
0422     void ignoreUpdateGeometries(bool ignore);
0423 
0424     void modelAboutToEmitLayoutChanged();
0425     void modelEmittedLayoutChanged();
0426 
0427     /**
0428      * This is called by the model to insulate us from certain QTreeView signals
0429      * This is because they may be spurious (caused by Model item rearrangements).
0430      */
0431     void ignoreCurrentChanges(bool ignore);
0432 
0433     /**
0434      * Expands or collapses the children of the specified item, recursively.
0435      */
0436     void setChildrenExpanded(const Item *parent, bool expand);
0437 
0438     /**
0439      * Finds the next message item with respect to the current item.
0440      * If there is no current item then the search starts from the beginning.
0441      * Returns 0 if no next message could be found.
0442      *
0443      * messageTypeFilter can be used to limit the selection to
0444      * a certain category of messages.
0445      * If loop is true then restarts from the beginning if end is
0446      * reached, otherwise it just returns 0 in this case.
0447      */
0448     Item *nextMessageItem(MessageTypeFilter messageTypeFilter, bool loop);
0449 
0450     /**
0451      * Finds message item that comes "after" the reference item.
0452      * If reference item is 0 then the search starts from the beginning.
0453      * Returns 0 if no next message could be found.
0454      *
0455      * messageTypeFilter can be used to limit the selection to
0456      * a certain category of messages.
0457      * If loop is true then restarts from the beginning if end is
0458      * reached, otherwise it just returns 0 in this case.
0459      */
0460     Item *messageItemAfter(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop);
0461 
0462     /**
0463      * Finds the first message item in the view.
0464      *
0465      * messageTypeFilter can be used to limit the selection to
0466      * a certain category of messages.
0467      *
0468      * Returns 0 if the view is empty.
0469      */
0470     Item *firstMessageItem(MessageTypeFilter messageTypeFilter);
0471 
0472     /**
0473      * Finds the previous message item with respect to the current item.
0474      * If there is no current item then the search starts from the end.
0475      * Returns 0 if no previous message could be found.
0476      *
0477      * messageTypeFilter can be used to limit the selection to
0478      * a certain category of messages.
0479      * If loop is true then restarts from the end if beginning is
0480      * reached, otherwise it just return 0 in this case.
0481      */
0482     Item *previousMessageItem(MessageTypeFilter messageTypeFilter, bool loop);
0483 
0484     /**
0485      * Returns the deepest child that is visible (i.e. not in a collapsed tree) of
0486      * the specified reference item.
0487      */
0488     Item *deepestExpandedChild(Item *referenceItem) const;
0489 
0490     /**
0491      * Finds message item that comes "before" the reference item.
0492      * If reference item is 0 then the search starts from the end.
0493      * Returns 0 if no next message could be found.
0494      *
0495      * messageTypeFilter can be used to limit the selection to
0496      * a certain category of messages.
0497      * If loop is true then restarts from the beginning if end is
0498      * reached, otherwise it just returns 0 in this case.
0499      */
0500     Item *messageItemBefore(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop);
0501 
0502     /**
0503      * Finds the last message item in the view.
0504      *
0505      * messageTypeFilter can be used to limit the selection to
0506      * a certain category of messages.
0507      *
0508      * Returns nullptr if the view is empty.
0509      */
0510     Item *lastMessageItem(MessageTypeFilter messageTypeFilter);
0511 
0512     /**
0513      * This is called by Model to signal that the initial loading stage of a newly
0514      * attached StorageModel is terminated.
0515      */
0516     void modelFinishedLoading();
0517 
0518     /**
0519      * Performs a change in the specified MessageItem status.
0520      * It first applies the change to the cached state in MessageItem and
0521      * then requests our parent widget to act on the storage.
0522      */
0523     void changeMessageStatus(MessageItem *it, Akonadi::MessageStatus set, Akonadi::MessageStatus unset);
0524     void changeMessageStatusRead(MessageItem *it, bool read);
0525 
0526     /**
0527      * Starts a short-delay timer connected to saveThemeColumnState().
0528      * Used to accumulate consecutive changes and break out of the call stack
0529      * up to the main event loop (since in the call stack the column state might be left undefined).
0530      */
0531     void triggerDelayedSaveThemeColumnState();
0532 
0533     /**
0534      * Starts a short-delay timer connected to applyThemeColumns().
0535      * Used to accumulate consecutive changes and break out of the call stack
0536      * up to the main event loop (since multiple resize events tend to be sent by Qt at startup).
0537      */
0538     void triggerDelayedApplyThemeColumns();
0539 
0540     /**
0541      * This is used by the selection functions to grow/shrink the existing selection
0542      * according to the newly selected item passed as parameter.
0543      * If movingUp is true then: if the newly selected item is above the current selection top
0544      * then the selection is expanded, otherwise it's shrunk. If movingUp is false then: if the
0545      * newly selected item is below the current selection bottom then the selection is expanded
0546      * otherwise it's shrunk.
0547      */
0548     void growOrShrinkExistingSelection(const QModelIndex &newSelectedIndex, bool movingUp);
0549 
0550 public Q_SLOTS:
0551     /**
0552      * Collapses all the group headers (if present in the current Aggregation)
0553      */
0554     void slotCollapseAllGroups();
0555 
0556     /**
0557      * Expands all the group headers (if present in the current Aggregation)
0558      */
0559     void slotExpandAllGroups();
0560 
0561     /**
0562      * Expands the current item.
0563      * If it's a Message, it expands its thread, if its a group header it expands the group
0564      */
0565     void slotExpandCurrentItem();
0566 
0567     /**
0568      * Collapses the current item.
0569      * If it's a Message, it collapses its thread, if its a group header it collapses the group
0570      */
0571     void slotCollapseCurrentItem();
0572 
0573     void slotExpandAllThreads();
0574 
0575     void slotCollapseAllThreads();
0576 
0577 protected Q_SLOTS:
0578 
0579     /**
0580      * Handles context menu requests for the header.
0581      */
0582     void slotHeaderContextMenuRequested(const QPoint &pnt);
0583 
0584     /**
0585      * Handles the actions of the header context menu for showing/hiding a column.
0586      */
0587     void slotShowHideColumn(int columnIndex);
0588 
0589     /**
0590      * Handles the Adjust Column Sizes action of the header context menu.
0591      */
0592     void slotAdjustColumnSizes();
0593 
0594     /**
0595      * Handles the Show Default Columns action of the header context menu.
0596      */
0597     void slotShowDefaultColumns();
0598 
0599     /**
0600      * Handles the Display Tooltips action of the header context menu.
0601      */
0602     void slotDisplayTooltips(bool showTooltips);
0603 
0604     /**
0605      * Handles section resizes in order to save the column widths
0606      */
0607     void slotHeaderSectionResized(int logicalIndex, int oldWidth, int newWidth);
0608 
0609     /**
0610      * Handles selection item management
0611      */
0612     void slotSelectionChanged(const QItemSelection &current, const QItemSelection &);
0613 
0614     /**
0615      * Saves the state of the columns (width and visibility) to the currently selected theme object.
0616      */
0617     void saveThemeColumnState();
0618 
0619     /**
0620      * Applies the theme columns to this view.
0621      * Columns visible by default are shown, the other are hidden.
0622      * Visible columns are assigned space inside the view by using the size hints and some heuristics.
0623      */
0624     void applyThemeColumns();
0625 
0626 private:
0627     class ViewPrivate;
0628     std::unique_ptr<ViewPrivate> const d;
0629 }; // class View
0630 } // namespace Core
0631 } // namespace MessageList