Qt Example with HexStringValidator fails - qt

I am using the following code from this example: https://doc.qt.io/qt-5/qtserialbus-can-example.html
enum {
MaxPayload = 8,
MaxPayloadFd = 64
};
HexStringValidator::HexStringValidator(QObject *parent) :
QValidator(parent),
m_maxLength(MaxPayload)
{
}
HexStringValidator::HexStringValidator(QObject *parent, uint maxLength) :
QValidator(parent),
m_maxLength(maxLength)
{
}
class HexStringValidator : public QValidator
{
Q_OBJECT
public:
explicit HexStringValidator(QObject *parent = nullptr);
explicit HexStringValidator(QObject *parent, uint maxLength);
QValidator::State validate(QString &input, int &pos) const;
void setMaxLength(int maxLength);
private:
uint m_maxLength = 0;
};
The problem is in this function:
I have a lineedit with this validator and call it with a valid Hex String.
ui->lineEditCANCommand->setValidator(new HexStringValidator(this, 4));
ui->lineEditCANCommand->setText("000003e9");
QValidator::State HexStringValidator::validate(QString &input, int &pos) const
{
const int maxSize = 2 * static_cast<int>(m_maxLength);
const QChar space = QLatin1Char(' ');
QString data = input;
data.remove(space);
if (data.isEmpty())
return Intermediate;
// limit maximum size and forbid trailing spaces
if ((data.size() > maxSize) || (data.size() == maxSize && input.endsWith(space)))
return Invalid;
// check if all input is valid
const QRegularExpression re(QStringLiteral("^[[:xdigit:]]*$"));
if (!re.match(data).hasMatch())
return Invalid;
// insert a space after every two hex nibbles
const QRegularExpression insertSpace(QStringLiteral("(?:[[:xdigit:]]{2} )*[[:xdigit:]]{3}"));
if (insertSpace.match(input).hasMatch()) {
input.insert(input.size() - 1, space);
pos = input.size();
}
return Acceptable;
}
The function is supposed to change this to "00 00 03 e9"
It however runs endless and creates
000003e 9
000003e 9
000003e 9
000003e 9
What is going wrong? Note, this code is from Qt and not mine.

