Add UIntSpinBox class

This commit is contained in:
GriffinR 2023-09-11 12:44:15 -04:00
parent b00c644099
commit 603df4defa
5 changed files with 266 additions and 12 deletions

View file

@ -321,7 +321,7 @@
</widget>
</item>
<item row="7" column="1">
<widget class="NoScrollSpinBox" name="spinBox_TerrainTypeMask">
<widget class="UIntSpinBox" name="spinBox_TerrainTypeMask">
<property name="toolTip">
<string>The mask used to read/write Terrain Type from the metatile's attributes data. If 0, this attribute is disabled.</string>
</property>
@ -378,7 +378,7 @@
</widget>
</item>
<item row="3" column="1">
<widget class="NoScrollSpinBox" name="spinBox_BehaviorMask">
<widget class="UIntSpinBox" name="spinBox_BehaviorMask">
<property name="toolTip">
<string>The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled.</string>
</property>
@ -411,7 +411,7 @@
</widget>
</item>
<item row="5" column="1">
<widget class="NoScrollSpinBox" name="spinBox_EncounterTypeMask">
<widget class="UIntSpinBox" name="spinBox_EncounterTypeMask">
<property name="toolTip">
<string>The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled.</string>
</property>
@ -438,7 +438,7 @@
</widget>
</item>
<item row="4" column="1">
<widget class="NoScrollSpinBox" name="spinBox_LayerTypeMask">
<widget class="UIntSpinBox" name="spinBox_LayerTypeMask">
<property name="toolTip">
<string>The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled.</string>
</property>
@ -671,6 +671,11 @@
<extends>QSpinBox</extends>
<header>noscrollspinbox.h</header>
</customwidget>
<customwidget>
<class>UIntSpinBox</class>
<extends>QAbstractSpinBox</extends>
<header>uintspinbox.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../resources/images.qrc"/>

67
include/ui/uintspinbox.h Normal file
View file

@ -0,0 +1,67 @@
#ifndef UINTSPINBOX_H
#define UINTSPINBOX_H
#include <QAbstractSpinBox>
#include <QLineEdit>
/*
QSpinBox stores minimum/maximum/value as ints. This class is a version of QAbstractSpinBox for values above 0x7FFFFFFF.
It minimally implements some QSpinBox-specific features like prefixes to simplify support for hex value input.
It also implements the scroll focus requirements of NoScrollSpinBox.
*/
class UIntSpinBox : public QAbstractSpinBox
{
Q_OBJECT
public:
explicit UIntSpinBox(QWidget *parent = nullptr);
~UIntSpinBox() {};
uint32_t value() const { return m_value; }
uint32_t minimum() const { return m_minimum; }
uint32_t maximum() const { return m_maximum; }
QString prefix() const { return m_prefix; }
int displayIntegerBase() const { return m_displayIntegerBase; }
bool hasPadding() const { return m_hasPadding; }
void setMinimum(uint32_t min);
void setMaximum(uint32_t max);
void setRange(uint32_t min, uint32_t max);
void setPrefix(const QString &prefix);
void setDisplayIntegerBase(int base);
void setHasPadding(bool enabled);
private:
uint32_t m_minimum;
uint32_t m_maximum;
uint32_t m_value;
QString m_prefix;
int m_displayIntegerBase;
bool m_hasPadding;
int m_numChars;
void updateEdit();
QString stripped(QString input) const;
virtual void stepBy(int steps) override;
virtual void wheelEvent(QWheelEvent *event) override;
virtual void focusOutEvent(QFocusEvent *event) override;
protected:
virtual uint32_t valueFromText(const QString &text) const;
virtual QString textFromValue(uint32_t val) const;
virtual QValidator::State validate(QString &input, int &pos) const override;
virtual QAbstractSpinBox::StepEnabled stepEnabled() const override;
public slots:
void setValue(uint32_t val);
void onEditFinished();
signals:
void valueChanged(uint32_t val);
void textChanged(const QString &text);
};
#endif // UINTSPINBOX_H

