/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/


#include <QtTest/QtTest>


#include <qtextedit.h>
#include <qtextcursor.h>
#include <qtextlist.h>
#include <qdebug.h>
#include <qapplication.h>
#include <qclipboard.h>
#include <qtextbrowser.h>
#include <private/qtextcontrol_p.h>
#include <qscrollbar.h>
#include <qtextobject.h>

#include <qabstracttextdocumentlayout.h>
#include <qtextdocumentfragment.h>

#include "qplaintextedit.h"

//Used in copyAvailable
typedef QPair<Qt::Key, Qt::KeyboardModifier> keyPairType;
typedef QList<keyPairType> pairListType;
Q_DECLARE_METATYPE(pairListType);
Q_DECLARE_METATYPE(keyPairType);
Q_DECLARE_METATYPE(QList<bool>);

#ifdef Q_WS_MAC
#include <Carbon/Carbon.h>
#endif

QT_FORWARD_DECLARE_CLASS(QPlainTextEdit)

//TESTED_CLASS=
//TESTED_FILES=gui/widgets/qtextedit.h gui/widgets/qtextedit.cpp

class tst_QPlainTextEdit : public QObject
{
    Q_OBJECT
public:
    tst_QPlainTextEdit();

public slots:
    void initTestCase();
    void init();
    void cleanup();
private slots:
    void getSetCheck();
#ifndef QT_NO_CLIPBOARD
    void clearMustNotChangeClipboard();
#endif
    void clearMustNotResetRootFrameMarginToDefault();
    void paragSeparatorOnPlaintextAppend();
#ifndef QT_NO_CLIPBOARD
    void selectAllSetsNotSelection();
#endif
    void asciiTab();
    void setDocument();
    void emptyAppend();
    void appendOnEmptyDocumentShouldReuseInitialParagraph();
    void cursorPositionChanged();
    void setTextCursor();
#ifndef QT_NO_CLIPBOARD
    void undoAvailableAfterPaste();
#endif
    void undoRedoAvailableRepetition();
    void appendShouldNotTouchTheSelection();
    void backspace();
    void shiftBackspace();
    void undoRedo();
    void preserveCharFormatInAppend();
#ifndef QT_NO_CLIPBOARD
    void copyAndSelectAllInReadonly();
#endif
    void ctrlAltInput();
    void noPropertiesOnDefaultTextEditCharFormat();
    void setPlainTextShouldEmitTextChangedOnce();
    void overwriteMode();
    void shiftDownInLineLastShouldSelectToEnd_data();
    void shiftDownInLineLastShouldSelectToEnd();
    void undoRedoShouldRepositionTextEditCursor();
    void lineWrapModes();
    void mouseCursorShape();
    void implicitClear();
    void undoRedoAfterSetContent();
    void numPadKeyNavigation();
    void moveCursor();
#ifndef QT_NO_CLIPBOARD
    void mimeDataReimplementations();
#endif
    void shiftEnterShouldInsertLineSeparator();
    void selectWordsFromStringsContainingSeparators_data();
    void selectWordsFromStringsContainingSeparators();
#ifndef QT_NO_CLIPBOARD
    void canPaste();
    void copyAvailable_data();
    void copyAvailable();
#endif
    void ensureCursorVisibleOnInitialShow();
    void setTextInsideResizeEvent();
    void colorfulAppend();
    void ensureVisibleWithRtl();
    void preserveCharFormatAfterSetPlainText();
    void extraSelections();
    void adjustScrollbars();
    void textObscuredByScrollbars();
    void setTextPreservesUndoRedoEnabled();
    void wordWrapProperty();
    void lineWrapProperty();
    void selectionChanged();
    void blockCountChanged();

private:
    void createSelection();
    int blockCount() const;
    int lineCount() const;
    bool nativeClipboardWorking();

    QPlainTextEdit *ed;
    qreal rootFrameMargin;
};

bool tst_QPlainTextEdit::nativeClipboardWorking()
{
#ifdef Q_WS_MAC
    PasteboardRef pasteboard;
    OSStatus status = PasteboardCreate(0, &pasteboard);
    if (status == noErr)
        CFRelease(pasteboard);
    return status == noErr;
#endif
    return true;
}

// Testing get/set functions
void tst_QPlainTextEdit::getSetCheck()
{
    QPlainTextEdit obj1;
    // QTextDocument * QPlainTextEdit::document()
    // void QPlainTextEdit::setDocument(QTextDocument *)
    QTextDocument *var1 = new QTextDocument;
    var1->setDocumentLayout(new QPlainTextDocumentLayout(var1));
    obj1.setDocument(var1);
    QCOMPARE(var1, obj1.document());
    obj1.setDocument((QTextDocument *)0);
    QVERIFY(var1 != obj1.document()); // QPlainTextEdit creates a new document when setting 0
    QVERIFY((QTextDocument *)0 != obj1.document());
    delete var1;


    // bool QPlainTextEdit::tabChangesFocus()
    // void QPlainTextEdit::setTabChangesFocus(bool)
    obj1.setTabChangesFocus(false);
    QCOMPARE(false, obj1.tabChangesFocus());
    obj1.setTabChangesFocus(true);
    QCOMPARE(true, obj1.tabChangesFocus());

    // LineWrapMode QPlainTextEdit::lineWrapMode()
    // void QPlainTextEdit::setLineWrapMode(LineWrapMode)
    obj1.setLineWrapMode(QPlainTextEdit::LineWrapMode(QPlainTextEdit::NoWrap));
    QCOMPARE(QPlainTextEdit::LineWrapMode(QPlainTextEdit::NoWrap), obj1.lineWrapMode());
    obj1.setLineWrapMode(QPlainTextEdit::LineWrapMode(QPlainTextEdit::WidgetWidth));
    QCOMPARE(QPlainTextEdit::LineWrapMode(QPlainTextEdit::WidgetWidth), obj1.lineWrapMode());
//     obj1.setLineWrapMode(QPlainTextEdit::LineWrapMode(QPlainTextEdit::FixedPixelWidth));
//     QCOMPARE(QPlainTextEdit::LineWrapMode(QPlainTextEdit::FixedPixelWidth), obj1.lineWrapMode());
//     obj1.setLineWrapMode(QPlainTextEdit::LineWrapMode(QPlainTextEdit::FixedColumnWidth));
//     QCOMPARE(QPlainTextEdit::LineWrapMode(QPlainTextEdit::FixedColumnWidth), obj1.lineWrapMode());


    // bool QPlainTextEdit::overwriteMode()
    // void QPlainTextEdit::setOverwriteMode(bool)
    obj1.setOverwriteMode(false);
    QCOMPARE(false, obj1.overwriteMode());
    obj1.setOverwriteMode(true);
    QCOMPARE(true, obj1.overwriteMode());

    // int QPlainTextEdit::tabStopWidth()
    // void QPlainTextEdit::setTabStopWidth(int)
    obj1.setTabStopWidth(0);
    QCOMPARE(0, obj1.tabStopWidth());
    obj1.setTabStopWidth(INT_MIN);
    QCOMPARE(0, obj1.tabStopWidth()); // Makes no sense to set a negative tabstop value
#if defined(QT_ARCH_WINDOWSCE) || defined (QT_ARCH_SYMBIAN)
    // due to rounding error in qRound when qreal==float
    // we cannot use INT_MAX for this check
    obj1.setTabStopWidth(SHRT_MAX*2);
    QCOMPARE(SHRT_MAX*2, obj1.tabStopWidth());
#else
    obj1.setTabStopWidth(INT_MAX);
    QCOMPARE(INT_MAX, obj1.tabStopWidth());
#endif

}

