Explorar el Código

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.
Tony Wasserka hace 11 años
padre
commit
bf6b23f4a0
Se han modificado 3 ficheros con 393 adiciones y 0 borrados
  1. 2 0
      src/citra_qt/CMakeLists.txt
  2. 303 0
      src/citra_qt/util/spinbox.cpp
  3. 88 0
      src/citra_qt/util/spinbox.hxx

+ 2 - 0
src/citra_qt/CMakeLists.txt

@@ -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 - 0
src/citra_qt/util/spinbox.cpp

@@ -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 - 0
src/citra_qt/util/spinbox.hxx

@@ -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;
+};