364 lines
22 KiB
C++
364 lines
22 KiB
C++
#include "UI/LassoControls.h"
|
||
#include "UI/KeyboardShortcuts.h"
|
||
#include <QBoxLayout>
|
||
#include <QFrame>
|
||
#include <QLabel>
|
||
#include <QSignalBlocker>
|
||
#include <QRegularExpressionValidator>
|
||
#include <QFocusEvent>
|
||
#include <QKeyEvent>
|
||
#include <algorithm>
|
||
#include <cmath>
|
||
|
||
SelectionAmountField::SelectionAmountField(EditorSession &session, int low, int high, std::function<double()> value,
|
||
std::function<void(double)> change, QWidget *parent)
|
||
: QLineEdit(parent), m_session(session), m_low(low), m_high(high), m_value(std::move(value)), m_change(std::move(change)), m_amount(low)
|
||
{
|
||
setFont(ToolHeaderStyle::controlFont());
|
||
setFixedWidth(40);
|
||
setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||
// Digits, signed below zero; the bounds clamp, as Swift's binding.
|
||
setValidator(new QRegularExpressionValidator(QRegularExpression(low < 0 ? QStringLiteral("-?[0-9]*") : QStringLiteral("[0-9]*")), this));
|
||
// Typing applies at once; an overflow reads as its end.
|
||
connect(this, &QLineEdit::textEdited, this, [this](const QString &text) {
|
||
if (text.isEmpty() || text == QLatin1String("-"))
|
||
return;
|
||
bool fits = false;
|
||
const qlonglong typed = text.toLongLong(&fits);
|
||
m_change(double(fits ? std::clamp<qlonglong>(typed, m_low, m_high) : text.startsWith(QLatin1Char('-')) ? m_low : m_high));
|
||
});
|
||
}
|
||
|
||
void SelectionAmountField::sync(int amount)
|
||
{
|
||
m_amount = amount;
|
||
if (!hasFocus() && !m_borrowed)
|
||
setText(QString::number(amount));
|
||
}
|
||
|
||
void SelectionAmountField::apply()
|
||
{
|
||
setText(QString::number(m_amount));
|
||
}
|
||
|
||
void SelectionAmountField::keyPressEvent(QKeyEvent *event)
|
||
{
|
||
// Return and Escape hand the canvas the keys.
|
||
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter || event->key() == Qt::Key_Escape) {
|
||
apply();
|
||
clearFocus();
|
||
m_session.requestCanvasFocus();
|
||
return;
|
||
}
|
||
if (event->key() != Qt::Key_Up && event->key() != Qt::Key_Down) {
|
||
QLineEdit::keyPressEvent(event);
|
||
return;
|
||
}
|
||
const int amount = (event->modifiers().testFlag(Qt::ShiftModifier) ? 10 : 1) * (event->key() == Qt::Key_Up ? 1 : -1);
|
||
const double stepped = std::clamp(m_value() + amount, double(m_low), double(m_high));
|
||
m_change(stepped);
|
||
setText(QString::number(std::lround(stepped)));
|
||
}
|
||
|
||
void SelectionAmountField::focusOutEvent(QFocusEvent *event)
|
||
{
|
||
m_borrowed = event->reason() == Qt::MenuBarFocusReason || event->reason() == Qt::PopupFocusReason;
|
||
if (!m_borrowed)
|
||
apply();
|
||
QLineEdit::focusOutEvent(event);
|
||
}
|
||
|
||
LassoControls::LassoControls(EditorSession &session, QWidget *parent)
|
||
: ToolHeaderBar(QString(), parent), m_session(session), m_marqueeKinds(new QButtonGroup(this)), m_lassoKinds(new QButtonGroup(this)),
|
||
m_modes(new QButtonGroup(this)),
|
||
m_rectangle(choice(m_marqueeKinds, QStringLiteral("marqueeRectangle"), rawValue(LassoKind::rectangle), QStringLiteral("Switch between Rectangle and Ellipse"),
|
||
[this] { m_session.cancelLasso(); m_session.setMarqueeKind(LassoKind::rectangle); })),
|
||
m_ellipse(choice(m_marqueeKinds, QStringLiteral("marqueeEllipse"), rawValue(LassoKind::ellipse), QStringLiteral("Switch between Rectangle and Ellipse"),
|
||
[this] { m_session.cancelLasso(); m_session.setMarqueeKind(LassoKind::ellipse); })),
|
||
m_freehand(choice(m_lassoKinds, QStringLiteral("lassoFreehand"), rawValue(LassoKind::freehand), QStringLiteral("Switch between Freehand and Polygonal"),
|
||
[this] { m_session.cancelLasso(); m_session.setLassoKind(LassoKind::freehand); })),
|
||
m_polygonal(choice(m_lassoKinds, QStringLiteral("lassoPolygonal"), rawValue(LassoKind::polygonal), QStringLiteral("Switch between Freehand and Polygonal"),
|
||
[this] { m_session.cancelLasso(); m_session.setLassoKind(LassoKind::polygonal); })),
|
||
m_replace(choice(m_modes, QStringLiteral("selectionModeNew"), rawValue(SelectionMode::replace), QStringLiteral("Hold Shift to add or Alt to subtract for one outline"),
|
||
[this] { m_session.setSelectionModeChoice(SelectionMode::replace); })),
|
||
m_add(choice(m_modes, QStringLiteral("selectionModeAdd"), rawValue(SelectionMode::add), QStringLiteral("Hold Shift to add or Alt to subtract for one outline"),
|
||
[this] { m_session.setSelectionModeChoice(SelectionMode::add); })),
|
||
m_subtract(choice(m_modes, QStringLiteral("selectionModeSubtract"), rawValue(SelectionMode::subtract), QStringLiteral("Hold Shift to add or Alt to subtract for one outline"),
|
||
[this] { m_session.setSelectionModeChoice(SelectionMode::subtract); })),
|
||
m_toleranceLabel(new QLabel(QStringLiteral("Tolerance"), this)),
|
||
m_tolerance(new SelectionAmountField(session, 0, 255, [this] { return double(m_session.wandSettings().tolerance); },
|
||
[this](double tolerance) { changeWand([tolerance](WandSettings &wand) { wand.tolerance = int(std::lround(tolerance)); }); }, this)),
|
||
m_sampleSize(new QComboBox(this)), m_sources(new QButtonGroup(this)),
|
||
m_thisLayer(choice(m_sources, QStringLiteral("wandThisLayer"), QStringLiteral("This Layer"),
|
||
QStringLiteral("Read colors from the active layer only, or from every visible layer as shown"),
|
||
[this] { changeWand([](WandSettings &wand) { wand.sampleAllLayers = false; }); })),
|
||
m_allLayers(choice(m_sources, QStringLiteral("wandAllLayers"), QStringLiteral("All Layers"),
|
||
QStringLiteral("Read colors from the active layer only, or from every visible layer as shown"),
|
||
[this] { changeWand([](WandSettings &wand) { wand.sampleAllLayers = true; }); })),
|
||
m_contiguous(new QCheckBox(QStringLiteral("Contiguous"), this)), m_wandModes(new QButtonGroup(this)),
|
||
m_wandMode(choice(m_wandModes, QStringLiteral("magicWandMode"), rawValue(WandMode::wand), QStringLiteral("Press Tab to switch between Wand and Object"),
|
||
[this] { m_session.setWandMode(WandMode::wand); })),
|
||
m_objectMode(choice(m_wandModes, QStringLiteral("magicObjectMode"), rawValue(WandMode::object), QStringLiteral("Press Tab to switch between Wand and Object"),
|
||
[this] { m_session.setWandMode(WandMode::object); })),
|
||
m_objectSources(new QButtonGroup(this)),
|
||
m_objectThisLayer(choice(m_objectSources, QStringLiteral("objectThisLayer"), QStringLiteral("This Layer"),
|
||
QStringLiteral("Analyze the active layer only, or every visible layer as shown"),
|
||
[this] { changeObject([](ObjectSelectionSettings &object) { object.sampleAllLayers = false; }); })),
|
||
m_objectAllLayers(choice(m_objectSources, QStringLiteral("objectAllLayers"), QStringLiteral("All Layers"),
|
||
QStringLiteral("Analyze the active layer only, or every visible layer as shown"),
|
||
[this] { changeObject([](ObjectSelectionSettings &object) { object.sampleAllLayers = true; }); })),
|
||
m_edgeLabel(new QLabel(QStringLiteral("Edge"), this)),
|
||
m_edge(new SelectionAmountField(session, -10, 10, [this] { return double(m_session.objectSelectionSettings().edgeOffset); },
|
||
[this](double edge) { changeObject([edge](ObjectSelectionSettings &object) { object.edgeOffset = int(std::lround(edge)); }); },
|
||
this)),
|
||
m_edgeUnit(new QLabel(QStringLiteral("px"), this)),
|
||
m_antialias(new QCheckBox(QStringLiteral("Anti-alias"), this)), m_expand(new QPushButton(QStringLiteral("Expand"), this)),
|
||
m_expandAmount(new SelectionAmountField(session, 1, 500, [this] { return double(m_session.selectionExpandAmount()); },
|
||
[this](double amount) { m_session.setSelectionExpandAmount(int(std::lround(amount))); }, this)),
|
||
m_contract(new QPushButton(QStringLiteral("Contract"), this)),
|
||
m_contractAmount(new SelectionAmountField(session, 1, 500, [this] { return double(m_session.selectionContractAmount()); },
|
||
[this](double amount) { m_session.setSelectionContractAmount(int(std::lround(amount))); }, this)),
|
||
m_feather(new QPushButton(QStringLiteral("Feather"), this)),
|
||
m_featherAmount(new SelectionAmountField(session, 1, 250, [this] { return double(m_session.selectionFeatherAmount()); },
|
||
[this](double amount) { m_session.setSelectionFeatherAmount(int(std::lround(amount))); }, this)),
|
||
m_empty(new QLabel(QStringLiteral("Empty selection"), this)), m_deselect(new QPushButton(QStringLiteral("Deselect"), this))
|
||
{
|
||
m_toleranceLabel->setFont(ToolHeaderStyle::controlFont());
|
||
m_tolerance->setObjectName(QStringLiteral("wandTolerance"));
|
||
m_tolerance->setFixedWidth(44);
|
||
m_tolerance->setToolTip(QStringLiteral("How far each color channel (0–255) can differ from the clicked color and still be selected"));
|
||
m_toleranceLabel->setToolTip(m_tolerance->toolTip());
|
||
m_sampleSize->setObjectName(QStringLiteral("wandSampleSize"));
|
||
m_sampleSize->setToolTip(QStringLiteral("Match the clicked pixel, or the average of the pixels around it"));
|
||
for (const WandSampleSize size : {WandSampleSize::point, WandSampleSize::threeByThree, WandSampleSize::fiveByFive})
|
||
m_sampleSize->addItem(::title(size), int(size));
|
||
connect(m_sampleSize, &QComboBox::activated, this, [this](int index) {
|
||
changeWand([index](WandSettings &wand) { wand.sampleSize = WandSampleSize(index); });
|
||
});
|
||
m_edge->setObjectName(QStringLiteral("objectEdge"));
|
||
for (QWidget *edge : std::initializer_list<QWidget *>{m_edgeLabel, m_edge, m_edgeUnit})
|
||
edge->setToolTip(QStringLiteral("Positive values tighten the detected mask inward; negative values expand it outward"));
|
||
m_edgeLabel->setObjectName(QStringLiteral("objectEdgeLabel"));
|
||
m_edgeLabel->setFont(ToolHeaderStyle::controlFont());
|
||
m_contiguous->setObjectName(QStringLiteral("wandContiguous"));
|
||
m_contiguous->setToolTip(QStringLiteral("Select only similar pixels connected to the one you click; off selects them everywhere"));
|
||
connect(m_contiguous, &QCheckBox::toggled, this, [this](bool on) { changeWand([on](WandSettings &wand) { wand.contiguous = on; }); });
|
||
// Rectangles snap to whole pixels, so smoothing applies elsewhere.
|
||
m_antialias->setObjectName(QStringLiteral("selectionAntialiased"));
|
||
connect(m_antialias, &QCheckBox::toggled, this, [this](bool on) { m_session.setSelectionAntialiased(on); });
|
||
auto *divider = new QFrame(this);
|
||
divider->setFrameShape(QFrame::VLine);
|
||
divider->setFixedHeight(18);
|
||
m_expand->setObjectName(QStringLiteral("expandSelection"));
|
||
m_expand->setToolTip(QStringLiteral("Expand the selection by this many pixels"));
|
||
connect(m_expand, &QPushButton::clicked, this, [this] { m_session.expandSelection(m_session.selectionExpandAmount()); });
|
||
m_expandAmount->setObjectName(QStringLiteral("selectionExpandAmount"));
|
||
m_expandAmount->setToolTip(m_expand->toolTip());
|
||
m_contract->setObjectName(QStringLiteral("contractSelection"));
|
||
m_contract->setToolTip(QStringLiteral("Contract the selection by this many pixels"));
|
||
connect(m_contract, &QPushButton::clicked, this, [this] { m_session.contractSelection(m_session.selectionContractAmount()); });
|
||
m_contractAmount->setObjectName(QStringLiteral("selectionContractAmount"));
|
||
m_contractAmount->setToolTip(m_contract->toolTip());
|
||
m_feather->setObjectName(QStringLiteral("featherSelection"));
|
||
m_feather->setToolTip(QStringLiteral("Fade the edge of the selection by this many pixels"));
|
||
connect(m_feather, &QPushButton::clicked, this, [this] { m_session.featherSelection(m_session.selectionFeatherAmount()); });
|
||
m_featherAmount->setObjectName(QStringLiteral("selectionFeatherAmount"));
|
||
m_featherAmount->setToolTip(m_feather->toolTip());
|
||
m_empty->setObjectName(QStringLiteral("emptySelection"));
|
||
m_empty->setFont(ToolHeaderStyle::controlFont());
|
||
m_empty->setForegroundRole(QPalette::PlaceholderText);
|
||
m_deselect->setObjectName(QStringLiteral("deselect"));
|
||
connect(m_deselect, &QPushButton::clicked, this, [this] { m_session.deselect(); });
|
||
// Swift's `unitSuffix`: a unit sits by each field.
|
||
for (QWidget *widget : std::initializer_list<QWidget *>{m_rectangle, m_ellipse, m_wandMode, m_objectMode, m_freehand, m_polygonal, m_replace, m_add,
|
||
m_subtract, m_toleranceLabel, m_tolerance, m_sampleSize, m_thisLayer, m_allLayers, m_contiguous,
|
||
m_objectThisLayer, m_objectAllLayers, m_edgeLabel, m_edge, m_edgeUnit, m_antialias,
|
||
divider, m_expand, m_expandAmount, new QLabel(QStringLiteral("px"), this), m_contract, m_contractAmount,
|
||
new QLabel(QStringLiteral("px"), this), m_feather, m_featherAmount,
|
||
new QLabel(QStringLiteral("px"), this)})
|
||
row->insertWidget(row->count() - 1, widget);
|
||
row->addWidget(m_empty);
|
||
row->addWidget(m_deselect);
|
||
connect(&m_session, &EditorSession::changed, this, &LassoControls::synchronize);
|
||
synchronize();
|
||
}
|
||
|
||
// One field of the wand's settings changes; the rest stay.
|
||
void LassoControls::changeWand(const std::function<void(WandSettings &)> &change)
|
||
{
|
||
WandSettings wand = m_session.wandSettings();
|
||
change(wand);
|
||
m_session.setWandSettings(wand);
|
||
}
|
||
|
||
// One field of Object mode's settings changes; the rest stay.
|
||
void LassoControls::changeObject(const std::function<void(ObjectSelectionSettings &)> &change)
|
||
{
|
||
ObjectSelectionSettings object = m_session.objectSelectionSettings();
|
||
change(object);
|
||
m_session.setObjectSelectionSettings(object);
|
||
}
|
||
|
||
// A segment of Swift's segmented picker: one checkable button.
|
||
QToolButton *LassoControls::choice(QButtonGroup *group, const QString &name, const QString &text, const QString &tip, const std::function<void()> &pick)
|
||
{
|
||
auto *button = new QToolButton(this);
|
||
button->setObjectName(name);
|
||
button->setText(text);
|
||
button->setToolTip(tip);
|
||
button->setFont(ToolHeaderStyle::controlFont());
|
||
button->setCheckable(true);
|
||
button->setFocusPolicy(Qt::NoFocus);
|
||
group->addButton(button);
|
||
connect(button, &QToolButton::clicked, this, [pick] { pick(); });
|
||
return button;
|
||
}
|
||
|
||
void LassoControls::synchronize()
|
||
{
|
||
const NavigationTool tool = m_session.tool();
|
||
title->setText(tool == NavigationTool::marquee ? QStringLiteral("Marquee") : tool == NavigationTool::wand ? QStringLiteral("Magic") : QStringLiteral("Lasso"));
|
||
const bool wand = tool == NavigationTool::wand;
|
||
const bool object = wand && m_session.wandMode() == WandMode::object;
|
||
for (QToolButton *button : {m_wandMode, m_objectMode})
|
||
button->setVisible(wand);
|
||
m_wandMode->setChecked(!object);
|
||
m_objectMode->setChecked(object);
|
||
for (QWidget *widget : std::initializer_list<QWidget *>{m_objectThisLayer, m_objectAllLayers, m_edgeLabel, m_edge, m_edgeUnit})
|
||
widget->setVisible(object);
|
||
m_objectThisLayer->setChecked(!m_session.objectSelectionSettings().sampleAllLayers);
|
||
m_objectAllLayers->setChecked(m_session.objectSelectionSettings().sampleAllLayers);
|
||
m_edge->sync(m_session.objectSelectionSettings().edgeOffset);
|
||
for (QToolButton *button : {m_rectangle, m_ellipse})
|
||
button->setVisible(tool == NavigationTool::marquee);
|
||
for (QToolButton *button : {m_freehand, m_polygonal})
|
||
button->setVisible(tool == NavigationTool::lasso);
|
||
m_rectangle->setChecked(m_session.marqueeKind() == LassoKind::rectangle);
|
||
m_ellipse->setChecked(m_session.marqueeKind() == LassoKind::ellipse);
|
||
m_freehand->setChecked(m_session.lassoKind() == LassoKind::freehand);
|
||
m_polygonal->setChecked(m_session.lassoKind() == LassoKind::polygonal);
|
||
// Held Shift or Alt, or an outline's mode, shows live.
|
||
const SelectionMode mode = m_session.displayedSelectionMode();
|
||
m_replace->setChecked(mode == SelectionMode::replace);
|
||
m_add->setChecked(mode == SelectionMode::add);
|
||
m_subtract->setChecked(mode == SelectionMode::subtract);
|
||
const WandSettings &colours = m_session.wandSettings();
|
||
for (QWidget *widget : std::initializer_list<QWidget *>{m_toleranceLabel, m_tolerance, m_sampleSize, m_thisLayer, m_allLayers, m_contiguous})
|
||
widget->setVisible(wand && !object);
|
||
m_tolerance->sync(colours.tolerance);
|
||
m_sampleSize->setCurrentIndex(int(colours.sampleSize));
|
||
m_thisLayer->setChecked(!colours.sampleAllLayers);
|
||
m_allLayers->setChecked(colours.sampleAllLayers);
|
||
if (m_contiguous->isChecked() != colours.contiguous)
|
||
m_contiguous->setChecked(colours.contiguous);
|
||
m_antialias->setVisible(tool == NavigationTool::lasso || tool == NavigationTool::wand || (tool == NavigationTool::marquee && m_session.marqueeKind() == LassoKind::ellipse));
|
||
m_antialias->setToolTip(object ? QStringLiteral("Smooth the detected object outline; turn off for the raw pixel mask")
|
||
: QStringLiteral("Smooth selection edges; turn off for hard pixel edges"));
|
||
if (m_antialias->isChecked() != m_session.selectionAntialiased())
|
||
m_antialias->setChecked(m_session.selectionAntialiased());
|
||
const bool modifies = m_session.canModifySelection();
|
||
for (QWidget *widget : std::initializer_list<QWidget *>{m_expand, m_expandAmount, m_contract, m_contractAmount, m_feather, m_featherAmount})
|
||
widget->setEnabled(modifies);
|
||
m_expandAmount->sync(m_session.selectionExpandAmount());
|
||
m_contractAmount->sync(m_session.selectionContractAmount());
|
||
m_featherAmount->sync(m_session.selectionFeatherAmount());
|
||
const std::optional<DocumentSelection> selection = m_session.selection();
|
||
m_empty->setVisible(selection && selection->isEmpty());
|
||
m_deselect->setVisible(selection.has_value());
|
||
m_deselect->setEnabled(m_session.canEditSelection());
|
||
setEnabled(!m_session.showsBusy() && m_session.document().has_value());
|
||
}
|
||
|
||
SelectionAmountSheet::SelectionAmountSheet(EditorSession &session, SelectionAmountOperation operation, QWidget *parent)
|
||
: QWidget(parent), m_session(session), m_maximum(operation == SelectionAmountOperation::feather ? 250 : 500),
|
||
m_slider(new QSlider(Qt::Horizontal, this)), m_input(new QLineEdit(this)),
|
||
m_note(new QLabel(QStringLiteral("Enter a whole number from 1 to %1 px.").arg(m_maximum), this)), m_ok(new QPushButton(QStringLiteral("OK"), this))
|
||
{
|
||
const int start = operation == SelectionAmountOperation::expand ? session.selectionExpandAmount()
|
||
: operation == SelectionAmountOperation::contract ? session.selectionContractAmount()
|
||
: session.selectionFeatherAmount();
|
||
setFixedWidth(380);
|
||
auto *column = new QVBoxLayout(this);
|
||
column->setContentsMargins(24, 24, 24, 24);
|
||
column->setSpacing(16);
|
||
auto *row = new QHBoxLayout;
|
||
row->setSpacing(10);
|
||
auto *title = new QLabel(QStringLiteral("Amount"), this);
|
||
title->setMinimumWidth(60);
|
||
title->setBuddy(m_slider);
|
||
m_slider->setObjectName(QStringLiteral("amountSlider"));
|
||
m_slider->setRange(1, m_maximum);
|
||
m_input->setObjectName(QStringLiteral("amountField"));
|
||
m_input->setAccessibleName(QStringLiteral("Amount"));
|
||
m_input->setPlaceholderText(QStringLiteral("Amount"));
|
||
m_input->setFixedWidth(56);
|
||
m_input->setAlignment(Qt::AlignRight);
|
||
m_input->setText(QString::number(start));
|
||
row->addWidget(title);
|
||
row->addWidget(m_slider, 1);
|
||
row->addWidget(m_input);
|
||
row->addSpacing(2);
|
||
row->addWidget(new QLabel(QStringLiteral("px"), this));
|
||
column->addLayout(row);
|
||
m_note->setObjectName(QStringLiteral("amountNote"));
|
||
m_note->setForegroundRole(QPalette::PlaceholderText);
|
||
QFont callout = m_note->font();
|
||
callout.setPixelSize(12);
|
||
m_note->setFont(callout);
|
||
// Hidden, the note keeps its room, as SwiftUI's opacity does.
|
||
QSizePolicy kept = m_note->sizePolicy();
|
||
kept.setRetainSizeWhenHidden(true);
|
||
m_note->setSizePolicy(kept);
|
||
column->addWidget(m_note);
|
||
auto *divider = new QFrame(this);
|
||
divider->setFrameShape(QFrame::HLine);
|
||
divider->setForegroundRole(QPalette::Mid);
|
||
column->addWidget(divider);
|
||
auto *buttons = new QHBoxLayout;
|
||
auto *cancel = new QPushButton(QStringLiteral("Cancel"), this);
|
||
cancel->setObjectName(QStringLiteral("amountCancel"));
|
||
cancel->setAutoDefault(false);
|
||
m_ok->setObjectName(QStringLiteral("amountOK"));
|
||
m_ok->setDefault(true);
|
||
// Swift's configuredNativeShortcut: Return and Escape, as remapped.
|
||
NativeShortcut::bind(*this, m_ok, cancel);
|
||
buttons->addWidget(cancel);
|
||
buttons->addStretch(1);
|
||
buttons->addWidget(m_ok);
|
||
column->addLayout(buttons);
|
||
connect(m_slider, &QSlider::valueChanged, this, [this](int value) {
|
||
m_input->setText(QString::number(value));
|
||
});
|
||
connect(m_input, &QLineEdit::textChanged, this, [this] { refresh(); });
|
||
connect(cancel, &QPushButton::clicked, this, [this] { m_session.setSelectionAmountOperation(std::nullopt); });
|
||
connect(m_ok, &QPushButton::clicked, this, [this] {
|
||
if (const std::optional<int> chosen = amount())
|
||
m_session.confirmSelectionAmount(*chosen);
|
||
});
|
||
refresh();
|
||
}
|
||
|
||
void SelectionAmountSheet::showEvent(QShowEvent *event)
|
||
{
|
||
QWidget::showEvent(event);
|
||
m_input->setFocus();
|
||
}
|
||
|
||
std::optional<int> SelectionAmountSheet::amount() const
|
||
{
|
||
bool ok = false;
|
||
const int value = m_input->text().toInt(&ok);
|
||
if (!ok || value < 1 || value > m_maximum)
|
||
return std::nullopt;
|
||
return value;
|
||
}
|
||
|
||
void SelectionAmountSheet::refresh()
|
||
{
|
||
const std::optional<int> chosen = amount();
|
||
const QSignalBlocker quiet(m_slider);
|
||
m_slider->setValue(chosen.value_or(1));
|
||
m_note->setVisible(!chosen);
|
||
m_ok->setEnabled(chosen.has_value());
|
||
}
|