class QtTestDocumentLayout : public QAbstractTextDocumentLayout
{
    Q_OBJECT
public:
    inline QtTestDocumentLayout(QPlainTextEdit *edit, QTextDocument *doc, int &itCount)
        : QAbstractTextDocumentLayout(doc), useBiggerSize(false), ed(edit), iterationCounter(itCount) {}

    virtual void draw(QPainter *, const QAbstractTextDocumentLayout::PaintContext &)  {}

    virtual int hitTest(const QPointF &, Qt::HitTestAccuracy ) const { return 0; }

    virtual void documentChanged(int, int, int) {}

    virtual int pageCount() const { return 1; }

    virtual QSizeF documentSize() const { return usedSize; }

    virtual QRectF frameBoundingRect(QTextFrame *) const { return QRectF(); }
    virtual QRectF blockBoundingRect(const QTextBlock &) const { return QRectF(); }

    bool useBiggerSize;
    QSize usedSize;

    QPlainTextEdit *ed;

    int &iterationCounter;
};

tst_QPlainTextEdit::tst_QPlainTextEdit()
{}

void tst_QPlainTextEdit::initTestCase()
{
#ifdef Q_OS_WINCE //disable magic for WindowsCE
    qApp->setAutoMaximizeThreshold(-1);
#endif
}

void tst_QPlainTextEdit::init()
{
    ed = new QPlainTextEdit(0);
    rootFrameMargin = ed->document()->documentMargin();
}

void tst_QPlainTextEdit::cleanup()
{
    delete ed;
    ed = 0;
}


void tst_QPlainTextEdit::createSelection()
{
    QTest::keyClicks(ed, "Hello World");
    /* go to start */
#ifndef Q_WS_MAC
    QTest::keyClick(ed, Qt::Key_Home, Qt::ControlModifier);
#else
    QTest::keyClick(ed, Qt::Key_Home);
#endif
    QCOMPARE(ed->textCursor().position(), 0);
    /* select until end of text */
#ifndef Q_WS_MAC
    QTest::keyClick(ed, Qt::Key_End, Qt::ControlModifier | Qt::ShiftModifier);
#else
    QTest::keyClick(ed, Qt::Key_End, Qt::ShiftModifier);
#endif
    QCOMPARE(ed->textCursor().position(), 11);
}
#ifndef QT_NO_CLIPBOARD
void tst_QPlainTextEdit::clearMustNotChangeClipboard()
{
    if (!nativeClipboardWorking())
        QSKIP("Clipboard not working with cron-started unit tests", SkipAll);
    ed->textCursor().insertText("Hello World");
    QString txt("This is different text");
    QApplication::clipboard()->setText(txt);
    ed->clear();
    QCOMPARE(QApplication::clipboard()->text(), txt);
}
#endif

void tst_QPlainTextEdit::clearMustNotResetRootFrameMarginToDefault()
{
    QCOMPARE(ed->document()->rootFrame()->frameFormat().margin(), rootFrameMargin);
    ed->clear();
    QCOMPARE(ed->document()->rootFrame()->frameFormat().margin(), rootFrameMargin);
}


void tst_QPlainTextEdit::paragSeparatorOnPlaintextAppend()
{
    ed->appendPlainText("Hello\nWorld");
    int cnt = 0;
    QTextBlock blk = ed->document()->begin();
    while (blk.isValid()) {
        ++cnt;
        blk = blk.next();
    }
    QCOMPARE(cnt, 2);
}

#ifndef QT_NO_CLIPBOARD
void tst_QPlainTextEdit::selectAllSetsNotSelection()
{
    if (!QApplication::clipboard()->supportsSelection()) {
        QSKIP("Test only relevant for systems with selection", SkipAll);
        return;
    }

    QApplication::clipboard()->setText(QString("foobar"), QClipboard::Selection);
    QVERIFY(QApplication::clipboard()->text(QClipboard::Selection) == QString("foobar"));

    ed->insertPlainText("Hello World");
    ed->selectAll();

    QCOMPARE(QApplication::clipboard()->text(QClipboard::Selection), QString::fromAscii("foobar"));
}
#endif

void tst_QPlainTextEdit::asciiTab()
{
    QPlainTextEdit edit;
    edit.setPlainText("\t");
    edit.show();
    qApp->processEvents();
    QCOMPARE(edit.toPlainText().at(0), QChar('\t'));
}

void tst_QPlainTextEdit::setDocument()
{
    QTextDocument *document = new QTextDocument(ed);
    document->setDocumentLayout(new QPlainTextDocumentLayout(document));
    QTextCursor(document).insertText("Test");
    ed->setDocument(document);
    QCOMPARE(ed->toPlainText(), QString("Test"));
}


int tst_QPlainTextEdit::blockCount() const
{
    int blocks = 0;
    for (QTextBlock block = ed->document()->begin(); block.isValid(); block = block.next())
        ++blocks;
    return blocks;
}

int tst_QPlainTextEdit::lineCount() const
{
    int lines = 0;
    for (QTextBlock block = ed->document()->begin(); block.isValid(); block = block.next()) {
        ed->document()->documentLayout()->blockBoundingRect(block);
        lines += block.layout()->lineCount();
    }
    return lines;
}

// Supporter issue #56783
void tst_QPlainTextEdit::emptyAppend()
{
    ed->appendPlainText("Blah");
    QCOMPARE(blockCount(), 1);
    ed->appendPlainText(QString::null);
    QCOMPARE(blockCount(), 2);
    ed->appendPlainText(QString("   "));
    QCOMPARE(blockCount(), 3);
}

void tst_QPlainTextEdit::appendOnEmptyDocumentShouldReuseInitialParagraph()
{
    QCOMPARE(blockCount(), 1);
    ed->appendPlainText("Blah");
    QCOMPARE(blockCount(), 1);
}


class CursorPositionChangedRecorder : public QObject
{
    Q_OBJECT
public:
    inline CursorPositionChangedRecorder(QPlainTextEdit *ed)
        : editor(ed)
    {
        connect(editor, SIGNAL(cursorPositionChanged()), this, SLOT(recordCursorPos()));
    }

    QList<int> cursorPositions;

private slots:
    void recordCursorPos()
    {
        cursorPositions.append(editor->textCursor().position());
    }

private:
    QPlainTextEdit *editor;
};

