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.
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
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.
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 :)
Related
I am using FLTK 1.3.5 and I would like to use the Multiline_Input object. I wonder if there is a way to automatically put a newline when the inserted text reaches the end of the input field, instead of doing it manually (check the uploaded images for an example). Moreover, the newline should be put at the end of a word. I searched here on SO and on the web, but I was not able to find anything useful.
Here it is the code used to generate the images above.
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Multiline_Input.H>
int main(int argc, char **argv) {
Fl_Window *G_win = 0;
G_win = new Fl_Window(200,200,"Test multi input");
Fl_Multiline_Input* in;
in = new Fl_Multiline_Input(50,50,120,100," Test:");
G_win->end();
G_win->show(argc, argv);
return Fl::run();
}
With FLTK, you can always catch keyboard events, e.g. using (or redefining) keyboard events
A human being don't type very fast, and computers are fast. Your keyboard event handler could change the multiline content at every keystroke
We plan to do so (in summer 2020) in the RefPerSys project.
Following Basile's suggestion, I investigated more on handling events and then I came up with a solution.
I derivated a class from the original Fl_Multiline_Input which basically controls that the char[] in Fl_Multiline_input->value() does not overcome the maximum width given by the geometry (considering the current Fl_font). Maybe it is not the best solution but, hey, it works! Of course, more performant/elegant strategies are more than welcome.
The code is down below.
#include "mymulti.hpp"
/*
This is needed for converting a std::string
to a char[] (required by Fl_Input). Again,
maybe more performant alternative are out there...
*/
char* str2char(std::string S){
char *out;
out = (char*) malloc ((S.size()+1)*sizeof(char));
strcpy(out,&S[0]);
out[S.size()] = '\0';
return out;
}
mymulti::mymulti(int x, int y, int h, int l):Fl_Multiline_Input(x, y, h,l)
{}
int mymulti::handle(int event)
{
switch (event) {
// When a key is pressed and the widget is waiting for input
case FL_KEYBOARD:
return handle_key(event, Fl::event_key());
default:
return Fl_Multiline_Input::handle(event);
};
}
int mymulti::handle_key(int event, int key) {
if (key==FL_BackSpace)
// Allowing Fl_Multiline_Input to handle the delection of characters
return Fl_Multiline_Input::handle(event);
else{
// Converting to std::string just because manipulation is easer
std::string s(this->value());
std::string s2;
/*
Search for the last newline char:
the subsequent substring must be shorter
than the width of the input field
*/
std::size_t found = s.find_last_of("\n");
if (found<std::string::npos)
s2= s.substr (found);
else
s2 = s;
/*
Mean length of a char (lower case)
under the current fl_font. One can add
uppercase letters and/or numbers and/or
other symbols.
*/
double lc = fl_width("abcdefghijklmnopqrstuvwxyz")/26;
// Maximum length for the substring
int string_length = this->w()/lc -1;
// if the substring is longer than the max allowed length, then add a newline
if (s2.length()>string_length)
s+="\n";
// Update the input field
this->value(str2char(s));
return Fl_Multiline_Input::handle(event);
}
}
The problem asks to create a program that asks the user to enter some text and that text will be surrounded by asterisks depending on the width of the screen for example if the user inputs "Hello world" the output should be:
****************
* Hello World! *
****************
I've tried to create the functions but I'm stuck becaus of a compiler error with the shown minimal code.
Question: Why does it tell me no matching function for within_width(text, 80)?
Some of the code I have is below:
#include <iostream>
#include <string>
void display_header (std::string &header) {
std::string text;
header = text;
}
bool within_width (std::string& text, unsigned short int& max_width) {
}
int main() {
std::string text;
std::cout << "Please enter header text: ";
std::getline(std::cin, text);
if (within_width(text, 80)) {
// call the display_header function and pass in the text
// inputted by the user
} else {
std::cout << text;
}
return 0;
}
This declaration of the function
bool within_width (std::string& text, unsigned short int& max_width)
asks for an unsigned short int variable, because it has a reference parameter, see the second &.
To satisfy it, you need to put the value 80 into a variable and give the variable as parameter.
unsigned short int MyWidth=80;
if (within_width(text, MyWidth))
Alternatively (but I assume you are not allowed) you can use a call by value parameter
bool within_width (std::string& text, unsigned short int max_width)
Then you could call as shown.
I won't give a full answer to the exercise here, just some clues.
the display_header() and within_width() functions need to know the string given in parameters but may not modify it ; thus the type of this parameter should be const std::string & (the const was missing).
the second parameter of the within_width() function is just an integer that will be compared to the length of the string ; you don't need to pass it by reference (or at least const), rather by value. Here, the (non-const) reference prevents from passing the literal constant 80.
(it seems to be the main concern of the question after edition)
You need to reason step by step.
all of this depends on the size of the string (12 for Hello World!) ; this information is available via size(text) (or text.size())
(https://en.cppreference.com/w/cpp/iterator/size)
(https://en.cppreference.com/w/cpp/string/basic_string/size)
This size will have to be compared to max_width
Displaying the line with header will require 4 more characters because * will be prepended and * will be appended.
Thus the two surrounding lines will have the length size(header)+4 too.
In order to create such a string made of *, you could use a constructor of std::string taking two parameters : the count of characters and the character to be repeated.
(https://en.cppreference.com/w/cpp/string/basic_string/basic_string)
Send all of this to std::cout in the correct order.
Edit: Just noticing that this answer probably goes far beyond the scope of the task you have been given (just filling in some skeleton that has been provided by your teacher).
I'll still leave it here to illustrate what could be done with arbitrary input. Maybe you want to experiment a little further than what you have been asked...
bool within_width(...)
Pretty simple: string.length() <= max – just wait a second, you need to consider asterisks and spaces at beginning and end of output, so: max - 4
But you can do better, you can split the string, best at word boundaries. That's a bit difficult more difficult, though:
std::vector<std::string> lines;
// we'll be starting with an initially empty line:
auto lineBegin = text.begin();
auto lineEnd = text.begin();
for(auto i = text.begin(); i != text.end(); ++)
// stop condition empty: we'll stop from inside the loop...
{
// ok, we need to find next whitespace...
// we might try using text.find_first_of("..."), but then we
// need to know any whitespace characters ourselves, so I personally
// would rather iterate manually and use isspace function to determine;
// advantage: we can do other checks at the same time, too
auto distance = std::distance(lineBegin, i);
if(std::distance(lineBegin, i) > maxLineLength)
{
if(lineEnd == lineBegin)
{
// OK, now we have a problem: the word itself is too long
// decide yourself, do you want to cut the word somewhere in the
// middle (you even might implement syllable division...)
// or just refuse to print (i. e. throw an exception you catch
// elsewhere) - decide yourself...
}
else
{
lines.emplace_back(lineBegin, lineEnd);
lineBegin = lineEnd; // start next line...
}
}
// OK, now handle current character appropriately
// note: no else: we need to handle the character in ANY case,
// if we terminated the previous line or not
if(std::isspace(static_cast<unsigned char>(*i)))
{
lineEnd = i;
}
// otherwise, we're inside a word and just go on
}
// last line hasn't been added!
lines.emplace_back(lineBegin, lineEnd);
Now you can calculate maximum length over all the strings contained. Best: Do this right when adding a new line to the vector, then you don't need a separate loop...
You might have noticed that I didn't remove whitespace at the end of the strings, so you wouldn't need to add you own one, apart, possibly, from the very last string (so you might add a lines.back() += ' ';).
The ugly part, so far, is that I left multiple subsequent whitespace. Best is removing before splitting into lines, but be aware that you need to leave at least one. So:
auto end = text.begin();
bool isInWord = false; // will remove leading whitespace, if there is
for(auto c : text)
{
if(std::isspace(static_cast<unsigned char>(c)))
{
if(isInWord)
{
*end++ = ' '; // add a single space
isInWord = false;
}
}
else
{
*end++ = c;
isInWord = true;
}
}
This would have moved all words towards the beginning of the string, but we yet to drop the surplus part of the string yet contained:
text.erase(end, text.end());
Fine, the rest is pretty simple:
iterate over maximum length, printing a single asterisk in every loop
iterate over all of your strings in the vector: std::cout << "* " << line << "*\n";
repeat the initial loop to print second line of asterisks
Finally: You introduced a fix line limit of 80 characters. If console is larger, you just won't be using the entire available width, which yet might be acceptable, if it is smaller, you will get lines broken at the wrong places.
You now could (but that's optional) try to detect the width of the console – which has been asked before, so I won't go any deeper into.
Final note: The code presented above is untested, so no guarantee to be bugfree!
I would like to ask about inserting and deleting characters from string.
Here is the code:
void Edit::input() {
int len = 0;
COORD cord;
cord.X = _x;
cord.Y = _y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cord);
while (true) {
char ch = _getch();
if (ch == 13)
{
break;
}
else if (ch == 8)
{
if (len == 0)
{
continue;
}
pusty.pop_back();
std::cout << "\b \b";
len--;
}
else if (len == 6) {
break;
}
else {
pusty.push_back(ch);
len++;
}
std::cout << ch;
}
}
What is the problem exactly? I've got the coordinates of X and Y of the window where text will be entered in this window, length of whole string is 6 characters. The problem is in a moment when i want to use backspace when len value is at the last element. Then backspace creates blank space on 7th index. The picture below shows the issue. How to avoid this problem ? Thanks for all feedbacks.
That extra space is due to using getch, which echos to the console as well as returning the character to you. This means that the 7th position on the console's output is reached and thus the background is painted.
You just need to set the background colour to black (in a similar manner to how you set it to turquoise in your example) before printing \b \b, then set it back to turquoise afterwards - this'll hide your phantom extra character.
Rather than using getch, you should really consider learning ncurses - it provides really helpful functions that can make life a lot easier in this sort of situation (like a getch equivalent that doesn't echo). And it's cross-platform, so you can use it on Windows.
I'm trying to write a sketch that allows a user to access data in EEPROM using the serial monitor. In the serial monitor the user should be able to type one of two commands: “read” and “write. "Read" should take one argument, an EEPROM address. "Write" should take two arguments, an EEPROM address and a value. For example, if the user types “read 7” then the contents of EEPROM address 7 should be printed to the serial monitor. If the user types “write 7 12” then the value 12 should be written into address 7 of the EEPROM. Any help is much appreciated. I'm not an expert in Arudino, still learning ;). In the code below I defined inByte to be the serail.read(). Now how do I extract numbers from the string "inByte" to assign to "val" and "addr"
void loop() {
String inByte;
if (Serial.available() > 0) {
// get incoming byte:
inByte = Serial.read();
}
if (inByte.startsWith("Write")) {
EEPROM.write(addr, val);
}
if (inByte.startsWith("Read")) {
val= EEPROM.read(addr);
}
delay(500);
}
Serial.read() only reads a single character. You should loop until no more input while filling your buffer or use a blocking function like Serial.readStringUntil() or Serial.readBytes() to fill a buffer for you.
https://www.arduino.cc/en/Serial/ReadStringUntil
https://www.arduino.cc/en/Serial/ReadBytes
Or you can use Serial.parseInt() twice to grab the two values directly into a pair of integers. This function will skip the non numerical text and grab the values. This method is also blocking.
https://www.arduino.cc/en/Reference/StreamParseInt
A patch I wrote to improve this function is available in the latest hourly build, but the old versions still work fine for simple numbers with the previous IDE's
The blocking methods can be tweaked using Serial.setTimeout() to change how long they wait for input (1000ms default)
https://www.arduino.cc/en/Serial/SetTimeout
[missed the other answer, there's half my answer gone]
I was going to say use Serial.readStringUntil('\n') in order to read a line at a time.
To address the part:
how do I extract numbers from the string "inByte" to assign to "val" and "addr"
This is less trivial than it might seem and a lot of things can go wrong. For simplicity, let's assume the input string is always in the format /^(Read|Write) (\d+)( \d+)?$/.
A simple way to parse it would be to find the spaces, isolate the number strings and call .toInt().
...
int val, addr;
int addrStart = 0;
while(inByte[addrStart] != ' ' && addrStart < inByte.length())
addrStart++;
addrStart++; //skip the space
int addrEnd = addrStart + 1;
while(inByte[addrEnd] != ' ' && addrEnd < inByte.length())
addrEnd++;
String addrStr = inByte.substring(addrStart, addrEnd); //excludes addrEnd
addr = addrStr.toInt();
if (inByte.startsWith("Write")) {
int valEnd = addrEnd+1;
while(inByte[varEnd] != ' ' && varEnd < inByte.length())
valEnd++;
String valStr = inByte.substring(addrEnd+1, valEnd);
val = valStr.toInt();
EEPROM.write(addr, val);
}
else if (inByte.startsWith("Read")) {
val = EEPROM.read(addr);
}
This can fail in all sorts of horrible ways if the input string has a double space or the numbers are malformed, or has any other subtle error.
If you're concerned with correctness, I suggest you look into a regex library, or even an standard format such as JSON - see ArduinoJson.
This is my first time I ask , so please can help. My question is how can I Add any character between any string like , mean adding dot after every c (small letter) , but I dont want to use any function , I want to write my own void function with passing only one parameter that should be an array of char , Can help please?
I learn how to check every character in the string with
while(*p!='\0')
{
/// What should I write here to check if there is any dot , then add after it
/// a small c
p++;
}
If you are going to do it the C way, I'd suggest you try something like this:
void adjust_string(char*output_p, int output_space, const char* input_p)
{
//while (there is still input left, and room in output buffer) {
while (*input_p!='\0' && output_space>2) {
//copy input character to output
//update the output pointer
//update the amount of room left in the output buffer
//if (its a special character) {
//add the extra character to output
//update the output pointer
//update the amount of room left in the output buffer
}
//update the input pointer
input_p++;
}
//null-terminate the output string
}
In the function that calls this function, you need to provide an array for the output to be placed into and specify its length, so you can't get a buffer overrun.
Note: in checking for room in the output buffer you need to take into account the possibility of adding the extra character in, and the room for the terminating null character.