Support clicking to toggle checkboxes in QTextEdit
Add QAbstractTextDocumentLayout::markerAt(pos) for hit testing. (Qt Quick TextEdit needs it too.) Finds out whether the position corresponds to a marker on a paragraph. I.e. it finds checkboxes in GitHub-flavored markdown. This enables editor classes to toggle checkboxes by clicking them. Use it in QTextEdit to add the checkbox toggling feature. Also show the "pointing finger" cursor when hovering a toggleable checkbox. Change-Id: I036c967ab45e14c836272eac2cc7c7d652543c89 Reviewed-by: Gatis Paeglis <gatis.paeglis@qt.io>
parent
12e0c7cbe0
commit
2c8fa9700c
|
|
@ -41,6 +41,7 @@
|
|||
#include <qtextformat.h>
|
||||
#include "qtextdocument_p.h"
|
||||
#include "qtextengine_p.h"
|
||||
#include "qtextlist.h"
|
||||
|
||||
#include "qabstracttextdocumentlayout_p.h"
|
||||
|
||||
|
|
@ -649,6 +650,36 @@ QTextFormat QAbstractTextDocumentLayout::formatAt(const QPointF &pos) const
|
|||
return pieceTable->formatCollection()->format(it->format);
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 5.14
|
||||
|
||||
Returns the block (probably a list item) whose \l{QTextBlockFormat::marker()}{marker}
|
||||
is found at the given position \a pos.
|
||||
*/
|
||||
QTextBlock QAbstractTextDocumentLayout::blockWithMarkerAt(const QPointF &pos) const
|
||||
{
|
||||
QTextBlock block = document()->firstBlock();
|
||||
while (block.isValid()) {
|
||||
if (block.blockFormat().marker() != QTextBlockFormat::NoMarker) {
|
||||
QRectF blockBr = blockBoundingRect(block);
|
||||
QTextBlockFormat blockFmt = block.blockFormat();
|
||||
QFontMetrics fm(block.charFormat().font());
|
||||
qreal totalIndent = blockFmt.indent() + blockFmt.leftMargin() + blockFmt.textIndent();
|
||||
if (block.textList())
|
||||
totalIndent += block.textList()->format().indent() * 40;
|
||||
QRectF adjustedBr = blockBr.adjusted(totalIndent - fm.height(), 0, totalIndent - blockBr.width(), fm.height() - blockBr.height());
|
||||
if (adjustedBr.contains(pos)) {
|
||||
//qDebug() << "hit block" << block.text() << blockBr << adjustedBr << "marker" << block.blockFormat().marker()
|
||||
// << "font" << block.charFormat().font() << "adj" << lineHeight << totalIndent;
|
||||
if (block.blockFormat().hasProperty(QTextFormat::BlockMarker))
|
||||
return block;
|
||||
}
|
||||
}
|
||||
block = block.next();
|
||||
}
|
||||
return QTextBlock();
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn QRectF QAbstractTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ public:
|
|||
QString anchorAt(const QPointF& pos) const;
|
||||
QString imageAt(const QPointF &pos) const;
|
||||
QTextFormat formatAt(const QPointF &pos) const;
|
||||
QTextBlock blockWithMarkerAt(const QPointF &pos) const;
|
||||
|
||||
virtual int pageCount() const = 0;
|
||||
virtual QSizeF documentSize() const = 0;
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ void QTextEditPrivate::init(const QString &html)
|
|||
QObject::connect(control, SIGNAL(copyAvailable(bool)), q, SIGNAL(copyAvailable(bool)));
|
||||
QObject::connect(control, SIGNAL(selectionChanged()), q, SIGNAL(selectionChanged()));
|
||||
QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SLOT(_q_cursorPositionChanged()));
|
||||
QObject::connect(control, SIGNAL(blockMarkerHovered(QTextBlock)), q, SLOT(_q_hoveredBlockWithMarkerChanged(QTextBlock)));
|
||||
|
||||
QObject::connect(control, SIGNAL(textChanged()), q, SLOT(updateMicroFocus()));
|
||||
|
||||
|
|
@ -187,6 +188,7 @@ void QTextEditPrivate::init(const QString &html)
|
|||
vbar->setSingleStep(20);
|
||||
|
||||
viewport->setBackgroundRole(QPalette::Base);
|
||||
q->setMouseTracking(true);
|
||||
q->setAcceptDrops(true);
|
||||
q->setFocusPolicy(Qt::StrongFocus);
|
||||
q->setAttribute(Qt::WA_KeyCompression);
|
||||
|
|
@ -228,6 +230,21 @@ void QTextEditPrivate::_q_cursorPositionChanged()
|
|||
#endif
|
||||
}
|
||||
|
||||
void QTextEditPrivate::_q_hoveredBlockWithMarkerChanged(const QTextBlock &block)
|
||||
{
|
||||
Q_Q(QTextEdit);
|
||||
Qt::CursorShape cursor = cursorToRestoreAfterHover;
|
||||
if (block.isValid() && !q->isReadOnly()) {
|
||||
QTextBlockFormat::MarkerType marker = block.blockFormat().marker();
|
||||
if (marker != QTextBlockFormat::NoMarker) {
|
||||
if (viewport->cursor().shape() != Qt::PointingHandCursor)
|
||||
cursorToRestoreAfterHover = viewport->cursor().shape();
|
||||
cursor = Qt::PointingHandCursor;
|
||||
}
|
||||
}
|
||||
viewport->setCursor(cursor);
|
||||
}
|
||||
|
||||
void QTextEditPrivate::pageUpDown(QTextCursor::MoveOperation op, QTextCursor::MoveMode moveMode)
|
||||
{
|
||||
QTextCursor cursor = control->textCursor();
|
||||
|
|
|
|||
|
|
@ -331,6 +331,7 @@ private:
|
|||
Q_PRIVATE_SLOT(d_func(), void _q_adjustScrollbars())
|
||||
Q_PRIVATE_SLOT(d_func(), void _q_ensureVisible(const QRectF &))
|
||||
Q_PRIVATE_SLOT(d_func(), void _q_cursorPositionChanged())
|
||||
Q_PRIVATE_SLOT(d_func(), void _q_hoveredBlockWithMarkerChanged(const QTextBlock &))
|
||||
friend class QTextEditControl;
|
||||
friend class QTextDocument;
|
||||
friend class QWidgetTextControl;
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ public:
|
|||
|
||||
void _q_currentCharFormatChanged(const QTextCharFormat &format);
|
||||
void _q_cursorPositionChanged();
|
||||
void _q_hoveredBlockWithMarkerChanged(const QTextBlock &block);
|
||||
|
||||
void updateDefaultTextOption();
|
||||
|
||||
|
|
@ -136,6 +137,8 @@ public:
|
|||
|
||||
QString placeholderText;
|
||||
|
||||
Qt::CursorShape cursorToRestoreAfterHover = Qt::IBeamCursor;
|
||||
|
||||
#ifdef QT_KEYPAD_NAVIGATION
|
||||
QBasicTimer deleteAllTimer;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1581,6 +1581,11 @@ void QWidgetTextControlPrivate::mousePressEvent(QEvent *e, Qt::MouseButton butto
|
|||
e->ignore();
|
||||
return;
|
||||
}
|
||||
bool wasValid = blockWithMarkerUnderMouse.isValid();
|
||||
blockWithMarkerUnderMouse = q->blockWithMarkerAt(pos);
|
||||
if (wasValid != blockWithMarkerUnderMouse.isValid())
|
||||
emit q->blockMarkerHovered(blockWithMarkerUnderMouse);
|
||||
|
||||
|
||||
cursorIsFocusIndicator = false;
|
||||
const QTextCursor oldSelection = cursor;
|
||||
|
|
@ -1599,6 +1604,8 @@ void QWidgetTextControlPrivate::mousePressEvent(QEvent *e, Qt::MouseButton butto
|
|||
selectedBlockOnTrippleClick = cursor;
|
||||
|
||||
anchorOnMousePress = QString();
|
||||
blockWithMarkerUnderMouse = QTextBlock();
|
||||
emit q->blockMarkerHovered(blockWithMarkerUnderMouse);
|
||||
|
||||
trippleClickTimer.stop();
|
||||
} else {
|
||||
|
|
@ -1738,6 +1745,11 @@ void QWidgetTextControlPrivate::mouseMoveEvent(QEvent *e, Qt::MouseButton button
|
|||
}
|
||||
selectionChanged(true);
|
||||
repaintOldAndNewSelection(oldSelection);
|
||||
} else {
|
||||
bool wasValid = blockWithMarkerUnderMouse.isValid();
|
||||
blockWithMarkerUnderMouse = q->blockWithMarkerAt(mousePos);
|
||||
if (wasValid != blockWithMarkerUnderMouse.isValid())
|
||||
emit q->blockMarkerHovered(blockWithMarkerUnderMouse);
|
||||
}
|
||||
|
||||
sendMouseEventToInputContext(e, QEvent::MouseMove, button, mousePos, modifiers, buttons, globalPos);
|
||||
|
|
@ -1787,6 +1799,26 @@ void QWidgetTextControlPrivate::mouseReleaseEvent(QEvent *e, Qt::MouseButton but
|
|||
emit q->microFocusChanged();
|
||||
}
|
||||
|
||||
// toggle any checkbox that the user clicks
|
||||
if ((interactionFlags & Qt::TextEditable) && (button & Qt::LeftButton) &&
|
||||
(blockWithMarkerUnderMouse.isValid()) && !cursor.hasSelection()) {
|
||||
QTextBlock markerBlock = q->blockWithMarkerAt(pos);
|
||||
if (markerBlock == blockWithMarkerUnderMouse) {
|
||||
auto fmt = blockWithMarkerUnderMouse.blockFormat();
|
||||
switch (fmt.marker()) {
|
||||
case QTextBlockFormat::Unchecked :
|
||||
fmt.setMarker(QTextBlockFormat::Checked);
|
||||
break;
|
||||
case QTextBlockFormat::Checked:
|
||||
fmt.setMarker(QTextBlockFormat::Unchecked);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
cursor.setBlockFormat(fmt);
|
||||
}
|
||||
}
|
||||
|
||||
if (interactionFlags & Qt::LinksAccessibleByMouse) {
|
||||
if (!(button & Qt::LeftButton))
|
||||
return;
|
||||
|
|
@ -2385,6 +2417,12 @@ QString QWidgetTextControl::anchorAtCursor() const
|
|||
return d->anchorForCursor(d->cursor);
|
||||
}
|
||||
|
||||
QTextBlock QWidgetTextControl::blockWithMarkerAt(const QPointF &pos) const
|
||||
{
|
||||
Q_D(const QWidgetTextControl);
|
||||
return d->doc->documentLayout()->blockWithMarkerAt(pos);
|
||||
}
|
||||
|
||||
bool QWidgetTextControl::overwriteMode() const
|
||||
{
|
||||
Q_D(const QWidgetTextControl);
|
||||
|
|
|
|||
|
|
@ -150,6 +150,8 @@ public:
|
|||
|
||||
QString anchorAtCursor() const;
|
||||
|
||||
QTextBlock blockWithMarkerAt(const QPointF &pos) const;
|
||||
|
||||
bool overwriteMode() const;
|
||||
void setOverwriteMode(bool overwrite);
|
||||
|
||||
|
|
@ -242,6 +244,7 @@ Q_SIGNALS:
|
|||
void microFocusChanged();
|
||||
void linkActivated(const QString &link);
|
||||
void linkHovered(const QString &);
|
||||
void blockMarkerHovered(const QTextBlock &block);
|
||||
void modificationChanged(bool m);
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
#include "QtGui/qtextdocumentfragment.h"
|
||||
#include "QtGui/qtextcursor.h"
|
||||
#include "QtGui/qtextformat.h"
|
||||
#include "QtGui/qtextobject.h"
|
||||
#if QT_CONFIG(menu)
|
||||
#include "QtWidgets/qmenu.h"
|
||||
#endif
|
||||
|
|
@ -227,6 +228,7 @@ public:
|
|||
|
||||
QString highlightedAnchor; // Anchor below cursor
|
||||
QString anchorOnMousePress;
|
||||
QTextBlock blockWithMarkerUnderMouse;
|
||||
bool hadSelectionOnMousePress;
|
||||
|
||||
bool ignoreUnusedNavigationEvents;
|
||||
|
|
|
|||
Loading…
Reference in New Issue