void tst_QPlainTextEdit::cursorPositionChanged()
{
    QSignalSpy spy(ed, SIGNAL(cursorPositionChanged()));

    spy.clear();
    QTest::keyClick(ed, Qt::Key_A);
    QCOMPARE(spy.count(), 1);

    QTextCursor cursor = ed->textCursor();
    cursor.movePosition(QTextCursor::Start);
    ed->setTextCursor(cursor);
    cursor.movePosition(QTextCursor::End);
    spy.clear();
    cursor.insertText("Test");
    QCOMPARE(spy.count(), 0);

    cursor.movePosition(QTextCursor::End);
    ed->setTextCursor(cursor);
    cursor.movePosition(QTextCursor::Start);
    spy.clear();
    cursor.insertText("Test");
    QCOMPARE(spy.count(), 1);

    spy.clear();
    QTest::keyClick(ed, Qt::Key_Left);
    QCOMPARE(spy.count(), 1);

    CursorPositionChangedRecorder spy2(ed);
    QVERIFY(ed->textCursor().position() > 0);
    ed->setPlainText("Hello World");
    QCOMPARE(spy2.cursorPositions.count(), 1);
    QCOMPARE(spy2.cursorPositions.at(0), 0);
    QCOMPARE(ed->textCursor().position(), 0);
}

void tst_QPlainTextEdit::setTextCursor()
{
    QSignalSpy spy(ed, SIGNAL(cursorPositionChanged()));

    ed->setPlainText("Test");
    QTextCursor cursor = ed->textCursor();
    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::NextCharacter);

    spy.clear();

    ed->setTextCursor(cursor);
    QCOMPARE(spy.count(), 1);
}

#ifndef QT_NO_CLIPBOARD
void tst_QPlainTextEdit::undoAvailableAfterPaste()
{
    if (!nativeClipboardWorking())
        QSKIP("Clipboard not working with cron-started unit tests", SkipAll);

    QSignalSpy spy(ed->document(), SIGNAL(undoAvailable(bool)));

    const QString txt("Test");
    QApplication::clipboard()->setText(txt);
    ed->paste();
    QVERIFY(spy.count() >= 1);
    QCOMPARE(ed->toPlainText(), txt);
}
#endif

class UndoRedoRecorder : public QObject
{
    Q_OBJECT
public:
    UndoRedoRecorder(QTextDocument *doc)
        : undoRepetitions(false)
        , redoRepetitions(false)
        , undoCount(0)
        , redoCount(0)
    {
        connect(doc, SIGNAL(undoAvailable(bool)), this, SLOT(undoAvailable(bool)));
        connect(doc, SIGNAL(redoAvailable(bool)), this, SLOT(redoAvailable(bool)));
    }

    bool undoRepetitions;
    bool redoRepetitions;

private slots:
    void undoAvailable(bool enabled) {
        if (undoCount > 0 && enabled == lastUndoEnabled)
            undoRepetitions = true;

        ++undoCount;
        lastUndoEnabled = enabled;
    }

    void redoAvailable(bool enabled) {
        if (redoCount > 0 && enabled == lastRedoEnabled)
            redoRepetitions = true;

        ++redoCount;
        lastRedoEnabled = enabled;
    }

private:
    bool lastUndoEnabled;
    bool lastRedoEnabled;

    int undoCount;
    int redoCount;
};

void tst_QPlainTextEdit::undoRedoAvailableRepetition()
{
    UndoRedoRecorder spy(ed->document());

    ed->textCursor().insertText("ABC\n\nDEF\n\nGHI\n");
    ed->textCursor().insertText("foo\n");
    ed->textCursor().insertText("bar\n");
    ed->undo(); ed->undo(); ed->undo();
    ed->redo(); ed->redo(); ed->redo();

    QVERIFY(!spy.undoRepetitions);
    QVERIFY(!spy.redoRepetitions);
}

void tst_QPlainTextEdit::appendShouldNotTouchTheSelection()
{
    QTextCursor cursor(ed->document());
    QTextCharFormat fmt;
    fmt.setForeground(Qt::blue);
    cursor.insertText("H", fmt);
    fmt.setForeground(Qt::red);
    cursor.insertText("ey", fmt);

    cursor.insertText("some random text inbetween");

    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
    QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::blue));
    cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
    QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::red));
    cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
    QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::red));
    QCOMPARE(cursor.selectedText(), QString("Hey"));

    ed->setTextCursor(cursor);
    QVERIFY(ed->textCursor().hasSelection());

    ed->appendHtml("<b>Some Bold Text</b>");
    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::NextCharacter);
    QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::blue));
}

void tst_QPlainTextEdit::backspace()
{
    QTextCursor cursor = ed->textCursor();

    QTextListFormat listFmt;
    listFmt.setStyle(QTextListFormat::ListDisc);
    listFmt.setIndent(1);
    cursor.insertList(listFmt);
    cursor.insertText("A");

    ed->setTextCursor(cursor);

    // delete 'A'
    QTest::keyClick(ed, Qt::Key_Backspace);
    QVERIFY(ed->textCursor().currentList());
    // delete list
    QTest::keyClick(ed, Qt::Key_Backspace);
    QVERIFY(!ed->textCursor().currentList());
    QCOMPARE(ed->textCursor().blockFormat().indent(), 1);
    // outdent paragraph
    QTest::keyClick(ed, Qt::Key_Backspace);
    QCOMPARE(ed->textCursor().blockFormat().indent(), 0);
}

void tst_QPlainTextEdit::shiftBackspace()
{
    QTextCursor cursor = ed->textCursor();

    QTextListFormat listFmt;
    listFmt.setStyle(QTextListFormat::ListDisc);
    listFmt.setIndent(1);
    cursor.insertList(listFmt);
    cursor.insertText("A");

    ed->setTextCursor(cursor);

    // delete 'A'
    QTest::keyClick(ed, Qt::Key_Backspace, Qt::ShiftModifier);
    QVERIFY(ed->textCursor().currentList());
    // delete list
    QTest::keyClick(ed, Qt::Key_Backspace, Qt::ShiftModifier);
    QVERIFY(!ed->textCursor().currentList());
    QCOMPARE(ed->textCursor().blockFormat().indent(), 1);
    // outdent paragraph
    QTest::keyClick(ed, Qt::Key_Backspace, Qt::ShiftModifier);
    QCOMPARE(ed->textCursor().blockFormat().indent(), 0);
}

void tst_QPlainTextEdit::undoRedo()
{
    ed->clear();
    QTest::keyClicks(ed, "abc d");
    QCOMPARE(ed->toPlainText(), QString("abc d"));
    ed->undo();
    QCOMPARE(ed->toPlainText(), QString());
    ed->redo();
    QCOMPARE(ed->toPlainText(), QString("abc d"));
#ifdef Q_WS_WIN
    // shortcut for undo
    QTest::keyClick(ed, Qt::Key_Backspace, Qt::AltModifier);
    QCOMPARE(ed->toPlainText(), QString());
    // shortcut for redo
    QTest::keyClick(ed, Qt::Key_Backspace, Qt::ShiftModifier|Qt::AltModifier);
    QCOMPARE(ed->toPlainText(), QString("abc d"));
#endif
}

