From 023f4ec1a135d1a3bd9ce659b095bd4ed2e8b806 Mon Sep 17 00:00:00 2001
From: Liang Qi <liang.qi@digia.com>
Date: Thu, 21 Feb 2013 11:38:10 +0100
Subject: [PATCH] Support QQuickItem.activeFocusOnTab in QtQuick.Controls

Change-Id: Ie23b504f590e6c4e7f2a1a9090c2dd8671389937
Reviewed-by: Jens Bache-Wiig <jens.bache-wiig@digia.com>
---
 src/controls/Button.qml                       |   2 +
 src/controls/CheckBox.qml                     |   2 +
 src/controls/ComboBox.qml                     |   2 +
 src/controls/GroupBox.qml                     |   2 +
 src/controls/Label.qml                        |   1 +
 src/controls/ProgressBar.qml                  |   2 +
 src/controls/RadioButton.qml                  |   2 +
 src/controls/ScrollView.qml                   |   2 +
 src/controls/Slider.qml                       |   2 +
 src/controls/SpinBox.qml                      |   2 +
 src/controls/StatusBar.qml                    |   1 +
 src/controls/Tab.qml                          |   2 +
 src/controls/TabView.qml                      |   2 +
 src/controls/TextArea.qml                     |   2 +
 src/controls/TextField.qml                    |   2 +
 src/controls/ToolBar.qml                      |   1 +
 src/controls/ToolButton.qml                   |   2 +
 src/private/AbstractCheckable.qml             |   2 +
 src/private/BasicButton.qml                   |   2 +
 src/private/Control.qml                       |   2 +
 src/private/ScrollBar.qml                     |   2 +
 src/private/TabBar.qml                        |   2 +
 .../activeFocusOnTab/activeFocusOnTab.pro     |  12 +
 .../data/activeFocusOnTab.qml                 | 206 +++++++++
 .../activeFocusOnTab/tst_activeFocusOnTab.cpp | 427 ++++++++++++++++++
 tests/auto/auto.pro                           |   2 +-
 tests/auto/shared/util.cpp                    | 138 ++++++
 tests/auto/shared/util.h                      | 111 +++++
 tests/auto/shared/util.pri                    |   8 +
 tests/auto/shared/visualtestutil.cpp          |  76 ++++
 tests/auto/shared/visualtestutil.h            | 117 +++++
 31 files changed, 1137 insertions(+), 1 deletion(-)
 create mode 100644 tests/auto/activeFocusOnTab/activeFocusOnTab.pro
 create mode 100644 tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml
 create mode 100644 tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp
 create mode 100644 tests/auto/shared/util.cpp
 create mode 100644 tests/auto/shared/util.h
 create mode 100644 tests/auto/shared/util.pri
 create mode 100644 tests/auto/shared/visualtestutil.cpp
 create mode 100644 tests/auto/shared/visualtestutil.h

