Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
See also [previous roadmap](./old_roadmap.md) for finished work.

- [x] Back navigation with mouse 4th button (mbits-os/quick_dra#109)
- [ ] Shortcut discovery (mbits-os/quick_dra#104, As a user, I want shortcuts to popup, when I press Ctrl for 2 seconds)
- [x] Shortcut discovery (mbits-os/quick_dra#104, As a user, I want shortcuts to popup, when I press Ctrl for 2 seconds)
- [x] Filter keyboard and mouse events to keep track, when to show and hide the shortcuts (mbits-os/quick_dra#107)
- [ ] Keep track of all widgets with shortcuts currently on screen (mbits-os/quick_dra#105)
- [ ] Show matching shortcuts, when Control is being held for one seconds (mbits-os/quick_dra#106)
- [x] Keep track of all widgets with shortcuts currently on screen (mbits-os/quick_dra#105)
- [x] Show matching shortcuts, when Control is being held for one seconds (mbits-os/quick_dra#106)
- [ ] Update documentation up to and including Quick-DRA GUI
- [ ] Automatic upload to ZUS ePłatnik
- [ ] Get access to Profil Zaufany sandbox
Expand Down
2 changes: 2 additions & 0 deletions libs/gui/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ set(SRCS
include/app/gui/PageStack.hpp
include/app/gui/ShortcutDiscovery.hpp
include/app/gui/types.hpp
include/app/gui/ToolTipLabel.hpp
src/app/gui/CurrentColor.cpp
src/app/gui/Globals.cpp
src/app/gui/HeaderTitle.cpp
src/app/gui/PagedWidget.cpp
src/app/gui/PageHeader.cpp
src/app/gui/PageStack.cpp
src/app/gui/ShortcutDiscovery.cpp
src/app/gui/ToolTipLabel.cpp
)

set(RES res/icons.qrc)
Expand Down
23 changes: 14 additions & 9 deletions libs/gui/gui/include/app/gui/ShortcutDiscovery.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ namespace ch = std::chrono;
QT_FORWARD_DECLARE_CLASS(QToolButton)

namespace quick_dra::gui {
class ToolTipLabel;

struct HolderPosition {
QRect geometry;
QWidget const* rectReference;
Expand Down Expand Up @@ -92,6 +94,17 @@ namespace quick_dra::gui {
struct PrivateTag {};

public:
struct LabelInfo {
QWidget* parent = nullptr;
ToolTipLabel* toolTip = nullptr;
QPoint origin{};
QString text{};

bool operator==(LabelInfo const& rhs) const noexcept {
return parent == rhs.parent && origin == rhs.origin && text == rhs.text;
}
};

struct Editor {
static Editor* current;

Expand Down Expand Up @@ -125,6 +138,7 @@ namespace quick_dra::gui {

Editor beginHolderUpdate();
std::deque<ShortcutHolderInfo> const& holders() const noexcept { return holders_; }
std::vector<LabelInfo> const& labels() const noexcept { return labels_; }

public slots:
void modifiersPressed(Qt::KeyboardModifiers);
Expand All @@ -140,15 +154,6 @@ namespace quick_dra::gui {
void labelsChanged();

private:
struct LabelInfo {
QWidget* parent = nullptr;
QWidget* toolTip = nullptr;
QPoint origin{};
QString text{};

auto operator<=>(LabelInfo const&) const noexcept = default;
};

enum class ModsState {
Stable = false,
Changed = true,
Expand Down
22 changes: 22 additions & 0 deletions libs/gui/gui/include/app/gui/ToolTipLabel.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2026 midnightBITS
// This code is licensed under MIT license (see LICENSE for details)

#pragma once

#include <QLabel>

namespace quick_dra::gui {
class ToolTipLabel : public QLabel {
Q_OBJECT

public:
ToolTipLabel(const QString& text, const QPoint& pos, QWidget* parent);

void hideTip();

protected:
bool eventFilter(QObject*, QEvent*) override;
void paintEvent(QPaintEvent* e) override;
void resizeEvent(QResizeEvent* e) override;
};
} // namespace quick_dra::gui
36 changes: 30 additions & 6 deletions libs/gui/gui/src/app/gui/ShortcutDiscovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <algorithm>
#include <app/gui/Globals.hpp>
#include <app/gui/ShortcutDiscovery.hpp>
#include <app/gui/ToolTipLabel.hpp>
#include <array>
#include <utility>
#include <vector>
Expand Down Expand Up @@ -206,16 +207,35 @@ namespace quick_dra::gui {
}

for (auto const& label : labels_) {
if (label.toolTip) label.toolTip->deleteLater(); // GCOV_EXCL_LINE (until #106)
if (label.toolTip) label.toolTip->hideTip();
}

labels_ = std::move(labels);
for (auto const& label : labels_) {
// TODO: mbits-os/quick_dra#106 Show matching shortcuts, when Control is being held for one seconds

for (auto& label : labels_) {
label.toolTip = new ToolTipLabel{label.text, label.origin, label.parent};
label.toolTip->showNormal();
}

labelsChanged();
}

static bool visibleInStack(QWidget const* bottom, QPoint const& pt) {
auto point = pt;
auto widget = bottom;

while (widget) {
if (!widget->rect().contains(point)) {
return false;
}
auto parent = qobject_cast<QWidget*>(widget->parent());
if (!parent) break;
point = widget->mapTo(parent, point);
widget = parent;
}
return true;
}

std::vector<ShortcutDiscovery::LabelInfo> ShortcutDiscovery::filterShortcuts() {
if (modifiers_ == Qt::NoModifier) {
return {};
Expand All @@ -241,7 +261,7 @@ namespace quick_dra::gui {
int lineHeight{fm.ascent() + fm.descent() + fm.leading()};
int em{fm.boundingRect("m").width()};

int vOffset = -lineHeight * 2 / 3;
int vOffset = -lineHeight * 4 / 5;

for (auto const& holder : holders_) {
if (!holder.isEnabled(holder.holder)) continue;
Expand All @@ -253,8 +273,12 @@ namespace quick_dra::gui {
auto const pos = holder.getPosition(holder.holder);
auto const topLevelParent = pos.rectReference->topLevelWidget();

auto const position =
pos.rectReference->mapTo(topLevelParent, pos.geometry.bottomLeft()) + QPoint{hOffset, vOffset};
auto const local = pos.geometry.bottomLeft() + QPoint{hOffset, vOffset};
if (!visibleInStack(pos.rectReference, local)) {
continue;
}

auto const position = pos.rectReference->mapToGlobal(local);
auto label = QKeySequence{mod.key() | (mod.keyboardModifiers() & ~modifiers_)}.toString();
hOffset += 2 * em + fm.boundingRect(label).width();

Expand Down
68 changes: 68 additions & 0 deletions libs/gui/gui/src/app/gui/ToolTipLabel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) 2026 midnightBITS
// This code is licensed under MIT license (see LICENSE for details)

#include <QApplication>
#include <QStyle>
#include <QStyleOptionFrame>
#include <QStylePainter>
#include <QToolTip>
#include <app/gui/ToolTipLabel.hpp>
#include <utility>

namespace quick_dra::gui {
ToolTipLabel::ToolTipLabel(const QString& value, const QPoint& pos, QWidget* parent)
: QLabel{parent, Qt::ToolTip | Qt::BypassGraphicsProxyWidget} {
setFont(QFont{"QTipLabel"});
setForegroundRole(QPalette::ToolTipText);
setBackgroundRole(QPalette::ToolTipBase);
setPalette(QToolTip::palette());
ensurePolished();
setMargin(1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, nullptr, this));
setFrameStyle(QFrame::NoFrame);
setAlignment(Qt::AlignLeft);
setIndent(1);
qApp->installEventFilter(this);
setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, nullptr, this) / 255.0);
setMouseTracking(true);
setText(value);

QFontMetrics fm{font()};
QSize extra{1, 0};

// Make it look good with the default ToolTip font on Mac, which has a small descent.
if (fm.descent() == 2 && fm.ascent() >= 11) ++extra.rheight();

setWordWrap(Qt::mightBeRichText(text()));
QSize sh = sizeHint();
move(pos);
resize(sh + extra);
}

void ToolTipLabel::hideTip() {
close();
deleteLater();
}

bool ToolTipLabel::eventFilter(QObject*, QEvent*) { return false; }

void ToolTipLabel::paintEvent(QPaintEvent* event) {
QStylePainter p(this);
QStyleOptionFrame opt;
opt.initFrom(this);
p.drawPrimitive(QStyle::PE_PanelTipLabel, opt);
p.end();

QLabel::paintEvent(event);
}

void ToolTipLabel::resizeEvent(QResizeEvent* event) {
QStyleHintReturnMask frameMask;
QStyleOption option;
option.initFrom(this);
if (style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask)) {
setMask(frameMask.region); // GCOV_EXCL_LINE
}

QLabel::resizeEvent(event);
}
} // namespace quick_dra::gui
35 changes: 32 additions & 3 deletions libs/gui/gui/tests/qt6/ShortcutDiscovery.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <QWheelEvent>
#include <Qt>
#include <app/gui/ShortcutDiscovery.hpp>
#include <app/gui/ToolTipLabel.hpp>
#include <app/utils/utils.hpp>
#include "GuiTest.hpp"
#include "ui_helpers.hpp"
Expand All @@ -31,6 +32,8 @@ namespace {

return Qt::NoModifier;
}

QString operator""_L1(char const* ptr, size_t len) { return QString::fromLatin1(ptr, static_cast<qsizetype>(len)); }
} // namespace

struct HUD {
Expand Down Expand Up @@ -297,18 +300,20 @@ void GuiTest::ShortcutDiscovery_gatherTooltips() {
QWidget widget{};
HUD hud{&widget};

auto layout = new QVBoxLayout{&widget};
auto parent = new QWidget{&widget};
auto layout = new QVBoxLayout{parent};
layout->setContentsMargins(5, 5, 5, 5);
layout->setSpacing(5);

std::vector<ShortcutWidget*> widgets{};
widgets.reserve(std::size(controls));
std::transform(std::begin(controls), std::end(controls), std::back_inserter(widgets), widgetMaker(&widget, layout));
std::transform(std::begin(controls), std::end(controls), std::back_inserter(widgets), widgetMaker(parent, layout));
int height = 5;
for (auto const& control : controls) {
height += std::get<0>(control) + 5;
}
widget.resize(200, height);
parent->resize(200, height);
widget.resize(200, height * 2 / 3);

widget.show();
QVERIFY(QTest::qWaitForWindowExposed(&widget));
Expand All @@ -333,6 +338,25 @@ void GuiTest::ShortcutDiscovery_gatherTooltips() {
QCOMPARE_EQ(labelsSpy.size(), 1);
QCOMPARE_EQ(activeSpy.size(), 1);

auto const previous = discovery.labels();
{
// "empty" update
discovery.beginHolderUpdate();
}
auto const now = discovery.labels();
auto actual = QList<std::pair<QPoint, QString>>{};
actual.reserve(static_cast<qsizetype>(now.size()));
std::transform(now.begin(), now.end(), std::back_inserter(actual),
[](ShortcutDiscovery::LabelInfo const& info) { return std::pair{info.origin, info.text}; });

labelsSpy.wait(100ms);
activeSpy.wait(100ms);
QCOMPARE_EQ(labelsSpy.size(), 1);
QCOMPARE_EQ(activeSpy.size(), 1);
QCOMPARE_EQ(previous, now);
QCOMPARE_EQ(actual, (QList{std::pair{QPoint{17, 49}, "Shift+E"_L1}, std::pair{QPoint{17, 108}, "Alt+Z"_L1},
std::pair{QPoint{71, 108}, "Z"_L1}}));

{
auto editor = discovery.beginHolderUpdate();
for (auto child : widgets) {
Expand Down Expand Up @@ -375,4 +399,9 @@ void GuiTest::ShortcutDiscovery_toolButton() {

button.setVisible(true);
QVERIFY(Support::isEnabled(&button));

// call painting the tool tip label
ToolTipLabel label{"Text", QPoint{}, nullptr};
label.showNormal();
QVERIFY(QTest::qWaitForWindowExposed(&label));
}
1 change: 0 additions & 1 deletion libs/gui/pages/include/app/main/MainWindow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ namespace quick_dra::gui {
private slots:
void updateTitle();
void reloadConfig();
void discoveryModifiers(Qt::KeyboardModifiers);

private:
void setupUi(Globals* globals);
Expand Down
27 changes: 0 additions & 27 deletions libs/gui/pages/src/app/main/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ namespace quick_dra::gui {
pageStack->push<HomePage>();

QObject::connect(globals, &Globals::configModifiedChanged, messageBar, &QWidget::setVisible);
QObject::connect(&pageStack->discovery(), &ShortcutDiscovery::modifiersChanged, this,
&MainWindow::discoveryModifiers);
}

void MainWindow::closeEvent(QCloseEvent* event) {
Expand Down Expand Up @@ -54,31 +52,6 @@ namespace quick_dra::gui {
pageStack->globals().reloadConfig();
}

// GCOV_EXCL_START -- temporary slot to verify signals via console
// TODO: remove this slot before closing of #104
void MainWindow::discoveryModifiers(Qt::KeyboardModifiers modifiers) {
std::string sMods{};
if (!modifiers) {
sMods = "NoModifier"s;
}
#define CHECK_MOD(NAME, LABEL) \
if (modifiers & Qt::NAME) { \
modifiers &= ~Qt::NAME; \
if (!sMods.empty()) sMods.push_back('+'); \
sMods.append(LABEL##sv); \
}
CHECK_MOD(ShiftModifier, "Shift");
CHECK_MOD(ControlModifier, "Ctrl");
CHECK_MOD(MetaModifier, "Meta");
CHECK_MOD(AltModifier, "Alt");
if (modifiers) {
if (!sMods.empty()) sMods.push_back('+');
sMods.append(std::to_string(modifiers.toInt()));
}
qDebug() << "[discovery]" << sMods.c_str();
}
// GCOV_EXCL_STOP

void MainWindow::storePosition(Globals* globals) {
auto settings = globals->createSettings();
settings.beginGroup("State");
Expand Down