In Qt6 problem should be gone.
There was related to this problem issue and two following commits:
Can example: Fix crash in payload hex editor,
CAN Example: Properly format payload hex string
It is possible to test since Qt 6.2.0.
First, I changed insertSpace regular expression to threeDigits:
QStringLiteral("[[:xdigit:]]{3}")
and insert space if 3 digits were added sequentially:
input.insert(match.capturedEnd() - 1, space);
That solves crash problem (infinite recursion).
Then I added auxilliary condition isEvenHex and formatter formatHexData with oneDigitAndSpace and threeDigits regular expressions that removes all extra spaces and add space after every two hex nibbles. I call them respectively on text change and sendButton click:
connect(m_ui->payloadEdit, &QLineEdit::textChanged, frameIdOrPayloadChanged);
connect(m_ui->sendButton, &QPushButton::clicked, [this]() {
...
m_ui->payloadEdit->setText(formatHexData(data));
...
}
Trying to solve your task I added:
QValidator::State HexStringValidator::validate(QString &input, int &pos) const
{
...
QString data = input;
const QRegularExpression twoSpaces(QStringLiteral("([\\s]{2})"));
if (twoSpaces.match(data).hasMatch())
return Invalid;
data.remove(space);
...
But there is one problem: if you are editing string in payload editor you may remove digits and two spaces can occure. The validator won't allow you remove characters between spaces. As a compromise I would introduce
threeSpaces(QStringLiteral("([\\s]{3})"));
validator instead of twoSpaces. With that one you may enter two spaces sequentially but still can edit string. I may ask QtSerialBus developers to include that change too.

Related

QLineEdit should accept hexadecimal values ranging from [0 - FFFFF]

I have a requirement where I want my QLineEdit should accept hexadecimal values ranging from [0 - FFFFF]. can someone help me out with this?
I have tried the below code, but it holds good for only 1 char display.
I don't see any code in your post, but what you're looking for is a validator, derived from the QValidator class. Create a sub-class, say HexValidator, and implement the "validate" method. You check the input string for the allowed characters and range and return the appropriate state.
You then assign the validator to the QLineEdit using the QLineEdit::setValidator method. Note that the QLineEdit doesn't take ownership of the validator, so you need to make sure you delete it separately or give it a parent so that it gets cleaned up when the parent is deleted. You can create a single validator and assign it to multiple fields if needed.
I was wrong in my comment about Qt docs having a Hex spin box example... so I found one in my archive instead. It could be more flexible, but OTOH it's very simple and short. I know this isn't a QLineEdit but maybe it'll help anyway. The QValidator used here could be used in a line edit also.
#ifndef _HEXSPINBOX_H_
#define _HEXSPINBOX_H_
#include <QSpinBox>
#include <QRegularExpressionValidator>
// NOTE: Since QSpinBox uses int as the storage type, the effective editing range
// is +/- 0x7FFF FFFF, so it can't handle a full unsigned int.
// QDoubleSpinBox would be a more suitable base class if a wider range is needed.
class HexSpinBox : public QSpinBox
{
Q_OBJECT
public:
HexSpinBox(QWidget *parent = nullptr,
bool showPrefix = false,
const QString &format = QStringLiteral("%x")) :
QSpinBox(parent),
format(format)
{
// Validates hex strings up to 8 chars with or w/out leading "0x" prefix.
// For arbitrary prefix/suffix, the regex could be built dynamically
// in validate(), or override setPrefix()/setSuffix() methods.
const QRegularExpression rx("(?:0[xX])?[0-9A-Fa-f]{1,8}");
validator = new QRegularExpressionValidator(rx, this);
setShowPrefix(showPrefix);
}
public slots:
void setShowPrefix(bool show)
{
if (show)
setPrefix(QStringLiteral("0x"));
else
setPrefix(QString());
}
void setFormat(const QString &text)
{
format = text;
lineEdit()->setText(textFromValue(value()));
}
protected:
QValidator::State validate(QString &text, int &pos) const override
{
return validator->validate(text, pos);
}
int valueFromText(const QString &text) const override
{
return text.toInt(0, 16);
}
QString textFromValue(int value) const override
{
return QString().sprintf(qPrintable(format), value);
}
private:
QRegularExpressionValidator *validator;
QString format;
};
#endif // _HEXSPINBOX_H_
Example:
#include "HexSpinBox.h"
#include <QApplication>
#include <QBoxLayout>
#include <QDialog>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QDialog d;
d.setLayout(new QVBoxLayout);
HexSpinBox* sb = new HexSpinBox(&d);
sb->setMaximum(0xFFFFF);
sb->setValue(0x0A234);
d.layout()->addWidget(sb);
HexSpinBox* sb2 = new HexSpinBox(&d, true, QStringLiteral("%05X"));
sb2->setMaximum(0xFFFFF);
sb2->setValue(0x0A234);
d.layout()->addWidget(sb2);
return d.exec();
}

Function validate of QAbstractSpinBox

I am reading this function in QT Docu
It says
QValidator::State QAbstractSpinBox::validate(QString &input, int &pos) const [virtual]
This virtual function is called by the QAbstractSpinBox to determine whether input is valid. The pos parameter indicates the position in the string. Reimplemented in the various subclasses.
I have a weird question because I dont really understand the document. The input here is a string, we determine whether the input is valid or not. So, why do we need the position in the string, for what? I thought the pos here is the length of the string but when I debugged, it is not true. So what does the pos here mean?
UPDATE:
Thanks to #mohabouje. In my case I use a class inherited from QAbstractSpinBox, I override this method and want to validate the string after changed it. How could I update this pos for validating?
QValidator::State MySpinBox::validate( QString &input, int &pos ) const
{
QString pureValue = stripped( input, &tmpPos, prefix(), suffix() ); //this is my function, i just want to remove also prefix and suffix
//I want to add group separator into the pureValue and validate it after that
//I want to add group separator here, not in the constructor with setGroupSeparatorShown(true);
//When i add group separator here, the separator appears simultaneously when we type
//When i set setGroupSeparatorShown(true) in the constructor, it appears after we finish editing and move to another thing (this element loses focus)
pureValue.insert(3, locale().groupSeparator());
input = pureValue;
// I think now 'pos' has changed, how could I update 'pos' to call the following function?
QValidator::State state = QDoubleSpinBox::validate( input, pos );
return state;
}
I was curious about the underlying implementation. I checked the source code in github.
QVariant QDoubleSpinBoxPrivate::validateAndInterpret(QString &input, int &pos,
QValidator::State &state) const
{
if (cachedText == input && !input.isEmpty()) {
state = cachedState;
QSBDEBUG() << "cachedText was '" << cachedText << "' state was "
<< state << " and value was " << cachedValue;
return cachedValue;
}
const double max = maximum.toDouble();
const double min = minimum.toDouble();
QString copy = stripped(input, &pos);
QSBDEBUG() << "input" << input << "copy" << copy;
int len = copy.size();
...
}
The parameters are used in a private function called stripped. This is the source code:
QString QAbstractSpinBoxPrivate::stripped(const QString &t, int *pos) const
{
QString text = t;
if (specialValueText.size() == 0 || text != specialValueText) {
int from = 0;
int size = text.size();
bool changed = false;
if (prefix.size() && text.startsWith(prefix)) {
from += prefix.size();
size -= from;
changed = true;
}
if (suffix.size() && text.endsWith(suffix)) {
size -= suffix.size();
changed = true;
}
if (changed)
text = text.mid(from, size);
}
const int s = text.size();
text = text.trimmed();
if (pos)
(*pos) -= (s - text.size());
return text;
}
So, If I understand properly, given a string and prefix/suffix configuration, the function takes the string and computes the real size of the data to be validated, ignoring the prefix and suffix.
The function returns the data already validated that may be parsed to compute the numerical value.
The original value of pos, the function subtract the difference of the size of the text to be validated and the size of the text after performing the trimming operation.

How to declare a negative number on a QTableView using SQlite

I am designing a major user interface with several fields.
In order to shrink the problem I created a small minimal application with 5 columns: name, image, dataDatabase, dateTime and coordNumber.
I have a MainWindow with a QTableView, as soon as I right click inside the QTableView an AddItemDialog opens up with:
1) nameLineEdit
2) ImLineEdit
3) imageLineEdit
4) dateTimeEdit
5) numLineEdit
The issue that I have is that I can't find a way to accept the 5) numLineEdit through the AddItemDialog when the number is negative.
Currently it only saves positive numbers. How to handle this exception?
I read from official document but I could not figure out. However always from this official source it seems that a negative number must be interpreted as "no limit" value. The explanation was short and didn't provide any useful small example, so I am still not sure how to proceed.
I am including the most important parts of the application below with the related description of the procedure I followed:
I created an Item with the fields item.h:
class Item
{
public:
Item(const double dateTime,
const QString &name = "", const QString &image = "",
int num, const QByteArray &imagesData = QByteArray());
QString name() const { return mName; }
QString image() const { return mImage; }
QByteArray imagesData() const { return mImagesData; }
double dateTime() const { return mDateTime; }
int num() const { return mNumberCoord; }
private:
QString mName;
QString mImage;
QByteArray mImagesData;
double mDateTime;
int mNumberCoord
};
and its related item.cpp
Item::Item(const double dateTime, int num,
const QString &name, const QString &image, int num,
const QByteArray &imagesData) :
mName(name),
mImage(image),
mImagesData(imagesData),
mDateTime(dateTime),
mNumberCoord(num)
{
}
I created a database.h table that will contain the parameters as follows:
class dataBase : public QObject
{
Q_OBJECT
public:
explicit dataBase(QObject *parent = nullptr);
bool inizializationDataBase(const QString &nameDataBase);
bool configureDataBase();
QString getError() const { return mError; }
bool addItem(const Item &item);
private:
QSqlDatabase mDatabase;
QString mError;
};
And its related database.cpp file - I am only including the most important piece of the code for this file:
#define CREATE_TABLE \
" CREATE TABLE IF NOT EXISTS Fish_Table" \
" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL" \
", name TEXT NOT NULL" \
", image TEXT NOT NULL" \
", dataDataBase BLOB NOT NULL" \
", dateTime DOUBLE NOT NULL" \
", num INTEGER NOT NULL)"
dataBase::dataBase(QObject *parent) : QObject(parent)
{
}
bool dataBase::addItem(const Item &item) {
QSqlQuery q;
q.prepare("INSERT INTO Fish_Table (name, image, dataDatabase, dateTime, num) VALUES (?,?,?,?,?)");
q.addBindValue(item.name());
q.addBindValue(item.image());
q.addBindValue(item.imagesData());
q.addBindValue(item.dateTime());
q.addBindValue(item.num());
bool ok = q.exec();
if (!ok) {
mError = q.lastError().text();
}
return ok;
}
and finally the AddItemDialog.cpp that contains the fields I am trying to pass to the QTableView of the MainWindow.
AddItemDialog.cpp
void AddItemDialog::on_buttonBox_accepted()
{
QFile dataBase(ui->imageLineEdit->text());
if (!dataBase.open(QIODevice::ReadOnly)) {
QMessageBox::critical(this, "Error", dataBase.errorString());
return;
}
mItem = Item(ui->dateTimeEdit->dateTime(),
ui->nameLineEdit->text(),
ui->ImLineEdit->text(),
ui->numLineEdit->text(),
dataBase.readAll());
dataBase.close();
accept();
}
I expect to save either positive or negative numbers on the QTableView, but as of now I can only save positive numbers. How to handle this exception?
So the answer is yes, it is possible to handle negative numbers. For some reasons I thought that SQL was not able to do that but below is a small example of how to handle that exception:
#define CREATE_TABLE \
" CREATE TABLE IF NOT EXISTS Fish_Table" \
" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL" \
", name TEXT NOT NULL" \
", image TEXT NOT NULL" \
", dataDataBase BLOB NOT NULL" \
", dateTime DOUBLE NOT NULL" \
", num INTEGER NOT NULL)"
dataBase::dataBase(QObject *parent) : QObject(parent)
{
}
bool dataBase::addItem(const Item &item) {
QSqlQuery q;
q.prepare("INSERT INTO Fish_Table (name, image, dataDatabase, dateTime, num) VALUES (name,descriptionOfImage,BLOB(actual image), put date&time,-200)");
q.addBindValue(item.name());
q.addBindValue(item.image());
q.addBindValue(item.imagesData());
q.addBindValue(item.dateTime());
q.addBindValue(item.num());
bool ok = q.exec();
if (!ok) {
mError = q.lastError().text();
}
return ok;
}
This is also a very valuable source.
If also there is any need to turn a positive number into a negative number than this is a possible solution:
UPDATE Table
SET field= (field * -1)
Where SIGN(field) = -1
Also this is very useful in case someone needs the SQL code for an additional source.

