Make username/password connect prompts hard to miss (issue #22)

The inline prompt bar previously had no explicit styling and rendered
in the same color as the rest of the tab; a tab showing the prompt in
the background had no indication anything needed attention. The bar
now uses a solid QPalette::Highlight fill with HighlightedText for the
label and a hand-drawn contrasting badge, and a background tab gets a
"(Needs input)" title suffix plus a distinct tab-bar color.

A first pass at the tab color (#6a1b9a) was reported unreadable in
dark mode; replaced with #ab47bc, tuned to match the visibility of the
existing connection-state colors.

Bump version to v2026.9.16.6.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 09:18:43 -06:00
co-authored by Claude Sonnet 5
parent b3cedcfa48
commit 1506d87719
6 changed files with 144 additions and 1 deletions
+75
View File
@@ -17,10 +17,14 @@
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPainter>
#include <QPalette>
#include <QPixmap>
#include <QPlainTextEdit>
#include <QApplication>
#include <QClipboard>
#include <QMimeData>
#include <QColor>
#include <QComboBox>
#include <QProcessEnvironment>
#include <QPushButton>
@@ -59,6 +63,34 @@ TerminalTheme themeForName(const QString& themeName)
return TerminalTheme::loadKonsoleTheme(
QStringLiteral(":/KodoTermThemes/konsole/Breeze.colorscheme"));
}
// A filled circle with a bold "?", used on the username/password prompt
// bar (issue #22). Drawn by hand rather than pulled from a QStyle standard
// icon because a themed icon's own internal colors are outside our
// control and could end up close in hue to the bar's own background,
// undermining the contrast the bar is trying to achieve; painting it
// ourselves guarantees fillColor/textColor are exactly the same
// guaranteed-contrasting pair used for the rest of the bar.
QPixmap questionMarkBadgePixmap(const QColor& fillColor, const QColor& textColor, int diameter)
{
QPixmap pixmap(diameter, diameter);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(Qt::NoPen);
painter.setBrush(fillColor);
painter.drawEllipse(0, 0, diameter, diameter);
QFont font = painter.font();
font.setBold(true);
font.setPixelSize(static_cast<int>(diameter * 0.65));
painter.setFont(font);
painter.setPen(textColor);
painter.drawText(QRect(0, 0, diameter, diameter), Qt::AlignCenter, QStringLiteral("?"));
return pixmap;
}
}
SessionTab::SessionTab(const Profile& profile,
@@ -93,10 +125,12 @@ SessionTab::SessionTab(const Profile& profile,
m_exportEventsButton(nullptr),
m_eventsPanel(nullptr),
m_passwordPromptBar(nullptr),
m_passwordPromptIcon(nullptr),
m_passwordPromptLabel(nullptr),
m_passwordPromptInput(nullptr),
m_passwordPromptConnectButton(nullptr),
m_passwordPromptCancelButton(nullptr),
m_awaitingUserInput(false),
m_eventSeverityFilter(EventSeverity::Info),
m_eventsPanelExpanded(preferences.eventsPanelExpanded)
{
@@ -341,9 +375,17 @@ SessionTab::~SessionTab()
QString SessionTab::tabTitle() const
{
if (m_awaitingUserInput) {
return QStringLiteral("%1 (Needs input)").arg(m_profile.name);
}
return QStringLiteral("%1 (%2)").arg(m_profile.name, stateSuffix());
}
bool SessionTab::awaitingUserInput() const
{
return m_awaitingUserInput;
}
void SessionTab::connectSession()
{
if (m_state == SessionState::Connecting || m_state == SessionState::Connected) {
@@ -806,21 +848,42 @@ void SessionTab::setupUi()
applyTerminalTheme(m_terminalThemeName);
// Styled distinctly (issue #22: this bar used to blend straight into
// the plain window background and was easy to miss, especially on a
// tab you weren't actively looking at). A solid QPalette::Highlight
// fill with QPalette::HighlightedText for the label is used rather
// than a subtle tint -- a faint tint proved to still be easy to miss,
// and Highlight/HighlightedText are the OS theme's own guaranteed-
// contrasting pair, so this stays readable under both light and dark
// themes without hardcoding a color.
auto* passwordPromptLayout = new QHBoxLayout();
m_passwordPromptIcon = new QLabel(this);
const QColor highlight = palette().color(QPalette::Highlight);
const QColor highlightedText = palette().color(QPalette::HighlightedText);
m_passwordPromptIcon->setPixmap(questionMarkBadgePixmap(highlightedText, highlight, 22));
m_passwordPromptLabel = new QLabel(this);
m_passwordPromptLabel->setObjectName(QStringLiteral("passwordPromptLabel"));
m_passwordPromptInput = new QLineEdit(this);
m_passwordPromptInput->setEchoMode(QLineEdit::Password);
m_passwordPromptConnectButton = new QPushButton(QStringLiteral("Connect"), this);
m_passwordPromptCancelButton = new QPushButton(QStringLiteral("Cancel"), this);
passwordPromptLayout->addWidget(m_passwordPromptIcon);
passwordPromptLayout->addWidget(m_passwordPromptLabel);
passwordPromptLayout->addWidget(m_passwordPromptInput, 1);
passwordPromptLayout->addWidget(m_passwordPromptConnectButton);
passwordPromptLayout->addWidget(m_passwordPromptCancelButton);
passwordPromptLayout->setContentsMargins(10, 8, 10, 8);
m_passwordPromptBar = new QWidget(this);
m_passwordPromptBar->setObjectName(QStringLiteral("passwordPromptBar"));
m_passwordPromptBar->setLayout(passwordPromptLayout);
m_passwordPromptBar->setAutoFillBackground(true);
m_passwordPromptBar->setVisible(false);
m_passwordPromptBar->setStyleSheet(
QStringLiteral("QWidget#passwordPromptBar { background-color: %1; }"
"QWidget#passwordPromptBar QLabel#passwordPromptLabel "
"{ color: %2; font-weight: bold; font-size: 11pt; }")
.arg(highlight.name(), highlightedText.name()));
rootLayout->addWidget(m_passwordPromptBar);
connect(m_passwordPromptConnectButton, &QPushButton::clicked, this, [this]() {
@@ -1161,12 +1224,24 @@ void SessionTab::showPasswordPrompt(const QString& labelText,
m_passwordPromptInput->setEchoMode(maskInput ? QLineEdit::Password : QLineEdit::Normal);
m_passwordPromptBar->setVisible(true);
m_passwordPromptInput->setFocus();
if (!m_awaitingUserInput) {
m_awaitingUserInput = true;
emit awaitingUserInputChanged(true);
emit tabTitleChanged(tabTitle());
}
}
void SessionTab::hidePasswordPrompt()
{
m_passwordPromptBar->setVisible(false);
m_passwordPromptCallback = nullptr;
if (m_awaitingUserInput) {
m_awaitingUserInput = false;
emit awaitingUserInputChanged(false);
emit tabTitleChanged(tabTitle());
}
}
bool SessionTab::validateProfileForConnect()