Well, I am kind of stuck on this issue - I am trying to change the value of a dial slider and I want to change it using an input with QLineEdit input field and so far I couldn't find anything that could have helped me on the internet.
Code snip
void MainWindow::on_lineEditGain_textChanged(const QString &arg1)
{
QString getVal=ui->lineEditGain->text();
// qint16 intVal = getVal.split(" ")[0].toInt();
connect(getVal, SIGNAL(textChanged(QString)), this, SLOT(setValue(QString)));
}
Create slots:
private slots:
void MySlot(QString text);
In the constructor connect textChanged() with MySlot():
connect(lineedit, &QLineEdit::textChanged, this, &YourWidget::MySlot);
and then
void YourWidget::MySlot(QString text)
{
QRegExp re("\\d*"); // a digit (\d), zero or more times (*)
if (re.exactMatch(text)){
int value = text.toInt();
if(value >= -127 && value<=127)
slider->setValue(value);
}
}
You could also place a QValidator
lineedit->setValidator( new QIntValidator(-127, 127, this) );
In your case:
void MainWindow::on_lineEditGain_textChanged(const QString &arg1)
{
QRegExp re("\\d*"); // a digit (\d), zero or more times (*)
if (re.exactMatch(arg1)){
int value = arg1.toInt();
if(value >= -127 && value<=127)
ui->slider->setValue(value);
}
}
Related
I have a QPlainTextEdit where I want to highlight the current line the user is on as well as all words similar to the word the user selected. This word highlight works fine on all lines except the currently selected one, because the "selected line" background style overrides the "selected word" style applied to the selected words.
My question is how can I make sure the word highlighing is done after the line highlight is done, so that they can both be active at the same time?
Screenshot to illustrate:
The yellow line is the current line being highlighted. The first 'test' is selected, so all others should have the lightblue background applied. All except the 'test' on the highlighted line do.
Minimal reproducible example:
MainWindow.h
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_mainWindow.h"
#include "TextEditor.h"
class mainWindow : public QMainWindow
{
Q_OBJECT
public:
mainWindow(QWidget *parent = Q_NULLPTR) : QMainWindow(parent)
{
ui.setupUi(this);
auto textEdit = new TextEditor(this);
textEdit->setPlainText("test lorem ipsum test\n test dolor sit test\test amet test");
ui.tabWidget->addTab(textEdit, "Editor");
}
private:
Ui::mainWindowClass ui;
};
TextEditor.h
#pragma once
#include <QPlainTextEdit>
class TextEditor : public QPlainTextEdit
{
Q_OBJECT
public:
TextEditor(QWidget* parent) : QPlainTextEdit(parent)
{
connect(this, &QPlainTextEdit::selectionChanged, this, &TextEditor::selectChangeHandler);
connect(this, &QPlainTextEdit::cursorPositionChanged, this, &TextEditor::highlightCurrentLine);
}
private:
std::vector<std::pair<int, QTextCharFormat>> highlightedWords_;
//Highlights the current line
void highlightCurrentLine()
{
QList<QTextEdit::ExtraSelection> extraSelections;
if (!isReadOnly())
{
QTextEdit::ExtraSelection selection;
selection.format.setBackground(Qt::yellow);
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = textCursor();
selection.cursor.clearSelection();
extraSelections.append(selection);
}
setExtraSelections(extraSelections);
}
//Highlights all words similar to the currently selected word
void selectChangeHandler()
{
//Unset previous selection
resetHighlightedWords();
//Ignore empty selections
if (textCursor().selectionStart() >= textCursor().selectionEnd())
return;
//We only care about fully selected words (nonalphanumerical characters on either side of selection)
auto plaintext = toPlainText();
auto prevChar = plaintext.mid(textCursor().selectionStart() - 1, 1).toStdString()[0];
auto nextChar = plaintext.mid(textCursor().selectionEnd(), 1).toStdString()[0];
if (isalnum(prevChar) || isalnum(nextChar))
return;
auto qselection = textCursor().selectedText();
auto selection = qselection.toStdString();
//We also only care about selections that do not themselves contain nonalphanumerical characters
if (std::find_if(selection.begin(), selection.end(), [](char c) { return !isalnum(c); }) != selection.end())
return;
//Highlight all matches of the given word in the editor
blockSignals(true);
highlightWord(qselection);
blockSignals(false);
}
//Removes highlight from selected words
void resetHighlightedWords()
{
if (highlightedWords_.empty())
return;
blockSignals(true);
auto cur = textCursor();
for (const auto& [index, oldFormat] : highlightedWords_)
{
cur.setPosition(index);
cur.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
cur.setCharFormat(oldFormat);
}
blockSignals(false);
highlightedWords_.clear();
}
//Applies the highlight style to all appearances of the given word
void highlightWord(const QString& word)
{
auto plaintext = toPlainText();
//Prepare text format
QTextCharFormat format;
format.setBackground(QColor::fromRgb(0x70, 0xED, 0xE0));
//Find all words in our document that match the selected word and apply the background format to them
size_t pos = 0;
auto reg = QRegExp("\\b" + word + "\\b");
auto cur = textCursor();
auto index = reg.indexIn(plaintext, pos);
while (index >= 0)
{
//Select matched text
cur.setPosition(index);
//Save old text style
highlightedWords_.push_back(std::make_pair(index, cur.charFormat()));
//Apply format
cur.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
cur.mergeCharFormat(format);
//Move to next match
auto len = (size_t)reg.matchedLength();
pos = index + (size_t)reg.matchedLength();
index = reg.indexIn(plaintext, pos);
}
}
};
Avoiding inheritance as a first resort was the right thing to do IMO, but in this particular case, it may be the simplest approach.
#include <QPainter>
TextEditor::TextEditor( QWidget* parent ) : QPlainTextEdit( parent )
{
connect( this, SIGNAL( cursorPositionChanged() ), viewport(), SLOT( update() ) );
//Just for brevity. Instead of repainting the whole thing on every cursor change,
//you'll want to filter for changes to the current block/line and only update the.
//changed portions. And accommodate resize, etc.
}
void TextEditor::paintEvent( QPaintEvent* pEvent )
{
QPainter painter( viewport() );
QRect r = cursorRect();
r.setLeft( 0 ); r.setRight( width() - 1 ); //Or more!
painter.setPen( Qt::NoPen );
painter.setBrush( QColor( 228, 242, 244, 200 ) );
painter.drawRect( r );
QPlainTextEdit::paintEvent( pEvent );
}
A background hint behind the cursor's block is a really nice UX improvement, as it makes the cursor position more immediately apparent at a glance in all sorts of scenarios. If you have several text editors up together, little details like that become even more important.
At first glance, setExtraSelections() looks like a fast and simple way to get there. And it is... But I find that it falls short when you want to take it to the next level, and, as you discovered, it doesn't play well with ANY other highlighting.
I suspect the built-in ExtraSelection method is meant to be a kind of quick and dirty brute force tool for quickly showing errors or breakpoints, i.e. things that are meant to really visually stand out for the user. It's basically like a secondary selection highlight behind the cursor selection, and so like all other selection highlights it will render behind text but in front of everything else. That means it will also eclipse any custom text background formatting you do using QTextFormat or QTextCharFormat, or even QSyntaxHighlighter. I don't find that acceptable, personally.
Another smaller problem with built-in selections or highlighting in general for this kind of background hint use case is that they don't cover the entire background of the text block. They stop a few pixels shy at the text area boundary or worse depending on margins, etc., making it look clunky to my eyes.
In terms of UI design, a current line indication generally needs to be subtler than most other indications and all other highlights, with lower contrast, and towards the very far background. It needs to look more like a part of the widget than part of the text. It's a hint not a selection, and I've found that balancing it all visually required more than ExtraSelections or regular text formatting was able to provide.
BTW, If you plan on making this more complex, e.g. a code editor, I would also recommend that you look into using QSyntaxHighlighter for your selected word pattern highlight. ( It will remove a LOT of the cursor control logic and ALL of the signal interruption. It will also scale better for when (if?) you add keyword, variable, comment, search terms, etc. Right now, your highlighting involves editing your document/data model directly, which is fine for your post or for simple text input, but likely to be problematic for other cases. )
EDIT
Here's more code that shows using an extended highlighter together with the paintEvent override. I'm going to use headers to hopefully make it clearer how this approach might integrate with your actual projects' classes.
First, the highlighter:
#include <QSyntaxHighlighter>
class QTextDocument;
class CQSyntaxHighlighterSelectionMatch : public QSyntaxHighlighter
{
Q_OBJECT
public:
explicit CQSyntaxHighlighterSelectionMatch( QTextDocument *parent = 0 );
public slots:
void SetSelectionTerm( QString term );
protected:
virtual void highlightBlock( const QString &text );
void ApplySelectionTermHighlight( const QString &text );
private:
QString m_strSelectionTerm;
struct HighlightingRule {
QRegExp pattern;
QTextCharFormat format;
};
HighlightingRule m_HighlightRuleSelectionTerm;
};
A quick and dirty implementation:
CQSyntaxHighlighterSelectionMatch::CQSyntaxHighlighterSelectionMatch( QTextDocument *parent )
: QSyntaxHighlighter( parent )
{
m_strSelectionTerm.clear();
m_HighlightRuleSelectionTerm.format.setBackground( QColor(255, 210, 120 ) );
//m_HighlightRuleSelectionTerm.format.setFontWeight( QFont::Bold ); //or italic, etc...
}
void CQSyntaxHighlighterSelectionMatch::SetSelectionTerm( QString txtIn )
{
if( txtIn == m_strSelectionTerm )
return;
if( !txtIn.isEmpty() )
{
txtIn = "\\b" + txtIn + "\\b";
if( txtIn == m_strSelectionTerm )
return;
}
m_strSelectionTerm = txtIn;
Qt::CaseSensitivity cs = Qt::CaseSensitive;
m_HighlightRuleSelectionTerm.pattern = QRegExp( m_strSelectionTerm, cs );
rehighlight();
}
void CQSyntaxHighlighterSelectionMatch::highlightBlock( const QString &text )
{
if( m_strSelectionTerm.length() > 1 )
ApplySelectionTermHighlight( text );
}
void CQSyntaxHighlighterSelectionMatch::ApplySelectionTermHighlight( const QString &text )
{
QRegExp expression( m_HighlightRuleSelectionTerm.pattern );
int index, length;
index = expression.indexIn( text );
while ( index >= 0 )
{
length = expression.matchedLength();
setFormat( index, length, m_HighlightRuleSelectionTerm.format );
index = expression.indexIn( text, index + length );
}
}
And here's how a QPlainTextEdit derived class might use something like the above:
#include <QPlainTextEdit>
class TextEditor : public QPlainTextEdit
{
Q_OBJECT
public:
TextEditor( QWidget *parent = 0 );
protected:
virtual void paintEvent( QPaintEvent *event );
private slots:
void CheckForCurrentBlockChange();
void FilterSelectionForSingleWholeWord();
private:
unsigned int m_uiCurrentBlock;
CQSyntaxHighlighterSelectionMatch *m_pHighlighter;
};
#include <QPainter>
TextEditor::TextEditor(QWidget *parent)
: QPlainTextEdit(parent)
{
//Instead of repainting on every cursor change, we can filter for changes to the current block/line
//connect( this, SIGNAL(cursorPositionChanged()), viewport(), SLOT(update()) );
connect( this, SIGNAL(cursorPositionChanged()), this, SLOT(CheckForCurrentBlockChange()) );
m_pHighlighter = new CQSyntaxHighlighterSelectionMatch( document() );
connect( this, SIGNAL(selectionChanged()), this, SLOT(FilterSelectionForSingleWholeWord()) );
}
void TextEditor::paintEvent( QPaintEvent* pEvent )
{
QPainter painter( viewport() );
QRect r = cursorRect();
r.setLeft( 0 );
r.setRight( width()-1 );
painter.setPen( Qt::NoPen );
painter.setBrush( QColor( 228, 242, 244 ) );
painter.drawRect( r );
QPlainTextEdit::paintEvent( pEvent );
}
void TextEditor::CheckForCurrentBlockChange()
{
QTextCursor tc = textCursor();
unsigned int b = (unsigned int)tc.blockNumber();
if( b == m_uiCurrentBlock )
return;
m_uiCurrentBlock = b;
viewport()->update(); //I'll just brute force paint everything for this example. Your real code can be smarter with it's repainting it matters...
}
void TextEditor::FilterSelectionForSingleWholeWord()
{
QTextCursor tc = textCursor();
QString currentSelection = tc.selectedText();
QStringList list = currentSelection.split(QRegExp("\\s+"), QString::SkipEmptyParts);
if( list.count() > 1 )
{
m_pHighlighter->SetSelectionTerm( "" );
return;
}
tc.movePosition( QTextCursor::StartOfWord );
tc.movePosition( QTextCursor::EndOfWord, QTextCursor::KeepAnchor );
QString word = tc.selectedText();
if( currentSelection != word )
{
m_pHighlighter->SetSelectionTerm( "" );
return;
}
m_pHighlighter->SetSelectionTerm( currentSelection );
}
This is the simplest way I know to provide the selection term functionality you are after while solving the issue of the background hint interfering with the selection term highlight when they are on the same block.
I would like to display a rectangle behind a word I selected like Qt Creator does here:
I am experimenting with the example of QSyntaxHighlighter. I am able to change styles based on keyword patterns. I would like to have graphics or widgets for custom autocompletion lists.
For autocompletion follow the Custom Completer Example or the Completer Example.
The code below follows the first one, which I blatantly, unashamedly copied and integrated into the BackgroundHighlighter class and main.cpp.
This answer will contain five files within a project along with a Qt Resource File.
highlighter.h (Highlighter Class for Syntax)
highlighter.cpp
backgroundHighlighter.h (BackgroundHighlighter Class)
backgroundHighlighter.cpp
main.cpp
res.qrc (optional, not needed, you can hardcode your text)
res (directory) (optional)
|- symbols.txt (optional, you can set your own default text)
|- wordlist.txt (optional, copied from example but you could use your own line-delimited word list and set this in main.cpp with a QStringListModel)
Note that the implementation of the Highlighter class for (1) and (2) can be found in the Qt Syntax Highlighter Example. I will leave its implementation as an exercise for the reader.
In calling the BackgroundHighlighter class, one can pass it a file name to load text from a file. (This wasn't in the OP's specification, but was convenient to implement due to the large amount of text I wanted to test.)
Also note that I integrated the Custom Completer Example into the class.
Here's backgroundHighlighter.h (3) (~45 lines, ~60 lines with completer):
#ifndef BACKGROUNDHIGHLIGHTER_H
#define BACKGROUNDHIGHLIGHTER_H
#include <QtWidgets>
#include <QtGui>
// this is the file to your highlighter
#include "myhighlighter.h"
class BackgroundHighlighter : public QTextEdit
{
Q_OBJECT
public:
BackgroundHighlighter(const QString &fileName = QString(), QWidget *parent = nullptr);
void loadFile(const QString &fileName);
void setCompleter(QCompleter *completer);
QCompleter *completer() const;
protected:
void keyPressEvent(QKeyEvent *e) override;
void focusInEvent(QFocusEvent *e) override;
public slots:
void onCursorPositionChanged();
private slots:
void insertCompletion(const QString &completion);
private:
// this is your syntax highlighter
Highlighter *syntaxHighlighter;
// stores the symbol being highlighted
QString highlightSymbol;
// stores the position (front of selection) where the cursor was originally placed
int mainHighlightPosition;
// stores character formats to be used
QTextCharFormat mainFmt; // refers to format block directly under the cursor
QTextCharFormat subsidiaryFmt; // refers to the formatting blocks on matching words
QTextCharFormat defaultFmt; // refers to the default format of the **entire** document which will be used in resetting the format
void setWordFormat(const int &position, const QTextCharFormat &format);
void runHighlight();
void clearHighlights();
void highlightMatchingSymbols(const QString &symbol);
// completer, copied from example
QString textUnderCursor() const;
QCompleter *c;
};
#endif // BACKGROUNDHIGHLIGHTER_H
And here's backgroundHighlighter.cpp (4) (~160 lines, ~250 lines with completer):
#include "backgroundhighlighter.h"
#include <QDebug>
// constructor
BackgroundHighlighter::BackgroundHighlighter(const QString &fileName, QWidget *parent) :
QTextEdit(parent)
{
// I like Monaco
setFont(QFont("Monaco"));
setMinimumSize(QSize(500, 200));
// load initial text from a file OR from a hardcoded default
if (!fileName.isEmpty())
loadFile(fileName);
else
{
QString defaultText = "This is a default text implemented by "
"a stackoverflow user. Please upvote the answer "
"at https://stackoverflow.com/a/53351512/10239789.";
setPlainText(defaultText);
}
// set the highlighter here
QTextDocument *doc = document();
syntaxHighlighter = new Highlighter(doc);
// TODO change brush/colours to match theme
mainFmt.setBackground(Qt::yellow);
subsidiaryFmt.setBackground(Qt::lightGray);
defaultFmt.setBackground(Qt::white);
// connect the signal to our handler
connect(this, &QTextEdit::cursorPositionChanged, this, &BackgroundHighlighter::onCursorPositionChanged);
}
// convenience function for reading a file
void BackgroundHighlighter::loadFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
return;
// the file could be in Plain Text OR Html
setText(file.readAll());
}
void BackgroundHighlighter::setCompleter(QCompleter *completer)
{
if (c)
QObject::disconnect(c, 0, this, 0);
c = completer;
if (!c)
return;
c->setWidget(this);
c->setCompletionMode(QCompleter::PopupCompletion);
c->setCaseSensitivity(Qt::CaseInsensitive);
QObject::connect(c, SIGNAL(activated(QString)),
this, SLOT(insertCompletion(QString)));
}
QCompleter *BackgroundHighlighter::completer() const
{
return c;
}
void BackgroundHighlighter::keyPressEvent(QKeyEvent *e)
{
if (c && c->popup()->isVisible()) {
// The following keys are forwarded by the completer to the widget
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Tab:
case Qt::Key_Backtab:
e->ignore();
return; // let the completer do default behavior
default:
break;
}
}
bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E
if (!c || !isShortcut) // do not process the shortcut when we have a completer
QTextEdit::keyPressEvent(e);
const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
if (!c || (ctrlOrShift && e->text().isEmpty()))
return;
static QString eow("~!##$%^&*()_+{}|:\"<>?,./;'[]\\-="); // end of word
bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
QString completionPrefix = textUnderCursor();
if (!isShortcut && (hasModifier || e->text().isEmpty()|| completionPrefix.length() < 3
|| eow.contains(e->text().right(1)))) {
c->popup()->hide();
return;
}
if (completionPrefix != c->completionPrefix()) {
c->setCompletionPrefix(completionPrefix);
c->popup()->setCurrentIndex(c->completionModel()->index(0, 0));
}
QRect cr = cursorRect();
cr.setWidth(c->popup()->sizeHintForColumn(0)
+ c->popup()->verticalScrollBar()->sizeHint().width());
c->complete(cr); // pop it up!
}
void BackgroundHighlighter::focusInEvent(QFocusEvent *e)
{
if (c)
c->setWidget(this);
QTextEdit::focusInEvent(e);
}
// convenience function for setting a `charFmt` at a `position`
void BackgroundHighlighter::setWordFormat(const int &position, const QTextCharFormat &charFmt)
{
QTextCursor cursor = textCursor();
cursor.setPosition(position);
cursor.select(QTextCursor::WordUnderCursor);
cursor.setCharFormat(charFmt);
}
// this will handle the `QTextEdit::cursorPositionChanged()` signal
void BackgroundHighlighter::onCursorPositionChanged()
{
// if cursor landed on different format, the `currentCharFormat` will be changed
// we need to change it back to white
setCurrentCharFormat(defaultFmt);
// this is the function you're looking for
runHighlight();
}
void BackgroundHighlighter::insertCompletion(const QString &completion)
{
if (c->widget() != this)
return;
QTextCursor tc = textCursor();
int extra = completion.length() - c->completionPrefix().length();
tc.movePosition(QTextCursor::Left);
tc.movePosition(QTextCursor::EndOfWord);
tc.insertText(completion.right(extra));
setTextCursor(tc);
}
QString BackgroundHighlighter::textUnderCursor() const
{
QTextCursor tc = textCursor();
tc.select(QTextCursor::WordUnderCursor);
return tc.selectedText();
}
/**
* BRIEF
* Check if new highlighting is needed
* Clear previous highlights
* Check if the word under the cursor is a symbol (i.e. matches ^[A-Za-z0-9_]+$)
* Highlight all relevant symbols
*/
void BackgroundHighlighter::runHighlight()
{
// retrieve cursor
QTextCursor cursor = textCursor();
// retrieve word under cursor
cursor.select(QTextCursor::WordUnderCursor);
QString wordUnder = cursor.selectedText();
qDebug() << "Word Under Cursor:" << wordUnder;
// get front of cursor, used later for storing in `highlightPositions` or `mainHighlightPosition`
int cursorFront = cursor.selectionStart();
// if the word under cursor is the same, then save time
// by skipping the process
if (wordUnder == highlightSymbol)
{
// switch formats
setWordFormat(mainHighlightPosition, subsidiaryFmt); // change previous main to subsidiary
setWordFormat(cursorFront, mainFmt); // change position under cursor to main
// update main position
mainHighlightPosition = cursorFront;
// jump the gun
return;
}
// clear previous highlights
if (mainHighlightPosition != -1)
clearHighlights();
// check if selected word is a symbol
if (!wordUnder.contains(QRegularExpression("^[A-Za-z0-9_]+$")))
{
qDebug() << wordUnder << "is not a symbol!";
return;
}
// set the highlight symbol
highlightSymbol = wordUnder;
// store the cursor position to check later
mainHighlightPosition = cursorFront;
// highlight all relevant symbols
highlightMatchingSymbols(wordUnder);
qDebug() << "Highlight done\n\n";
}
// clear previously highlights
void BackgroundHighlighter::clearHighlights()
{
QTextCursor cursor = textCursor();
// wipe the ENTIRE document with the default background, this should be REALLY fast
// WARNING: this may have unintended consequences if you have other backgrounds you want to keep
cursor.select(QTextCursor::Document);
cursor.setCharFormat(defaultFmt);
// reset variables
mainHighlightPosition = -1;
highlightSymbol.clear();
}
// highlight all matching symbols
void BackgroundHighlighter::highlightMatchingSymbols(const QString &symbol)
{
// highlight background of congruent symbols
QString docText = toPlainText();
// use a regex with \\b to look for standalone symbols
QRegularExpression regexp("\\b" + symbol + "\\b");
// loop through all matches in the text
int matchPosition = docText.indexOf(regexp);
while (matchPosition != -1)
{
// if the position
setWordFormat(matchPosition, matchPosition == mainHighlightPosition ? mainFmt : subsidiaryFmt);
// find next match
matchPosition = docText.indexOf(regexp, matchPosition + 1);
}
}
Finally, here's main.cpp (5) (~10 lines, ~45 lines with completer)
#include <QApplication>
#include <backgroundhighlighter.h>
QAbstractItemModel *modelFromFile(const QString& fileName, QCompleter *completer)
{
QFile file(fileName);
if (!file.open(QFile::ReadOnly))
return new QStringListModel(completer);
#ifndef QT_NO_CURSOR
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
#endif
QStringList words;
while (!file.atEnd()) {
QByteArray line = file.readLine();
if (!line.isEmpty())
words << line.trimmed();
}
#ifndef QT_NO_CURSOR
QApplication::restoreOverrideCursor();
#endif
return new QStringListModel(words, completer);
}
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
BackgroundHighlighter bh(":/res/symbols.txt");
QCompleter *completer = new QCompleter();
completer->setModel(modelFromFile(":/res/wordlist.txt", completer));
// use this and comment the above if you don't have or don't want to use wordlist.txt
// QStringListModel *model = new QStringListModel(QStringList() << "aaaaaaa" << "aaaaab" << "aaaabb" << "aaacccc",
completer);
// completer->setModel(model);
completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setWrapAround(false);
bh.setCompleter(completer);
bh.show();
return a.exec();
}
In res.qrc add a / prefix and add files (res/symbols.txt, res/wordlist.txt) from the res/ subdirectory.
I have tested with a symbols.txt file resembling
symbol1 symbol2 symbol3 symbol4 symbol5
symbol1 symbol2 symbol3 symbol4 symbol5
symbol1 symbol2 symbol3 symbol4 symbol5
// ... ditto 500 lines
It takes about 1 second, which probably isn't ideal (100ms is probably more ideal).
However, you might want to watch over for the line count as it grows. With the same text file at 1000 lines, the program will start to take approx. 3 seconds for highlighting.
Note that... I haven't optimised it entirely. There could possibly be a better implementation which formats only when the symbol scrolls into the user's view. This is just a suggestion. How to implement it I don't know.
Notes
For reference, I've attached symbols.txt and wordlist.txt on github.
If you want to change the background colour of formatting, go to lines 27 to 29 of backgroundhighlighter.cpp. There, you can see that I centralised the formatting.
BackgroundHighlighter::clearHighlights() might clear away any background highlights originally added as it sets the ENTIRE document's character background to the default format. This may be an unintended consequence of the result.
I'm having a hard time figuring out how to get the position(column and row) and the content in the QLineEdit. I'm using a eventFilter to get the signal but from there i'm stuck. any advice? Thank you
ui->tableWidget->setRowCount(5);
ui->tableWidget->setColumnCount(5);
QStringList wordList;
wordList << "alpha" << "omega" << "omega2" << "omega3" <<"omicron" << "zeta";
for(int i = 0; i<5;i++)
{
QLineEdit *lineEdit = new QLineEdit;
QCompleter *completer = new QCompleter(wordList);
completer->setCaseSensitivity(Qt::CaseInsensitive);
lineEdit->installEventFilter(this);
lineEdit->setCompleter(completer);
ui->tableWidget->setCellWidget(i,i,lineEdit);
}
....
bool MainWindow::eventFilter(QObject * object, QEvent *event)
{
}
I would like to get the position when I finish editing. I would like to pick a word from the list either through up and down key or left mouse click. Once a word is picked that word would populate the QLineEdit. Then i would want to know the position. Now, if the user writes a text different from the content of the list then no position should be returned. I'm only interested on whats in the "wordList". Thank you
As you indicate in your comments, you only want to obtain the text when an element that is set in the QCompleter is selected, for this we must use the void QCompleter::activated(const QString & text) signal.
To do this, a slot is created and the connection is made:
*.h
private slots:
void onActivated(const QString &text);
*.cpp
QCompleter *completer = new QCompleter(wordList);
...
connect(completer, qOverload<const QString &>(&QCompleter::activated), this, &MainWindow::onActivated);
There are 2 possible solutions:
The first to use the position of the QLineEdit that we obtain through the widget() method of the QCompleter, and the QCompleter we obtain it through sender() which is the object that emits the signal and pos(). then we get the QModelIndex with indexAt(), and this has the information of the row and column:
void MainWindow::onActivated(const QString &text)
{
QCompleter *completer = static_cast<QCompleter *>(sender());
QModelIndex ix = ui->tableWidget->indexAt(completer->widget()->pos());
if(ix.isValid()){
qDebug()<<ix.row()<<ix.column()<<text;
}
}
Or the row and column is saved as a property:
QCompleter *completer = new QCompleter(wordList);
...
completer->setProperty("row", i);
completer->setProperty("column", i);
void MainWindow::onActivated(const QString &text)
{
QCompleter *completer = static_cast<QCompleter *>(sender());
qDebug()<< completer->property("row").toInt()<<completer->property("column").toInt()<<text;
}
In the following link you can find both complete examples
I am building a C++ GUI application on QT Creator.
I changed the location to Portuguese/Brazil, now only comma is the decimal separator.
I need the QDoubleSpinBox to get as decimal separator dot and comma.
Officialy comma is the separator in Portuguese, but some keyboard only have points in the numeric part.
Please help,
subClass QDoubleSpinBox and reimplement the virtual method validate
full solution here :
customSpinBox.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QRegExpValidator>
#include <QDoubleSpinBox>
class CustomSpinBox : public QDoubleSpinBox {
Q_OBJECT
public:
explicit CustomSpinBox(QWidget* parent =0);
virtual QValidator::State validate(QString & text, int & pos) const;
private:
QRegExpValidator* validator;
};
#endif // WIDGET_H
customSpinBox.cpp
CustomSpinBox::CustomSpinBox(QWidget *parent):QDoubleSpinBox(parent),
validator(new QRegExpValidator(this))
{
validator->setRegExp(QRegExp("\\d{1,}(?:[,.]{1})\\d*"));
}
QValidator::State CustomSpinBox::validate(QString &text, int &pos) const
{
return validator->validate(text,pos);
}
I tried to converted the solution from basslo to Qt 6.0 but it failed to accept integer numbers. The following solution works fine for me with Qt 6.0. Also it did not work, wehen a suffix is set for the spin with i.e spinBox->setSuffix("mm");
My current solution below:
class CRelaxedDoubleSpinBox : public QDoubleSpinBox {
Q_OBJECT
bool doConvert = false;
QString decimalStr;
public:
explicit CRelaxedDoubleSpinBox(QWidget* parent =0, double minVal = -100, double maxVal = 100.) : QDoubleSpinBox(parent)
{
// setStepType(QAbstractSpinBox::AdaptiveDecimalStepType);
setMinimum(minVal);
setMaximum(maxVal);
setAlignment(Qt::AlignRight);
QLocale curLocale;
decimalStr = curLocale.decimalPoint();
doConvert = curLocale.decimalPoint() != ".";
}
virtual QValidator::State validate(QString & text, int & pos) const
{
QString s(text);
if(doConvert)
s = s.replace(".", decimalStr);
return QDoubleSpinBox::validate(s,pos);
}
double valueFromText(const QString& text) const
{
QString s(text);
if(doConvert)
s = s.replace(".", decimalStr);
return QDoubleSpinBox::valueFromText(s);
}
};
You can subclass QDoubleSpinBox and reimplement the validate method to accept both the dot and the comma as the decimal separator. I think you can get by with just adding a special check when the input is a period and allow it to be accepted (provided there are no other periods or commas in the string), but otherwise call the base class implementation. I haven't compiled or tested this, but I think this is pretty close:
MyDoubleSpinBox::validate (QString &input, int &pos)
{
if (input == ".")
{
return (text ().contains (".") || text ().contains (",")) ? QValidator::Invalid : QValidator::Acceptable;
}
return QDoubleSpinBox::validate (input, pos);
}
I'm using PySide6 and I adapted to the Python language the wonderful solution provided by #RED SOFT ADAIR to Python:
from PySide6.QtWidgets import QDoubleSpinBox,
class CustomDoubleSpinbox(QDoubleSpinBox):
def validate(self, text: str, pos: int) -> object:
text = text.replace(".", ",")
return QDoubleSpinBox.validate(self, text, pos)
def valueFromText(self, text: str) -> float:
text = text.replace(",", ".")
return float(text)
Here it is in case anyone ended up here in a similar situation.
Solution given by basslo does not work for me. I also need to reimplement the valueFromText method. Otherwise, when I press the enter key in the DoubleSpinBox, the value is set to 0.
Here is the class
doublespinboxwithcommasandpoints.h
#ifndef DOUBLESPINBOWITHCOMMASANDPOINTS_H
#define DOUBLESPINBOWITHCOMMASANDPOINTS_H
#include <QDoubleSpinBox>
#include <QRegularExpressionValidator>
class DoubleSpinBoxWithCommasAndPoints : public QDoubleSpinBox
{
Q_OBJECT
public:
explicit DoubleSpinBoxWithCommasAndPoints(QWidget* parent = nullptr);
virtual ~DoubleSpinBoxWithCommasAndPoints();
QValidator::State validate(QString & text, int & pos) const override;
qreal valueFromText(const QString &text) const override;
private:
QRegularExpressionValidator* m_validator;
QString current_suffix;
};
#endif // DOUBLESPINBOWITHCOMMASANDPOINTS_H
doublespinboxwithcommasandpoints.cpp
#include "doublespinboxwithcommasandpoints.h"
DoubleSpinBoxWithCommasAndPoints::DoubleSpinBoxWithCommasAndPoints(QWidget *parent)
: QDoubleSpinBox(parent)
, m_validator(nullptr)
, current_suffix(QString())
{
// validate positive or negative number written with "." or ","
// and that may have a suffix at the end
// also match empty string "(?![\\s\\S]\r\n)" to allow user to clear the whole field
const QString regex = QStringLiteral("((?![\\s\\S]\r\n)|-?\\d{1,}(?:[,.]{1})?\\d*)");
m_validator = new QRegularExpressionValidator(QRegularExpression(regex), this);
// check if a suffix has been added
connect(this, &DoubleSpinBoxWithCommasAndPoints::textChanged,
[this](){
if(!suffix().isEmpty()) {
if(current_suffix.localeAwareCompare(suffix()) == 0)
return;
else
current_suffix = suffix();
}
QString previous_regex = m_validator->regularExpression().pattern();
// remove the ending ")"
previous_regex.chop(1);
QString new_regex = previous_regex +
QStringLiteral("(?:") + suffix() + QStringLiteral(")?)");
m_validator->setRegularExpression(QRegularExpression(new_regex));
});
}
DoubleSpinBoxWithCommasAndPoints::~DoubleSpinBoxWithCommasAndPoints()
{
}
QValidator::State DoubleSpinBoxWithCommasAndPoints::validate(QString &text, int &pos) const
{
return m_validator->validate(text, pos);
}
qreal DoubleSpinBoxWithCommasAndPoints::valueFromText(const QString &text) const
{
QString temp = text;
temp.replace(QStringLiteral(","), QStringLiteral(".")); // replace comma with dot before toDouble()
temp.remove(suffix()); // remove suffix at the end of the value
return temp.toDouble();
}
Few points:
this code works with Qt6
the regular expression accepts negative numbers as well
the regular expression accepts numbers without comma/point
the regular expression supports suffix (code can be adapted to also support prefix)
the regular expression matchs empty string which allows users to clear the whole field
I have a simple program, in which I need to display japanese characters. It has a bit strange behavour( in my opinion ), sometimes it displays character as it is, sometimes it displays it like square.
My program logic is initiate vector, that contains alphabet InitAlphabet(); and then randomly choose a character painter.drawStaticText(0, 0, alphabet[randomNumb]);.
Can you pls tell why is this happening?
Code:
private:
Ui::Widget *ui;
QVector<QString> alphabet;
QVector<QString> translation;
int randomNumb;
void Widget::InitAlphabet()
{
alphabet.push_back("あ");
alphabet.push_back("い");
alphabet.push_back("う");
alphabet.push_back("え");
alphabet.push_back("お");
alphabet.push_back("か");
alphabet.push_back("き");
alphabet.push_back("く");
alphabet.push_back("け");
alphabet.push_back("こ");
alphabet.push_back("さ");
alphabet.push_back("し");
alphabet.push_back("す");
alphabet.push_back("せ");
alphabet.push_back("そ");
alphabet.push_back("た");
alphabet.push_back("ち");
alphabet.push_back("つ");
alphabet.push_back("て");
alphabet.push_back("と");
alphabet.push_back("な");
alphabet.push_back("に");
alphabet.push_back("ぬ");
alphabet.push_back("ね");
alphabet.push_back("の");
alphabet.push_back("は");
alphabet.push_back("ひ");
alphabet.push_back("ふ");
alphabet.push_back("へ");
alphabet.push_back("ほ");
alphabet.push_back("ま");
alphabet.push_back("み");
alphabet.push_back("む");
alphabet.push_back("め");
alphabet.push_back("も");
alphabet.push_back("や");
alphabet.push_back("ゆ");
alphabet.push_back("よ");
alphabet.push_back("ら");
alphabet.push_back("り");
alphabet.push_back("る");
alphabet.push_back("れ");
alphabet.push_back("ろ");
alphabet.push_back("わ");
alphabet.push_back("ん");
}
int Widget::getRandomNumber()
{
return qrand() % alphabet.length();
}
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.drawStaticText(0, 0, alphabet[randomNumb]);
// painter.drawStaticText(50, 50, translation[randomNumb]);
}