QSpinBox with Unsigned Int for Hex Input

There are many questions in QSpinBox related to restricting the use of int as a data type. Often people want to display large numbers. In my case, I want to show a 32-digit unsigned integer in hexadecimal format. This means my range will be [0x0, 0xFFFFFFFF]. The largest regular QSpinBox can go 0x7FFFFFFF. Answering my own question, the solution I came up with is to simply force int to be treated as unsigned int, overriding the appropriate display and validation functions.

+7
source share
5 answers

The result is quite simple and it works well. Exchange here if someone can take advantage of this. It has 32-bit mode and 16-bit mode.

Example of HexSpinBox

class HexSpinBox : public QSpinBox { public: HexSpinBox(bool only16Bits, QWidget *parent = 0) : QSpinBox(parent), m_only16Bits(only16Bits) { setPrefix("0x"); setDisplayIntegerBase(16); if (only16Bits) setRange(0, 0xFFFF); else setRange(INT_MIN, INT_MAX); } unsigned int hexValue() const { return u(value()); } void setHexValue(unsigned int value) { setValue(i(value)); } protected: QString textFromValue(int value) const { return QString::number(u(value), 16).toUpper(); } int valueFromText(const QString &text) const { return i(text.toUInt(0, 16)); } QValidator::State validate(QString &input, int &pos) const { QString copy(input); if (copy.startsWith("0x")) copy.remove(0, 2); pos -= copy.size() - copy.trimmed().size(); copy = copy.trimmed(); if (copy.isEmpty()) return QValidator::Intermediate; input = QString("0x") + copy.toUpper(); bool okay; unsigned int val = copy.toUInt(&okay, 16); if (!okay || (m_only16Bits && val > 0xFFFF)) return QValidator::Invalid; return QValidator::Acceptable; } private: bool m_only16Bits; inline unsigned int u(int i) const { return *reinterpret_cast<unsigned int *>(&i); } inline int i(unsigned int u) const { return *reinterpret_cast<int *>(&u); } }; 
+7
source

If you don't need the full 32 bits, you can do it very simply:

 #pragma once #include <QSpinBox> class PaddedSpinBox : public QSpinBox { public: PaddedSpinBox(QWidget *parent = 0) : QSpinBox(parent) { } protected: QString textFromValue(int value) const override { // Pad to the width of maximum(). int width = QString::number(maximum(), displayIntegerBase()).size(); return QString("%1").arg(value, width, displayIntegerBase(), QChar('0')).toUpper(); } }; 

In the form designer (or something else) you simply set:

  • prefix : 0x
  • displayIntegerBase : 16
  • maximum : 255 (or something else)

If you need the full 32 bits, you will have to use casting tricks or maybe just use line editing.

+2
source

I ran into the same problem, but using PyQt, so I could not avoid the range check that Qt did in C under the hood.

The workaround was to use QDoulbeSpinbox and pass the int value to textFromValue.

