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') }