From 2c8fa9700cb1eb5f1587bec46b7060ec93c6b1d2 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Fri, 10 May 2019 20:20:00 +0200 Subject: 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 --- src/widgets/widgets/qtextedit.cpp | 17 +++++++++++++ src/widgets/widgets/qtextedit.h | 1 + src/widgets/widgets/qtextedit_p.h | 3 +++ src/widgets/widgets/qwidgettextcontrol.cpp | 38 ++++++++++++++++++++++++++++ src/widgets/widgets/qwidgettextcontrol_p.h | 3 +++ src/widgets/widgets/qwidgettextcontrol_p_p.h | 2 ++ 6 files changed, 64 insertions(+) (limited to 'src/widgets') diff --git a/src/widgets/widgets/qtextedit.cpp b/src/widgets/widgets/qtextedit.cpp index 5f734258b2..01f7c34f93 100644 --- a/src/widgets/widgets/qtextedit.cpp +++ b/src/widgets/widgets/qtextedit.cpp @@ -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(); diff --git a/src/widgets/widgets/qtextedit.h b/src/widgets/widgets/qtextedit.h index 3b7e610786..09ef44b7b2 100644 --- a/src/widgets/widgets/qtextedit.h +++ b/src/widgets/widgets/qtextedit.h @@ -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; diff --git a/src/widgets/widgets/qtextedit_p.h b/src/widgets/widgets/qtextedit_p.h index c4ee75c78d..f7b4d15318 100644 --- a/src/widgets/widgets/qtextedit_p.h +++ b/src/widgets/widgets/qtextedit_p.h @@ -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 diff --git a/src/widgets/widgets/qwidgettextcontrol.cpp b/src/widgets/widgets/qwidgettextcontrol.cpp index f85c7cdc6d..af3b03cd9e 100644 --- a/src/widgets/widgets/qwidgettextcontrol.cpp +++ b/src/widgets/widgets/qwidgettextcontrol.cpp @@ -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); diff --git a/src/widgets/widgets/qwidgettextcontrol_p.h b/src/widgets/widgets/qwidgettextcontrol_p.h index e521e7b356..59bf5466e6 100644 --- a/src/widgets/widgets/qwidgettextcontrol_p.h +++ b/src/widgets/widgets/qwidgettextcontrol_p.h @@ -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: diff --git a/src/widgets/widgets/qwidgettextcontrol_p_p.h b/src/widgets/widgets/qwidgettextcontrol_p_p.h index 6ccdfafe2b..c77a31bedf 100644 --- a/src/widgets/widgets/qwidgettextcontrol_p_p.h +++ b/src/widgets/widgets/qwidgettextcontrol_p_p.h @@ -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; -- cgit v1.2.3