Here is my code (it also implements a right-click menu to change the display base):

 from __future__ import division from __future__ import print_function from __future__ import unicode_literals from future_builtins import * import re import sys from PyQt4.QtCore import (QRegExp, Qt) from PyQt4.QtGui import (QApplication, QRegExpValidator, QDoubleSpinBox) from PyQt4.QtCore import pyqtSlot,SIGNAL,SLOT from PyQt4 import QtCore, QtGui # Regex adapted from Mark Pilgrim "Dive Into Python" book class QHexSpinBox(QDoubleSpinBox): def __init__(self, parent=None): super(QHexSpinBox, self).__init__(parent) self.mode = 'dec' self.setContextMenuPolicy(Qt.CustomContextMenu); regex = QRegExp("[x0-9A-Fa-f]{1,8}") regex.setCaseSensitivity(Qt.CaseInsensitive) self.hexvalidator = QRegExpValidator(regex, self) regex = QRegExp("[0-9]{1,10}") regex.setCaseSensitivity(Qt.CaseInsensitive) self.decvalidator = QRegExpValidator(regex, self) regex = QRegExp("[b0-1]{1,64}") regex.setCaseSensitivity(Qt.CaseInsensitive) self.binvalidator = QRegExpValidator(regex, self) self.setRange(1, 999999) self.connect(self,SIGNAL("customContextMenuRequested(QPoint)"), self,SLOT("contextMenuRequested(QPoint)")) @pyqtSlot(QtCore.QPoint) def contextMenuRequested(self,point): menu = QtGui.QMenu() hex = menu.addAction("Hex") dec = menu.addAction("Dec") bin = menu.addAction("Bin") self.connect(hex,SIGNAL("triggered()"), self,SLOT("hex()")) self.connect(dec,SIGNAL("triggered()"), self,SLOT("dec()")) self.connect(bin,SIGNAL("triggered()"), self,SLOT("bin()")) menu.exec_(self.mapToGlobal(point)) @pyqtSlot() def hex(self): self.mode = 'hex' self.setValue(self.value()) @pyqtSlot() def dec(self): self.mode = 'dec' self.setValue(self.value()) @pyqtSlot() def bin(self): self.mode = 'bin' self.setValue(self.value()) def validate(self, text, pos): if self.mode == 'hex': return self.hexvalidator.validate(text, pos) if self.mode == 'dec': return self.decvalidator.validate(text, pos) if self.mode == 'bin': return self.binvalidator.validate(text, pos) def valueFromText(self, text): if self.mode == 'hex': return int(unicode(text), 16) elif self.mode == 'dec': return int(unicode(text)) elif self.mode == 'bin': return int(unicode(text), 2) def textFromValue(self, value): value = int(value) if self.mode == 'hex': return hex(value) elif self.mode == 'dec': return str(value) elif self.mode =='bin': return "0b{0:b}".format(value) 
+1
source

I know this is an old answer, but came from Google. Here is my solution with pyside 1.2.4, based to some extent on the Techniquab solution, but not having problems with integer overflow:

 from PySide import QtCore, QtGui from numpy import base_repr from PySide.QtGui import QRegExpValidator class QBaseSpinBox(QtGui.QAbstractSpinBox): valueChanged = QtCore.Signal(int) _value = 0 default_value = 0 base = 10 def __init__(self, parent=None): self.setRange(None, None) QtGui.QAbstractSpinBox.__init__(self, parent) self.set_base(self.base) self.lineEdit().setValidator(QRegExpValidator(self)) self.default_value = self.value() self.lineEdit().textChanged.connect(self.textChanged) self.lineEdit().setContextMenuPolicy(QtCore.Qt.CustomContextMenu); self.lineEdit().customContextMenuRequested.connect(self.contextMenuRequested) @QtCore.Slot() def contextMenuRequested(self, point): menu = self.lineEdit().createStandardContextMenu() #QtGui.QMenu() actionDefault = menu.addAction("&Set Default Value of %s" % self.textFromValue(self.default_value), shortcut=QtCore.Qt.CTRL | QtCore.Qt.Key_D) #QtGui.QKeySequence("Ctrl+D"))) menu.insertSeparator(actionDefault) actionDefault.triggered.connect(self.menuActionDefault_triggered) menu.exec_(self.mapToGlobal(point)) @QtCore.Slot() def menuActionDefault_triggered(self): self.setValue(self.default_value) def value(self): return self._value def setValue(self, value): if self.validate(value) == QtGui.QValidator.Invalid: self.setValue(self._value) return changed = False if self._value != value: changed = True self._value = value self.lineEdit().setText(self.textFromValue(value)) if changed: self.valueChanged.emit(self._value) @QtCore.Slot() def stepBy(self, value): self.setValue(self._value + value) QtGui.QAbstractSpinBox.stepBy(self, self._value) def stepEnabled(self): return QtGui.QAbstractSpinBox.StepDownEnabled | QtGui.QAbstractSpinBox.StepUpEnabled @QtCore.Slot() def textChanged(self, text): try: self.setValue(int(text, self.base)) except: self.setValue(self._value) def setRange(self, _min, _max): self.minimum = _min if _min != None else 0 self.maximum = _max if _max != None else 0xFFFFFFFFFFFFFFFF def validate(self, input): if not input: return QtGui.QValidator.Intermediate try: try: value = int(input, self.base) except TypeError: value = input if not (self.minimum <= input <= self.maximum): raise Exception() except Exception as ex: return QtGui.QValidator.Invalid return QtGui.QValidator.Acceptable def valueFromText(self, text): return int(text, self.base) def textFromValue(self, value): return base_repr(value, self.base).upper() def set_default_value(self, value): self.default_value = int(value) #self.setValue(self.default_value) self.set_base(self.base) # Redo the tooltip def set_base(self, base): self.base = base min = self.textFromValue(self.minimum) max = self.textFromValue(self.maximum) default = self.textFromValue(self.default_value) self.lineEdit().setToolTip("Base %d\nRange: %s-%s\nDefault Value: %s" % (self.base, min, max, default)) 