// Task #70465
void tst_QPlainTextEdit::preserveCharFormatInAppend()
{
    ed->appendHtml("First para");
    ed->appendHtml("<b>Second para</b>");
    ed->appendHtml("third para");

    QTextCursor cursor(ed->textCursor());

    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::NextCharacter);
    QCOMPARE(cursor.charFormat().fontWeight(), (int)QFont::Normal);
    QCOMPARE(cursor.block().text(), QString("First para"));

    cursor.movePosition(QTextCursor::NextBlock);
    cursor.movePosition(QTextCursor::NextCharacter);
    QCOMPARE(cursor.charFormat().fontWeight(), (int)QFont::Bold);
    QCOMPARE(cursor.block().text(), QString("Second para"));

    cursor.movePosition(QTextCursor::NextBlock);
    cursor.movePosition(QTextCursor::NextCharacter);
    QCOMPARE(cursor.charFormat().fontWeight(), (int)QFont::Normal);
    QCOMPARE(cursor.block().text(), QString("third para"));
}

#ifndef QT_NO_CLIPBOARD
void tst_QPlainTextEdit::copyAndSelectAllInReadonly()
{
    if (!nativeClipboardWorking())
        QSKIP("Clipboard not working with cron-started unit tests", SkipAll);

    ed->setReadOnly(true);
    ed->setPlainText("Hello World");

    QTextCursor cursor = ed->textCursor();
    cursor.clearSelection();
    ed->setTextCursor(cursor);
    QVERIFY(!ed->textCursor().hasSelection());

    QCOMPARE(ed->toPlainText(), QString("Hello World"));

    // shouldn't do anything
    QTest::keyClick(ed, Qt::Key_A);

    QCOMPARE(ed->toPlainText(), QString("Hello World"));

    QTest::keyClick(ed, Qt::Key_A, Qt::ControlModifier);

    QVERIFY(ed->textCursor().hasSelection());

    QApplication::clipboard()->setText(QString());
    QVERIFY(QApplication::clipboard()->text().isEmpty());

    QTest::keyClick(ed, Qt::Key_C, Qt::ControlModifier);
    QCOMPARE(QApplication::clipboard()->text(), QString("Hello World"));
}
#endif

void tst_QPlainTextEdit::ctrlAltInput()
{
    QTest::keyClick(ed, Qt::Key_At, Qt::ControlModifier | Qt::AltModifier);
    QCOMPARE(ed->toPlainText(), QString("@"));
}

void tst_QPlainTextEdit::noPropertiesOnDefaultTextEditCharFormat()
{
    // there should be no properties set on the default/initial char format
    // on a text edit. Font properties instead should be taken from the
    // widget's font (in sync with defaultFont property in document) and the
    // foreground color should be taken from the palette.
    QCOMPARE(ed->textCursor().charFormat().properties().count(), 0);
}

void tst_QPlainTextEdit::setPlainTextShouldEmitTextChangedOnce()
{
    QSignalSpy spy(ed, SIGNAL(textChanged()));
    ed->setPlainText("Yankee Doodle");
    QCOMPARE(spy.count(), 1);
    ed->setPlainText("");
    QCOMPARE(spy.count(), 2);
}

void tst_QPlainTextEdit::overwriteMode()
{
    QVERIFY(!ed->overwriteMode());
    QTest::keyClicks(ed, "Some first text");

    QCOMPARE(ed->toPlainText(), QString("Some first text"));

    ed->setOverwriteMode(true);

    QTextCursor cursor = ed->textCursor();
    cursor.setPosition(5);
    ed->setTextCursor(cursor);

    QTest::keyClicks(ed, "shiny");
    QCOMPARE(ed->toPlainText(), QString("Some shiny text"));

    cursor.movePosition(QTextCursor::End);
    ed->setTextCursor(cursor);

    QTest::keyClick(ed, Qt::Key_Enter);

    ed->setOverwriteMode(false);
    QTest::keyClicks(ed, "Second paragraph");

    QCOMPARE(blockCount(), 2);

    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::EndOfBlock);

    QCOMPARE(cursor.position(), 15);
    ed->setTextCursor(cursor);

    ed->setOverwriteMode(true);

    QTest::keyClicks(ed, " blah");

    QCOMPARE(blockCount(), 2);

    QTextBlock block = ed->document()->begin();
    QCOMPARE(block.text(), QString("Some shiny text blah"));
    block = block.next();
    QCOMPARE(block.text(), QString("Second paragraph"));
}

void tst_QPlainTextEdit::shiftDownInLineLastShouldSelectToEnd_data()
{
    // shift cursor-down in the last line should select to the end of the document

    QTest::addColumn<QString>("input");
    QTest::addColumn<int>("totalLineCount");

    QTest::newRow("1") << QString("Foo\nBar") << 2;
    QTest::newRow("2") << QString("Foo\nBar") + QChar(QChar::LineSeparator) + QString("Baz") << 3;
}

void tst_QPlainTextEdit::shiftDownInLineLastShouldSelectToEnd()
{
    QFETCH(QString, input);
    QFETCH(int, totalLineCount);

    ed->setPlainText(input);
    ed->show();

    // ensure we're layouted
    for (QTextBlock block = ed->document()->begin(); block.isValid(); block = block.next())
        ed->document()->documentLayout()->blockBoundingRect(block);

    QCOMPARE(blockCount(), 2);

    int lineCount = 0;
    for (QTextBlock block = ed->document()->begin(); block.isValid(); block = block.next())
        lineCount += block.layout()->lineCount();
    QCOMPARE(lineCount, totalLineCount);

    QTextCursor cursor = ed->textCursor();
    cursor.movePosition(QTextCursor::Start);
    ed->setTextCursor(cursor);

    for (int i = 0; i < lineCount; ++i) {
        QTest::keyClick(ed, Qt::Key_Down, Qt::ShiftModifier);
    }

    input.replace(QLatin1Char('\n'), QChar(QChar::ParagraphSeparator));
    QCOMPARE(ed->textCursor().selectedText(), input);
    QVERIFY(ed->textCursor().atEnd());

    // also test that without shift modifier the cursor does not move to the end
    // for Key_Down in the last line
    cursor.movePosition(QTextCursor::Start);
    ed->setTextCursor(cursor);
    for (int i = 0; i < lineCount; ++i) {
        QTest::keyClick(ed, Qt::Key_Down);
    }
    QVERIFY(!ed->textCursor().atEnd());
}

