diff --git a/tests/resources.qrc b/tests/resources.qrc
index c2c5401e4..7682af02b 100644
--- a/tests/resources.qrc
+++ b/tests/resources.qrc
@@ -3,6 +3,7 @@
languages/fr.qm
languages/en.qm
ui/components/scrollBar/ForceScrollBar.qml
+ ui/components/searchBox/SearchBox.qml
ui/components/misc/MenuEntry.qml
ui/components/timeline/Timeline.qml
ui/components/image/Icon.qml
@@ -16,6 +17,7 @@
ui/components/contact/Contact.qml
ui/components/contact/ContactDescription.qml
ui/components/contact/Avatar.qml
+ ui/components/popup/DropDownMenu.qml
ui/components/dialog/ConfirmDialog.qml
ui/components/dialog/DialogDescription.qml
ui/components/dialog/DialogPlus.qml
diff --git a/tests/ui/components/contact/Avatar.qml b/tests/ui/components/contact/Avatar.qml
index a0724fbb6..535285b2b 100644
--- a/tests/ui/components/contact/Avatar.qml
+++ b/tests/ui/components/contact/Avatar.qml
@@ -1,5 +1,5 @@
import QtQuick 2.7
-import QtGraphicalEffects 1.0 // OpacityMask.
+import QtGraphicalEffects 1.0
// ===================================================================
diff --git a/tests/ui/components/popup/DropDownMenu.qml b/tests/ui/components/popup/DropDownMenu.qml
new file mode 100644
index 000000000..c2afb7c5c
--- /dev/null
+++ b/tests/ui/components/popup/DropDownMenu.qml
@@ -0,0 +1,163 @@
+import QtGraphicalEffects 1.0
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+
+import 'qrc:/ui/components/contact'
+import 'qrc:/ui/components/form'
+import 'qrc:/ui/components/scrollBar'
+
+Rectangle {
+ readonly property int entryHeight: 50
+
+ property int maxMenuHeight
+
+ border {
+ color: '#CCCCCC'
+ width: 2
+ }
+ implicitHeight: {
+ var height = model.count * entryHeight
+ return height > maxMenuHeight ? maxMenuHeight : height
+ }
+ visible: false
+
+ function show () {
+ visible = true
+ }
+
+ function hide () {
+ visible = false
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ id: listContainer
+
+ ListView {
+ ScrollBar.vertical: ForceScrollBar { }
+ anchors.fill: parent
+ boundsBehavior: Flickable.StopAtBounds
+ clip: true
+ highlightRangeMode: ListView.ApplyRange
+ id: list
+ spacing: 0
+ height: console.log(model.count) || count
+
+ // TODO: Remove, use C++ model instead.
+ model: ListModel {
+ id: model
+
+ ListElement {
+ $presence: 'connected'
+ $sipAddress: 'jim.williams.zzzz.yyyy.kkkk.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'connected'
+ $sipAddress: 'toto.lala.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'disconnected'
+ $sipAddress: 'machin.truc.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'absent'
+ $sipAddress: 'hey.listen.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'do_not_disturb'
+ $sipAddress: 'valentin.cognito.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'do_not_disturb'
+ $sipAddress: 'charles.henri.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'disconnected'
+ $sipAddress: 'yesyes.nono.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'connected'
+ $sipAddress: 'nsa.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'connected'
+ $sipAddress: 'jim.williams.zzzz.yyyy.kkkk.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'connected'
+ $sipAddress: 'toto.lala.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'disconnected'
+ $sipAddress: 'machin.truc.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'absent'
+ $sipAddress: 'hey.listen.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'do_not_disturb'
+ $sipAddress: 'valentin.cognito.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'do_not_disturb'
+ $sipAddress: 'charles.henri.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'disconnected'
+ $sipAddress: 'yesyes.nono.sip.linphone.org'
+ $username: 'Toto'
+ }
+ ListElement {
+ $presence: 'connected'
+ $sipAddress: 'nsa.sip.linphone.org'
+ $username: 'Toto'
+ }
+ }
+
+ delegate: Contact {
+ presence: $presence
+ sipAddress: $sipAddress
+ username: $username
+ width: parent.width
+
+ actions: [
+ ActionButton {
+ icon: 'call'
+ onClicked: console.log('clicked')
+ },
+
+ ActionButton {
+ icon: 'cam'
+ onClicked: console.log('cam clicked')
+ }
+ ]
+ }
+ }
+ }
+
+ DropShadow {
+ anchors.fill: listContainer
+ color: "#80000000"
+ horizontalOffset: 2
+ radius: 8.0
+ samples: 15
+ source: listContainer
+ verticalOffset: 2
+ visible: true
+ }
+}
diff --git a/tests/ui/components/searchBox/SearchBox.qml b/tests/ui/components/searchBox/SearchBox.qml
new file mode 100644
index 000000000..9012246ff
--- /dev/null
+++ b/tests/ui/components/searchBox/SearchBox.qml
@@ -0,0 +1,77 @@
+import QtGraphicalEffects 1.0
+import QtQuick 2.7
+import QtQuick.Controls 2.0
+
+import 'qrc:/ui/components/popup'
+
+// ===================================================================
+
+Item {
+ property alias placeholderText: searchField.placeholderText
+ property alias maxMenuHeight: menu.maxMenuHeight
+
+ implicitHeight: searchField.height
+
+ function hideMenu () {
+ menu.hide()
+ shadow.visible = false
+ }
+
+ function showMenu () {
+ menu.show()
+ shadow.visible = true
+ }
+
+ TextField {
+ signal searchTextChanged (string text)
+
+ anchors.fill: parent
+ background: Rectangle {
+ implicitHeight: 30
+ }
+ id: searchField
+
+ Keys.onEscapePressed: focus = false
+
+ onActiveFocusChanged: {
+ // Menu is hidden if `TextField` AND `FocusScope` focus
+ // are lost.
+ if (activeFocus) {
+ showMenu()
+ } else if (!scope.activeFocus) {
+ hideMenu()
+ }
+ }
+ onTextChanged: searchTextChanged(text)
+ }
+
+ // Handle focus from content. (Buttons...)
+ FocusScope {
+ anchors.top: searchField.bottom
+ id: scope
+ z: 999 // Menu must be above any component.
+
+ Keys.onEscapePressed: focus = false
+
+ onActiveFocusChanged: !searchField.activeFocus &&
+ !activeFocus &&
+ hideMenu()
+
+ DropDownMenu {
+ id: menu
+ width: searchField.width
+ }
+ }
+
+ DropShadow {
+ anchors.fill: searchField
+ color: "#80000000"
+ horizontalOffset: 2
+ id: shadow
+ radius: 8.0
+ samples: 15
+ source: searchField
+ verticalOffset: 2
+ visible: false
+ }
+}
diff --git a/tests/ui/views/mainWindow/mainWindow.qml b/tests/ui/views/mainWindow/mainWindow.qml
index 5737ccb94..7a9a5933c 100644
--- a/tests/ui/views/mainWindow/mainWindow.qml
+++ b/tests/ui/views/mainWindow/mainWindow.qml
@@ -6,6 +6,7 @@ import 'qrc:/ui/components/collapse'
import 'qrc:/ui/components/contact'
import 'qrc:/ui/components/form'
import 'qrc:/ui/components/misc'
+import 'qrc:/ui/components/searchBox'
import 'qrc:/ui/components/timeline'
import 'qrc:/ui/scripts/utils.js' as Utils
@@ -51,24 +52,19 @@ ApplicationWindow {
// User actions.
ActionButton {
+ Layout.preferredWidth: 16
onClicked: Utils.openWindow('manageAccounts', mainWindow)
}
ActionButton {
+ Layout.preferredWidth: 16
onClicked: Utils.openWindow('newCall', mainWindow)
}
// Search.
- TextField {
- signal searchTextChanged (string text)
-
- background: Rectangle {
- color: '#FFFFFF'
- implicitHeight: 30
- }
- id: searchText
+ SearchBox {
Layout.fillWidth: true
- onTextChanged: searchTextChanged(text)
+ maxMenuHeight: mainWindow.height - 100
placeholderText: qsTr('mainSearchBarPlaceholder')
}