Reputation: 90523
I have a Qt widget which should only accept a hex string as input. It is very simple to restrict the input characters to [0-9A-Fa-f]
, but I would like to have it display with a delimiter between "bytes" so for example if the delimiter is a space, and the user types 0011223344
I would like the line edit to display 00 11 22 33 44
Now if the user presses the backspace key 3 times, then I want it to display 00 11 22 3
.
I almost have what i want, so far there is only one subtle bug involving using the delete key to remove a delimiter. Does anyone have a better way to implement this validator? Here's my code so far:
class HexStringValidator : public QValidator {
public:
HexStringValidator(QObject * parent) : QValidator(parent) {}
public:
virtual void fixup(QString &input) const {
QString temp;
int index = 0;
// every 2 digits insert a space if they didn't explicitly type one
Q_FOREACH(QChar ch, input) {
if(std::isxdigit(ch.toAscii())) {
if(index != 0 && (index & 1) == 0) {
temp += ' ';
}
temp += ch.toUpper();
++index;
}
}
input = temp;
}
virtual State validate(QString &input, int &pos) const {
if(!input.isEmpty()) {
// TODO: can we detect if the char which was JUST deleted
// (if any was deleted) was a space? and special case this?
// as to not have the bug in this case?
const int char_pos = pos - input.left(pos).count(' ');
int chars = 0;
fixup(input);
pos = 0;
while(chars != char_pos) {
if(input[pos] != ' ') {
++chars;
}
++pos;
}
// favor the right side of a space
if(input[pos] == ' ') {
++pos;
}
}
return QValidator::Acceptable;
}
};
For now this code is functional enough, but I'd love to have it work 100% as expected. Obviously the ideal would be the just separate the display of the hex string from the actual characters stored in the QLineEdit
's internal buffer but I have no idea where to start with that and I imagine is a non-trivial undertaking.
In essence, I would like to have a Validator which conforms to this regex: "[0-9A-Fa-f]( [0-9A-Fa-f])*"
but I don't want the user to ever have to type a space as delimiter. Likewise, when editing what they types, the spaces should be managed implicitly.
Upvotes: 9
Views: 12579
Reputation: 146
I had the same requirement of separating 2 hex nibbles with a space and a Google search landed me here. As suggested by Lohrun, I attempted paintEvent based approach initially and it worked as expected for keyboard inputs. However, I could not handle the mouse clicks properly. Next, I tried his first suggestion of re-implementing the keyPressEvent
along with mousePressEvent
and I could achieve the requirement with both keyboard and mouse.
My implementation QHexTextEdit.cpp
#include "QHexTextEdit.h"
QHexTextEdit::QHexTextEdit(QWidget *parent) : QTextEdit(parent)
{
setLineWrapMode(QTextEdit::NoWrap); // Disable word wrap
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Disable vertical scrollbar
mText = "";
mCurserPositionPre = 0;
}
void QHexTextEdit::keyPressEvent(QKeyEvent *event)
{
int pressedKey = event->key();
QTextCursor cursor = textCursor();
int cursorPosition = cursor.position();
int newCursorPosition = cursorPosition;
int index = cursorPosition - int(cursorPosition/3);
if(((pressedKey>=Qt::Key_0) && (pressedKey<=Qt::Key_9)) || ((pressedKey>=Qt::Key_A) && (pressedKey<=Qt::Key_F)) )
{
mText.insert(index, event->text());
newCursorPosition = cursorPosition + (cursorPosition % 3 + 1);
}
else if (pressedKey == Qt::Key_Backspace)
{
if(index!=0)
{
mText.remove(index-1,1);
newCursorPosition = cursorPosition -2 + (cursorPosition)%3;
}
}
else if (pressedKey == Qt::Key_Delete)
{
if(index!=mText.length())
{
mText.remove(index,1);
newCursorPosition = cursorPosition;
}
}
else if (pressedKey == Qt::Key_Left)
{
if(index!=0)
{
newCursorPosition = cursorPosition -2 + (cursorPosition)%3;
}
}
else if (pressedKey == Qt::Key_Right)
{
if(index!=mText.length())
{
newCursorPosition = cursorPosition + (cursorPosition+1)%3;
}
}
// Allow only single-line editing
else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
{
event->ignore(); // Ignore Enter key press to prevent new lines
}
QString temp;
for(int i=0; i<mText.length(); i++)
{
temp.append(mText.at(i).toUpper());
if(i%2==1)
temp.append(" ");
}
setText(temp);
cursor.setPosition(newCursorPosition);
setTextCursor(cursor);
mCurserPositionPre = newCursorPosition;
}
void QHexTextEdit::mousePressEvent(QMouseEvent *event)
{
QTextCursor cursor = cursorForPosition(event->pos());
int cursorPosition = cursor.position();
int newCursorPosition = cursorPosition;
if(cursorPosition%3==2)
{
newCursorPosition = cursorPosition + 1;
cursor.setPosition(newCursorPosition);
mCurserPositionPre = newCursorPosition;
}
QTextEdit::mousePressEvent(event);
setTextCursor(cursor);
}
QHexTextEdit::~QHexTextEdit()
{
}
QHexTextEdit.h
#ifndef Q_HEXTEXTEDIT_H
#define Q_HEXTEXTEDIT_H
#include <QTextEdit>
#include <QPainter>
#include <QStyleOptionFrame>
#include <QTextCursor>
#include <QTextLayout>
class QHexTextEdit : public QTextEdit
{
Q_OBJECT
public:
QHexTextEdit(QWidget *parent = nullptr);
~QHexTextEdit() override;
protected:
void keyPressEvent(QKeyEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
private:
QString mText;
int mCurserPositionPre;
};
#endif
Upvotes: 0
Reputation: 105
Robin's solution is good and works. But I think you can do best!
Use this for input mask:
ui->lineEdit->setInputMask("HH-HH-HH-HH");
and in the ui, R-click on lineEdit -> Go to Slots... -> textChanged. In the slot function write this code:
int c = ui->lineEdit->cursorPosition();
ui->lineEdit->setText(arg1.toUpper());
ui->lineEdit->setCursorPosition(c); // to not jump cursor's position
Now you have a lineEdit with Hex input, in uppercase, with dash-separators.
have a good code time :)
Upvotes: 1
Reputation: 6802
I will propose three approaches :
You can reimplement the QLineEdit::keyPressEvent()
to handle backslash differently when the character just left to the QLineEdit
's cursor is a space. Using this approach, you can also automatically add spaces when a new character is typed.
Another approach is to create a new slot, connected to the QLineEdit::textChanged()
signal. This signal is emitted when the text is changed. In this slot, you can handle the creation and deletion of spaces accordingly to your needs.
Finally, you can create a new class, derived from QLineEdit
that reimplements the QLineEdit::paintEvent()
method. With this approach, you can display space between your hex words that are not stored in the QLineEdit
buffer.
Upvotes: 1
Reputation: 1688
Evan, try this:
QLineEdit * edt = new QLineEdit( this );
edt->setInputMask( "Hh hh hh hh" );
The inputMask takes care of the spacing, and the "h" stands for a optional hex character (the "H" for a non-optional). Only drawback: You have to know the maximum input length in advance. My example above allows only for four bytes.
Best regards, Robin
Upvotes: 6