void tst_QPlainTextEdit::undoRedoShouldRepositionTextEditCursor()
{
    ed->setPlainText("five\nlines\nin\nthis\ntextedit");
    QTextCursor cursor = ed->textCursor();
    cursor.movePosition(QTextCursor::Start);

    ed->setUndoRedoEnabled(false);
    ed->setUndoRedoEnabled(true);

    QVERIFY(!ed->document()->isUndoAvailable());
    QVERIFY(!ed->document()->isRedoAvailable());

    cursor.insertText("Blah");

    QVERIFY(ed->document()->isUndoAvailable());
    QVERIFY(!ed->document()->isRedoAvailable());

    cursor.movePosition(QTextCursor::End);
    ed->setTextCursor(cursor);

    QVERIFY(QMetaObject::invokeMethod(ed, "undo"));

    QVERIFY(!ed->document()->isUndoAvailable());
    QVERIFY(ed->document()->isRedoAvailable());

    QCOMPARE(ed->textCursor().position(), 0);

    cursor.movePosition(QTextCursor::End);
    ed->setTextCursor(cursor);

    QVERIFY(QMetaObject::invokeMethod(ed, "redo"));

    QVERIFY(ed->document()->isUndoAvailable());
    QVERIFY(!ed->document()->isRedoAvailable());

    QCOMPARE(ed->textCursor().position(), 4);
}

void tst_QPlainTextEdit::lineWrapModes()
{
    QWidget *window = new QWidget;
    ed->setParent(window);
    window->show();
    ed->show();
    ed->setPlainText("a b c d e f g h i j k l m n o p q r s t u v w x y z");
    ed->setLineWrapMode(QPlainTextEdit::NoWrap);
    QCOMPARE(lineCount(), 1);
    ed->setLineWrapMode(QPlainTextEdit::WidgetWidth);

    // QPlainTextEdit does lazy line layout on resize, only for the visible blocks.
    // We thus need to make it wide enough to show something visible.
    int minimumWidth = 2 * ed->document()->documentMargin();
    minimumWidth += ed->fontMetrics().width(QLatin1Char('a'));
    ed->resize(minimumWidth, 1000);
    QCOMPARE(lineCount(), 26);
    ed->setParent(0);
    delete window;
}

void tst_QPlainTextEdit::mouseCursorShape()
{
#ifndef QT_NO_CURSOR
    // always show an IBeamCursor, see change 170146
    QVERIFY(!ed->isReadOnly());
    QVERIFY(ed->viewport()->cursor().shape() == Qt::IBeamCursor);

    ed->setReadOnly(true);
    QVERIFY(ed->viewport()->cursor().shape() == Qt::IBeamCursor);

    ed->setPlainText("Foo");
    QVERIFY(ed->viewport()->cursor().shape() == Qt::IBeamCursor);
#endif
}

void tst_QPlainTextEdit::implicitClear()
{
    // test that QPlainTextEdit::setHtml, etc. avoid calling clear() but instead call
    // QTextDocument::setHtml/etc. instead, which also clear the contents and
    // cached resource but preserve manually added resources. setHtml on a textedit
    // should behave the same as on a document with respect to that.
    // see also clearResources() autotest in qtextdocument

    // regular resource for QTextDocument
    QUrl testUrl(":/foobar");
    QVariant testResource("hello world");

    ed->document()->addResource(QTextDocument::ImageResource, testUrl, testResource);
    QVERIFY(ed->document()->resource(QTextDocument::ImageResource, testUrl) == testResource);

    ed->setPlainText("Blah");
    QVERIFY(ed->document()->resource(QTextDocument::ImageResource, testUrl) == testResource);

    ed->setPlainText("<b>Blah</b>");
    QVERIFY(ed->document()->resource(QTextDocument::ImageResource, testUrl) == testResource);

    ed->clear();
    QVERIFY(!ed->document()->resource(QTextDocument::ImageResource, testUrl).isValid());
    QVERIFY(ed->toPlainText().isEmpty());
}

#ifndef QT_NO_CLIPBOARD
void tst_QPlainTextEdit::copyAvailable_data()
{
    QTest::addColumn<pairListType>("keystrokes");
    QTest::addColumn<QList<bool> >("copyAvailable");
    QTest::addColumn<QString>("function");

    pairListType keystrokes;
    QList<bool> copyAvailable;

    keystrokes << qMakePair(Qt::Key_B, Qt::NoModifier) <<  qMakePair(Qt::Key_B, Qt::NoModifier)
               << qMakePair(Qt::Key_Left, Qt::ShiftModifier);
    copyAvailable << true ;
    QTest::newRow(QString("Case1 B,B, <- + shift | signals: true").toLatin1())
        << keystrokes << copyAvailable << QString();

    keystrokes.clear();
    copyAvailable.clear();

    keystrokes << qMakePair(Qt::Key_T, Qt::NoModifier) << qMakePair(Qt::Key_A, Qt::NoModifier)
               <<  qMakePair(Qt::Key_A, Qt::NoModifier) << qMakePair(Qt::Key_Left, Qt::ShiftModifier);
    copyAvailable << true << false;
    QTest::newRow(QString("Case2 T,A,A, <- + shift, cut() | signals: true, false").toLatin1())
        << keystrokes << copyAvailable << QString("cut");

    keystrokes.clear();
    copyAvailable.clear();

    keystrokes << qMakePair(Qt::Key_T, Qt::NoModifier) << qMakePair(Qt::Key_A, Qt::NoModifier)
               <<  qMakePair(Qt::Key_A, Qt::NoModifier) << qMakePair(Qt::Key_Left, Qt::ShiftModifier)
               << qMakePair(Qt::Key_Left, Qt::ShiftModifier)  << qMakePair(Qt::Key_Left, Qt::ShiftModifier);
    copyAvailable << true;
    QTest::newRow(QString("Case3 T,A,A, <- + shift, <- + shift, <- + shift, copy() | signals: true").toLatin1())
        << keystrokes << copyAvailable << QString("copy");

    keystrokes.clear();
    copyAvailable.clear();

    keystrokes << qMakePair(Qt::Key_T, Qt::NoModifier) << qMakePair(Qt::Key_A, Qt::NoModifier)
               <<  qMakePair(Qt::Key_A, Qt::NoModifier) << qMakePair(Qt::Key_Left, Qt::ShiftModifier)
               << qMakePair(Qt::Key_Left, Qt::ShiftModifier)  << qMakePair(Qt::Key_Left, Qt::ShiftModifier)
               << qMakePair(Qt::Key_X, Qt::ControlModifier);
    copyAvailable << true << false;
    QTest::newRow(QString("Case4 T,A,A, <- + shift, <- + shift, <- + shift, ctrl + x, paste() | signals: true, false").toLatin1())
        << keystrokes << copyAvailable << QString("paste");

    keystrokes.clear();
    copyAvailable.clear();

    keystrokes << qMakePair(Qt::Key_B, Qt::NoModifier) <<  qMakePair(Qt::Key_B, Qt::NoModifier)
               << qMakePair(Qt::Key_Left, Qt::ShiftModifier) << qMakePair(Qt::Key_Left, Qt::NoModifier);
    copyAvailable << true << false;
    QTest::newRow(QString("Case5 B,B, <- + shift, <- | signals: true, false").toLatin1())
        << keystrokes << copyAvailable << QString();

    keystrokes.clear();
    copyAvailable.clear();

    keystrokes << qMakePair(Qt::Key_B, Qt::NoModifier) <<  qMakePair(Qt::Key_A, Qt::NoModifier)
               << qMakePair(Qt::Key_Left, Qt::ShiftModifier) << qMakePair(Qt::Key_Left, Qt::NoModifier)
               << qMakePair(Qt::Key_Right, Qt::ShiftModifier);
    copyAvailable << true << false << true << false;
    QTest::newRow(QString("Case6 B,A, <- + shift, ->, <- + shift | signals: true, false, true, false").toLatin1())
        << keystrokes << copyAvailable << QString("cut");

    keystrokes.clear();
    copyAvailable.clear();

    keystrokes << qMakePair(Qt::Key_T, Qt::NoModifier) << qMakePair(Qt::Key_A, Qt::NoModifier)
               <<  qMakePair(Qt::Key_A, Qt::NoModifier) << qMakePair(Qt::Key_Left, Qt::ShiftModifier)
               << qMakePair(Qt::Key_Left, Qt::ShiftModifier)  << qMakePair(Qt::Key_Left, Qt::ShiftModifier)
               << qMakePair(Qt::Key_X, Qt::ControlModifier);
    copyAvailable << true << false << true;
    QTest::newRow(QString("Case7 T,A,A, <- + shift, <- + shift, <- + shift, ctrl + x, undo() | signals: true, false, true").toLatin1())
        << keystrokes << copyAvailable << QString("undo");
}

