QtPass Developer documentation
QtPass test coverage
Current view: top level - src/deselectabletreeview.h (source / functions) Coverage Total Hit
Test: QtPass Lines: 84.4 % 32 27
Test Date: 2026-11-14 13:19:18 Functions: 83.3 % 6 5

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : /**
       4              :  * @file
       5              :  * @brief DeselectableTreeView: a QTreeView that clears its selection when
       6              :  *        empty space or an already-selected row is clicked.
       7              :  */
       8              : #ifndef SRC_DESELECTABLETREEVIEW_H_
       9              : #define SRC_DESELECTABLETREEVIEW_H_
      10              : 
      11              : #include <QGuiApplication>
      12              : #include <QMouseEvent>
      13              : #include <QStyleHints>
      14              : #include <QTimer>
      15              : #include <QTreeView>
      16              : 
      17              : /**
      18              :  * @class DeselectableTreeView
      19              :  * @ingroup ui
      20              :  * @brief A QTreeView that clears its selection, and emits emptyClicked(),
      21              :  *        when the user clicks empty space or an already-selected item.
      22              :  *
      23              :  * The press records whether the row under the pointer was selected, the
      24              :  * release handles selection immediately and arms a single-shot timer for the
      25              :  * platform's double-click interval, and only when no double-click arrives in
      26              :  * that time does deselect() clear the selection. Opening an entry by
      27              :  * double-click therefore always wins over deselecting it.
      28              :  */
      29              : class DeselectableTreeView : public QTreeView {
      30              :   Q_OBJECT
      31              : 
      32              : public:
      33              :   /**
      34              :    * @brief Construct the view and arm the single-shot deselect timer.
      35              :    * @param parent Parent widget forwarded to QTreeView.
      36              :    */
      37          131 :   explicit DeselectableTreeView(QWidget *parent) : QTreeView(parent) {
      38          131 :     deselectTimer.setSingleShot(true);
      39          131 :     connect(&deselectTimer, &QTimer::timeout, this,
      40          131 :             &DeselectableTreeView::deselect);
      41          131 :   }
      42              :   /**
      43              :    * @brief Default destructor.
      44              :    */
      45          262 :   ~DeselectableTreeView() override = default;
      46              : 
      47              : signals:
      48              :   /**
      49              :    * @brief Emitted after deselect() has cleared the selection because of a
      50              :    *        single click on empty space or on a selected row; MainWindow
      51              :    *        clears the password panel in response.
      52              :    */
      53              :   void emptyClicked();
      54              : 
      55              : private:
      56              :   /**
      57              :    * @brief Set by mouseDoubleClickEvent() so a deselect() that fires after a
      58              :    *        double-click's first release does nothing.
      59              :    */
      60              :   bool doubleClickHappened = false;
      61              :   /**
      62              :    * @brief Whether the row under the last press was already selected; read
      63              :    *        and reset by the matching release.
      64              :    */
      65              :   bool clickSelected = false;
      66              :   /**
      67              :    * @brief Single-shot timer, started by a qualifying release for one
      68              :    *        double-click interval; its timeout runs deselect().
      69              :    */
      70              :   QTimer deselectTimer;
      71              : 
      72              :   /**
      73              :    * @brief Remember whether the pressed row was already selected and cancel
      74              :    *        any pending deselect from the previous click.
      75              :    * @param event The mouse press; forwarded to QTreeView.
      76              :    */
      77            6 :   void mousePressEvent(QMouseEvent *event) override {
      78              :     // A new press belongs to a new click: whatever the previous release left
      79              :     // pending must not clear the selection this press is about to make.
      80            6 :     deselectTimer.stop();
      81            6 :     clickSelected =
      82            6 :         selectionModel()->isSelected(indexAt(event->position().toPoint()));
      83            6 :     QTreeView::mousePressEvent(event);
      84            6 :   }
      85              : 
      86              :   /**
      87              :    * @brief Let QTreeView handle the release at once, then, if the press was
      88              :    *        on a selected row and the release is over empty space or a still
      89              :    *        selected row, start deselectTimer.
      90              :    * @param event The mouse release; forwarded to QTreeView first.
      91              :    */
      92            6 :   void mouseReleaseEvent(QMouseEvent *event) override {
      93            6 :     const QModelIndex item = indexAt(event->position().toPoint());
      94            6 :     const bool pressWasOnSelection = clickSelected;
      95            6 :     clickSelected = false;
      96              : 
      97              :     // Handle the release immediately: waiting here would delay selection —
      98              :     // and therefore every decrypt — by the double-click interval.
      99            6 :     QTreeView::mouseReleaseEvent(event);
     100              : 
     101              :     const bool emptyOrSelected =
     102            6 :         !item.isValid() || selectionModel()->isSelected(item);
     103            6 :     if (!pressWasOnSelection || !emptyOrSelected)
     104            2 :       return;
     105              : 
     106              :     // Deselecting is the one decision that must wait: a double-click starts
     107              :     // with a release, and opening the editor must win over clearing the
     108              :     // selection. Ask the platform how long a double-click may take instead
     109              :     // of assuming 200 ms.
     110            4 :     doubleClickHappened = false;
     111            4 :     deselectTimer.start(
     112              :         QGuiApplication::styleHints()->mouseDoubleClickInterval());
     113              :   }
     114              : 
     115              :   /**
     116              :    * @brief Timer slot: clear the selection and current index and emit
     117              :    *        emptyClicked(), unless a double-click happened in the meantime.
     118              :    */
     119            0 :   void deselect() {
     120            0 :     if (doubleClickHappened)
     121              :       return;
     122            0 :     clearSelection();
     123            0 :     selectionModel()->setCurrentIndex(QModelIndex(),
     124              :                                       QItemSelectionModel::Select);
     125            0 :     emit emptyClicked();
     126              :   }
     127              : 
     128              :   /**
     129              :    * @brief Cancel the pending deselect so the double-click opens the entry
     130              :    *        instead of clearing the selection.
     131              :    * @param event The double-click; forwarded to QTreeView.
     132              :    */
     133            1 :   void mouseDoubleClickEvent(QMouseEvent *event) override {
     134            1 :     doubleClickHappened = true;
     135            1 :     deselectTimer.stop();
     136            1 :     QTreeView::mouseDoubleClickEvent(event);
     137            1 :   }
     138              : };
     139              : 
     140              : #endif // SRC_DESELECTABLETREEVIEW_H_
        

Generated by: LCOV version 2.0-1