Extended Custom Completer Example
-
Hi,
I have been working on a extension for the text edit completer example. My goal was to add a hint to the suggested completion like Qt does.
The code works for me but I guess it could be done better. Also the current style is very ugly on Windows. I am looking for suggestion on how to improve the code and to make the popup nicer.Here is how you need to change the above mentioned example to add hints to the suggestion.
- In the TextEdit Class Definition you need to include a new delegate for the completer:
#include "completerdelegate.h"
- In the TextEdit Class Implementation
In the constructor you need to add two lines with the new delegate:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), completer(0) { createMenu(); cDeligate = new CompleterDelegate; // added completingTextEdit = new TextEdit; completer = new QCompleter(this); completer->setModel(modelFromFile(":/resources/wordlist.txt")); completer->popup()->setItemDelegate(cDeligate); // added. Needs to be done every time the model is set. completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setWrapAround(false); completingTextEdit->setCompleter(completer); setCentralWidget(completingTextEdit); resize(500, 300); setWindowTitle(tr("Completer")); }
The text under curser function is quite limited. In case there is no space after the curser it will not return the correct word. Like QStr|) will return ")" and not "QStr". I fixed that like this:
QString CodeEditor::textUnderCursor() const{ QTextCursor tc = textCursor(); tc.select(QTextCursor::WordUnderCursor); QString mText = tc.selectedText(); if(eow.contains(mText.left(1))){ tc.setPosition(tc.selectionStart()-1); tc.select(QTextCursor::WordUnderCursor); mText = tc.selectedText(); } return mText.trimmed(); }
Where my end of word string looks like this:
eow = "~!@#$%^&*)+}|:<>?,./;'[]\\-=\"";
In the key press event function a small modification is required to calculate the correct new width of the popup:
QRect cr = cursorRect(); cr.setWidth(c->popup()->sizeHintForColumn(0)+ c->popup()->sizeHintForColumn(1) + c->popup()->verticalScrollBar()->sizeHint().width()); c->complete(cr); // popup it up!
Here is still a small bug. The longest word will have a box of two lines...
The model from file function needs to be entirely rewritten. In stead of a QStringListModel you need a QStandardItemModel
QStandardItemModel *standardItemModel = new QStandardItemModel(this); while(XY){ QList<QStandardItem *> tmpList; tmpList << new QStandardItem("String for completion"); tmpList << new QStandardItem("Hint that should be displayed"); standardItemModel->appendRow(tmpList); } return standardItemModel
- And the new delegate:
Header
#ifndef COMPLETERDELEGATE_H #define COMPLETERDELEGATE_H #include <QObject> #include <QApplication> #include <QStyledItemDelegate> #include <QPainter> #include <QStyle> #include <QTextDocument> #include <QAbstractTextDocumentLayout> #include <QStyleOption> #include <QDebug> class CompleterDelegate : public QStyledItemDelegate { public: CompleterDelegate(); protected: void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const; QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const; }; #endif // COMPLETERDELEGATE_H
Probably not all includes are required but I was just adding until it worked...
and the cpp file
#include "completerdelegate.h" CompleterDelegate::CompleterDelegate() { } void CompleterDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem optionV4 = option; initStyleOption(&optionV4, index); QStyleOptionViewItem hint = option; initStyleOption(&hint, index.sibling(index.row(),1)); QStyle *style = optionV4.widget? optionV4.widget->style() : QApplication::style(); QTextDocument doc; doc.setHtml(optionV4.text+" <b>"+hint.text+"</b>"); // Painting item without text optionV4.text = QString(); style->drawControl(QStyle::CE_ItemViewItem, &optionV4, painter); QAbstractTextDocumentLayout::PaintContext ctx; QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &optionV4); painter->save(); painter->translate(textRect.topLeft()); painter->setClipRect(textRect.translated(-textRect.topLeft())); // Highlighting text if item is selected if (optionV4.state & QStyle::State_Selected){ ctx.palette.setColor(QPalette::Text, optionV4.palette.color(QPalette::Active, QPalette::HighlightedText)); QStyleOptionViewItem toolTip = option; initStyleOption(&toolTip, index.sibling(index.row(),2)); QToolTip::showText(QPoint(textRect.right() + 15, textRect.top()), toolTip.text ); } doc.documentLayout()->draw(painter, ctx); painter->restore(); } QSize CompleterDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem optionV4 = option; initStyleOption(&optionV4, index); QStyleOptionViewItem hint = option; initStyleOption(&hint, index.sibling(index.row(),1)); QTextDocument doc; doc.setHtml(optionV4.text+" <b>"+hint.text+"</b>"); doc.setTextWidth(optionV4.rect.width()); return QSize(doc.idealWidth(), doc.size().height()); }
The popup will now show the hint in bold right after the suggested completion. The hint will not be inserted on enter.
Room for improvements:
- On Windows this style is rather ugly since the highlighted word is white with a light grey background and light blue border. The styling needs to be done completely different but I don't know how, yet.
- I could not manage to get some space between the suggestion and the hint. I tried <span>, <div> and table to get all hints aligned but all html and css tags besides bold and italic were ignored.
So I hope that someone could give me some feedback on how to improve the implementation and the delegate.
Thanks!
-
I made a small update on the code. In the delegate I reworked the highlight section:
QAbstractTextDocumentLayout::PaintContext ctx; QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &optionV4); painter->save(); painter->translate(textRect.topLeft()); painter->setClipRect(textRect.translated(-textRect.topLeft())); // Highlighting text if item is selected if (optionV4.state & QStyle::State_Selected){ ctx.palette.setColor(QPalette::Text, optionV4.palette.color(QPalette::Active, QPalette::HighlightedText)); QStyleOptionViewItem toolTip = option; initStyleOption(&toolTip, index.sibling(index.row(),2)); QToolTip::showText(QPoint(textRect.right() + 15, textRect.top()), toolTip.text ); } doc.documentLayout()->draw(painter, ctx); painter->restore();
This gives me a nice tool tip but not at the right location. I want it next to the completer but I cannot figure out how to get the coordinates. Since the delegate is not a widget I cannot use this->maptoglobal(). Any ideas on how I could position the tool tip next to the highlighted element?