//Tests the copyAvailable slot for several cases
void tst_QPlainTextEdit::copyAvailable()
{
    QFETCH(pairListType,keystrokes);
    QFETCH(QList<bool>, copyAvailable);
    QFETCH(QString, function);

#ifdef Q_WS_MAC
    QSKIP("copyAvailable has never passed on Mac, task to fix is 132482", SkipAll);
#endif
    ed->clear();
    QApplication::clipboard()->clear();
    QVERIFY(!ed->canPaste());
    QSignalSpy spyCopyAvailabe(ed, SIGNAL(copyAvailable(bool)));

    //Execute Keystrokes
    foreach(keyPairType keyPair, keystrokes) {
        QTest::keyClick(ed, keyPair.first, keyPair.second );
    }

    //Execute ed->"function"
    if (function == "cut")
        ed->cut();
    else if (function == "copy")
        ed->copy();
    else if (function == "paste")
        ed->paste();
    else if (function == "undo")
        ed->paste();
    else if (function == "redo")
        ed->paste();

    //Compare spied signals
    QEXPECT_FAIL("Case7 T,A,A, <- + shift, <- + shift, <- + shift, ctrl + x, undo() | signals: true, false, true",
        "Wrong undo selection behaviour. Should be fixed in some future release. (See task: 132482)", Abort);
    QCOMPARE(spyCopyAvailabe.count(), copyAvailable.count());
    for (int i=0;i<spyCopyAvailabe.count(); i++) {
        QVariant variantSpyCopyAvailable = spyCopyAvailabe.at(i).at(0);
        QVERIFY2(variantSpyCopyAvailable.toBool() == copyAvailable.at(i), QString("Spied singnal: %1").arg(i).toLatin1());
    }
}
#endif

void tst_QPlainTextEdit::undoRedoAfterSetContent()
{
    QVERIFY(!ed->document()->isUndoAvailable());
    QVERIFY(!ed->document()->isRedoAvailable());
    ed->setPlainText("Foobar");
    QVERIFY(!ed->document()->isUndoAvailable());
    QVERIFY(!ed->document()->isRedoAvailable());
    ed->setPlainText("<p>bleh</p>");
    QVERIFY(!ed->document()->isUndoAvailable());
    QVERIFY(!ed->document()->isRedoAvailable());
}

void tst_QPlainTextEdit::numPadKeyNavigation()
{
    ed->setPlainText("Hello World");
    QCOMPARE(ed->textCursor().position(), 0);
    QTest::keyClick(ed, Qt::Key_Right, Qt::KeypadModifier);
    QCOMPARE(ed->textCursor().position(), 1);
}

