Highlight text that matches a search string in Qt5 - c++

First let me explain what I'm trying to achieve:
In your browser, hit Ctrl + f and type "q". The result of that operation is what I'm trying to achieve. It seems like this should be a solved problem, but despite the many hours I've spent researching, reading documentation, and asking around in the Qt IRC, I'm still stuck. Maybe somebody here will be able to give me a hand.
The following Qt classes are what I'm currently dealing with, in case you want to brush up a bit.:
Qt::DisplayRole
QModelIndex::data
QAbstractItemView::setDelegate
QTextEdit::setExtraSelections (only as a reference)
QAbstractDocumentLayout::Selection
QTextDocument::drawContents
QStyledItemDelegate::paint
QStyleOptionViewItem
QStyleOption::rect
QTableView
QAbstractItemModel::match
Learn to Model/View Program with Qt
So now, let me explain how I have things set up, and what the results of my research and interactions with the Qt IRC has lead me to believe I should do.
I am using a QStandardItemModel along with a QTableView. Each row that gets appended to the QStandardItemModel has several columns. As we know, each of these columns is represented in the QStandardItemModel as a QModelIndex. We can pull its displayed text by accessing its data(Qt::DisplayRole).
Conveniently, given a search string, QStandardItemModel::match will return a QModelIndexList of every QModelIndex that, well, matched in the column. Of course, if you have several columns in every row, you would need to preform this match over each column, but I can worry about performance here later.
So cool, that problem is solved. We have our list of every QModelIndex that had a matching string. Now, what I want to do is highlight that string in each column. For example:
Search string: "col"
This is a column | This is aColumn | This is aColumn | This is a co
Parts that would appear highlighted are what I have in bold above. In order to achieve this, I knew from reading the Model/View docs that I needed to subclass a QStyledItemDelegate and reimplement its paint function. So I started there.
The next problem to solve would be, how in the world to select a specific piece of text in the DisplayRole and only highlight it? You can't use Qt::BackgroundRole, that would set the entire index's background color. Enter QTextDocument.
I still need to do more digging to see how exactly I can implement that behavior, but from what I was told in Qt IRC, QTextEdit has a function called setExtraSelections. Looking at how that is implemented, it leverages QAbstractTextDocumentLayout::Selection and various cursor functions available to me in QTextDocument.
However, sadly I cannot even begin to solve that problem, because the first step is to actually ensure that I can render a QTextDocument to the QTableView with my reimplemented custom delegate paint function. This is where I'm stuck currently. I've seen this post, here and the one it references:
Similar question that doesn't solve my issue
Its not exactly what I was looking for, but I figured it would help me at least get something rendering. It looks though like in his code, he's drawing the control (which is literally what QStyledItemDelegate does), and then tries to paint his QTextDocument over it. Maybe that's not what that is doing but it sure looks like it.
In any case, when I tried that, it looked like the QTextDocument had no effect. If I commented out the drawControl call, then no text would be rendered at all. Here is my code for that:
void CustomDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
if (index.column() == contentColumn)
{
painter->save();
QTextDocument contentDocument;
//opt.widget->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
opt.text = "Things";
painter->setBrush(QBrush(Qt::darkCyan));
contentDocument.drawContents(painter, opt.rect);
painter->restore();
}
else
{
QStyledItemDelegate::paint(painter, option, index);
}
}
You can see I just typed text "Things" to see if I could just get that to render. To no avail.
Before summarizing my question I want to mention that no, I cannot use a QTextEdit. There are specific columns that I need to word wrap, while others I do not want to word wrap. QTableView displays the data exactly as I expect to see it.
So my question is this:
Is the QTextDocument rendering through the paint event of a subclassed QStyledItemDelegate the preferred way to handle this? If not, how else should I deal with it?
If so, how do I make it work as I expect? What is wrong with the code that I have?
Bonus 2 parter question
Once I can render, how can I actually leverage the QTextDocument API to highlight only specific pieces, given that the list of model indexes that contain the text to be highlighted is discovered in a completely different function and at a different time than when the paint function executes?
Update 0
Using the QTextDocument API to make multiple selections is looking like an impossible feat. The column that I would be drawing contents to needs to word wrap and expand.
From what I can tell, I calling drawContents manually means handling resizing, word wrapping, and many other things manually which is not a path I want to go down if that is indeed the case.
I've got another way in mind, I will update if it works.
Update 1
I have accomplished what I want in another way. Unfortunately, I cannot use Qt for what I want. Qt's Model/View system is entirely too inefficient and getting it to do what I need causes it to be extremely and unacceptably slow. How did I accomplish the highlighting?
I will describe in an answer. If nobody gives me a better answer, I will pick mine as the best.

