Add UIntSpinBox class
This commit is contained in:
parent
b00c644099
commit
603df4defa
5 changed files with 266 additions and 12 deletions
|
@ -321,7 +321,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="1">
|
<item row="7" column="1">
|
||||||
<widget class="NoScrollSpinBox" name="spinBox_TerrainTypeMask">
|
<widget class="UIntSpinBox" name="spinBox_TerrainTypeMask">
|
||||||
<property name="toolTip">
|
<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>
|
<string>The mask used to read/write Terrain Type from the metatile's attributes data. If 0, this attribute is disabled.</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -378,7 +378,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item row="3" column="1">
|
||||||
<widget class="NoScrollSpinBox" name="spinBox_BehaviorMask">
|
<widget class="UIntSpinBox" name="spinBox_BehaviorMask">
|
||||||
<property name="toolTip">
|
<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>
|
<string>The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled.</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -411,7 +411,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="1">
|
<item row="5" column="1">
|
||||||
<widget class="NoScrollSpinBox" name="spinBox_EncounterTypeMask">
|
<widget class="UIntSpinBox" name="spinBox_EncounterTypeMask">
|
||||||
<property name="toolTip">
|
<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>
|
<string>The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled.</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -438,7 +438,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="NoScrollSpinBox" name="spinBox_LayerTypeMask">
|
<widget class="UIntSpinBox" name="spinBox_LayerTypeMask">
|
||||||
<property name="toolTip">
|
<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>
|
<string>The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled.</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -671,6 +671,11 @@
|
||||||
<extends>QSpinBox</extends>
|
<extends>QSpinBox</extends>
|
||||||
<header>noscrollspinbox.h</header>
|
<header>noscrollspinbox.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>UIntSpinBox</class>
|
||||||
|
<extends>QAbstractSpinBox</extends>
|
||||||
|
<header>uintspinbox.h</header>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../resources/images.qrc"/>
|
<include location="../resources/images.qrc"/>
|
||||||
|
|
67
include/ui/uintspinbox.h
Normal file
67
include/ui/uintspinbox.h
Normal 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
|
|
@ -100,7 +100,8 @@ SOURCES += src/core/block.cpp \
|
||||||
src/mainwindow.cpp \
|
src/mainwindow.cpp \
|
||||||
src/project.cpp \
|
src/project.cpp \
|
||||||
src/settings.cpp \
|
src/settings.cpp \
|
||||||
src/log.cpp
|
src/log.cpp \
|
||||||
|
src/ui/uintspinbox.cpp
|
||||||
|
|
||||||
HEADERS += include/core/block.h \
|
HEADERS += include/core/block.h \
|
||||||
include/core/blockdata.h \
|
include/core/blockdata.h \
|
||||||
|
@ -192,7 +193,8 @@ HEADERS += include/core/block.h \
|
||||||
include/scripting.h \
|
include/scripting.h \
|
||||||
include/scriptutility.h \
|
include/scriptutility.h \
|
||||||
include/settings.h \
|
include/settings.h \
|
||||||
include/log.h
|
include/log.h \
|
||||||
|
include/ui/uintspinbox.h
|
||||||
|
|
||||||
FORMS += forms/mainwindow.ui \
|
FORMS += forms/mainwindow.ui \
|
||||||
forms/prefabcreationdialog.ui \
|
forms/prefabcreationdialog.ui \
|
||||||
|
|
|
@ -42,10 +42,12 @@ void ProjectSettingsEditor::connectSignals() {
|
||||||
connect(combo, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::markEdited);
|
connect(combo, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::markEdited);
|
||||||
for (auto checkBox : ui->centralwidget->findChildren<QCheckBox *>())
|
for (auto checkBox : ui->centralwidget->findChildren<QCheckBox *>())
|
||||||
connect(checkBox, &QCheckBox::stateChanged, this, &ProjectSettingsEditor::markEdited);
|
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 *>())
|
for (auto lineEdit : ui->centralwidget->findChildren<QLineEdit *>())
|
||||||
connect(lineEdit, &QLineEdit::textEdited, this, &ProjectSettingsEditor::markEdited);
|
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() {
|
void ProjectSettingsEditor::markEdited() {
|
||||||
|
@ -71,10 +73,10 @@ void ProjectSettingsEditor::initUi() {
|
||||||
ui->spinBox_FillMetatile->setMaximum(Project::getNumMetatilesTotal() - 1);
|
ui->spinBox_FillMetatile->setMaximum(Project::getNumMetatilesTotal() - 1);
|
||||||
|
|
||||||
// TODO: These need to be subclassed (or changed to line edits) to handle larger values
|
// TODO: These need to be subclassed (or changed to line edits) to handle larger values
|
||||||
ui->spinBox_BehaviorMask->setMaximum(INT_MAX);
|
ui->spinBox_BehaviorMask->setMaximum(UINT_MAX);
|
||||||
ui->spinBox_EncounterTypeMask->setMaximum(INT_MAX);
|
ui->spinBox_EncounterTypeMask->setMaximum(UINT_MAX);
|
||||||
ui->spinBox_LayerTypeMask->setMaximum(INT_MAX);
|
ui->spinBox_LayerTypeMask->setMaximum(UINT_MAX);
|
||||||
ui->spinBox_TerrainTypeMask->setMaximum(INT_MAX);
|
ui->spinBox_TerrainTypeMask->setMaximum(UINT_MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectSettingsEditor::createProjectPathsTable() {
|
void ProjectSettingsEditor::createProjectPathsTable() {
|
||||||
|
|
178
src/ui/uintspinbox.cpp
Normal file
178
src/ui/uintspinbox.cpp
Normal 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);
|
||||||
|
}
|
Loading…
Reference in a new issue