@SGaist Thanks! I've done the behavior I need without using editors.
I'll leave the code here. In the future, it may be useful to someone:
class Document : public QTextDocument
{
Q_OBJECT
public:
explicit Document(QObject *parent = nullptr)
: QTextDocument{parent}
, textCursor_{this}
{
setDocumentMargin(0);
}
int selectionStartPos() const
{
return textCursor_.selectionStart();
}
int selectionEndPos() const
{
return textCursor_.selectionEnd();
}
void setCursorPosition(int pos, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor)
{
clearSelection();
textCursor_.setPosition(pos, mode);
highlightSelection();
}
bool moveCursorPosition(QTextCursor::MoveOperation op,
QTextCursor::MoveMode mm= QTextCursor::MoveAnchor,
int n = 1)
{
clearSelection();
const bool result = textCursor_.movePosition(op, mm, n);
highlightSelection();
return result;
}
void select(QTextCursor::SelectionType selection)
{
clearSelection();
textCursor_.select(selection);
selectedWordOnDoubleClick_ = textCursor_;
highlightSelection();
}
void extendWordwiseSelection(int newPos)
{
QTextCursor selected = textCursor_;
QTextCursor tempCursor = textCursor_;
tempCursor.setPosition(newPos, QTextCursor::KeepAnchor);
if (!tempCursor.movePosition(QTextCursor::StartOfWord))
return;
const int wordStartPos = tempCursor.position();
if (!tempCursor.movePosition(QTextCursor::EndOfWord))
return;
const int wordEndPos = tempCursor.position();
if (newPos < selectedWordOnDoubleClick_.position()) {
setCursorPosition(selected.selectionEnd());
setCursorPosition(wordStartPos, QTextCursor::KeepAnchor);
} else {
setCursorPosition(selected.selectionStart());
setCursorPosition(wordEndPos, QTextCursor::KeepAnchor);
}
}
QString selectedText() const
{
return textCursor_.selectedText();
}
private:
void clearSelection()
{
QTextCharFormat defaultFormat;
textCursor_.setCharFormat(defaultFormat);
}
void highlightSelection()
{
QTextCharFormat selectFormat = textCursor_.charFormat();
selectFormat.setBackground(QApplication::palette().highlight().color());
selectFormat.setForeground(QApplication::palette().highlightedText().color());
textCursor_.setCharFormat(selectFormat);
}
private:
QTextCursor textCursor_;
QTextCursor selectedWordOnDoubleClick_;
};
class Delegate : public QStyledItemDelegate
{
public:
Delegate(QObject *parent = nullptr)
: QStyledItemDelegate{parent}
{
}
void paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const override
{
painter->save();
painter->fillRect(option.rect, Qt::red);
if (index == interactiveIndex_) {
painter->translate(option.rect.topLeft());
doc_->documentLayout()->draw(painter, QAbstractTextDocumentLayout::PaintContext());
} else {
const auto text = index.data(Model::MyText).toString();
painter->drawText(option.rect, text);
}
painter->restore();
}
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
return QSize{option.rect.width(), 40};
}
QModelIndex interactiveIndex() const
{
return interactiveIndex_;
}
void setInteractiveIndex(const QModelIndex &newInteractiveIndex)
{
interactiveIndex_ = newInteractiveIndex;
}
QSharedPointer<Document> doc() const
{
return doc_;
}
void setDoc(QSharedPointer<Document> newDoc)
{
doc_ = newDoc;
}
private:
QModelIndex interactiveIndex_;
QSharedPointer<Document> doc_;
};
class Model : public QAbstractListModel
{
public:
enum Roles {
MyText = Qt::UserRole + 1
};
Model(QObject *parent = nullptr)
: QAbstractListModel{parent}
{
texts_.append("One One One\n"
"One One One");
texts_.append("Two Two Two\n"
"Two Two Two");
texts_.append("Three Three Three\n"
"Three Three Three");
texts_.append("Four Four Four\n"
"Four Four Four");
}
int rowCount(const QModelIndex &parent) const override
{
return texts_.size();
}
QVariant data(const QModelIndex &index, int role) const override
{
if (role == Roles::MyText)
return texts_[index.row()];
return QVariant{};
}
Qt::ItemFlags flags(const QModelIndex &index) const override
{
return QAbstractItemModel::flags(index);
}
private:
QStringList texts_;
};
class View : public QListView
{
public:
View(QWidget *parent = nullptr)
: QListView{parent}
, model_{new Model{this}}
, delegate_{new Delegate{this}}
{
setModel(model_);
setItemDelegate(delegate_);
setMouseTracking(true);
}
protected:
void mousePressEvent(QMouseEvent *event) override
{
pressedPos_ = event->pos();
previousPressedIndex_ = pressedIndex_;
pressedIndex_ = indexAt(pressedPos_);
if (pressedIndex_ != previousPressedIndex_) {
// Repaint previous pressed as static text. Means no selection draws
update(previousPressedIndex_);
}
const QString text = pressedIndex_.data(Model::MyText).toString();
QSharedPointer<Document> doc{new Document};
doc->setPlainText(text);
const int startPos = getRelativeCursorPos(doc, pressedIndex_, pressedPos_);
doc->setCursorPosition(startPos);
delegate_->setInteractiveIndex(pressedIndex_);
delegate_->setDoc(doc);
wordSelection_ = false;
QListView::mousePressEvent(event);
}
void mouseMoveEvent(QMouseEvent *event) override
{
const QPoint currentMousePos = event->pos();
if (indexAt(currentMousePos).isValid()) {
if ((!QApplication::overrideCursor() || QApplication::overrideCursor()->shape() != Qt::IBeamCursor)) {
QApplication::setOverrideCursor(Qt::IBeamCursor);
}
} else {
QApplication::restoreOverrideCursor();
}
if (event->buttons() & Qt::LeftButton) {
const QPoint currentMousePos = event->pos();
QSharedPointer<Document> doc = delegate_->doc();
const int endPos = getRelativeCursorPos(doc, pressedIndex_, currentMousePos);
if (wordSelection_) {
doc->extendWordwiseSelection(endPos);
} else {
doc->setCursorPosition(endPos, QTextCursor::KeepAnchor);
}
update(pressedIndex_);
}
QListView::mouseMoveEvent(event);
}
void mouseDoubleClickEvent(QMouseEvent *event) override
{
QSharedPointer<Document> doc = delegate_->doc();
doc->select(QTextCursor::WordUnderCursor);
update(pressedIndex_);
wordSelection_ = true;
QListView::mouseDoubleClickEvent(event);
}
void mouseReleaseEvent(QMouseEvent *event) override
{
QListView::mouseReleaseEvent(event);
QSharedPointer<Document> doc = delegate_->doc();
QApplication::clipboard()->setText(doc->selectedText());
}
private:
int getRelativeCursorPos(const QSharedPointer<Document> &doc,
const QModelIndex &index,
const QPoint &mousePos) const
{
const QRect itemRect = visualRect(index);
const QPoint relativePos = mousePos - itemRect.topLeft();
return doc->documentLayout()->hitTest(relativePos, Qt::FuzzyHit);
}
private:
Model *model_ = nullptr;
Delegate *delegate_ = nullptr;
QModelIndex previousPressedIndex_;
QModelIndex pressedIndex_;
QPoint pressedPos_;
bool wordSelection_ = false;
};
Perhaps the code is too big to ask if it can be improved somewhere. I did everything I could. But still I will leave the post unresolved for a short time. Maybe there will be people who will advise me to improve my code. I am open and grateful to anyone who can help improve the implementation in any way.