summaryrefslogtreecommitdiff
path: root/src/citra_qt/util/spinbox.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/citra_qt/util/spinbox.cpp')
-rw-r--r--src/citra_qt/util/spinbox.cpp303
1 files changed, 303 insertions, 0 deletions
diff --git a/src/citra_qt/util/spinbox.cpp b/src/citra_qt/util/spinbox.cpp
new file mode 100644
index 000000000..f9988409f
--- /dev/null
+++ b/src/citra_qt/util/spinbox.cpp
@@ -0,0 +1,303 @@
1// Licensed under GPLv2 or any later version
2// Refer to the license.txt file included.
3
4
5// Copyright 2014 Tony Wasserka
6// All rights reserved.
7//
8// Redistribution and use in source and binary forms, with or without
9// modification, are permitted provided that the following conditions are met:
10//
11// * Redistributions of source code must retain the above copyright
12// notice, this list of conditions and the following disclaimer.
13// * Redistributions in binary form must reproduce the above copyright
14// notice, this list of conditions and the following disclaimer in the
15// documentation and/or other materials provided with the distribution.
16// * Neither the name of the owner nor the names of its contributors may
17// be used to endorse or promote products derived from this software
18// without specific prior written permission.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32#include <QLineEdit>
33#include <QRegExpValidator>
34
35#include "common/log.h"
36
37#include "spinbox.hxx"
38
39CSpinBox::CSpinBox(QWidget* parent) : QAbstractSpinBox(parent), base(10), min_value(-100), max_value(100), value(0), num_digits(0)
40{
41 // TODO: Might be nice to not immediately call the slot.
42 // Think of an address that is being replaced by a different one, in which case a lot
43 // invalid intermediate addresses would be read from during editing.
44 connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(OnEditingFinished()));
45
46 UpdateText();
47}
48
49void CSpinBox::SetValue(qint64 val)
50{
51 auto old_value = value;
52 value = std::max(std::min(val, max_value), min_value);
53
54 if (old_value != value) {
55 UpdateText();
56 emit ValueChanged(value);
57 }
58}
59
60void CSpinBox::SetRange(qint64 min, qint64 max)
61{
62 min_value = min;
63 max_value = max;
64
65 SetValue(value);
66 UpdateText();
67}
68
69void CSpinBox::stepBy(int steps)
70{
71 auto new_value = value;
72 // Scale number of steps by the currently selected digit
73 // TODO: Move this code elsewhere and enable it.
74 // TODO: Support for num_digits==0, too
75 // TODO: Support base!=16, too
76 // TODO: Make the cursor not jump back to the end of the line...
77 /*if (base == 16 && num_digits > 0) {
78 int digit = num_digits - (lineEdit()->cursorPosition() - prefix.length()) - 1;
79 digit = std::max(0, std::min(digit, num_digits - 1));
80 steps <<= digit * 4;
81 }*/
82
83 // Increment "new_value" by "steps", and perform annoying overflow checks, too.
84 if (steps < 0 && new_value + steps > new_value) {
85 new_value = std::numeric_limits<qint64>::min();
86 } else if (steps > 0 && new_value + steps < new_value) {
87 new_value = std::numeric_limits<qint64>::max();
88 } else {
89 new_value += steps;
90 }
91
92 SetValue(new_value);
93 UpdateText();
94}
95
96QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const
97{
98 StepEnabled ret = StepNone;
99
100 if (value > min_value)
101 ret |= StepDownEnabled;
102
103 if (value < max_value)
104 ret |= StepUpEnabled;
105
106 return ret;
107}
108
109void CSpinBox::SetBase(int base)
110{
111 this->base = base;
112
113 UpdateText();
114}
115
116void CSpinBox::SetNumDigits(int num_digits)
117{
118 this->num_digits = num_digits;
119
120 UpdateText();
121}
122
123void CSpinBox::SetPrefix(const QString& prefix)
124{
125 this->prefix = prefix;
126
127 UpdateText();
128}
129
130void CSpinBox::SetSuffix(const QString& suffix)
131{
132 this->suffix = suffix;
133
134 UpdateText();
135}
136
137static QString StringToInputMask(const QString& input) {
138 QString mask = input;
139
140 // ... replace any special characters by their escaped counterparts ...
141 mask.replace("\\", "\\\\");
142 mask.replace("A", "\\A");
143 mask.replace("a", "\\a");
144 mask.replace("N", "\\N");
145 mask.replace("n", "\\n");
146 mask.replace("X", "\\X");
147 mask.replace("x", "\\x");
148 mask.replace("9", "\\9");
149 mask.replace("0", "\\0");
150 mask.replace("D", "\\D");
151 mask.replace("d", "\\d");
152 mask.replace("#", "\\#");
153 mask.replace("H", "\\H");
154 mask.replace("h", "\\h");
155 mask.replace("B", "\\B");
156 mask.replace("b", "\\b");
157 mask.replace(">", "\\>");
158 mask.replace("<", "\\<");
159 mask.replace("!", "\\!");
160
161 return mask;
162}
163
164void CSpinBox::UpdateText()
165{
166 // If a fixed number of digits is used, we put the line edit in insertion mode by setting an
167 // input mask.
168 QString mask;
169 if (num_digits != 0) {
170 mask += StringToInputMask(prefix);
171
172 // For base 10 and negative range, demand a single sign character
173 if (HasSign())
174 mask += "X"; // identified as "-" or "+" in the validator
175
176 // Uppercase digits greater than 9.
177 mask += ">";
178
179 // The greatest signed 64-bit number has 19 decimal digits.
180 // TODO: Could probably make this more generic with some logarithms.
181 // For reference, unsigned 64-bit can have up to 20 decimal digits.
182 int digits = (num_digits != 0) ? num_digits
183 : (base == 16) ? 16
184 : (base == 10) ? 19
185 : 0xFF; // fallback case...
186
187 // Match num_digits digits
188 // Digits irrelevant to the chosen number base are filtered in the validator
189 mask += QString("H").repeated(std::max(num_digits, 1));
190
191 // Switch off case conversion
192 mask += "!";
193
194 mask += StringToInputMask(suffix);
195 }
196 lineEdit()->setInputMask(mask);
197
198 // Set new text without changing the cursor position. This will cause the cursor to briefly
199 // appear at the end of the line and then to jump back to its original position. That's
200 // a bit ugly, but better than having setText() move the cursor permanently all the time.
201 int cursor_position = lineEdit()->cursorPosition();
202 lineEdit()->setText(TextFromValue());
203 lineEdit()->setCursorPosition(cursor_position);
204}
205
206QString CSpinBox::TextFromValue()
207{
208 return prefix
209 + QString(HasSign() ? ((value < 0) ? "-" : "+") : "")
210 + QString("%1").arg(abs(value), num_digits, base, QLatin1Char('0')).toUpper()
211 + suffix;
212}
213
214qint64 CSpinBox::ValueFromText()
215{
216 unsigned strpos = prefix.length();
217
218 QString num_string = text().mid(strpos, text().length() - strpos - suffix.length());
219 return num_string.toLongLong(nullptr, base);
220}
221
222bool CSpinBox::HasSign() const
223{
224 return base == 10 && min_value < 0;
225}
226
227void CSpinBox::OnEditingFinished()
228{
229 // Only update for valid input
230 QString input = lineEdit()->text();
231 int pos = 0;
232 if (QValidator::Acceptable == validate(input, pos))
233 SetValue(ValueFromText());
234}
235
236QValidator::State CSpinBox::validate(QString& input, int& pos) const
237{
238 if (!prefix.isEmpty() && input.left(prefix.length()) != prefix)
239 return QValidator::Invalid;
240
241 int strpos = prefix.length();
242
243 // Empty "numbers" allowed as intermediate values
244 if (strpos >= input.length() - HasSign() - suffix.length())
245 return QValidator::Intermediate;
246
247 _dbg_assert_(Frontend, base <= 10 || base == 16);
248 QString regexp;
249
250 // Demand sign character for negative ranges
251 if (HasSign())
252 regexp += "[+\\-]";
253
254 // Match digits corresponding to the chosen number base.
255 regexp += QString("[0-%1").arg(std::min(base, 9));
256 if (base == 16) {
257 regexp += "a-fA-F";
258 }
259 regexp += "]";
260
261 // Specify number of digits
262 if (num_digits > 0) {
263 regexp += QString("{%1}").arg(num_digits);
264 } else {
265 regexp += "+";
266 }
267
268 // Match string
269 QRegExp num_regexp(regexp);
270 int num_pos = strpos;
271 QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length());
272
273 if (!num_regexp.exactMatch(sub_input) && num_regexp.matchedLength() == 0)
274 return QValidator::Invalid;
275
276 sub_input = sub_input.left(num_regexp.matchedLength());
277 bool ok;
278 qint64 val = sub_input.toLongLong(&ok, base);
279
280 if (!ok)
281 return QValidator::Invalid;
282
283 // Outside boundaries => don't accept
284 if (val < min_value || val > max_value)
285 return QValidator::Invalid;
286
287 // Make sure we are actually at the end of this string...
288 strpos += num_regexp.matchedLength();
289
290 if (!suffix.isEmpty() && input.mid(strpos) != suffix) {
291 return QValidator::Invalid;
292 } else {
293 strpos += suffix.length();
294 }
295
296 if (strpos != input.length())
297 return QValidator::Invalid;
298
299 // At this point we can say for sure that the input is fine. Let's fix it up a bit though
300 input.replace(num_pos, sub_input.length(), sub_input.toUpper());
301
302 return QValidator::Acceptable;
303}