QtPass 2.0.0
Developer documentation
Loading...
Searching...
No Matches
processoutputpanel.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
9#include "processinfo.h"
10#include <QHBoxLayout>
11#include <QScrollBar>
12#include <QTextCursor>
13#include <QTextDocument>
14#include <QTextEdit>
15#include <QToolButton>
16
18 : QDockWidget(tr("Process output"), parent) {
19 setObjectName(QStringLiteral("processOutputDock"));
20 setFeatures(QDockWidget::DockWidgetMovable |
21 QDockWidget::DockWidgetFloatable);
22 setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea);
23
24 auto *contents = new QWidget(this);
25 contents->setObjectName(QStringLiteral("processOutputWidget"));
26 auto *layout = new QHBoxLayout(contents);
27 layout->setObjectName(QStringLiteral("processOutputLayout"));
28 layout->setContentsMargins(0, 0, 0, 0);
29 m_clearButton = new QToolButton(contents);
30 m_clearButton->setObjectName(QStringLiteral("clearOutputButton"));
31 m_clearButton->setText(tr("Clear"));
32 m_clearButton->setToolTip(tr("Clear output"));
33 layout->addWidget(m_clearButton);
34 m_edit = new QTextEdit(contents);
35 m_edit->setObjectName(QStringLiteral("processOutputEdit"));
36 m_edit->setReadOnly(true);
37 m_edit->setAcceptRichText(false);
38 layout->addWidget(m_edit);
39 setWidget(contents);
40
41 connect(m_clearButton, &QToolButton::clicked, this,
43
44 // Hysteresis: while the user is actively dragging the slider, don't
45 // touch m_autoScroll on every tick — a brief overshoot at maximum
46 // would silently re-arm auto-scroll without an explicit release. Only
47 // commit on slider release. Wheel/keyboard scroll never sets
48 // isSliderDown(), so they still update immediately.
49 QScrollBar *bar = m_edit->verticalScrollBar();
50 connect(bar, &QScrollBar::valueChanged, this, [this, bar]() {
51 if (bar->isSliderDown())
52 return;
53 m_autoScroll = bar->value() >= bar->maximum();
54 });
55 connect(bar, &QScrollBar::sliderReleased, this,
56 [this, bar]() { m_autoScroll = bar->value() >= bar->maximum(); });
57}
58
59void ProcessOutputPanel::append(const QString &output, bool isError,
60 const QString &linePrefix) {
61 QStringList lines = output.split('\n', Qt::SkipEmptyParts);
62 for (QString &line : lines) {
63 // Right-trim only: remove trailing CR and whitespace, preserve leading
64 // indentation
65 line.remove('\r');
66 while (!line.isEmpty() && line.back().isSpace()) {
67 line.chop(1);
68 }
69 if (line.isEmpty()) {
70 continue;
71 }
72
73 m_counter++;
74 const QColor textColor =
75 isError ? QColor(Qt::red) : m_edit->palette().color(QPalette::Text);
76 // Apply the optional prefix per line so multi-line output stays
77 // attributed to its command (e.g. all 3 lines of a `git push` show
78 // "git push: ..." rather than only the first).
79 const QString prefixed =
80 linePrefix.isEmpty() ? line : linePrefix + QStringLiteral(": ") + line;
81 // white-space: pre keeps the indentation the trimming above preserved;
82 // rich text would otherwise collapse it.
83 m_edit->append(
84 QStringLiteral(
85 "<span style=\"color: %1; white-space: pre;\">%2: %3</span>")
86 .arg(textColor.name(), QString::number(m_counter),
87 prefixed.toHtmlEscaped()));
88 }
89
90 limitLines();
91
92 if (m_autoScroll) {
93 m_edit->verticalScrollBar()->setValue(
94 m_edit->verticalScrollBar()->maximum());
95 }
96}
97
99 QTextDocument *doc = m_edit->document();
100 const int excess = doc->blockCount() - MaxLines;
101 if (excess <= 0) {
102 return;
103 }
104 QTextCursor cursor(doc);
105 cursor.movePosition(QTextCursor::Start);
106 cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, excess);
107 cursor.removeSelectedText();
108}
109
111 m_edit->clear();
112 m_counter = 0;
113 m_autoScroll = true;
114}
115
117 return Enums::processLabel(pid);
118}
119
121 return Enums::processInfo(pid).secret;
122}
static constexpr int MaxLines
Oldest lines are dropped once the document holds more than this.
void limitLines()
Drop the oldest text blocks once the document holds more than MaxLines; called after every append().
static auto isSensitiveProcess(Enums::PROCESS pid) -> bool
Whether a process's output may carry secrets and must never reach this long-lived panel (pass show,...
void clear()
Drop all output, reset the counter, re-arm auto-scroll.
int m_counter
Running line number shown in front of each line; reset by clear(). Keeps counting past MaxLines.
ProcessOutputPanel(QWidget *parent=nullptr)
Build the console (Clear button + read-only text edit).
static auto processName(Enums::PROCESS pid) -> QString
Human-readable command label for a process id.
bool m_autoScroll
Whether append() scrolls to the bottom: cleared when the user scrolls up, set again when the scroll b...
QTextEdit * m_edit
Read-only text edit holding the output; child of the dock.
QToolButton * m_clearButton
The "Clear" button wired to clear().
void append(const QString &output, bool isError, const QString &linePrefix=QString())
Append output, one numbered line per non-empty input line.
auto processLabel(PROCESS pid) -> QString
The command label for pid, as the output panel shows it.
PROCESS
Identifies different subprocess operations used in QtPass.
Definition enums.h:43
auto processInfo(PROCESS pid) -> const ProcessInfo &
What is known about pid.
Enums::ProcessInfo, the one table that names each kind of subprocess and says whether its output may ...
ProcessOutputPanel: the dockable console that shows numbered, colour-coded git/pass/gpg output.