A best way to draw a lot of independent characters in Qt5?

I'm writing an application that displays a lot of text. It's not words and sentences though, it's binary data displayed in CP437 charset. Current form:
I'm having a problem though with drawing those characters. I need to draw each character one by one, because later I would like to apply different coloring. Those characters should have a transparent background as well, because later I would like to draw sections and ranges with different colors in the background (to group those characters based on some criteria).
The application supports multiple opened files at the same time, but when there are multiple files opened, the drawing starts to be noticeable on fast i7, so it's probably badly written.
What would be the best approach to draw this kind of data in Qt5? Should I just prerender characters to a bitmap and start from there, or it actually is possible to draw lots of characters by using normal Qt functions to draw text?
Edit: I'm using a normal QFrame widget that does drawing in paintEvent, using QPainter. Is this a wrong approach? I've read some docs on QGraphicsScene, from which I've remembered that it's best used in situations where a widget needs to have some control on the objects it draws. I don't need any control on what I draw; I just need to draw it and that's all. I won't reference any particular character after I'll draw it.
The widget has 2000 lines, so I won't paste the whole code, but currently my drawing approach is like this:
First, create a table (cache) with 256 entries, put the iterator counter to i variable,
For each entry, create a QStaticText object that contains drawing information about a character identified by ASCII code taken from i variable,
Later, in the drawing function, for each byte in the input stream (i.e. from the file), draw the data using QStaticText from the cache table. So, to draw ASCII character 0x7A, I'll look up QStaticText from index 0x7a in cache table, and feed this QStaticText object into the QPainter object.
I was also experimenting with a different approach, rendering the whole line in one QPainter::drawText call, and indeed it was faster, but I've lost possibility of coloring each character with different color. I would like to have this possibility.
The use of a QGraphicsScene wouldn't improve things - it's an additional layer on top of a QWidget. You're after raw performance, so you shouldn't be using it.
You could implement a QTextDocument as a viewmodel for the visible section of your memory buffer/file, but painting the fresh QTextDocument each time you scroll wouldn't be any faster than drawing things directly on a QWidget.
Using QStaticText is a step in the right direction, but insufficient: rendering QStaticText still requires the rasterization of the glyph's shape. You can do better and cache the pixmap of each QChar, QColor combination that you wish to render: this will be much faster than rasterizing character outlines, whether using QStaticText or not.
Instead of drawing individual characters, you then draw pixmaps from the cache. This commit demonstrates this approach. The character drawing method is:
void drawChar(const QPointF & pos, QChar ch, QColor color, QPainter & p) {
auto & glyph = m_cache[{ch, color}];
if (glyph.isNull()) {
glyph = QPixmap{m_glyphRect.size().toSize()};
glyph.fill(Qt::white);
QPainter p{&glyph};
p.setPen(color);
p.setFont(m_font);
p.drawText(m_glyphPos, {ch});
}
p.drawPixmap(pos, glyph);
}
You could also cache each (character,foreground,background) tuple. Alas, this gets quickly out of hand when there are many foreground/background combinations.
If all of your backgrounds are of the same color (e.g. white), you'd wish to store a negative mask of the character: the glyph has a white background and a transparent shape. This commit demonstrates this approach. The glyph rectangle is filled with glyph color, then a white mask is applied on top:
void drawChar(const QPointF & pos, QChar ch, QColor color, QPainter & p) {
auto & glyph = m_glyphs[ch];
if (glyph.isNull()) {
glyph = QImage{m_glyphRect.size().toSize(), QImage::Format_ARGB32_Premultiplied};
glyph.fill(Qt::white);
QPainter p{&glyph};
p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
p.setFont(m_font);
p.drawText(m_glyphPos, {ch});
}
auto rect = m_glyphRect;
rect.moveTo(pos);
p.fillRect(rect, color);
p.drawImage(pos, glyph);
}
Instead of storing a fully pre-rendered character of a given color, you could store just the alpha mask and composite them on-demand:
Start with a pre-rendered white glyph on a transparent background (CompositionMode_Source).
Fill the glyph rect with background in CompositionMode_SourceOut: the background will remain with a hole for the character itself.
Fill the glyph rect with foreground in CompositionMode_DestinationOver: the foreground will fill the hole.
(Optional) Draw the composite on the widget, if you're not painting on the widget already.
This turns out to be reasonably fast, and the rendering is fully parallelizable - see the example below.
Note: The pre-rendered glyph could use further premultiplication of the color with alpha to appear less thick.
Yet another approach, with excellent performance, would be to emulate a text-mode display using the GPU. Store the pre-rendered glyph outlines in a texture, store the glyph indices and colors to be rendered in an array, and use OpenGL and two shaders to do the rendering. This example might be a starting point to implement such an approach.
A complete example, using CPU rendering across multiple threads, follows.
We start with the backing store view, used to produce QImages that are views into the backing store for a given widget, and can be used to parallelize painting.
On a 2013 iMac, this code repaints the full-screen widget in about 8ms.
// https://github.com/KubaO/stackoverflown/tree/master/questions/hex-widget-40458515
#include <QtConcurrent>
#include <QtWidgets>
#include <algorithm>
#include <array>
#include <cmath>
struct BackingStoreView {
QImage *dst = {};
uchar *data = {};
const QWidget *widget = {};
explicit BackingStoreView(const QWidget *widget) {
if (!widget || !widget->window()) return;
dst = dynamic_cast<QImage*>(widget->window()->backingStore()->paintDevice());
if (!dst || dst->depth() % 8) return;
auto byteDepth = dst->depth()/8;
auto pos = widget->mapTo(widget->window(), {});
data = const_cast<uchar*>(dst->constScanLine(pos.y()) + byteDepth * pos.x());
this->widget = widget;
}
// A view onto the backing store of a given widget
QImage getView() const {
if (!data) return {};
QImage ret(data, widget->width(), widget->height(), dst->bytesPerLine(), dst->format());
ret.setDevicePixelRatio(widget->devicePixelRatio());
return ret;
}
// Is a given image exactly this view?
bool isAView(const QImage &img) const {
return data && img.bits() == data && img.depth() == dst->depth()
&& img.width() == widget->width() && img.height() == widget->height()
&& img.bytesPerLine() == dst->bytesPerLine() && img.format() == dst->format();
}
};
Then, the CP437 character set:
static auto const CP437 = QStringLiteral(
" ☺☻♥♦♣♠•◘○◙♂♀♪♫☼▶◀↕‼¶§▬↨↑↓→←∟↔▲▼"
"␣!\"#$%&'()*+,-./0123456789:;<=>?"
"#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~ "
"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒ"
"áíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐"
"└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀"
"αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ");
The HexView widget derives from QAbstractScrollArea and visualizes a memory-mapped chunk of data:
class HexView : public QAbstractScrollArea {
Q_OBJECT
QImage const m_nullImage;
const int m_addressChars = 8;
const int m_dataMargin = 4;
const char * m_data = {};
size_t m_dataSize = 0;
size_t m_dataStart = 0;
QSize m_glyphSize;
QPointF m_glyphPos;
int m_charsPerLine, m_lines;
QMap<QChar, QImage> m_glyphs;
QFont m_font{"Monaco"};
QFontMetricsF m_fm{m_font};
struct DrawUnit { QPoint pos; const QImage *glyph; QColor fg, bg; };
QFutureSynchronizer<void> m_sync;
QVector<DrawUnit> m_chunks;
QVector<QImage> m_stores;
using chunk_it = QVector<DrawUnit>::const_iterator;
using store_it = QVector<QImage>::const_iterator;
static inline QChar decode(char ch) { return CP437[uchar(ch)]; }
inline int xStep() const { return m_glyphSize.width(); }
inline int yStep() const { return m_glyphSize.height(); }
void initData() {
int const width = viewport()->width() - m_addressChars*xStep() - m_dataMargin;
m_charsPerLine = (width > 0) ? width/xStep() : 0;
m_lines = viewport()->height()/yStep();
if (m_charsPerLine && m_lines) {
verticalScrollBar()->setRange(0, m_dataSize/m_charsPerLine);
verticalScrollBar()->setValue(m_dataStart/m_charsPerLine);
} else {
verticalScrollBar()->setRange(0, 0);
}
}
const QImage &glyph(QChar ch) {
auto &glyph = m_glyphs[ch];
if (glyph.isNull()) {
QPointF extent = m_fm.boundingRect(ch).translated(m_glyphPos).bottomRight();
glyph = QImage(m_glyphSize, QImage::Format_ARGB32_Premultiplied);
glyph.fill(Qt::transparent);
QPainter p{&glyph};
p.setPen(Qt::white);
p.setFont(m_font);
p.translate(m_glyphPos);
p.scale(std::min(1.0, (m_glyphSize.width()-1)/extent.x()),
std::min(1.0, (m_glyphSize.height()-1)/extent.y()));
p.drawText(QPointF{}, {ch});
}
return glyph;
}
The parallelized rendering is done in class methods - they don't modify the state of the widget, other than accessing read-only data, and rendering into the backing store. The threads each act on isolated lines in the store.
static void drawChar(const DrawUnit & u, QPainter &p) {
const QRect rect(u.pos, u.glyph->size());
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawImage(u.pos, *u.glyph);
p.setCompositionMode(QPainter::CompositionMode_SourceOut);
p.fillRect(rect, u.bg);
p.setCompositionMode(QPainter::CompositionMode_DestinationOver);
p.fillRect(rect, u.fg);
}
static QFuture<void> submitChunks(chunk_it begin, chunk_it end, store_it store) {
return QtConcurrent::run([begin, end, store]{
QPainter p(const_cast<QImage*>(&*store));
for (auto it = begin; it != end; it++)
drawChar(*it, p);
});
}
This method distributes the chunks of work between threads:
int processChunks() {
m_stores.resize(QThread::idealThreadCount());
BackingStoreView view(viewport());
if (!view.isAView(m_stores.last()))
std::generate(m_stores.begin(), m_stores.end(), [&view]{ return view.getView(); });
std::ptrdiff_t jobSize = std::max(128, (m_chunks.size() / m_stores.size())+1);
auto const cend = m_chunks.cend();
int refY = 0;
auto store = m_stores.cbegin();
for (auto it = m_chunks.cbegin(); it != cend;) {
auto end = it + std::min(cend-it, jobSize);
while (end != cend && (end->pos.y() == refY || (refY = end->pos.y(), false)))
end++; // break chunks across line boundaries
m_sync.addFuture(submitChunks(it, end, store));
it = end;
store++;
}
m_sync.waitForFinished();
m_sync.clearFutures();
m_chunks.clear();
return store - m_stores.cbegin();
}
The remainder of the implementation is uncontroversial:
protected:
void paintEvent(QPaintEvent *ev) override {
QElapsedTimer time;
time.start();
QPainter p{viewport()};
QPoint pos;
QPoint const step{xStep(), 0};
auto dividerX = m_addressChars*xStep() + m_dataMargin/2.;
p.drawLine(dividerX, 0, dividerX, viewport()->height());
int offset = 0;
QRect rRect = ev->rect();
p.end();
while (offset < m_charsPerLine*m_lines && m_dataStart + offset < m_dataSize) {
const auto address = QString::number(m_dataStart + offset, 16);
pos += step * (m_addressChars - address.size());
for (auto c : address) {
if (QRect(pos, m_glyphSize).intersects(rRect))
m_chunks.push_back({pos, &glyph(c), Qt::black, Qt::white});
pos += step;
}
pos += {m_dataMargin, 0};
auto bytes = std::min(m_dataSize - offset, (size_t)m_charsPerLine);
for (int n = bytes; n; n--) {
if (QRect(pos, m_glyphSize).intersects(rRect))
m_chunks.push_back({pos, &glyph(decode(m_data[m_dataStart + offset])), Qt::red, Qt::white});
pos += step;
offset ++;
}
pos = {0, pos.y() + yStep()};
}
int jobs = processChunks();
newStatus(QStringLiteral("%1ms n=%2").arg(time.nsecsElapsed()/1e6).arg(jobs));
}
void resizeEvent(QResizeEvent *) override {
initData();
}
void scrollContentsBy(int, int dy) override {
m_dataStart = verticalScrollBar()->value() * (size_t)m_charsPerLine;
viewport()->scroll(0, dy * m_glyphSize.height(), viewport()->rect());
}
public:
HexView(QWidget * parent = nullptr) : HexView(nullptr, 0, parent) {}
HexView(const char * data, size_t size, QWidget * parent = nullptr) :
QAbstractScrollArea{parent}, m_data(data), m_dataSize(size)
{
QRectF glyphRectF{0., 0., 1., 1.};
for (int i = 0x20; i < 0xE0; ++i)
glyphRectF = glyphRectF.united(m_fm.boundingRect(CP437[i]));
m_glyphPos = -glyphRectF.topLeft();
m_glyphSize = QSize(std::ceil(glyphRectF.width()), std::ceil(glyphRectF.height()));
initData();
}
void setData(const char * data, size_t size) {
if (data == m_data && size == m_dataSize) return;
m_data = data;
m_dataSize = size;
m_dataStart = 0;
initData();
viewport()->update();
}
Q_SIGNAL void newStatus(const QString &);
};
We leverage modern 64-bit systems and memory-map the source file to be visualized by the widget. For test purposes, a view of the character set is also available:
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QFile file{app.applicationFilePath()};
if (!file.open(QIODevice::ReadOnly)) return 1;
auto *const map = (const char*)file.map(0, file.size(), QFile::MapPrivateOption);
if (!map) return 2;
QWidget ui;
QGridLayout layout{&ui};
HexView view;
QRadioButton exe{"Executable"};
QRadioButton charset{"Character Set"};
QLabel status;
layout.addWidget(&view, 0, 0, 1, 4);
layout.addWidget(&exe, 1, 0);
layout.addWidget(&charset, 1, 1);
layout.addWidget(&status, 1, 2, 1, 2);
QObject::connect(&exe, &QPushButton::clicked, [&]{
view.setData(map, (size_t)file.size());
});
QObject::connect(&charset, &QPushButton::clicked, [&]{
static std::array<char, 256> data;
std::iota(data.begin(), data.end(), char(0));
view.setData(data.data(), data.size());
});
QObject::connect(&view, &HexView::newStatus, &status, &QLabel::setText);
charset.click();
ui.resize(1000, 800);
ui.show();
return app.exec();
}
#include "main.moc"
One solution I sometimes use is to keep a cache of pre-rendered lines. I normally use a doubly-linked LRU list of entries with about twice the lines that can be seen on the screen. Every time a line is used for rendering is moved to the front of the list; when I need to create a new line and the current cache count is past the limit I reuse the last entry in the list.
By storing the final result of individual lines you can repaint the display very quickly as probably in many cases most of the lines will not change from one frame to the next (including when scrolling).
The increased complexity is also reasonably confined in having to invalidate the line when you change the content.

