Displaying UTF characters in Qt - c++

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]);
}

Related

Qt highlighting selected line overwrites highlighting of individual words

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.

Parent function terminates whenever I try to call QTextCharFormat on QTextCursor selection

I recently ran into a weird issue where my QPlainTextEdit::selectionChanged handler function terminates prematurely whenever QTextCursor::mergeCharFormat/setCharFormat/setBlockCharFormat is called. Additionally, after terminating it gets called again and runs into the same issue, leading to an infinite loop.
I'm trying to replicate a feature present in many text editors (such as Notepad++) where upon selecting a word, all similar words in the entire document are highlighted. My TextEditor class is overloaded from QPlainTextEdit.
The minimal reproducible example is as follows:
main.cpp:
#include "mainWindow.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
mainWindow w;
w.show();
return a.exec();
}
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 dolor sit test");
ui.tabWidget->addTab(textEdit, "Editor"); //Or any other way of adding the widget to the window
}
private:
Ui::mainWindowClass ui;
};
TextEditor.h:
The regex highlighter part is based on this SO answer.
#pragma once
#include <QPlainTextEdit>
class TextEditor : public QPlainTextEdit
{
Q_OBJECT
public:
TextEditor(QWidget* parent) : QPlainTextEdit(parent)
{
connect(this, &QPlainTextEdit::selectionChanged, this, &TextEditor::selectChangeHandler);
}
private:
void selectChangeHandler()
{
//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;
//Prepare text format
QTextCharFormat format;
format.setBackground(Qt::green);
//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(qselection);
auto cur = textCursor();
auto index = reg.indexIn(plaintext, pos);
while (index >= 0)
{
//Select matched text and apply format
cur.setPosition(index);
cur.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
cur.mergeCharFormat(format); //This causes the selectChangeHandler function to terminate and then execute again, causing an infinite loop leading to a stack overflow
//Move to next match
pos = index + (size_t)reg.matchedLength();
index = reg.indexIn(plaintext, pos);
}
}
};
I suspect the format fails to apply for some reason, possibly causing an exception that gets caught inside Qt and terminates the parent function. I tried adding my own try-catch handler around the problematic area, but it did nothing (as expected).
I'm not sure whether this is my fault or a bug inside Qt. Does anybody know what I'm doing wrong or how to work around this issue?
An infinite loop is being generated because it seems that getting the text changes also changes the selection. One possible solution is to block the signals using QSignalBlocker:
void selectChangeHandler()
{
const QSignalBlocker blocker(this); // <--- this line
//Ignore empty selections
if (textCursor().selectionStart() >= textCursor().selectionEnd())
return;
// ...

Style Sheet ignored on class inherited from QWidget [duplicate]

I have several custom widget in my current project. I wish to apply stylesheets to them and when I do so inside Qt Creator, it appears to work. However, when executing the program, no stylesheet is used. The stylesheets for the Qt widgets are working normally.
Does anyone have any advice?
WidgetUnits.h
#ifndef WIDGETUNITS_H
#define WIDGETUNITS_H
#include <QList>
#include <QWidget>
#include <QPainter>
#include <Widgets/JECButton.h>
#include <Unit.h>
#include <Time.h>
namespace Ui
{
class WidgetUnits;
}
class WidgetUnits : public QWidget
{
Q_OBJECT
public:
explicit WidgetUnits(QWidget *parent = 0);
~WidgetUnits();
void setNumTimes(const int& numTimes);
public slots:
void updatePictures(const Time* time);
protected:
void paintEvent(QPaintEvent *event);
private:
void checkNewQueue(const QList<QList<Unit*>*>* units);
Ui::WidgetUnits *ui;
const int pictureWidth; // The width of the Unit pictures.
const int pictureHeight; // The height of the Unit pictures.
QList<QList<JECButton*>*> buttonPictures; // The Units' pictures. The outer QList stores the QList of pictures for a given tick.
// The inner QList stores the JECButtons for the specific tick.
};
WidgetUnits.cpp
#include "WidgetUnits.h"
#include "ui_WidgetUnits.h"
WidgetUnits::WidgetUnits(QWidget *parent):
QWidget(parent),
ui(new Ui::WidgetUnits),
pictureWidth(36),
pictureHeight(36)
{
ui->setupUi(this);
}
WidgetUnits::~WidgetUnits()
{
delete ui;
}
void WidgetUnits::updatePictures(const Time *time)
{
// Only showing units that started to get built this turn.
checkNewQueue(time->getUnits());
checkNewQueue(time->getBuildings());
checkNewQueue(time->getUpgrades());
// Updating the position of the remaining pictures (after some were removed).
// Checking the maximum number of Units made in one tick.
int maxNewQueue = 0;
for (int a = 0; a < buttonPictures.length(); ++a)
{
if (buttonPictures.at(a)->length() > maxNewQueue)
{
maxNewQueue = buttonPictures.at(a)->length();
}
}
if (buttonPictures.length() > 0)
{
this->setGeometry(0, 0, buttonPictures.length() * 130,
maxNewQueue * (pictureWidth + 10) + 20);
QList<JECButton*>* tickButtons = 0;
for (int a = 0; a < buttonPictures.length(); ++a)
{
tickButtons = buttonPictures.at(a);
for (int b = 0; b < tickButtons->length(); ++b)
{
tickButtons->at(b)->move(a * 130, b * (pictureHeight + 10));
}
}
}
update();
}
void WidgetUnits::checkNewQueue(const QList<QList<Unit *> *> *units)
{
if (units != 0)
{
const Unit* currentUnit = 0;
JECButton* currentButton = 0;
for (int a = 0; a < units->length(); ++a)
{
buttonPictures.append(new QList<JECButton*>());
for (int b = 0; b < units->at(a)->length(); ++b)
{
currentUnit = units->at(a)->at(b);
// Verifying that there is an item in the queue and the queue action was started this turn.
if (currentUnit->getQueue() != 0 && currentUnit->getAction()->getTimeStart() == currentUnit->getAction()->getTimeCurrent()
&& (currentUnit->getAction()->getType() == Action::BUILD || currentUnit->getAction()->getType() == Action::TRAIN ||
currentUnit->getAction()->getType() == Action::UPGRADE))
{
buttonPictures.last()->append(new JECButton(this));
currentButton = buttonPictures.last()->last();
QImage* image = new QImage(currentUnit->getQueue()->getUnitBase()->getImage().scaled(pictureWidth, pictureHeight));
currentButton->setImage(*image);
currentButton->setGeometry(0, 0, currentButton->getImage().width(),
currentButton->getImage().height());
currentButton->setColorHover(QColor(0, 0, 225));
currentButton->setColorPressed(QColor(120, 120, 120));
currentButton->setImageOwner(true);
currentButton->setVisible(true);
}
}
}
}
}
void WidgetUnits::setNumTimes(const int &numTimes)
{
// Appending new button lists for added ticks.
for (int a = buttonPictures.length(); a < numTimes; ++a)
{
buttonPictures.append(new QList<JECButton*>());
}
}
void WidgetUnits::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
}
The widget is visible- I set a tooltip which it showed me (It's just the same color of the QScrollArea it's sitting in).
I had a similar problem and it was solved using jecjackal's comment. As sjwarner said, it would be much more noticeable in the form of an answer. So I'll provide it. For the benefit of any future viewers. Again, it isn't my answer! Appreciate jecjackal for it!
As it is said in the Qt's stylesheets reference, applying CSS styles to custom widgets inherited from QWidget requires reimplementing paintEvent() in that way:
void CustomWidget::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
Without doing it your custom widgets will support only the background, background-clip and background-origin properties.
You can read about it here: Qt Stylesheets reference in the section "List of Stylable Widgets" -> QWidget.
There is an answer much easier than writing your own paintEvent: subclass QFrame instead of QWidget and it will work right away:
class WidgetUnits : public QFrame
{
Q_OBJECT
....
For completeness, the same problem is present in PyQt. You can apply a stylesheet to a subclassed QWidget by adding similar code:
def paintEvent(self, pe):
opt = QtGui.QStyleOption()
opt.init(self)
p = QtGui.QPainter(self)
s = self.style()
s.drawPrimitive(QtGui.QStyle.PE_Widget, opt, p, self)
I had same problem with pyside. I post my solution just for completeness.
It is almost like in PyQt as Pieter-Jan Busschaert proposed.
only difference is you need to call initFrom instead of init
def paintEvent(self, evt):
super(FreeDockWidget,self).paintEvent(evt)
opt = QtGui.QStyleOption()
opt.initFrom(self)
p = QtGui.QPainter(self)
s = self.style()
s.drawPrimitive(QtGui.QStyle.PE_Widget, opt, p, self)
One other thing you need to make sure is that you define your custom widget in your css file the following way:
FreeDockWidget{...}
and not like often recommended
QDockWidget#FreeDockWidget{...}
Calling setAttribute(Qt::WA_StyledBackground, true) for the custom widget worked for me.
Setting Qt::WA_StyledBackground to true only works if you remember to add Q_OBJECT to your class. With these two changes you don't need to reimplement paintEvent.

How to display graphics objects behind or foreground of text inside QTextEdit in Qt?

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.

Changing the value of a dial slide with an input in QT?

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);
}
}