| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- // Licensed under GPLv2 or any later version
- // 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 <cstdlib>
- #include <QLineEdit>
- #include <QRegExpValidator>
- #include "citra_qt/util/spinbox.h"
- #include "common/assert.h"
- CSpinBox::CSpinBox(QWidget* parent)
- : QAbstractSpinBox(parent), min_value(-100), max_value(100), value(0), base(10), 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 += ">";
- // 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(std::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;
- int strpos = prefix.length();
- // Empty "numbers" allowed as intermediate values
- if (strpos >= input.length() - HasSign() - suffix.length())
- return QValidator::Intermediate;
- DEBUG_ASSERT(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;
- }
|