void tst_QPlainTextEdit::moveCursor()
{
    ed->setPlainText("Test");

    QSignalSpy cursorMovedSpy(ed, SIGNAL(cursorPositionChanged()));

    QCOMPARE(ed->textCursor().position(), 0);
    ed->moveCursor(QTextCursor::NextCharacter);
    QCOMPARE(ed->textCursor().position(), 1);
    QCOMPARE(cursorMovedSpy.count(), 1);
    ed->moveCursor(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
    QCOMPARE(ed->textCursor().position(), 2);
    QCOMPARE(cursorMovedSpy.count(), 2);
    QCOMPARE(ed->textCursor().selectedText(), QString("e"));
}

class MyTextEdit : public QPlainTextEdit
{
public:
    inline MyTextEdit()
        : createMimeDataCallCount(0),
          canInsertCallCount(0),
          insertCallCount(0)
    {}

    mutable int createMimeDataCallCount;
    mutable int canInsertCallCount;
    mutable int insertCallCount;

    virtual QMimeData *createMimeDataFromSelection() const {
        createMimeDataCallCount++;
        return QPlainTextEdit::createMimeDataFromSelection();
    }
    virtual bool canInsertFromMimeData(const QMimeData *source) const {
        canInsertCallCount++;
        return QPlainTextEdit::canInsertFromMimeData(source);
    }
    virtual void insertFromMimeData(const QMimeData *source) {
        insertCallCount++;
        QPlainTextEdit::insertFromMimeData(source);
    }

};

#ifndef QT_NO_CLIPBOARD
void tst_QPlainTextEdit::mimeDataReimplementations()
{
    MyTextEdit ed;
    ed.setPlainText("Hello World");

    QCOMPARE(ed.createMimeDataCallCount, 0);
    QCOMPARE(ed.canInsertCallCount, 0);
    QCOMPARE(ed.insertCallCount, 0);

    ed.selectAll();

    QCOMPARE(ed.createMimeDataCallCount, 0);
    QCOMPARE(ed.canInsertCallCount, 0);
    QCOMPARE(ed.insertCallCount, 0);

    ed.copy();

    QCOMPARE(ed.createMimeDataCallCount, 1);
    QCOMPARE(ed.canInsertCallCount, 0);
    QCOMPARE(ed.insertCallCount, 0);

#ifdef QT_BUILD_INTERNAL
    QTextControl *control = qFindChild<QTextControl *>(&ed);
    QVERIFY(control);

    control->canInsertFromMimeData(QApplication::clipboard()->mimeData());

    QCOMPARE(ed.createMimeDataCallCount, 1);
    QCOMPARE(ed.canInsertCallCount, 1);
    QCOMPARE(ed.insertCallCount, 0);

    ed.paste();

    QCOMPARE(ed.createMimeDataCallCount, 1);
    QCOMPARE(ed.canInsertCallCount, 1);
    QCOMPARE(ed.insertCallCount, 1);
#endif
}
#endif

void tst_QPlainTextEdit::shiftEnterShouldInsertLineSeparator()
{
    QTest::keyClick(ed, Qt::Key_A);
    QTest::keyClick(ed, Qt::Key_Enter, Qt::ShiftModifier);
    QTest::keyClick(ed, Qt::Key_B);
    QString expected;
    expected += 'a';
    expected += QChar::LineSeparator;
    expected += 'b';
    QCOMPARE(ed->textCursor().block().text(), expected);
}

void tst_QPlainTextEdit::selectWordsFromStringsContainingSeparators_data()
{
    QTest::addColumn<QString>("testString");
    QTest::addColumn<QString>("selectedWord");

    QStringList wordSeparators;
    wordSeparators << "." << "," << "?" << "!" << ":" << ";" << "-" << "<" << ">" << "["
                   << "]" << "(" << ")" << "{" << "}" << "=" << "\t"<< QString(QChar::Nbsp);

    foreach (QString s, wordSeparators)
        QTest::newRow(QString("separator: " + s).toLocal8Bit()) << QString("foo") + s + QString("bar") << QString("foo");
}

void tst_QPlainTextEdit::selectWordsFromStringsContainingSeparators()
{
    QFETCH(QString, testString);
    QFETCH(QString, selectedWord);
    ed->setPlainText(testString);
    QTextCursor cursor = ed->textCursor();
    cursor.movePosition(QTextCursor::StartOfLine);
    cursor.select(QTextCursor::WordUnderCursor);
    QVERIFY(cursor.hasSelection());
    QCOMPARE(cursor.selection().toPlainText(), selectedWord);
    cursor.clearSelection();
}

#ifndef QT_NO_CLIPBOARD
void tst_QPlainTextEdit::canPaste()
{
    if (!nativeClipboardWorking())
        QSKIP("Clipboard not working with cron-started unit tests", SkipAll);

    QApplication::clipboard()->setText(QString());
    QVERIFY(!ed->canPaste());
    QApplication::clipboard()->setText("Test");
    QVERIFY(ed->canPaste());
    ed->setTextInteractionFlags(Qt::NoTextInteraction);
    QVERIFY(!ed->canPaste());
}
#endif

void tst_QPlainTextEdit::ensureCursorVisibleOnInitialShow()
{
    QString manyPagesOfPlainText;
    for (int i = 0; i < 800; ++i)
        manyPagesOfPlainText += QLatin1String("Blah blah blah blah blah blah\n");

    ed->setPlainText(manyPagesOfPlainText);
    QCOMPARE(ed->textCursor().position(), 0);

    ed->moveCursor(QTextCursor::End);
    ed->show();
    QVERIFY(ed->verticalScrollBar()->value() > 10);

    ed->moveCursor(QTextCursor::Start);
    QVERIFY(ed->verticalScrollBar()->value() < 10);
    ed->hide();
    ed->verticalScrollBar()->setValue(ed->verticalScrollBar()->maximum());
    ed->show();
    QCOMPARE(ed->verticalScrollBar()->value(), ed->verticalScrollBar()->maximum());
}

class TestEdit : public QPlainTextEdit
{
public:
    TestEdit() : resizeEventCalled(false) {}

    bool resizeEventCalled;

protected:
    virtual void resizeEvent(QResizeEvent *e)
    {
        QPlainTextEdit::resizeEvent(e);
        setPlainText("<img src=qtextbrowser-resizeevent.png width=" + QString::number(size().width()) + "><br>Size is " + QString::number(size().width()) + " x " + QString::number(size().height()));
        resizeEventCalled = true;
    }
};

void tst_QPlainTextEdit::setTextInsideResizeEvent()
{
    TestEdit edit;
    edit.show();
    edit.resize(800, 600);
    QVERIFY(edit.resizeEventCalled);
}

void tst_QPlainTextEdit::colorfulAppend()
{
    QTextCharFormat fmt;

    fmt.setForeground(QBrush(Qt::red));
    ed->mergeCurrentCharFormat(fmt);
    ed->appendPlainText("Red");
    fmt.setForeground(QBrush(Qt::blue));
    ed->mergeCurrentCharFormat(fmt);
    ed->appendPlainText("Blue");
    fmt.setForeground(QBrush(Qt::green));
    ed->mergeCurrentCharFormat(fmt);
    ed->appendPlainText("Green");

    QCOMPARE(ed->document()->blockCount(), 3);
    QTextBlock block = ed->document()->begin();
    QCOMPARE(block.begin().fragment().text(), QString("Red"));
    QVERIFY(block.begin().fragment().charFormat().foreground().color() == Qt::red);
    block = block.next();
    QCOMPARE(block.begin().fragment().text(), QString("Blue"));
    QVERIFY(block.begin().fragment().charFormat().foreground().color() == Qt::blue);
    block = block.next();
    QCOMPARE(block.begin().fragment().text(), QString("Green"));
    QVERIFY(block.begin().fragment().charFormat().foreground().color() == Qt::green);
}

void tst_QPlainTextEdit::ensureVisibleWithRtl()
{
    ed->setLayoutDirection(Qt::RightToLeft);
    ed->setLineWrapMode(QPlainTextEdit::NoWrap);
    QString txt(500, QChar(QLatin1Char('a')));
    QCOMPARE(txt.length(), 500);
    ed->setPlainText(txt);
    ed->resize(100, 100);
    ed->show();

    qApp->processEvents();

    QVERIFY(ed->horizontalScrollBar()->maximum() > 0);

    ed->moveCursor(QTextCursor::Start);
    QCOMPARE(ed->horizontalScrollBar()->value(), ed->horizontalScrollBar()->maximum());
    ed->moveCursor(QTextCursor::End);
    QCOMPARE(ed->horizontalScrollBar()->value(), 0);
    ed->moveCursor(QTextCursor::Start);
    QCOMPARE(ed->horizontalScrollBar()->value(), ed->horizontalScrollBar()->maximum());
    ed->moveCursor(QTextCursor::End);
    QCOMPARE(ed->horizontalScrollBar()->value(), 0);
}

void tst_QPlainTextEdit::preserveCharFormatAfterSetPlainText()
{
    QTextCharFormat fmt;
    fmt.setForeground(QBrush(Qt::blue));
    ed->mergeCurrentCharFormat(fmt);
    ed->setPlainText("This is blue");
    ed->appendPlainText("This should still be blue");
    QTextBlock block = ed->document()->begin();
    block = block.next();
    QCOMPARE(block.text(), QString("This should still be blue"));
    QVERIFY(block.begin().fragment().charFormat().foreground().color() == QColor(Qt::blue));
}

void tst_QPlainTextEdit::extraSelections()
{
    ed->setPlainText("Hello World");

    QTextCursor c = ed->textCursor();
    c.movePosition(QTextCursor::Start);
    c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
    const int endPos = c.position();

    QTextEdit::ExtraSelection sel;
    sel.cursor = c;
    ed->setExtraSelections(QList<QTextEdit::ExtraSelection>() << sel);

    c.movePosition(QTextCursor::Start);
    c.movePosition(QTextCursor::NextWord);
    const int wordPos = c.position();
    c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
    sel.cursor = c;
    ed->setExtraSelections(QList<QTextEdit::ExtraSelection>() << sel);

    QList<QTextEdit::ExtraSelection> selections = ed->extraSelections();
    QCOMPARE(selections.count(), 1);
    QCOMPARE(selections.at(0).cursor.position(), endPos);
    QCOMPARE(selections.at(0).cursor.anchor(), wordPos);
}

void tst_QPlainTextEdit::adjustScrollbars()
{
// For some reason ff is defined to be << on Mac Panther / gcc 3.3
#undef ff
    QFont ff(ed->font());
    ff.setFamily("Tahoma");
    ff.setPointSize(11);
    ed->setFont(ff);
    ed->setMinimumSize(140, 100);
    ed->setMaximumSize(140, 100);
    ed->show();
    QLatin1String txt("\nabc def ghi jkl mno pqr stu vwx");
    ed->setPlainText(txt + txt + txt + txt);

    QVERIFY(ed->verticalScrollBar()->maximum() > 0);

    ed->moveCursor(QTextCursor::End);
    int oldMaximum = ed->verticalScrollBar()->maximum();
    QTextCursor cursor = ed->textCursor();
    cursor.insertText(QLatin1String("\n"));
    cursor.deletePreviousChar();
    QCOMPARE(ed->verticalScrollBar()->maximum(), oldMaximum);
}

class SignalReceiver : public QObject
{
    Q_OBJECT
public:
    SignalReceiver() : received(0) {}

    int receivedSignals() const { return received; }
    QTextCharFormat charFormat() const { return format; }

public slots:
    void charFormatChanged(const QTextCharFormat &tcf) { ++received; format = tcf; }

private:
    QTextCharFormat format;
    int received;
};

void tst_QPlainTextEdit::textObscuredByScrollbars()
{
    ed->textCursor().insertText(
            "ab cab cab c abca kjsdf lka sjd lfk jsal df j kasdf abc ab abc "
            "a b c d e f g h i j k l m n o p q r s t u v w x y z "
            "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
            "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
            "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
            "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
            "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
            "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
            "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
            "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
            "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
            "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
    );
    ed->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    ed->show();

    QSize documentSize = ed->document()->documentLayout()->documentSize().toSize();
    QSize viewportSize = ed->viewport()->size();

    QVERIFY(documentSize.width() <= viewportSize.width());
}

void tst_QPlainTextEdit::setTextPreservesUndoRedoEnabled()
{
    QVERIFY(ed->isUndoRedoEnabled());

    ed->setPlainText("Test");

    QVERIFY(ed->isUndoRedoEnabled());

    ed->setUndoRedoEnabled(false);
    QVERIFY(!ed->isUndoRedoEnabled());
    ed->setPlainText("Test2");
    QVERIFY(!ed->isUndoRedoEnabled());

    ed->setPlainText("<p>hello");
    QVERIFY(!ed->isUndoRedoEnabled());
}

void tst_QPlainTextEdit::wordWrapProperty()
{
    {
        QPlainTextEdit edit;
        QTextDocument *doc = new QTextDocument(&edit);
        doc->setDocumentLayout(new QPlainTextDocumentLayout(doc));
        edit.setDocument(doc);
        edit.setWordWrapMode(QTextOption::NoWrap);
        QVERIFY(doc->defaultTextOption().wrapMode() == QTextOption::NoWrap);
    }
    {
        QPlainTextEdit edit;
        QTextDocument *doc = new QTextDocument(&edit);
        doc->setDocumentLayout(new QPlainTextDocumentLayout(doc));
        edit.setWordWrapMode(QTextOption::NoWrap);
        edit.setDocument(doc);
        QVERIFY(doc->defaultTextOption().wrapMode() == QTextOption::NoWrap);
    }
}

void tst_QPlainTextEdit::lineWrapProperty()
{
    QVERIFY(ed->wordWrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere);
    QVERIFY(ed->lineWrapMode() == QPlainTextEdit::WidgetWidth);
    ed->setLineWrapMode(QPlainTextEdit::NoWrap);
    QVERIFY(ed->lineWrapMode() == QPlainTextEdit::NoWrap);
    QVERIFY(ed->wordWrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere);
    QVERIFY(ed->document()->defaultTextOption().wrapMode() == QTextOption::NoWrap);
}

void tst_QPlainTextEdit::selectionChanged()
{
    ed->setPlainText("Hello World");

    ed->moveCursor(QTextCursor::Start);

    QSignalSpy selectionChangedSpy(ed, SIGNAL(selectionChanged()));

    QTest::keyClick(ed, Qt::Key_Right);
    QCOMPARE(ed->textCursor().position(), 1);
    QCOMPARE(selectionChangedSpy.count(), 0);

    QTest::keyClick(ed, Qt::Key_Right, Qt::ShiftModifier);
    QCOMPARE(ed->textCursor().position(), 2);
    QCOMPARE(selectionChangedSpy.count(), 1);

    QTest::keyClick(ed, Qt::Key_Right, Qt::ShiftModifier);
    QCOMPARE(ed->textCursor().position(), 3);
    QCOMPARE(selectionChangedSpy.count(), 2);

    QTest::keyClick(ed, Qt::Key_Right, Qt::ShiftModifier);
    QCOMPARE(ed->textCursor().position(), 4);
    QCOMPARE(selectionChangedSpy.count(), 3);

    QTest::keyClick(ed, Qt::Key_Right);
    QCOMPARE(ed->textCursor().position(), 5);
    QCOMPARE(selectionChangedSpy.count(), 4);

    QTest::keyClick(ed, Qt::Key_Right);
    QCOMPARE(ed->textCursor().position(), 6);
    QCOMPARE(selectionChangedSpy.count(), 4);
}

void tst_QPlainTextEdit::blockCountChanged()
{
    QSignalSpy blockCountCpangedSpy(ed, SIGNAL(blockCountChanged(int)));
    ed->setPlainText("Hello");
    QCOMPARE(blockCountCpangedSpy.count(), 0);
    ed->setPlainText("Hello World");
    QCOMPARE(blockCountCpangedSpy.count(), 0);
    ed->setPlainText("Hello \n World \n this \n has \n more \n blocks \n than \n just \n one");
    QCOMPARE(blockCountCpangedSpy.count(), 1);
    ed->setPlainText("One");
    QCOMPARE(blockCountCpangedSpy.count(), 2);
    ed->setPlainText("One \n Two");
    QCOMPARE(blockCountCpangedSpy.count(), 3);
    ed->setPlainText("Three \n Four");
    QCOMPARE(blockCountCpangedSpy.count(), 3);
}


QTEST_MAIN(tst_QPlainTextEdit)
#include "tst_qplaintextedit.moc"