I believe what you need to do here is override this method in your QItemDelegate subclass:
void QItemDelegate::drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QString &text) const
The problem with QItemDelegate's implementation of this method is at the end, where we see this method call (it's actually called in two places, but since they are identical calls I'm just showing it once):
d->textLayout.draw(painter, layoutRect.topLeft(), QVector<QTextLayout::FormatRange>(), layoutRect);
It's that third argument that you'd want to change -- in QItemDelegate::drawDisplay(), it's hard-coded to always be an empty QVector, which means that all of the characters in the drawn text-string will be always have the same formatting. If you could somehow get it to be called with a QVector<QTextLayout::FormatRange> that contained your per-substring formatting preferences instead, you would get the effect you want.
Of course, the devil is in the details. If you don't mind hacking the Qt source code to fit your purpose, you could do that; but that would be an ugly solution since it would mean your program would no longer work correctly when compiled with a non-customized Qt release (and you'd have to re-patch every time you upgraded to a new Qt release).
So instead you'd probably want to start by copying the contents of the QItemDelegate::drawDisplay() method over to your subclass's method, then modifying the copied version as necessary. (Of course that has its own problems, since QItemDelegate::drawDisplay() references private member variables that your subclass doesn't have access to, but you can probably work around those. You might also want to check with the Qt people to see if there are any legal problems with copying out a method body like that; I'm not sure if there are or not. If you're not comfortable with copying it, you can at least look at it for inspiration about the sorts of things your implementation of drawDisplay() will probably also need to do)

Essentially, I found that getting the QTableView to display rich text was a common use case that people on forums were trying to accomplish. Since this was a solved problem, I tried to see how I could leverage HTML.
First, I set up my custom delegate to handle rich text. Then I had an algorithm that went a bit like this:
clear all html tags from the display role in every row of the specified column
for every row in the specified column
populate a list of QModelIndex with matching text in the Display Role
for every QModelIndex with matching text in the display role
while there is another occurance of the matching string in the display role
inject html highlight (span) around the matching word
This is, of course, extremely, painfully, unacceptably slow. It does work though, has the exact same effect as hitting ctrl + f. But, I cannot use it. Its a shame that Qt doesn't support something as basic as this. Oh well.

My approach to that problem was to use the paint function from the delegate to render one or several positions from a cursor in a QTextDocument.
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if ( !index.isValid() )
return;
// Higlight some text
{
QString dataHighlight QString("col"); // The text to highlight.
QString value = index.model()->data(index, Qt::DisplayRole).toString();
QTextDocument *doc = new QTextDocument(value);
QTextCharFormat selection;
int position = 0;
QTextCursor cur;
// We have to iterate through the QTextDocument to find ALL matching places
do {
cur = doc->find(dataHighlight,position);
cur.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
cur.selectionStart();
cur.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
cur.selectionEnd();
position = cur.position();
selection.setBackground(Qt::yellow);
cur.setCharFormat(selection);
} while (!cur.isNull());
painter->save();
painter->translate(option.rect.x(), option.rect.y());
doc->drawContents(painter);
painter->restore();
delete doc;
}
}

Related

QTreeView scroll to current row after sort