Storing Variables in a Class

I am trying to keep track of a set of data by declaring a class.
The class is initialized with a unique ID but then fills the rest of the variables out later in the code after some calculations.
First, is that even an acceptable way to do this?
Second, I'm trying to pass it a char array but it does not want to take the value. Is this the correct way to define the fileName and call it back when creating the file?
Here's the example, I define a variable from the Customer class then try to store its filename:
#ifndef customer_h
#define customer_h
class Customer
{
public:
Customer (char *number);
char *ID;
double current;
double voltage;
double powerConsumption;
double remainingCredit;
int relay;
char *lastName;
char *firstName;
char *fileName;
private:
};
Customer::Customer(char *number)
{
ID = number;
}
#endif
void setup()
{
cust1.fileName = getFileName(cust1.ID);
}
char *getFileName(char *customerID)
{
char *charID;
String newID;
for (int i = strlen(customerID)-4; i<= strlen(customerID)-1; i++)
{
newID += customerID[i];
}
newID += ".csv";
int lenID = newID.length() + 1;
char fileName[lenID];
newID.toCharArray(fileName,lenID);
return fileName;
}
Thanks a lot in advance for any help and info you can provide!
Be better to use a structured rather than a class. They are pretty similar but in this case seems like a structure will be much better go and read about the differences and you'll see

Resources