Cockatrice/libcockatrice_utility/libcockatrice/utility/qt_utils.h
BruebachL eac149656b
[QtUtils] Add window fade, path opening, bold font and confirmation helpers (#7621)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-24 21:38:54 +02:00

208 lines
6.6 KiB
C++

#ifndef COCKATRICE_QT_UTILS_H
#define COCKATRICE_QT_UTILS_H
#include <QDesktopServices>
#include <QFont>
#include <QFontDatabase>
#include <QLayout>
#include <QMessageBox>
#include <QObject>
#include <QPropertyAnimation>
#include <QScrollArea>
#include <QString>
#include <QUrl>
#include <QWidget>
namespace QtUtils
{
/**
* @brief Returns the nearest ancestor of @p obj that casts to @c T.
*
* Walks the QObject parent chain. QWidget::parentWidget() is the same pointer as
* QObject::parent() for widgets, so this also covers the common "find the parent tab,
* splitter or scroll area" lookups.
*
* @param obj Object to start from. May be null.
* @param includeSelf Also test @p obj itself before walking upwards.
* @return The first matching ancestor, or nullptr if there is none.
*/
template <typename T> T *findParentOfType(const QObject *obj, bool includeSelf = false)
{
const QObject *p = includeSelf ? obj : (obj ? obj->parent() : nullptr);
while (p) {
if (auto casted = qobject_cast<T *>(const_cast<QObject *>(p))) {
return casted;
}
p = p->parent();
}
return nullptr;
}
/**
* @brief How clearLayoutRec() disposes of the widgets it removes.
*
* Deferred is the safe default: deleteLater() lets a widget finish handling the event that
* triggered the rebuild. Use Immediate when the caller rebuilds the same layout right away
* and a lingering widget would be visible or otherwise in the way until the next event loop
* turn.
*/
enum class DeletePolicy
{
Deferred,
Immediate
};
/**
* @brief Removes and deletes every item in @p l, recursing into nested layouts.
*
* Widgets are disposed of according to @p policy; spacers and nested layouts are simply
* deleted.
*/
static inline void clearLayoutRec(QLayout *l, DeletePolicy policy = DeletePolicy::Deferred)
{
if (!l) {
return;
}
while (QLayoutItem *it = l->takeAt(0)) {
if (QWidget *w = it->widget()) {
if (policy == DeletePolicy::Immediate) {
delete w;
} else {
w->deleteLater();
}
}
if (QLayout *sub = it->layout()) {
clearLayoutRec(sub, policy);
}
delete it;
}
}
/**
* @brief Creates a layout of type @c LayoutT with zero margins.
*
* @param parent Parent widget, or nullptr when the layout is added later.
* @param spacing Optional spacing. Values below zero leave the style's default spacing
* untouched; pass a non-negative value to call setSpacing().
* @param margin Uniform margin. Defaults to zero.
*/
template <typename LayoutT> LayoutT *makeLayout(QWidget *parent = nullptr, int spacing = -1, int margin = 0)
{
auto *layout = new LayoutT(parent);
layout->setContentsMargins(margin, margin, margin, margin);
if (spacing >= 0) {
layout->setSpacing(spacing);
}
return layout;
}
/**
* @brief Forces @p layout to recompute and repaint immediately.
*
* Convenience for the invalidate/activate/update sequence used after changing a layout's
* contents outside of a resize event.
*/
inline void relayout(QLayout *layout)
{
if (!layout) {
return;
}
layout->invalidate();
layout->activate();
layout->update();
}
/**
* @brief Creates a QScrollArea using the conventions the client applies nearly everywhere:
* the inner widget resizes with the viewport and the horizontal bar is disabled.
*
* @param parent Parent widget, or nullptr for a parentless scroll area.
* @param verticalPolicy Vertical scroll bar policy.
* @param horizontalPolicy Horizontal scroll bar policy.
*/
static inline QScrollArea *makeScrollArea(QWidget *parent = nullptr,
Qt::ScrollBarPolicy verticalPolicy = Qt::ScrollBarAsNeeded,
Qt::ScrollBarPolicy horizontalPolicy = Qt::ScrollBarAlwaysOff)
{
auto *scrollArea = new QScrollArea(parent);
scrollArea->setWidgetResizable(true);
scrollArea->setVerticalScrollBarPolicy(verticalPolicy);
scrollArea->setHorizontalScrollBarPolicy(horizontalPolicy);
return scrollArea;
}
/**
* @brief A monospace font at the system UI font's point size.
*
* Used for log dumps, deck codes and other text that has to line up.
*/
static inline QFont monospaceFont()
{
QFont font(QStringLiteral("monospace"));
font.setStyleHint(QFont::Monospace);
const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize();
if (systemPointSize > 0) {
font.setPointSize(systemPointSize);
}
return font;
}
/**
* @brief Fades @p window between two opacity values.
*
* @param window Window to animate.
* @param startOpacity Starting opacity.
* @param endOpacity Ending opacity.
* @param durationMs Duration in milliseconds.
* @param hideWhenFinished Hide the window when the animation finishes.
* @return The running animation. It is owned by @p window and deletes itself when stopped.
*/
static inline QPropertyAnimation *
fadeWindow(QWidget *window, qreal startOpacity, qreal endOpacity, int durationMs, bool hideWhenFinished = false)
{
auto *animation = new QPropertyAnimation(window, "windowOpacity", window);
animation->setDuration(durationMs);
animation->setStartValue(startOpacity);
animation->setEndValue(endOpacity);
if (hideWhenFinished) {
QObject::connect(animation, &QPropertyAnimation::finished, window, &QWidget::hide);
}
animation->start(QAbstractAnimation::DeleteWhenStopped);
return animation;
}
/**
* @brief Opens @p path with the platform's file manager or default handler.
*/
static inline bool openPathInFileManager(const QString &path)
{
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}
/**
* @brief Returns @p font with bold styling enabled.
*/
static inline QFont boldFont(QFont font)
{
font.setBold(true);
return font;
}
/**
* @brief Shows a Yes/No confirmation dialog and returns true when the user confirms.
*
* @param parent Parent widget.
* @param title Dialog title.
* @param text Dialog text.
* @param defaultButton Button focused by default. NoButton lets Qt choose.
*/
static inline bool confirm(QWidget *parent,
const QString &title,
const QString &text,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton)
{
return QMessageBox::question(parent, title, text, QMessageBox::Yes | QMessageBox::No, defaultButton) ==
QMessageBox::Yes;
}
} // namespace QtUtils
#endif // COCKATRICE_QT_UTILS_H