mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-22 17:10:06 +00:00
citra-qt: Add a utility spinbox class called CSpinBox.
This class has a few advantages over the regular QSpinBox: - QSpinBox stores its as signed 32 bit integers, which for instance is unsuitable for representing memory addresses. CSpinBox uses 64 bit integers instead. - QSpinBox does not provide an easy way to handle number input from bases different than 10. - QSpinBox is quite inflexible in general and almost any sort of customization requires reimplementing it anyway.
This commit is contained in:
parent
8db65723d2
commit
bf6b23f4a0
@ -11,6 +11,7 @@ set(SRCS
|
||||
debugger/graphics_cmdlists.cpp
|
||||
debugger/ramview.cpp
|
||||
debugger/registers.cpp
|
||||
util/spinbox.cpp
|
||||
bootmanager.cpp
|
||||
hotkeys.cpp
|
||||
main.cpp
|
||||
@ -26,6 +27,7 @@ set(HEADERS
|
||||
debugger/graphics_cmdlists.hxx
|
||||
debugger/ramview.hxx
|
||||
debugger/registers.hxx
|
||||
util/spinbox.hxx
|
||||
bootmanager.hxx
|
||||
hotkeys.hxx
|
||||
main.hxx
|
||||
|
303
src/citra_qt/util/spinbox.cpp
Normal file
303
src/citra_qt/util/spinbox.cpp
Normal file
@ -0,0 +1,303 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
||||
// Copyright 2014 Tony Wasserka
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the owner nor the names of its contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QRegExpValidator>
|
||||
|
||||
#include "common/log.h"
|
||||
|
||||
#include "spinbox.hxx"
|
||||
|
||||
CSpinBox::CSpinBox(QWidget* parent) : QAbstractSpinBox(parent), base(10), min_value(-100), max_value(100), value(0), num_digits(0)
|
||||
{
|
||||
// TODO: Might be nice to not immediately call the slot.
|
||||
// Think of an address that is being replaced by a different one, in which case a lot
|
||||
// invalid intermediate addresses would be read from during editing.
|
||||
connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(OnEditingFinished()));
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void CSpinBox::SetValue(qint64 val)
|
||||
{
|
||||
auto old_value = value;
|
||||
value = std::max(std::min(val, max_value), min_value);
|
||||
|
||||
if (old_value != value) {
|
||||
UpdateText();
|
||||
emit ValueChanged(value);
|
||||
}
|
||||
}
|
||||
|
||||
void CSpinBox::SetRange(qint64 min, qint64 max)
|
||||
{
|
||||
min_value = min;
|
||||
max_value = max;
|
||||
|
||||
SetValue(value);
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void CSpinBox::stepBy(int steps)
|
||||
{
|
||||
auto new_value = value;
|
||||
// Scale number of steps by the currently selected digit
|
||||
// TODO: Move this code elsewhere and enable it.
|
||||
// TODO: Support for num_digits==0, too
|
||||
// TODO: Support base!=16, too
|
||||
// TODO: Make the cursor not jump back to the end of the line...
|
||||
/*if (base == 16 && num_digits > 0) {
|
||||
int digit = num_digits - (lineEdit()->cursorPosition() - prefix.length()) - 1;
|
||||
digit = std::max(0, std::min(digit, num_digits - 1));
|
||||
steps <<= digit * 4;
|
||||
}*/
|
||||
|
||||
// Increment "new_value" by "steps", and perform annoying overflow checks, too.
|
||||
if (steps < 0 && new_value + steps > new_value) {
|
||||
new_value = std::numeric_limits<qint64>::min();
|
||||
} else if (steps > 0 && new_value + steps < new_value) {
|
||||
new_value = std::numeric_limits<qint64>::max();
|
||||
} else {
|
||||
new_value += steps;
|
||||
}
|
||||
|
||||
SetValue(new_value);
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const
|
||||
{
|
||||
StepEnabled ret = StepNone;
|
||||
|
||||
if (value > min_value)
|
||||
ret |= StepDownEnabled;
|
||||
|
||||
if (value < max_value)
|
||||
ret |= StepUpEnabled;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CSpinBox::SetBase(int base)
|
||||
{
|
||||
this->base = base;
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void CSpinBox::SetNumDigits(int num_digits)
|
||||
{
|
||||
this->num_digits = num_digits;
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void CSpinBox::SetPrefix(const QString& prefix)
|
||||
{
|
||||
this->prefix = prefix;
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
void CSpinBox::SetSuffix(const QString& suffix)
|
||||
{
|
||||
this->suffix = suffix;
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
static QString StringToInputMask(const QString& input) {
|
||||
QString mask = input;
|
||||
|
||||
// ... replace any special characters by their escaped counterparts ...
|
||||
mask.replace("\\", "\\\\");
|
||||
mask.replace("A", "\\A");
|
||||
mask.replace("a", "\\a");
|
||||
mask.replace("N", "\\N");
|
||||
mask.replace("n", "\\n");
|
||||
mask.replace("X", "\\X");
|
||||
mask.replace("x", "\\x");
|
||||
mask.replace("9", "\\9");
|
||||
mask.replace("0", "\\0");
|
||||
mask.replace("D", "\\D");
|
||||
mask.replace("d", "\\d");
|
||||
mask.replace("#", "\\#");
|
||||
mask.replace("H", "\\H");
|
||||
mask.replace("h", "\\h");
|
||||
mask.replace("B", "\\B");
|
||||
mask.replace("b", "\\b");
|
||||
mask.replace(">", "\\>");
|
||||
mask.replace("<", "\\<");
|
||||
mask.replace("!", "\\!");
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
void CSpinBox::UpdateText()
|
||||
{
|
||||
// If a fixed number of digits is used, we put the line edit in insertion mode by setting an
|
||||
// input mask.
|
||||
QString mask;
|
||||
if (num_digits != 0) {
|
||||
mask += StringToInputMask(prefix);
|
||||
|
||||
// For base 10 and negative range, demand a single sign character
|
||||
if (HasSign())
|
||||
mask += "X"; // identified as "-" or "+" in the validator
|
||||
|
||||
// Uppercase digits greater than 9.
|
||||
mask += ">";
|
||||
|
||||
// The greatest signed 64-bit number has 19 decimal digits.
|
||||
// TODO: Could probably make this more generic with some logarithms.
|
||||
// For reference, unsigned 64-bit can have up to 20 decimal digits.
|
||||
int digits = (num_digits != 0) ? num_digits
|
||||
: (base == 16) ? 16
|
||||
: (base == 10) ? 19
|
||||
: 0xFF; // fallback case...
|
||||
|
||||
// Match num_digits digits
|
||||
// Digits irrelevant to the chosen number base are filtered in the validator
|
||||
mask += QString("H").repeated(std::max(num_digits, 1));
|
||||
|
||||
// Switch off case conversion
|
||||
mask += "!";
|
||||
|
||||
mask += StringToInputMask(suffix);
|
||||
}
|
||||
lineEdit()->setInputMask(mask);
|
||||
|
||||
// Set new text without changing the cursor position. This will cause the cursor to briefly
|
||||
// appear at the end of the line and then to jump back to its original position. That's
|
||||
// a bit ugly, but better than having setText() move the cursor permanently all the time.
|
||||
int cursor_position = lineEdit()->cursorPosition();
|
||||
lineEdit()->setText(TextFromValue());
|
||||
lineEdit()->setCursorPosition(cursor_position);
|
||||
}
|
||||
|
||||
QString CSpinBox::TextFromValue()
|
||||
{
|
||||
return prefix
|
||||
+ QString(HasSign() ? ((value < 0) ? "-" : "+") : "")
|
||||
+ QString("%1").arg(abs(value), num_digits, base, QLatin1Char('0')).toUpper()
|
||||
+ suffix;
|
||||
}
|
||||
|
||||
qint64 CSpinBox::ValueFromText()
|
||||
{
|
||||
unsigned strpos = prefix.length();
|
||||
|
||||
QString num_string = text().mid(strpos, text().length() - strpos - suffix.length());
|
||||
return num_string.toLongLong(nullptr, base);
|
||||
}
|
||||
|
||||
bool CSpinBox::HasSign() const
|
||||
{
|
||||
return base == 10 && min_value < 0;
|
||||
}
|
||||
|
||||
void CSpinBox::OnEditingFinished()
|
||||
{
|
||||
// Only update for valid input
|
||||
QString input = lineEdit()->text();
|
||||
int pos = 0;
|
||||
if (QValidator::Acceptable == validate(input, pos))
|
||||
SetValue(ValueFromText());
|
||||
}
|
||||
|
||||
QValidator::State CSpinBox::validate(QString& input, int& pos) const
|
||||
{
|
||||
if (!prefix.isEmpty() && input.left(prefix.length()) != prefix)
|
||||
return QValidator::Invalid;
|
||||
|
||||
unsigned strpos = prefix.length();
|
||||
|
||||
// Empty "numbers" allowed as intermediate values
|
||||
if (strpos >= input.length() - HasSign() - suffix.length())
|
||||
return QValidator::Intermediate;
|
||||
|
||||
_dbg_assert_(GUI, base <= 10 || base == 16);
|
||||
QString regexp;
|
||||
|
||||
// Demand sign character for negative ranges
|
||||
if (HasSign())
|
||||
regexp += "[+\\-]";
|
||||
|
||||
// Match digits corresponding to the chosen number base.
|
||||
regexp += QString("[0-%1").arg(std::min(base, 9));
|
||||
if (base == 16) {
|
||||
regexp += "a-fA-F";
|
||||
}
|
||||
regexp += "]";
|
||||
|
||||
// Specify number of digits
|
||||
if (num_digits > 0) {
|
||||
regexp += QString("{%1}").arg(num_digits);
|
||||
} else {
|
||||
regexp += "+";
|
||||
}
|
||||
|
||||
// Match string
|
||||
QRegExp num_regexp(regexp);
|
||||
int num_pos = strpos;
|
||||
QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length());
|
||||
|
||||
if (!num_regexp.exactMatch(sub_input) && num_regexp.matchedLength() == 0)
|
||||
return QValidator::Invalid;
|
||||
|
||||
sub_input = sub_input.left(num_regexp.matchedLength());
|
||||
bool ok;
|
||||
qint64 val = sub_input.toLongLong(&ok, base);
|
||||
|
||||
if (!ok)
|
||||
return QValidator::Invalid;
|
||||
|
||||
// Outside boundaries => don't accept
|
||||
if (val < min_value || val > max_value)
|
||||
return QValidator::Invalid;
|
||||
|
||||
// Make sure we are actually at the end of this string...
|
||||
strpos += num_regexp.matchedLength();
|
||||
|
||||
if (!suffix.isEmpty() && input.mid(strpos) != suffix) {
|
||||
return QValidator::Invalid;
|
||||
} else {
|
||||
strpos += suffix.length();
|
||||
}
|
||||
|
||||
if (strpos != input.length())
|
||||
return QValidator::Invalid;
|
||||
|
||||
// At this point we can say for sure that the input is fine. Let's fix it up a bit though
|
||||
input.replace(num_pos, sub_input.length(), sub_input.toUpper());
|
||||
|
||||
return QValidator::Acceptable;
|
||||
}
|
88
src/citra_qt/util/spinbox.hxx
Normal file
88
src/citra_qt/util/spinbox.hxx
Normal file
@ -0,0 +1,88 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
||||
// Copyright 2014 Tony Wasserka
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the owner nor the names of its contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractSpinBox>
|
||||
#include <QtGlobal>
|
||||
|
||||
class QVariant;
|
||||
|
||||
/**
|
||||
* A custom spin box widget with enhanced functionality over Qt's QSpinBox
|
||||
*/
|
||||
class CSpinBox : public QAbstractSpinBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CSpinBox(QWidget* parent = nullptr);
|
||||
|
||||
void stepBy(int steps) override;
|
||||
StepEnabled stepEnabled() const override;
|
||||
|
||||
void SetValue(qint64 val);
|
||||
|
||||
void SetRange(qint64 min, qint64 max);
|
||||
|
||||
void SetBase(int base);
|
||||
|
||||
void SetPrefix(const QString& prefix);
|
||||
void SetSuffix(const QString& suffix);
|
||||
|
||||
void SetNumDigits(int num_digits);
|
||||
|
||||
QValidator::State validate(QString& input, int& pos) const override;
|
||||
|
||||
signals:
|
||||
void ValueChanged(qint64 val);
|
||||
|
||||
private slots:
|
||||
void OnEditingFinished();
|
||||
|
||||
private:
|
||||
void UpdateText();
|
||||
|
||||
bool HasSign() const;
|
||||
|
||||
QString TextFromValue();
|
||||
qint64 ValueFromText();
|
||||
|
||||
qint64 min_value, max_value;
|
||||
|
||||
qint64 value;
|
||||
|
||||
QString prefix, suffix;
|
||||
|
||||
int base;
|
||||
|
||||
int num_digits;
|
||||
};
|
Loading…
Reference in New Issue
Block a user