diff --git a/src/controls/Button.qml b/src/controls/Button.qml
index 5317e6147..6872a143a 100644
--- a/src/controls/Button.qml
+++ b/src/controls/Button.qml
@@ -83,6 +83,8 @@ BasicButton {
     */
     property url iconSource
 
+    activeFocusOnTab: true
+
     Accessible.name: text
 
     style: Qt.createComponent(Settings.THEME_PATH + "/ButtonStyle.qml", button)
diff --git a/src/controls/CheckBox.qml b/src/controls/CheckBox.qml
index c459e812b..163575331 100644
--- a/src/controls/CheckBox.qml
+++ b/src/controls/CheckBox.qml
@@ -132,6 +132,8 @@ AbstractCheckable {
 
     style: Qt.createComponent(Settings.THEME_PATH + "/CheckBoxStyle.qml", checkBox)
 
+    activeFocusOnTab: true
+
     Accessible.role: Accessible.CheckBox
     Accessible.name: text
 
diff --git a/src/controls/ComboBox.qml b/src/controls/ComboBox.qml
index 8942683e3..47efb700e 100644
--- a/src/controls/ComboBox.qml
+++ b/src/controls/ComboBox.qml
@@ -93,6 +93,8 @@ Control {
 
     style: Qt.createComponent(Settings.THEME_PATH + "/ComboBoxStyle.qml", comboBox)
 
+    activeFocusOnTab: true
+
     Accessible.role: Accessible.ComboBox
 
     MouseArea {
diff --git a/src/controls/GroupBox.qml b/src/controls/GroupBox.qml
index 0571d1fb7..16c40d922 100644
--- a/src/controls/GroupBox.qml
+++ b/src/controls/GroupBox.qml
@@ -151,6 +151,8 @@ Item {
     Accessible.role: Accessible.Grouping
     Accessible.name: title
 
+    activeFocusOnTab: false
+
     Loader {
         id: loader
         property alias control: groupbox
diff --git a/src/controls/Label.qml b/src/controls/Label.qml
index e983ecc79..db6de97f4 100644
--- a/src/controls/Label.qml
+++ b/src/controls/Label.qml
@@ -81,6 +81,7 @@ Text {
 
     id: label
     color: pal.text
+    activeFocusOnTab: false
     renderType: Text.NativeRendering
     SystemPalette {
         id: pal
diff --git a/src/controls/ProgressBar.qml b/src/controls/ProgressBar.qml
index e49ae8be8..381936544 100644
--- a/src/controls/ProgressBar.qml
+++ b/src/controls/ProgressBar.qml
@@ -112,6 +112,8 @@ Control {
         setValue(value)
     }
 
+    activeFocusOnTab: false
+
     Accessible.role: Accessible.ProgressBar
     Accessible.name: value
 
diff --git a/src/controls/RadioButton.qml b/src/controls/RadioButton.qml
index bfb37a606..87f572578 100644
--- a/src/controls/RadioButton.qml
+++ b/src/controls/RadioButton.qml
@@ -77,6 +77,8 @@ import "Styles/Settings.js" as Settings
 AbstractCheckable {
     id: radioButton
 
+    activeFocusOnTab: true
+
     Accessible.role: Accessible.RadioButton
 
     /*!
diff --git a/src/controls/ScrollView.qml b/src/controls/ScrollView.qml
index 8cf5b48ae..6bdc7c2a5 100644
--- a/src/controls/ScrollView.qml
+++ b/src/controls/ScrollView.qml
@@ -138,6 +138,8 @@ FocusScope {
     /*! \internal */
     property alias verticalScrollBar: scroller.verticalScrollBar
 
+    activeFocusOnTab: true
+
     /*! \internal */
     onContentItemChanged: {
 
diff --git a/src/controls/Slider.qml b/src/controls/Slider.qml
index 7f7231acf..bb2102ff2 100644
--- a/src/controls/Slider.qml
+++ b/src/controls/Slider.qml
@@ -167,6 +167,8 @@ Control {
     /*! \internal */
     property bool __horizontal: orientation === Qt.Horizontal
 
+    activeFocusOnTab: true
+
     Accessible.role: Accessible.Slider
     Accessible.name: value
 
diff --git a/src/controls/SpinBox.qml b/src/controls/SpinBox.qml
index 576a13c10..de836b24b 100644
--- a/src/controls/SpinBox.qml
+++ b/src/controls/SpinBox.qml
@@ -206,6 +206,8 @@ Control {
     /*! \internal */
     onValueChanged: if (__initialized) input.setValue(value)
 
+    activeFocusOnTab: true
+
     Accessible.name: input.text
     Accessible.role: Accessible.SpinBox
 
diff --git a/src/controls/StatusBar.qml b/src/controls/StatusBar.qml
index f0a6011f8..b3987cb4a 100644
--- a/src/controls/StatusBar.qml
+++ b/src/controls/StatusBar.qml
@@ -69,6 +69,7 @@ Item {
     id: statusbar
     implicitHeight: 20
     implicitWidth: parent ? parent.width : style.implicitWidth
+    activeFocusOnTab: false
     StyleItem {
         id: style
         anchors.fill: parent
diff --git a/src/controls/Tab.qml b/src/controls/Tab.qml
index b7d4fb7f5..3c86cc3c4 100644
--- a/src/controls/Tab.qml
+++ b/src/controls/Tab.qml
@@ -60,6 +60,8 @@ Loader {
     active: false
     visible: false
 
+    activeFocusOnTab: false
+
     /*! \internal */
     onVisibleChanged: if (visible) active = true
 
diff --git a/src/controls/TabView.qml b/src/controls/TabView.qml
index 2248c8569..d24e677c4 100644
--- a/src/controls/TabView.qml
+++ b/src/controls/TabView.qml
@@ -141,6 +141,8 @@ FocusScope {
         count = __tabs.length
     }
 
+    activeFocusOnTab: false
+
     Component {
         id: tabcomp
         Tab {}
diff --git a/src/controls/TextArea.qml b/src/controls/TextArea.qml
index 474011479..c34315c35 100644
--- a/src/controls/TextArea.qml
+++ b/src/controls/TextArea.qml
@@ -616,6 +616,8 @@ ScrollView {
     flickableItem.contentWidth: edit.paintedWidth + (2 * documentMargins)
     frameVisible: true
 
+    activeFocusOnTab: true
+
     Accessible.role: Accessible.EditableText
 
     /*!
diff --git a/src/controls/TextField.qml b/src/controls/TextField.qml
index 8a9173a93..8fd9353bf 100644
--- a/src/controls/TextField.qml
+++ b/src/controls/TextField.qml
@@ -539,6 +539,8 @@ Control {
             textInput.forceActiveFocus();
     }
 
+    activeFocusOnTab: true
+
     Accessible.name: text
     Accessible.role: Accessible.EditableText
     Accessible.description: placeholderText
diff --git a/src/controls/ToolBar.qml b/src/controls/ToolBar.qml
index fd2924ac4..0c555cb55 100644
--- a/src/controls/ToolBar.qml
+++ b/src/controls/ToolBar.qml
@@ -71,6 +71,7 @@ import QtQuick.Controls.Private 1.0
 Item {
     implicitHeight: toolbar.implicitHeight
     implicitWidth: parent ? parent.width : toolbar.implicitWidth
+    activeFocusOnTab: false
     Accessible.role: Accessible.ToolBar
     StyleItem {
         id: toolbar
diff --git a/src/controls/ToolButton.qml b/src/controls/ToolButton.qml
index 007bbec78..445ab96b2 100644
--- a/src/controls/ToolButton.qml
+++ b/src/controls/ToolButton.qml
@@ -74,6 +74,8 @@ BasicButton {
     /*! The label text. */
     property string text
 
+    activeFocusOnTab: true
+
     Accessible.name: text
 
     style: Qt.createComponent(Settings.THEME_PATH + "/ToolButtonStyle.qml", button)
diff --git a/src/private/AbstractCheckable.qml b/src/private/AbstractCheckable.qml
index f0acf48fb..905d4b653 100644
--- a/src/private/AbstractCheckable.qml
+++ b/src/private/AbstractCheckable.qml
@@ -106,6 +106,8 @@ Control {
     */
     readonly property alias __containsMouse: mouseArea.containsMouse
 
+    activeFocusOnTab: true
+
     MouseArea {
         id: mouseArea
         focus: true
diff --git a/src/private/BasicButton.qml b/src/private/BasicButton.qml
index 412028a08..f0d2a9ece 100644
--- a/src/private/BasicButton.qml
+++ b/src/private/BasicButton.qml
@@ -122,6 +122,8 @@ Control {
         onTriggered: button.clicked()
     }
 
+    activeFocusOnTab: true
+
     Keys.onPressed: {
         if (event.key === Qt.Key_Space && !event.isAutoRepeat && !behavior.pressed)
             behavior.keyPressed = true;
diff --git a/src/private/Control.qml b/src/private/Control.qml
index e18e83f30..06bcdce64 100644
--- a/src/private/Control.qml
+++ b/src/private/Control.qml
@@ -65,6 +65,8 @@ FocusScope {
     /* \internal */
     implicitHeight: __panel ? __panel.implicitHeight: 0
 
+    activeFocusOnTab: false
+
     Loader {
         id: panelLoader
         anchors.fill: parent
diff --git a/src/private/ScrollBar.qml b/src/private/ScrollBar.qml
index a81414e2c..d11fcef6b 100644
--- a/src/private/ScrollBar.qml
+++ b/src/private/ScrollBar.qml
@@ -59,6 +59,8 @@ Item {
     property Component style: Qt.createComponent("../" + Settings.THEME_PATH + "/ScrollBarStyle.qml", scrollbar)
     property alias styleItem: loader.item
 
+    activeFocusOnTab: false
+
     Accessible.role: Accessible.ScrollBar
     implicitWidth: loader.implicitWidth
     implicitHeight: loader.implicitHeight
diff --git a/src/private/TabBar.qml b/src/private/TabBar.qml
index a96ee1005..266d20e1a 100644
--- a/src/private/TabBar.qml
+++ b/src/private/TabBar.qml
@@ -51,6 +51,8 @@ FocusScope {
     height: tabrow.height
     width: tabrow.width
 
+    activeFocusOnTab: true
+
     Keys.onRightPressed: {
         if (tabView && tabView.currentIndex < tabView.count - 1)
             tabView.currentIndex = tabView.currentIndex + 1
diff --git a/tests/auto/activeFocusOnTab/activeFocusOnTab.pro b/tests/auto/activeFocusOnTab/activeFocusOnTab.pro
new file mode 100644
index 000000000..1a32e0e6d
--- /dev/null
+++ b/tests/auto/activeFocusOnTab/activeFocusOnTab.pro
@@ -0,0 +1,12 @@
+CONFIG += testcase
+TARGET = tst_activeFocusOnTab
+macx:CONFIG -= app_bundle
+
+SOURCES += tst_activeFocusOnTab.cpp
+
+include (../shared/util.pri)
+
+TESTDATA = data/*
+
+QT += widgets core-private gui-private v8-private qml-private quick-private testlib
+DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0
diff --git a/tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml b/tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml
new file mode 100644
index 000000000..1c95dc50e
--- /dev/null
+++ b/tests/auto/activeFocusOnTab/data/activeFocusOnTab.qml
@@ -0,0 +1,206 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**   * Redistributions of source code must retain the above copyright
+**     notice, this list of conditions and the following disclaimer.
+**   * Redistributions in binary form must reproduce the above copyright
+**     notice, this list of conditions and the following disclaimer in
+**     the documentation and/or other materials provided with the
+**     distribution.
+**   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+**     of its contributors may be used to endorse or promote products derived
+**     from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Controls 1.0
+
+Item {
+    id: main
+    objectName: "main"
+    width: 800
+    height: 600
+    focus: true
+    Component.onCompleted: button1.focus = true
+    Column {
+        anchors.fill: parent
+        id: column
+        objectName: "column"
+        Button {
+            id: button1
+            objectName: "button1"
+            text: "button 1"
+        }
+        Button {
+            id: button2
+            objectName: "button2"
+            text: "button 2"
+        }
+        Label {
+            id: label
+            objectName: "label"
+            text: "label"
+        }
+        ToolButton {
+            id: toolbutton
+            objectName: "toolbutton"
+            iconSource: "images/testIcon.png"
+            tooltip: "Test Icon"
+        }
+        ListModel {
+            id: choices
+            ListElement { text: "Banana" }
+            ListElement { text: "Orange" }
+            ListElement { text: "Apple" }
+            ListElement { text: "Coconut" }
+        }
+        ComboBox {
+            id: combobox;
+            objectName: "combobox"
+            model: choices;
+        }
+        GroupBox {
+            id: group1
+            objectName: "group1"
+            title: "GroupBox 1"
+            checkable: true
+            __checkbox.objectName: "group1_checkbox"
+            Row {
+                CheckBox {
+                    id: checkbox1
+                    objectName: "checkbox1"
+                    text: "Text frame"
+                    checked: true
+                }
+                CheckBox {
+                    id: checkbox2
+                    objectName: "checkbox2"
+                    text: "Tickmarks"
+                    checked: false
+                }
+            }
+        }
+        GroupBox {
+            id: group2
+            objectName: "group2"
+            title: "GroupBox 2"
+            Row {
+                RadioButton {
+                    id: radiobutton1
+                    objectName: "radiobutton1"
+                    text: "North"
+                    checked: true
+                }
+                RadioButton {
+                    id: radiobutton2
+                    objectName: "radiobutton2"
+                    text: "South"
+                }
+            }
+        }
+        //Page
+        //ProgressBar maybe not need
+        ProgressBar {
+            id: progressbar
+            objectName: "progressbar"
+            indeterminate: true
+        }
+        //ScrollArea
+        Slider {
+            id: slider
+            objectName: "slider"
+            value: 0.5
+        }
+        SpinBox {
+            id: spinbox
+            objectName: "spinbox"
+            width: 70
+            minimumValue: 0
+            maximumValue: 100
+            value: 50
+        }
+        //SplitterColumn and SplitterRow false
+        //StatusBar false
+        TabView {
+            id: tabview
+            objectName: "tabview"
+            width: 200
+            Tab {
+                id: tab1
+                objectName: "tab1"
+                title: "Tab1"
+                Column {
+                    id: column_in_tab1
+                    objectName: "column_in_tab1"
+                    anchors.fill: parent
+                    Button {
+                        id: tab1_btn1
+                        objectName: "tab1_btn1"
+                        text: "button 1 in Tab1"
+                    }
+                    Button {
+                        id: tab1_btn2
+                        objectName: "tab1_btn2"
+                        text: "button 2 in Tab1"
+                    }
+                }
+            }
+            Tab {
+                id: tab2
+                objectName: "tab2"
+                title: "Tab2"
+                Column {
+                    id: column_in_tab2
+                    objectName: "column_in_tab2"
+                    anchors.fill: parent
+                    Button {
+                        id: tab2_btn1
+                        objectName: "tab2_btn1"
+                        text: "button 1 in Tab2"
+                    }
+                    Button {
+                        id: tab2_btn2
+                        objectName: "tab2_btn2"
+                        text: "button 2 in Tab2"
+                    }
+                }
+            }
+        }
+        TextField {
+            id: textfield
+            objectName: "textfield"
+            text: "abc"
+        }
+        TextArea {
+            id: textarea
+            objectName: "textarea"
+            text: "abc"
+        }
+    }
+}
diff --git a/tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp b/tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp
new file mode 100644
index 000000000..19d0d194c
--- /dev/null
+++ b/tests/auto/activeFocusOnTab/tst_activeFocusOnTab.cpp
@@ -0,0 +1,427 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** 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, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <qtest.h>
+#include <QtTest/QSignalSpy>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/qqmlcontext.h>
+#include <QtQuick/qquickview.h>
+#include <QtQuick/private/qquickitem_p.h>
+#include "../shared/util.h"
+#include "../shared/visualtestutil.h"
+
+using namespace QQuickVisualTestUtil;
+
+class tst_activeFocusOnTab : public QQmlDataTest
+{
+    Q_OBJECT
+public:
+    tst_activeFocusOnTab();
+
+private slots:
+    void initTestCase();
+    void cleanup();
+
+    void activeFocusOnTab();
+private:
+    QQmlEngine engine;
+};
+
+tst_activeFocusOnTab::tst_activeFocusOnTab()
+{
+}
+
+void tst_activeFocusOnTab::initTestCase()
+{
+    QQmlDataTest::initTestCase();
+}
+
+void tst_activeFocusOnTab::cleanup()
+{
+}
+
+void tst_activeFocusOnTab::activeFocusOnTab()
+{
+    QQuickView *window = new QQuickView(0);
+    window->setBaseSize(QSize(800,600));
+
+    window->setSource(testFileUrl("activeFocusOnTab.qml"));
+    window->show();
+    window->requestActivate();
+    QVERIFY(QTest::qWaitForWindowActive(window));
+    QVERIFY(QGuiApplication::focusWindow() == window);
+
+    // original: button1
+    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "button1");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: button1->button2
+    QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "button2");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: button2->toolbutton
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "toolbutton");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: toolbutton->combobox
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "combobox");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: combobox->group1_checkbox
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "group1");
+    QVERIFY(item);
+    QQuickItem *subitem = findItem<QQuickItem>(item, "group1_checkbox");
+    QVERIFY(subitem);
+    QVERIFY(subitem->hasActiveFocus());
+
+    // Tab: group1->checkbox1
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "checkbox1");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: checkbox1->checkbox2
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "checkbox2");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: checkbox2->radiobutton1
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "radiobutton1");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: radiobutton1->radiobutton2
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "radiobutton2");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: radiobutton2->slider
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "slider");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: slider->spinbox
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "spinbox");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: spinbox->tab1_btn1
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    for (int i = 0; i < 2; ++i) {
+        QGuiApplication::sendEvent(window, &key);
+        QVERIFY(key.isAccepted());
+    }
+
+    item = findItem<QQuickItem>(window->rootObject(), "tab1_btn1");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: tab1_btn1->tab1_btn2
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "tab1_btn2");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: tab1_btn2->textfield
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "textfield");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: textfield->textarea
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "textarea");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: textarea->textfield
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "textfield");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: textfield->tab1_btn2
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "tab1_btn2");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: tab1_btn2->tab1_btn1
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "tab1_btn1");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: tab1_btn1->tab1
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+/*
+    // Right: tab1->tab2
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    // Tab: tab2->tab2_btn1
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "tab2_btn1");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: tab2_btn1->tab2_btn2
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "tab2_btn2");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // Tab: tab2_btn2->textfield
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "textfield");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: textfield->tab2_btn2
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "tab2_btn2");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: tab2_btn2->tab2_btn1
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "tab2_btn1");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: tab2_btn1->tab2
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+*/
+    // BackTab: tab2->spinbox
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "spinbox");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: spinbox->slider
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "slider");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: slider->radiobutton2
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "radiobutton2");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: radiobutton2->radiobutton1
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "radiobutton1");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: radiobutton1->checkbox2
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "checkbox2");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: checkbox2->checkbox1
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "checkbox1");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: checkbox1->group1_checkbox
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "group1_checkbox");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: group1_checkbox->combobox
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "combobox");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: combobox->toolbutton
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "toolbutton");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: toolbutton->button2
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "button2");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: button2->button1
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "button1");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    // BackTab: button1->textarea
+    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
+    QGuiApplication::sendEvent(window, &key);
+    QVERIFY(key.isAccepted());
+
+    item = findItem<QQuickItem>(window->rootObject(), "textarea");
+    QVERIFY(item);
+    QVERIFY(item->hasActiveFocus());
+
+    delete window;
+}
+
+QTEST_MAIN(tst_activeFocusOnTab)
+
+#include "tst_activeFocusOnTab.moc"
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index dcbf1f8b5..927b7ccac 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -1,3 +1,3 @@
 TEMPLATE = subdirs
-SUBDIRS += testplugin controls
+SUBDIRS += testplugin controls activeFocusOnTab
 CONFIG += ordered
diff --git a/tests/auto/shared/util.cpp b/tests/auto/shared/util.cpp
new file mode 100644
index 000000000..e0b14d60e
--- /dev/null
+++ b/tests/auto/shared/util.cpp
@@ -0,0 +1,138 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** 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, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "util.h"
+
+#include <QtQml/QQmlComponent>
+#include <QtQml/QQmlError>
+#include <QtQml/QQmlContext>
+#include <QtQml/QQmlEngine>
+#include <QtCore/QTextStream>
+#include <QtCore/QDebug>
+#include <QtCore/QMutexLocker>
+
+QQmlDataTest *QQmlDataTest::m_instance = 0;
+
+QQmlDataTest::QQmlDataTest() :
+#ifdef QT_TESTCASE_BUILDDIR
+    m_dataDirectory(QTest::qFindTestData("data", QT_QMLTEST_DATADIR, 0, QT_TESTCASE_BUILDDIR)),
+#else
+    m_dataDirectory(QTest::qFindTestData("data", QT_QMLTEST_DATADIR, 0)),
+#endif
+
+    m_dataDirectoryUrl(QUrl::fromLocalFile(m_dataDirectory + QLatin1Char('/')))
+{
+    m_instance = this;
+}
+
+QQmlDataTest::~QQmlDataTest()
+{
+    m_instance = 0;
+}
+
+void QQmlDataTest::initTestCase()
+{
+    QVERIFY2(!m_dataDirectory.isEmpty(), "'data' directory not found");
+    m_directory = QFileInfo(m_dataDirectory).absolutePath();
+    QVERIFY2(QDir::setCurrent(m_directory), qPrintable(QLatin1String("Could not chdir to ") + m_directory));
+}
+
+QString QQmlDataTest::testFile(const QString &fileName) const
+{
+    if (m_directory.isEmpty())
+        qFatal("QQmlDataTest::initTestCase() not called.");
+    QString result = m_dataDirectory;
+    result += QLatin1Char('/');
+    result += fileName;
+    return result;
+}
+
+QByteArray QQmlDataTest::msgComponentError(const QQmlComponent &c,
+                                                   const QQmlEngine *engine /* = 0 */)
+{
+    QString result;
+    const QList<QQmlError> errors = c.errors();
+    QTextStream str(&result);
+    str << "Component '" << c.url().toString() << "' has " << errors.size()
+        << " errors: '";
+    for (int i = 0; i < errors.size(); ++i) {
+        if (i)
+            str << ", '";
+        str << errors.at(i).toString() << '\'';
+
+    }
+    if (!engine)
+        if (QQmlContext *context = c.creationContext())
+            engine = context->engine();
+    if (engine) {
+        str << " Import paths: (" << engine->importPathList().join(QStringLiteral(", "))
+            << ") Plugin paths: (" << engine->pluginPathList().join(QStringLiteral(", "))
+            << ')';
+    }
+    return result.toLocal8Bit();
+}
+
+Q_GLOBAL_STATIC(QMutex, qQmlTestMessageHandlerMutex)
+
+QQmlTestMessageHandler *QQmlTestMessageHandler::m_instance = 0;
+
+void QQmlTestMessageHandler::messageHandler(QtMsgType, const QMessageLogContext &, const QString &message)
+{
+    QMutexLocker locker(qQmlTestMessageHandlerMutex());
+    if (QQmlTestMessageHandler::m_instance)
+        QQmlTestMessageHandler::m_instance->m_messages.push_back(message);
+}
+
+QQmlTestMessageHandler::QQmlTestMessageHandler()
+{
+    QMutexLocker locker(qQmlTestMessageHandlerMutex());
+    Q_ASSERT(!QQmlTestMessageHandler::m_instance);
+    QQmlTestMessageHandler::m_instance = this;
+    m_oldHandler = qInstallMessageHandler(messageHandler);
+}
+
+QQmlTestMessageHandler::~QQmlTestMessageHandler()
+{
+    QMutexLocker locker(qQmlTestMessageHandlerMutex());
+    Q_ASSERT(QQmlTestMessageHandler::m_instance);
+    qInstallMessageHandler(m_oldHandler);
+    QQmlTestMessageHandler::m_instance = 0;
+}
diff --git a/tests/auto/shared/util.h b/tests/auto/shared/util.h
new file mode 100644
index 000000000..dc9303825
--- /dev/null
+++ b/tests/auto/shared/util.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** 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, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLTESTUTILS_H
+#define QQMLTESTUTILS_H
+
+#include <QtCore/QDir>
+#include <QtCore/QUrl>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QStringList>
+#include <QtTest/QTest>
+
+QT_FORWARD_DECLARE_CLASS(QQmlComponent)
+QT_FORWARD_DECLARE_CLASS(QQmlEngine)
+
+/* Base class for tests with data that are located in a "data" subfolder. */
+
+class QQmlDataTest : public QObject
+{
+    Q_OBJECT
+public:
+    QQmlDataTest();
+    virtual ~QQmlDataTest();
+
+    QString testFile(const QString &fileName) const;
+    inline QString testFile(const char *fileName) const
+        { return testFile(QLatin1String(fileName)); }
+    inline QUrl testFileUrl(const QString &fileName) const
+        { return QUrl::fromLocalFile(testFile(fileName)); }
+    inline QUrl testFileUrl(const char *fileName) const
+        { return testFileUrl(QLatin1String(fileName)); }
+
+    inline QString dataDirectory() const { return m_dataDirectory; }
+    inline QUrl dataDirectoryUrl() const { return m_dataDirectoryUrl; }
+    inline QString directory() const  { return m_directory; }
+
+    static inline QQmlDataTest *instance() { return m_instance; }
+
+    static QByteArray msgComponentError(const QQmlComponent &,
+                                        const QQmlEngine *engine = 0);
+
+public slots:
+    virtual void initTestCase();
+
+private:
+    static QQmlDataTest *m_instance;
+
+    const QString m_dataDirectory;
+    const QUrl m_dataDirectoryUrl;
+    QString m_directory;
+};
+
+class QQmlTestMessageHandler
+{
+    Q_DISABLE_COPY(QQmlTestMessageHandler)
+public:
+    QQmlTestMessageHandler();
+    ~QQmlTestMessageHandler();
+
+    const QStringList &messages() const { return m_messages; }
+    const QString messageString() const { return m_messages.join(QLatin1Char('\n')); }
+
+    void clear() { m_messages.clear(); }
+
+private:
+    static void messageHandler(QtMsgType, const QMessageLogContext &, const QString &message);
+
+    static QQmlTestMessageHandler *m_instance;
+    QStringList m_messages;
+    QtMessageHandler m_oldHandler;
+};
+
+#endif // QQMLTESTUTILS_H
diff --git a/tests/auto/shared/util.pri b/tests/auto/shared/util.pri
new file mode 100644
index 000000000..38c2e6a1a
--- /dev/null
+++ b/tests/auto/shared/util.pri
@@ -0,0 +1,8 @@
+QT += core-private gui-private v8-private qml-private quick-private
+
+HEADERS += $$PWD/visualtestutil.h \
+           $$PWD/util.h
+SOURCES += $$PWD/visualtestutil.cpp \
+           $$PWD/util.cpp
+
+DEFINES += QT_QMLTEST_DATADIR=\\\"$${_PRO_FILE_PWD_}/data\\\"
diff --git a/tests/auto/shared/visualtestutil.cpp b/tests/auto/shared/visualtestutil.cpp
new file mode 100644
index 000000000..4b5c201a3
--- /dev/null
+++ b/tests/auto/shared/visualtestutil.cpp
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** 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, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "visualtestutil.h"
+
+#include <QtQuick/QQuickItem>
+#include <QtCore/QDebug>
+
+bool QQuickVisualTestUtil::delegateVisible(QQuickItem *item)
+{
+    return item->isVisible() && !QQuickItemPrivate::get(item)->culled;
+}
+
+QQuickItem *QQuickVisualTestUtil::findVisibleChild(QQuickItem *parent, const QString &objectName)
+{
+    QQuickItem *item = 0;
+    QList<QQuickItem*> items = parent->findChildren<QQuickItem*>(objectName);
+    for (int i = 0; i < items.count(); ++i) {
+        if (items.at(i)->isVisible() && !QQuickItemPrivate::get(items.at(i))->culled) {
+            item = items.at(i);
+            break;
+        }
+    }
+    return item;
+}
+
+void QQuickVisualTestUtil::dumpTree(QQuickItem *parent, int depth)
+{
+    static QString padding("                       ");
+    for (int i = 0; i < parent->childItems().count(); ++i) {
+        QQuickItem *item = qobject_cast<QQuickItem*>(parent->childItems().at(i));
+        if (!item)
+            continue;
+        qDebug() << padding.left(depth*2) << item;
+        dumpTree(item, depth+1);
+    }
+}
+
diff --git a/tests/auto/shared/visualtestutil.h b/tests/auto/shared/visualtestutil.h
new file mode 100644
index 000000000..5edb5d08c
--- /dev/null
+++ b/tests/auto/shared/visualtestutil.h
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** 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, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQUICKVISUALTESTUTIL_H
+#define QQUICKVISUALTESTUTIL_H
+
+#include <QtQuick/QQuickItem>
+#include <QtQml/QQmlExpression>
+
+#include <QtQuick/private/qquickitem_p.h>
+
+namespace QQuickVisualTestUtil
+{
+    QQuickItem *findVisibleChild(QQuickItem *parent, const QString &objectName);
+
+    void dumpTree(QQuickItem *parent, int depth = 0);
+
+    bool delegateVisible(QQuickItem *item);
+
+    /*
+       Find an item with the specified objectName.  If index is supplied then the
+       item must also evaluate the {index} expression equal to index
+    */
+    template<typename T>
+    T *findItem(QQuickItem *parent, const QString &objectName, int index = -1)
+    {
+        const QMetaObject &mo = T::staticMetaObject;
+        for (int i = 0; i < parent->childItems().count(); ++i) {
+            QQuickItem *item = qobject_cast<QQuickItem*>(parent->childItems().at(i));
+            if (!item)
+                continue;
+            if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName)) {
+                if (index != -1) {
+                    QQmlExpression e(qmlContext(item), item, "index");
+                    if (e.evaluate().toInt() == index)
+                        return static_cast<T*>(item);
+                } else {
+                    return static_cast<T*>(item);
+                }
+            }
+            item = findItem<T>(item, objectName, index);
+            if (item)
+                return static_cast<T*>(item);
+        }
+
+        return 0;
+    }
+
+    template<typename T>
+    QList<T*> findItems(QQuickItem *parent, const QString &objectName, bool visibleOnly = true)
+    {
+        QList<T*> items;
+        const QMetaObject &mo = T::staticMetaObject;
+        for (int i = 0; i < parent->childItems().count(); ++i) {
+            QQuickItem *item = qobject_cast<QQuickItem*>(parent->childItems().at(i));
+            if (!item || (visibleOnly && (!item->isVisible() || QQuickItemPrivate::get(item)->culled)))
+                continue;
+            if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName))
+                items.append(static_cast<T*>(item));
+            items += findItems<T>(item, objectName);
+        }
+
+        return items;
+    }
+
+    template<typename T>
+    QList<T*> findItems(QQuickItem *parent, const QString &objectName, const QList<int> &indexes)
+    {
+        QList<T*> items;
+        for (int i=0; i<indexes.count(); i++)
+            items << qobject_cast<QQuickItem*>(findItem<T>(parent, objectName, indexes[i]));
+        return items;
+    }
+}
+
+#define QQUICK_VERIFY_POLISH(item) \
+    QTRY_COMPARE(QQuickItemPrivate::get(item)->polishScheduled, false)
+
+#endif // QQUICKVISUALTESTUTIL_H
-- 
GitLab