QtPass Developer documentation
QtPass test coverage
Current view: top level - src/secretlineedit.cpp (source / functions) Coverage Total Hit
Test: QtPass Lines: 72.6 % 62 45
Test Date: 2026-11-14 13:19:18 Functions: 77.8 % 9 7

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : /**
       4              :  * @file
       5              :  * @brief Implements SecretLineEdit: the four copy paths (Ctrl+C, Cut, context
       6              :  * menu, X11 primary selection) routed through buildClipboardMimeData().
       7              :  */
       8              : #include "secretlineedit.h"
       9              : #include "clipboardmanager.h"
      10              : #include <QAction>
      11              : #include <QApplication>
      12              : #include <QContextMenuEvent>
      13              : #include <QKeyEvent>
      14              : #include <QMenu>
      15              : #include <QMouseEvent>
      16              : 
      17          181 : SecretLineEdit::SecretLineEdit(QWidget *parent) : QLineEdit(parent) {}
      18              : 
      19            2 : auto SecretLineEdit::createSecretContextMenu() -> QMenu * {
      20            2 :   QMenu *menu = createStandardContextMenu();
      21           16 :   for (QAction *action : menu->actions()) {
      22              :     // The standard actions call QLineEdit::copy() and cut(), which are not
      23              :     // virtual.
      24           24 :     if (action->objectName() == QLatin1String("edit-copy")) {
      25            2 :       disconnect(action, &QAction::triggered, this, nullptr);
      26            2 :       connect(action, &QAction::triggered, this,
      27            5 :               [this] { copySelection(QClipboard::Clipboard); });
      28           20 :     } else if (action->objectName() == QLatin1String("edit-cut")) {
      29            1 :       disconnect(action, &QAction::triggered, this, nullptr);
      30            2 :       connect(action, &QAction::triggered, this, [this] { cutSelection(); });
      31              :     }
      32              :   }
      33            2 :   return menu;
      34              : }
      35              : 
      36          133 : void SecretLineEdit::keyPressEvent(QKeyEvent *event) {
      37          133 :   if (event->matches(QKeySequence::Copy)) {
      38            6 :     copySelection(QClipboard::Clipboard);
      39              :     event->accept();
      40            6 :     return;
      41              :   }
      42          127 :   if (event->matches(QKeySequence::Cut)) {
      43            3 :     cutSelection();
      44              :     event->accept();
      45            3 :     return;
      46              :   }
      47              :   // QLineEdit copies what these delete (Ctrl+K and Ctrl+U with the X11 key
      48              :   // bindings), as a cut.
      49          124 :   const bool toEnd = event->matches(QKeySequence::DeleteEndOfLine);
      50          124 :   if (toEnd || event->matches(QKeySequence::DeleteCompleteLine)) {
      51            0 :     if (!isReadOnly()) {
      52            0 :       const int from = toEnd ? cursorPosition() : 0;
      53            0 :       setSelection(from, static_cast<int>(text().size()) - from);
      54            0 :       cutSelection();
      55              :     }
      56              :     event->accept();
      57            0 :     return;
      58              :   }
      59          372 :   withoutPlainSelectionCopy([this, event] { QLineEdit::keyPressEvent(event); });
      60              :   // Where QLineEdit would have copied: after a key it handled, not after a
      61              :   // lone modifier or another key it ignores.
      62          124 :   if (event->isAccepted()) {
      63          107 :     publishSelection();
      64              :   }
      65              : }
      66              : 
      67            0 : void SecretLineEdit::mouseReleaseEvent(QMouseEvent *event) {
      68            0 :   withoutPlainSelectionCopy(
      69            0 :       [this, event] { QLineEdit::mouseReleaseEvent(event); });
      70            0 :   if (event->button() == Qt::LeftButton) {
      71            0 :     publishSelection();
      72              :   }
      73            0 : }
      74              : 
      75            0 : void SecretLineEdit::contextMenuEvent(QContextMenuEvent *event) {
      76            0 :   QMenu *menu = createSecretContextMenu();
      77            0 :   menu->setAttribute(Qt::WA_DeleteOnClose);
      78            0 :   menu->popup(event->globalPos());
      79            0 : }
      80              : 
      81           10 : void SecretLineEdit::copySelection(QClipboard::Mode mode) {
      82              :   // As QLineEdit itself: nothing leaves a field that shows dots.
      83           10 :   if (echoMode() != QLineEdit::Normal || !hasSelectedText()) {
      84            2 :     return;
      85              :   }
      86            8 :   const QString text = selectedText();
      87            8 :   QApplication::clipboard()->setMimeData(buildClipboardMimeData(text), mode);
      88            8 :   emit copied(text);
      89              : }
      90              : 
      91            4 : void SecretLineEdit::cutSelection() {
      92              :   // As QLineEdit::cut(): a field that shows dots deletes without copying.
      93            4 :   if (isReadOnly() || !hasSelectedText()) {
      94            1 :     return;
      95              :   }
      96            3 :   copySelection(QClipboard::Clipboard);
      97            3 :   del();
      98              : }
      99              : 
     100          124 : void SecretLineEdit::withoutPlainSelectionCopy(
     101              :     const std::function<void()> &handler) {
     102              :   // QLineEdit fills the primary selection with plain text itself, at the end
     103              :   // of every key it handles and on a left-button release, but only in Normal
     104              :   // echo mode (QWidgetLineControl::copy()). Replacing that afterwards still
     105              :   // leaves a moment in which a clipboard manager could take the unmarked
     106              :   // text, so the base handler runs in Password mode instead: same length,
     107              :   // so cursor and selection stay where they are, and no copy.
     108              :   // Editable, the field keeps Normal mode (see the class description) and
     109              :   // publishSelection() replaces the plain copy.
     110          124 :   if (echoMode() != QLineEdit::Normal || !isReadOnly()) {
     111          122 :     handler();
     112          122 :     return;
     113              :   }
     114            2 :   setEchoMode(QLineEdit::Password);
     115            2 :   handler();
     116            2 :   setEchoMode(QLineEdit::Normal);
     117              : }
     118              : 
     119          107 : void SecretLineEdit::publishSelection() {
     120          107 :   if (QApplication::clipboard()->supportsSelection()) {
     121            0 :     copySelection(QClipboard::Selection);
     122              :   }
     123          107 : }
        

Generated by: LCOV version 2.0-1