View file

@ -100,7 +100,8 @@ SOURCES += src/core/block.cpp \
src/mainwindow.cpp \
src/project.cpp \
src/settings.cpp \
src/log.cpp
src/log.cpp \
src/ui/uintspinbox.cpp
HEADERS += include/core/block.h \
include/core/blockdata.h \
@ -192,7 +193,8 @@ HEADERS += include/core/block.h \
include/scripting.h \
include/scriptutility.h \
include/settings.h \
include/log.h
include/log.h \
include/ui/uintspinbox.h
FORMS += forms/mainwindow.ui \
forms/prefabcreationdialog.ui \

View file

@ -42,10 +42,12 @@ void ProjectSettingsEditor::connectSignals() {
connect(combo, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::markEdited);
for (auto checkBox : ui->centralwidget->findChildren<QCheckBox *>())
connect(checkBox, &QCheckBox::stateChanged, this, &ProjectSettingsEditor::markEdited);
for (auto spinBox : ui->centralwidget->findChildren<NoScrollSpinBox *>())
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), [this](int) { this->markEdited(); });
for (auto lineEdit : ui->centralwidget->findChildren<QLineEdit *>())
connect(lineEdit, &QLineEdit::textEdited, this, &ProjectSettingsEditor::markEdited);
for (auto spinBox : ui->centralwidget->findChildren<NoScrollSpinBox *>())
connect(spinBox, &QSpinBox::textChanged, this, &ProjectSettingsEditor::markEdited);
for (auto spinBox : ui->centralwidget->findChildren<UIntSpinBox *>())
connect(spinBox, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::markEdited);
}
void ProjectSettingsEditor::markEdited() {
@ -71,10 +73,10 @@ void ProjectSettingsEditor::initUi() {
ui->spinBox_FillMetatile->setMaximum(Project::getNumMetatilesTotal() - 1);
// TODO: These need to be subclassed (or changed to line edits) to handle larger values
ui->spinBox_BehaviorMask->setMaximum(INT_MAX);
ui->spinBox_EncounterTypeMask->setMaximum(INT_MAX);
ui->spinBox_LayerTypeMask->setMaximum(INT_MAX);
ui->spinBox_TerrainTypeMask->setMaximum(INT_MAX);
ui->spinBox_BehaviorMask->setMaximum(UINT_MAX);
ui->spinBox_EncounterTypeMask->setMaximum(UINT_MAX);
ui->spinBox_LayerTypeMask->setMaximum(UINT_MAX);
ui->spinBox_TerrainTypeMask->setMaximum(UINT_MAX);
}
void ProjectSettingsEditor::createProjectPathsTable() {

178
src/ui/uintspinbox.cpp Normal file
View file

@ -0,0 +1,178 @@
#include "uintspinbox.h"
UIntSpinBox::UIntSpinBox(QWidget *parent)
: QAbstractSpinBox(parent)
{
// Don't let scrolling hijack focus.
setFocusPolicy(Qt::StrongFocus);
m_minimum = 0;
m_maximum = 99;
m_value = m_minimum;
m_displayIntegerBase = 10;
m_numChars = 2;
m_hasPadding = false;
this->updateEdit();
connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(onEditFinished()));
};
void UIntSpinBox::setValue(uint32_t val) {
if (m_value != val) {
m_value = val;
emit valueChanged(m_value);
}
this->updateEdit();
}
uint32_t UIntSpinBox::valueFromText(const QString &text) const {
return this->stripped(text).toUInt(nullptr, m_displayIntegerBase);
}
QString UIntSpinBox::textFromValue(uint32_t val) const {
if (m_hasPadding)
return m_prefix + QString("%1").arg(val, m_numChars, m_displayIntegerBase, QChar('0')).toUpper();
return m_prefix + QString::number(val, m_displayIntegerBase).toUpper();
}
void UIntSpinBox::setMinimum(uint32_t min) {
this->setRange(min, qMax(min, m_maximum));
}
void UIntSpinBox::setMaximum(uint32_t max) {
this->setRange(qMin(m_minimum, max), max);
}
void UIntSpinBox::setRange(uint32_t min, uint32_t max) {
max = qMax(min, max);
if (m_maximum == max && m_minimum == min)
return;
if (m_maximum != max) {
// Update number of characters for padding
m_numChars = 0;
for (uint32_t i = max; i != 0; i /= m_displayIntegerBase)
m_numChars++;
}
m_minimum = min;
m_maximum = max;
if (m_value < min)
m_value %= min;
else if (m_value > max)
m_value %= max;
this->updateEdit();
}
void UIntSpinBox::setPrefix(const QString &prefix) {
if (m_prefix != prefix) {
m_prefix = prefix;
this->updateEdit();
}
}
void UIntSpinBox::setDisplayIntegerBase(int base) {
if (base < 2 || base > 36)
base = 10;
if (m_displayIntegerBase != base) {
m_displayIntegerBase = base;
this->updateEdit();
}
}
void UIntSpinBox::setHasPadding(bool enabled) {
if (m_hasPadding != enabled) {
m_hasPadding = enabled;
this->updateEdit();
}
}
void UIntSpinBox::updateEdit() {
const QString text = this->textFromValue(m_value);
if (text != this->lineEdit()->text()) {
this->lineEdit()->setText(text);
emit textChanged(this->lineEdit()->text());
}
}
void UIntSpinBox::onEditFinished() {
int pos = this->lineEdit()->cursorPosition();
QString input = this->lineEdit()->text();
auto state = this->validate(input, pos);
if (state == QValidator::Acceptable) {
// Valid input
m_value = this->valueFromText(input);
} else if (state == QValidator::Intermediate) {
// User has deleted all the number text.
// If they did this by selecting all text and then hitting delete
// make sure to put the cursor back in front of the prefix.
m_value = m_minimum;
this->lineEdit()->setCursorPosition(m_prefix.length());
}
}
void UIntSpinBox::stepBy(int steps)
{
auto new_value = m_value;
if (steps < 0 && new_value + steps > new_value) {
new_value = 0;
} else if (steps > 0 && new_value + steps < new_value) {
new_value = UINT_MAX;
} else {
new_value += steps;
}
this->setValue(new_value);
}
QString UIntSpinBox::stripped(QString input) const {
if (m_prefix.length() && input.startsWith(m_prefix))
input.remove(0, m_prefix.length());
return input.trimmed();
}
QValidator::State UIntSpinBox::validate(QString &input, int &pos) const {
QString copy(input);
input = m_prefix;
// Adjust for prefix
copy = this->stripped(copy);
if (copy.isEmpty())
return QValidator::Intermediate;
// Editing the prefix (if not deleting all text) is not allowed
if (pos < m_prefix.length())
return QValidator::Invalid;
bool ok;
uint32_t num = copy.toUInt(&ok, m_displayIntegerBase);
if (!ok || num < m_minimum || num > m_maximum)
return QValidator::Invalid;
input += copy.toUpper();
return QValidator::Acceptable;
}
QAbstractSpinBox::StepEnabled UIntSpinBox::stepEnabled() const {
QAbstractSpinBox::StepEnabled flags = QAbstractSpinBox::StepNone;
if (m_value < m_maximum)
flags |= QAbstractSpinBox::StepUpEnabled;
if (m_value > m_minimum)
flags |= QAbstractSpinBox::StepDownEnabled;
return flags;
}
void UIntSpinBox::wheelEvent(QWheelEvent *event) {
// Only allow scrolling to modify contents when it explicitly has focus.
if (hasFocus())
QAbstractSpinBox::wheelEvent(event);
}
void UIntSpinBox::focusOutEvent(QFocusEvent *event) {
this->updateEdit();
QAbstractSpinBox::focusOutEvent(event);
}