aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHannu Koivisto <hannu.koivisto@vincit.fi>2010-11-15 16:33:15 +0200
committerPekka Vuorela <pekka.ta.vuorela@nokia.com>2010-11-16 16:28:00 +0200
commita55a852e2eb183f45fa7bca2ce08a477aab27995 (patch)
tree44aa8e56c1df513217259d62cc1273665ff813a6
parent9557195cb065be6134987d35c286f0ef6a2c53c9 (diff)
New: MTextEdit: double click to select a word
RevBy: Pekka Vuorela
-rwxr-xr-xsrc/corelib/widgets/mtextedit.cpp102
-rwxr-xr-xsrc/corelib/widgets/mtextedit_p.h9
-rw-r--r--tests/ut_mtextedit/ut_mtextedit.cpp92
-rw-r--r--tests/ut_mtextedit/ut_mtextedit.h2
4 files changed, 172 insertions, 33 deletions
diff --git a/src/corelib/widgets/mtextedit.cpp b/src/corelib/widgets/mtextedit.cpp
index 64007aff..d5351d52 100755
--- a/src/corelib/widgets/mtextedit.cpp
+++ b/src/corelib/widgets/mtextedit.cpp
@@ -61,6 +61,7 @@ M_REGISTER_WIDGET(MTextEdit)
namespace {
const char * const UrlContentToolbarFile("UrlContentToolbar.xml");
const char * const EmailContentToolbarFile("EmailContentToolbar.xml");
+ const int IgnorePreeditChangeAfterDoubleClickInterval(500); // in ms
}
@@ -312,7 +313,10 @@ MTextEditPrivate::MTextEditPrivate()
editActive(false),
omitInputMethodEvents(false),
updateMicroFocusDisabled(0),
- pendingMicroFocusUpdate(false)
+ pendingMicroFocusUpdate(false),
+ doubleClick(false),
+ previousReleaseWordStart(0),
+ previousReleaseWordEnd(0)
{
}
@@ -1900,10 +1904,19 @@ void MTextEdit::handleMousePress(int cursorPosition, QGraphicsSceneMouseEvent *e
{
Q_D(MTextEdit);
- if (textInteractionFlags() != Qt::NoTextInteraction && location) {
- QString text = document()->toPlainText();
- MBreakIterator breakIterator(text);
+ MBreakIterator breakIterator(document()->toPlainText());
+ d->doubleClick = d->lastMousePressTime.isValid()
+ && (d->lastMousePressTime.elapsed() < QApplication::doubleClickInterval())
+ && (breakIterator.previousInclusive(cursorPosition) == d->previousReleaseWordStart)
+ && (breakIterator.next(cursorPosition) == d->previousReleaseWordEnd);
+
+ if (d->doubleClick) {
+ d->lastMousePressTime = QTime();
+ } else {
+ d->lastMousePressTime.start();
+ }
+ if (textInteractionFlags() != Qt::NoTextInteraction && location) {
if (breakIterator.isBoundary(cursorPosition) == true) {
*location = MTextEdit::WordBoundary;
} else {
@@ -1932,14 +1945,17 @@ void MTextEdit::handleMouseRelease(int eventCursorPosition, QGraphicsSceneMouseE
deselect();
- if (d->isPositionOnPreedit(eventCursorPosition) == false) {
+ d->previousReleaseWordStart = 0;
+ d->previousReleaseWordEnd = 0;
+
+ if (d->isPositionOnPreedit(eventCursorPosition) == false
+ || (d->doubleClick && (textInteractionFlags() & Qt::TextSelectableByMouse))) {
// input context takes care of releases happening on top of preedit, the rest
// is handled here
d->commitPreedit();
QString text = document()->toPlainText();
MBreakIterator breakIterator(text);
- QInputContext *ic = qApp->inputContext();
// clicks on word boundaries move the cursor
if (breakIterator.isBoundary(eventCursorPosition) == true) {
@@ -1952,37 +1968,47 @@ void MTextEdit::handleMouseRelease(int eventCursorPosition, QGraphicsSceneMouseE
if (location) {
*location = MTextEdit::Word;
}
- if (inputMethodCorrectionEnabled()) {
+ d->previousReleaseWordStart = breakIterator.previousInclusive(eventCursorPosition);
+ d->previousReleaseWordEnd = breakIterator.next(eventCursorPosition);
+ if (inputMethodCorrectionEnabled() || d->doubleClick) {
// clicks on words remove them from the normal contents and makes them preedit.
- int start = breakIterator.previousInclusive(eventCursorPosition);
- int end = breakIterator.next(eventCursorPosition);
+ const int start(d->previousReleaseWordStart);
+ const int end(d->previousReleaseWordEnd);
QString preedit = text.mid(start, end - start);
- d->storePreeditTextStyling(start, end);
d->cursor()->setPosition(start);
d->cursor()->setPosition(end, QTextCursor::KeepAnchor);
- QTextDocumentFragment preeditFragment = d->cursor()->selection();
- d->cursor()->removeSelectedText();
-
- // offer the word to input context as preedit. if the input context accepts it and
- // plays nicely, it should offer the preedit right back, changing the mode to
- // active.
- bool injectionAccepted = false;
-
- if (ic) {
- MPreeditInjectionEvent event(preedit, eventCursorPosition - start);
- QCoreApplication::sendEvent(ic, &event);
-
- injectionAccepted = event.isAccepted();
- }
+ if (d->doubleClick) { // select the word
+ d->setMode(MTextEditModel::EditModeSelect);
+ model()->updateCursor();
+ emit selectionChanged();
+ d->sendCopyAvailable(true);
+ d->doubleClickSelectionTime = QTime::currentTime();
+ } else { // activate pre-edit
+ d->storePreeditTextStyling(start, end);
+ QTextDocumentFragment preeditFragment = d->cursor()->selection();
+ d->cursor()->removeSelectedText();
+
+ // offer the word to input context as preedit. if the input context accepts it and
+ // plays nicely, it should offer the preedit right back, changing the mode to
+ // active.
+ bool injectionAccepted = false;
+
+ QInputContext *ic = qApp->inputContext();
+ if (ic) {
+ MPreeditInjectionEvent event(preedit, eventCursorPosition - start);
+ QCoreApplication::sendEvent(ic, &event);
+
+ injectionAccepted = event.isAccepted();
+ }
- // if injection wasn't supported, put the text back and fall back to cursor changing
- if (injectionAccepted == false) {
- d->cursor()->insertFragment(preeditFragment);
- d->setCursorPosition(eventCursorPosition);
- d->preeditStyling.clear();
+ // if injection wasn't supported, put the text back and fall back to cursor changing
+ if (injectionAccepted == false) {
+ d->cursor()->insertFragment(preeditFragment);
+ d->setCursorPosition(eventCursorPosition);
+ d->preeditStyling.clear();
+ }
}
-
} else {
d->setCursorPosition(eventCursorPosition);
}
@@ -2148,11 +2174,23 @@ void MTextEdit::inputMethodEvent(QInputMethodEvent *event)
// FIXME: replacement info not honored.
Q_D(MTextEdit);
+ QString preedit = event->preeditString();
+
+ // The first click of a double click sequence causes pre-edit injection, which implies
+ // that the IC sends pre-edit to the IM server and the input method plugin may send it
+ // back with correct formatting. Due to asynchronous operation the pre-edit sent by
+ // the plugin may arrive after the second click of the double click sequence, at which
+ // point it will remove double click selection unless we ignore it here. The logic to
+ // recognize exactly that particular pre-edit sending by time and content is not 100%
+ // precise but should be pretty safe.
+ if ((d->doubleClickSelectionTime.addMSecs(IgnorePreeditChangeAfterDoubleClickInterval) > QTime::currentTime())
+ && !preedit.isEmpty() && preedit == selectedText()) {
+ d->doubleClickSelectionTime = QTime();
+ return;
+ }
if (d->omitInputMethodEvents) {
return;
}
-
- QString preedit = event->preeditString();
QString commitString = event->commitString();
bool emitReturnPressed = false;
diff --git a/src/corelib/widgets/mtextedit_p.h b/src/corelib/widgets/mtextedit_p.h
index 4d13856b..cb40cd00 100755
--- a/src/corelib/widgets/mtextedit_p.h
+++ b/src/corelib/widgets/mtextedit_p.h
@@ -24,6 +24,7 @@
#include <QString>
#include <QList>
#include <QInputMethodEvent>
+#include <QTime>
class QGraphicsSceneMouseEvent;
class QValidator;
@@ -153,6 +154,14 @@ private:
int updateMicroFocusDisabled;
bool pendingMicroFocusUpdate;
+ QTime lastMousePressTime;
+ // was the last mouse press event a double click event?
+ bool doubleClick;
+ // start and end indices of the word over which the mouse was released the last time
+ int previousReleaseWordStart;
+ int previousReleaseWordEnd;
+ // the last time when double click selection was done
+ QTime doubleClickSelectionTime;
};
#endif
diff --git a/tests/ut_mtextedit/ut_mtextedit.cpp b/tests/ut_mtextedit/ut_mtextedit.cpp
index b532da3e..96a154df 100644
--- a/tests/ut_mtextedit/ut_mtextedit.cpp
+++ b/tests/ut_mtextedit/ut_mtextedit.cpp
@@ -37,6 +37,7 @@
#include <QTextEdit>
#include <QInputContext>
#include <QInputContextFactory>
+#include <MPreeditInjectionEvent>
#include <mtextedit.h>
#include <mtexteditview.h>
@@ -62,6 +63,7 @@ Q_DECLARE_METATYPE(Ut_MTextEdit::KeyList);
Q_DECLARE_METATYPE(Qt::KeyboardModifiers);
Q_DECLARE_METATYPE(Qt::FocusReason);
Q_DECLARE_METATYPE(Qt::Key)
+Q_DECLARE_METATYPE(Qt::TextInteractionFlag);
const QString Ut_MTextEdit::testString = QString("jallajalla");
@@ -153,6 +155,15 @@ public:
return false;
}
+ bool event(QEvent *event)
+ {
+ if (event->type() == MPreeditInjectionEvent::eventNumber()) {
+ event->accept();
+ return true;
+ }
+ return QInputContext::event(event);
+ }
+
void update()
{
++updateCallCount;
@@ -1167,6 +1178,85 @@ void Ut_MTextEdit::testSelection()
}
+void Ut_MTextEdit::testDoubleClick_data()
+{
+ QTest::addColumn<Qt::TextInteractionFlag>("interactionFlag");
+
+ QTest::newRow("selectionEnabled") << Qt::TextSelectableByMouse;
+ QTest::newRow("selectionDisabled") << Qt::NoTextInteraction;
+}
+
+
+void Ut_MTextEdit::testDoubleClick()
+{
+ QFETCH(Qt::TextInteractionFlag, interactionFlag);
+ const bool enabled(interactionFlag & Qt::TextSelectableByMouse);
+
+ m_subject->setTextInteractionFlags(interactionFlag);
+
+ QSignalSpy copyAvailableSpy(m_subject.get(), SIGNAL(copyAvailable(bool)));
+ m_subject->setText("xyzzy quux");
+
+ QGraphicsSceneMouseEvent dummyEvent;
+
+ // Double click the first word
+ m_subject->handleMousePress(2, &dummyEvent, NULL);
+ m_subject->handleMouseRelease(2, &dummyEvent, NULL);
+ {
+ QInputMethodEvent event("xyzzy", QList<QInputMethodEvent::Attribute>());
+ m_subject->inputMethodEvent(&event);
+ }
+ m_subject->handleMousePress(3, &dummyEvent, NULL);
+ m_subject->handleMouseRelease(3, &dummyEvent, NULL);
+
+ QCOMPARE(copyAvailableSpy.count(), enabled ? 1 : 0);
+ if (enabled) {
+ QCOMPARE(copyAvailableSpy.first().count(), 1);
+ QVERIFY(copyAvailableSpy.first().first().toBool());
+ }
+ copyAvailableSpy.clear();
+
+ // Click the 2nd. word to loose the selection
+ m_subject->handleMousePress(8, &dummyEvent, NULL);
+ m_subject->handleMouseRelease(8, &dummyEvent, NULL);
+ {
+ QInputMethodEvent event("quux", QList<QInputMethodEvent::Attribute>());
+ m_subject->inputMethodEvent(&event);
+ }
+ QCOMPARE(copyAvailableSpy.count(), enabled ? 1 : 0);
+ if (enabled) {
+ QCOMPARE(copyAvailableSpy.first().count(), 1);
+ QVERIFY(!copyAvailableSpy.first().first().toBool());
+ }
+ copyAvailableSpy.clear();
+
+ // Click too slowly -> no selection
+ m_subject->handleMousePress(2, &dummyEvent, NULL);
+ m_subject->handleMouseRelease(2, &dummyEvent, NULL);
+ {
+ QInputMethodEvent event("xyzzy", QList<QInputMethodEvent::Attribute>());
+ m_subject->inputMethodEvent(&event);
+ }
+ QTest::qWait(QApplication::doubleClickInterval()*2);
+ m_subject->handleMousePress(3, &dummyEvent, NULL);
+ m_subject->handleMouseRelease(3, &dummyEvent, NULL);
+
+ QCOMPARE(copyAvailableSpy.count(), 0);
+
+ // Click fast, but different words -> no selection
+ m_subject->handleMousePress(8, &dummyEvent, NULL);
+ m_subject->handleMouseRelease(8, &dummyEvent, NULL);
+ {
+ QInputMethodEvent event("quux", QList<QInputMethodEvent::Attribute>());
+ m_subject->inputMethodEvent(&event);
+ }
+ m_subject->handleMousePress(2, &dummyEvent, NULL);
+ m_subject->handleMouseRelease(2, &dummyEvent, NULL);
+
+ QCOMPARE(copyAvailableSpy.count(), 0);
+}
+
+
void Ut_MTextEdit::testAutoSelection()
{
Qt::TextInteractionFlag testSelection[] = {
@@ -1184,7 +1274,7 @@ void Ut_MTextEdit::testAutoSelection()
for (unsigned n = 0; n < sizeof(testSelection) / sizeof(testSelection[0]); ++n) {
qDebug() << n << testSelection[n];
m_subject->setTextInteractionFlags(testSelection[n]);
-
+
m_subject->setAutoSelectionEnabled(false);
QVERIFY(m_subject->isAutoSelectionEnabled() == false);
diff --git a/tests/ut_mtextedit/ut_mtextedit.h b/tests/ut_mtextedit/ut_mtextedit.h
index 716361a3..b16c5272 100644
--- a/tests/ut_mtextedit/ut_mtextedit.h
+++ b/tests/ut_mtextedit/ut_mtextedit.h
@@ -95,6 +95,8 @@ private slots:
//void testFeedback();
void testBadData();
void testSelection();
+ void testDoubleClick_data();
+ void testDoubleClick();
void testAutoSelection();
void testPrompt();
void testValidator();