I'm adapting QT Creator Basic Sort/Filter Model Example to work with my own source data (my movie library). That link shows what the example display looks like, and here's a link to the relatively small amount of source code. I don't really understand everything I'm doing, but mostly things have gone well until I hit this current problem...
How can I update the view so currently-selected row ALWAYS stays visible after changing sort order?
I hope someone will tell me exactly what C++ code needs to added / modified in the actual example program. There's probably no point in posting links to QT documentation, or "similar" code doing much the same thing in other contexts, because I don't think I'll be able to understand how to adapt such information to my own needs.
I'm using latest QT Creator 6.0.2 running under latest Linux Mint 20.3 Cinnamon, but I'm not sure that makes any difference, and I don't know what if any other information might be relevant.
EDIT:
I assume the extra code to force "scroll to current selection after sort" logically fits in QT Creator source code file examples/ widgets/ itemviews/ basicsortfiltermodel/ window.cpp
I commented out line 122 in that file (suppress display of the "source" table, because I only want the "proxy" view). Other than that, the source file in my derived project is identical to the original as held here on GitHub (along with the header & main source for the example project, per my second link above).
Effectively, I'm asking for the smallest possible change to the actual example project code to ensure the currently selected line never disappears from view after sorting.
The exact example from QT documentation has less than a dozen rows of data, so exact control of display positioning doesn't matter much. But I'll normally have a lot of rows visible, and the currently selected row could appear at any position within the visible window. Sometimes, "current row" might not actually be visible before a sort, but I'd like it to always be visible after.
It might be asking a lot, but I'd be seriously impressed if anyone comes up with succinct code to ensure that wherever possible, the currently highlighted row stays in position (doesn't jump up or down) while the rows above and/or below change as a result of sorting.
If you want to scroll to current row after sorting, then I would suggest doing the following four changes to your code.
You should implemented your own model by inheriting from QStandardItemModel and in this derived model class:
add a signal sorted() to your model class
override virtual method sort() (https://doc.qt.io/qt-5/qabstractitemmodel.html#sort)
void YourModel::sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override
{
QStadardItemModel::sort(column, order);
emit sorted(); // this is the signal you added
}
Then create a method which will access the tree view and would do this:
void scrollAfterSort()
{
treeView->scrollTo(treeView->currentIndex());
}
Connect YourModel::sorted() signal to this scrollAfterSort() method.

How do I write a WYSIWYG editor?

I would like to program a WYSIWYG editor for HTML. I'm looking for a high level approach, which I will eventually be implementing in C++.
My initial approach is to create a hierarchy of classes which extend a common base class (node). So object "body" would contain object "p" which would contain object "b" which would contain some text.
class node {
node *parent;
vector<node> children;
string name;
map<string,string> attributes;
string text;
virtual void render(const rect &rect, const point &offset) = 0;
virtual void onEvent(const event &e);
}
The main engine would call something like body.render(screen, point(0, 0)), which would recursively render its children.
The cursor would be represented by a pointer into the object hierarchy, and each node would have its own internal cursor state, and would respond to keyboard events when it is the selected node.
For example, if the user hits the left-arrow, and the "p" node is selected, the "p" node's reaction to the keypress might be to change the current node to the parent of "p".
Abstractly, it seems like this could work, the closest thing I can find to what I'm looking for is Sigil, which at first glance seems pretty intimidating to study (main.cpp is 70k).
Before I go down this road, I was wondering if anyone had a simpler approach, or can see any pitfalls with this method.
A simpler approach, if you can do this, is to embed a web browser in your application and set contentEditable="true" on <body>. If you're on Windows, you can use the built-in one, or you can embed an engine like Gecko or WebKit.
You can look at doing color picking.
Basically you render everything twice. Once is what is displayed to the user.
The other is a bunch of bounding boxes done in unique random colours that you can turn back into pointers via a hashtable lookup or whatever. This allows you to quickly locate what is under a mouse cursor without having to recursively look up every object. You might want to miss the currently selecvted object from the color picking that way you can click twice to get the object below you current one (if you want to keep going for lower you will need to mess around with zorder depth somehow. Maybe don't render anything in the selected elements bounding box region that is over the zorder of your currently selected object).
Finally once you have selected the specific element you can enter a sub editing mode. For example turning a block of text into an editable text box with a blinking cursor, drawing borders/handles around your element to allow you to resize/manipulate it and showing a list of properties.
Realistically if your looking at making a HTML WYSIWYG editor, that will also require a HTML rendering engine. Unless you plan to make your own from scratch (which is a massive undertaking by itself), or only plan on implementing a very limited subset of HTML (which would still be fairly time-consuming). Most HTML renderer engines will include some kind of support for tapping into things like object picking. It might be worth grabbing webkit and seeing what it supports. Maybe look at the Chromium source code (Chrome includes an inbuilt webpage inspector that isn't too far off being an editor (it allows object picking and runtime editing of properties but the editing isn't totally WYSIWYG since its done in an external window, but that might be enough for what you want.
You might even do better to look at writing you editor (or parts of it) as some kind of JavaScript/Grease Monkey extension that you can load over the page being edited. You could write your whole editor in HTML or just add some support handle wrapper script that communicates to your native program.

Is it possible to reference individual tabs of a QTabWidget by tab number?

Very quick question here. I was wondering if it is possible for me to reference individual tabs from a QTabWidget by number. This will save me a lot of time, as I am generating an unknown number of tabs during run-time. I could not find anything in the QT documentation, but I feel like this is a very basic feature that should be included. I am thinking something like this (not real code just an idea, I realize tabNumber() doesn't exist):
ui->tabArea->tabNumber(12);
If there isn't a public function, perhaps there's some other way? Please don't suggest referencing tabs by name because that is out of the question (potentially 100's of tabs), and I have already tried it.
If you want the tab with a certain index, use widget():
QWidget* tab = tabWidget->widget( index );
I think the setCurrentIndex() method is what you are looking for.

Qt::How to lower the text in a QSpinBox

I'm using a spinbox with a custom font which looks too high in the spinbox. How do I move the text lower?
I have already reimplemented QStyle and made the font lower in another widget but I can't find where to do it with the spinbox. There must be a QRect somewhere where you can just move the top of it but I don't know and can't seem to find where it is.
Qt specifies a QStyle::SC_SpinBoxEditField, which appears to be what you want to modify. If I recall correctly from a few years ago when I was doing stuff with styles, you should be able to hook into getting options for that subcontrol, which would include the rect within which it is supposed to be drawn. Modifying that might get the result you want. If not, it is a place to begin searching for your answer.
This is more of a guess than a positive answer, but you might be able to do this with stylesheets:
spinbox->setStyleSheet("QSpinBox { bottom: -2px;}");
Ideally there would be a subcontrol or something for just the text, but the stylesheet documentation doesn't list one, which might imply the above will have undesirable consequences.
You can do:
spinBox->setAlignment(Qt::AlignCenter);//Or the Align Flag that you want
I hope this help.

Qt - How to do superscripts and subscripts in a QLineEdit?

I need to have the ability to use superscripts asnd subscripts in a QLineEdit in Qt 4.6. I know how to do superscripts and subscripts in a QTextEdit as seen below but I can't figure out how to do them in QLineEdit because the class doesn't contain a mergeCurrentCharFormat() function like QTextEdit does. Please help. Thanks
void MainWindow::superscriptFormat()
{
QTextCharFormat format;
format.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
if(ui->txtEdit->hasFocus())
ui->txtEdit->mergeCurrentCharFormat(format);
}
QLineEdit wasn't really made for this type of thing, as it was designed for simple text entry. You have a few options, however. The simplest one is to do as Hostile Fork suggested and use a QTextEdit, and add a style override to not show the scroll bar (which I assume would remove the arrows). The more complex one would be to either inherit QLineEdit and do your own drawing, or to make your own widget completely that appears similar to the QLineEdits do.