+1
source

Thanks @ ZX2C4 for the answer. I modified the HexSpinBox class a bit:

  1. You can set the prefix.
  2. you can set the maximum range (in case of INT_MAX < maxRange < UINT_MAX there are errors).
  3. You can disable the filling of fields 0 .
  4. the width of the margins is automatically calculated.

hexspinbox.h

 #ifndef HEXSPINBOX_H #define HEXSPINBOX_H #include <QSpinBox> class HexSpinBox : public QSpinBox { Q_OBJECT public: HexSpinBox(QWidget *parent = nullptr); unsigned int hexValue() const { return u(value()); } void setHexValue(unsigned int value) { setValue(i(value)); } void setRange(unsigned int max); bool fillField() const { return m_fillField; } void setFillField(bool fillFieldWidth) { m_fillField = fillFieldWidth; } protected: QString textFromValue(int value) const; int valueFromText(const QString &text) const; QValidator::State validate(QString &input, int &pos) const; private: unsigned int m_maxRange = UINT_MAX; bool m_fillField = true; inline unsigned int u(int i) const { return *reinterpret_cast<unsigned int *>(&i); } inline int i(unsigned int u) const { return *reinterpret_cast<int *>(&u); } }; #endif // HEXSPINBOX_H 

hexspinbox.cpp

 #include "hexspinbox.h" HexSpinBox::HexSpinBox(QWidget *parent) : QSpinBox(parent), m_maxRange(maximum()) { setDisplayIntegerBase(16); } void HexSpinBox::setRange(unsigned int max) { m_maxRange = max; if (m_maxRange <= INT_MAX) { QSpinBox::setRange(0, int(m_maxRange)); } else { QSpinBox::setRange(INT_MIN, INT_MAX); } } QString HexSpinBox::textFromValue(int value) const { int fillField = 0; if (m_fillField) { uint m = m_maxRange; while (m) { m >>= 4; ++fillField; } } return QString("%1").arg(u(value), fillField, 16, QLatin1Char('0')).toUpper(); } int HexSpinBox::valueFromText(const QString &text) const { return i(text.toUInt(nullptr, 16)); } QValidator::State HexSpinBox::validate(QString &input, int &pos) const { QString copy(input); QString pref = prefix(); if (copy.startsWith(pref)) copy.remove(pref); pos -= copy.size() - copy.trimmed().size(); copy = copy.trimmed(); if (copy.isEmpty()) return QValidator::Intermediate; input = pref + copy.toUpper(); bool okay; unsigned int val = copy.toUInt(&okay, 16); if (!okay || val > m_maxRange) return QValidator::Invalid; return QValidator::Acceptable; } 

You can use the class for the range [0x0, 0xFFFFFFFF]:

 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); ui->hexspinbox->setRange(UINT_MAX); // or 0xFF =) ui->hexspinbox->setPrefix("0x"); } 
0
source

Source: https://habr.com/ru/post/977261/


All Articles