Compare commits

...

8 Commits

Author SHA1 Message Date
8990162f99 Doc: fix package name and clarify instruction
Change-Id: I833f62eac5ffba820d1fdd95f9979c250f56fcf2
2023-06-08 12:58:23 -04:00
1f0e2e92ad build: Add dependencies.
Add libsystemd-dev to APT_DEPENDENCIES
    Add systemd-devel to ZYPPER_DEPENDENCIES
    Add systemd-libs to PACMAN_DEPENDENCIES

Change-Id: Ie997202acd85b04b7c4376cf936b98e04dcb23b6
2023-06-08 12:58:02 -04:00
7743c14598 misc: bump libjami version
Change-Id: I684c5c5888f1e2a0c53b2335a19f06a03ce1b96a
2023-06-08 12:56:01 -04:00
c47cfe446d notifications: gnu/linux: do a lookup for incoming trust requests
Attempt a name directory lookup for trust requests before popping a notification. Fall back to the display name, then peer URI if needed.

Gitlab: #1141
Change-Id: Ie91c3fdf518cb8f27d8f0d6a74f015e9c4034d42
2023-06-08 10:25:32 -04:00
948e2cc837 callactionbar: avoid multiple resets
GitLab: #1073

Change-Id: I7dc2ab632170a959b8d29979deacd3fb03ff6671
2023-06-08 08:44:14 -04:00
b611685653 settingsmaterialtextedit: fix focus changes
Change-Id: I289b610e43317470d061e7ecd6338bfa805c5ce7
GitLab: #1171
2023-06-07 16:10:46 -04:00
26d16c38b8 conversationextrapanel: close details on request/sync
Change-Id: Iec52dc4400bc92283b8a0f7eb5643c758902dc19
GitLab: #1149
2023-06-06 17:17:13 -04:00
5508f28c63 packaging: windows: fix Beta deployment script
Change-Id: I8facd465cd0c8eb0509b66b6f87008c6a86e89bd
2023-06-06 11:56:56 -04:00
13 changed files with 221 additions and 107 deletions

View File

@ -22,7 +22,7 @@ The files will be installed in `/usr/lib/libqt-jami`.
sudo apt install gnupg dirmngr ca-certificates curl --no-install-recommends
curl -s https://dl.jami.net/public-key.gpg | sudo tee /usr/share/keyrings/jami-archive-keyring.gpg > /dev/null
sudo sh -c "echo 'deb [signed-by=/usr/share/keyrings/jami-archive-keyring.gpg] https://dl.jami.net/nightly/debian_<VERSION>/ jami main' > /etc/apt/sources.list.d/jami.list"
sudo apt-get update && sudo apt-get install jami
sudo apt-get update && sudo apt-get install libqt-jami
```
#### Install libqt-jami, Ubuntu based
@ -88,7 +88,7 @@ for getting the latest development versions; otherwise, you can use
submodule).
```bash
./build.py --init [--qt=<path/to/qt> (this is required for qmlformatting to work)]
./build.py --init --qt=<path/to/qt> # qt path is required for qmlformatting to work
```
Then you will need to install dependencies:

View File

@ -92,7 +92,7 @@ ZYPPER_INSTALL_SCRIPT = [
ZYPPER_DEPENDENCIES = [
# build system
'autoconf', 'autoconf-archive', 'automake', 'cmake', 'make', 'patch', 'gcc-c++',
'libtool', 'which', 'pandoc', 'nasm', 'doxygen', 'graphviz',
'libtool', 'which', 'pandoc', 'nasm', 'doxygen', 'graphviz', 'systemd-devel',
# contrib dependencies
'curl', 'gzip', 'bzip2',
# daemon
@ -152,7 +152,7 @@ APT_DEPENDENCIES = [
'libspeex-dev', 'libspeexdsp-dev', 'libswscale-dev', 'libtool',
'libudev-dev', 'libyaml-cpp-dev', 'sip-tester', 'swig',
'uuid-dev', 'yasm', 'libjsoncpp-dev', 'libva-dev', 'libvdpau-dev', 'libmsgpack-dev',
'pandoc', 'nasm', 'dpkg-dev'
'pandoc', 'nasm', 'dpkg-dev', 'libsystemd-dev'
]
APT_CLIENT_DEPENDENCIES = [
@ -181,7 +181,7 @@ PACMAN_DEPENDENCIES = [
'gcc', 'ffmpeg', 'boost', 'cppunit', 'libdbus', 'dbus-c++', 'libe-book', 'expat',
'jack', 'opus', 'pcre', 'libpulse', 'speex', 'speexdsp', 'libtool', 'yaml-cpp',
'swig', 'yasm', 'make', 'patch', 'pkg-config',
'automake', 'libva', 'libvdpau', 'openssl', 'pandoc', 'nasm'
'automake', 'libva', 'libvdpau', 'openssl', 'pandoc', 'nasm', 'systemd-libs'
]
PACMAN_CLIENT_DEPENDENCIES = [

2
daemon

Submodule daemon updated: c814c4de0a...65c8631975

View File

@ -294,11 +294,11 @@ def build(config_str, qt_dir, tests):
sys.exit(1)
def deploy_runtimes(qt_dir):
def deploy_runtimes(config_str, qt_dir):
"""Deploy the dependencies to the runtime directory."""
print("Deploying runtime dependencies")
runtime_dir = os.path.join(repo_root_dir, "x64", "Release")
runtime_dir = os.path.join(repo_root_dir, "x64", config_str)
stamp_file = os.path.join(runtime_dir, ".deploy.stamp")
if os.path.exists(stamp_file):
return
@ -533,7 +533,7 @@ def main():
if not parsed_args.skip_build:
build(config_str, parsed_args.qt, do_tests)
if not parsed_args.skip_deploy:
deploy_runtimes(parsed_args.qt)
deploy_runtimes(config_str, parsed_args.qt)
if parsed_args.subcommand == "pack":
do_build(False)

View File

@ -35,10 +35,11 @@ IndexRangeFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& s
{
auto index = sourceModel()->index(sourceRow, 0, sourceParent);
bool predicate = true;
bool enabled = sourceModel()->data(index, CallControl::Role::Enabled).toBool();
if (filterRole() != Qt::DisplayRole) {
predicate = sourceModel()->data(index, filterRole()).toInt() != 0;
}
return sourceRow <= max_ && sourceRow >= min_ && predicate;
return sourceRow <= max_ && sourceRow >= min_ && predicate && enabled;
}
void
@ -197,10 +198,12 @@ CallControlListModel::data(const QModelIndex& index, int role) const
auto item = data_.at(index.row());
switch (role) {
case Role::ItemAction:
case CallControl::Role::ItemAction:
return QVariant::fromValue(item.itemAction);
case Role::UrgentCount:
case CallControl::Role::UrgentCount:
return QVariant::fromValue(item.urgentCount);
case CallControl::Role::Enabled:
return QVariant::fromValue(item.enabled);
}
return QVariant();
}
@ -212,6 +215,7 @@ CallControlListModel::roleNames() const
QHash<int, QByteArray> roles;
roles[ItemAction] = "ItemAction";
roles[UrgentCount] = "UrgentCount";
roles[Enabled] = "Enabled";
return roles;
}
@ -232,6 +236,24 @@ CallControlListModel::setUrgentCount(QVariant item, int count)
}
}
void
CallControlListModel::setEnabled(QObject* obj, bool enabled)
{
beginResetModel();
auto it = std::find_if(data_.cbegin(), data_.cend(), [obj](const auto& item) {
return item.itemAction == obj;
});
if (it != data_.cend()) {
auto row = std::distance(data_.cbegin(), it);
if (row >= rowCount())
return;
data_[row].enabled = enabled;
auto idx = index(row, 0);
Q_EMIT dataChanged(idx, idx);
}
endResetModel();
}
void
CallControlListModel::addItem(const CallControl::Item& item)
{
@ -264,15 +286,15 @@ CallOverlayModel::CallOverlayModel(LRCInstance* instance, QObject* parent)
}
void
CallOverlayModel::addPrimaryControl(const QVariant& action)
CallOverlayModel::addPrimaryControl(const QVariant& action, bool enabled)
{
primaryModel_->addItem(CallControl::Item {action.value<QObject*>()});
primaryModel_->addItem(CallControl::Item {action.value<QObject*>(), enabled});
}
void
CallOverlayModel::addSecondaryControl(const QVariant& action)
CallOverlayModel::addSecondaryControl(const QVariant& action, bool enabled)
{
secondaryModel_->addItem(CallControl::Item {action.value<QObject*>()});
secondaryModel_->addItem(CallControl::Item {action.value<QObject*>(), enabled});
setControlRanges();
}
@ -282,6 +304,13 @@ CallOverlayModel::setUrgentCount(QVariant row, int count)
secondaryModel_->setUrgentCount(row, count);
}
void
CallOverlayModel::setEnabled(QObject* obj, bool enabled)
{
primaryModel_->setEnabled(obj, enabled);
secondaryModel_->setEnabled(obj, enabled);
}
QVariant
CallOverlayModel::primaryModel()
{
@ -363,7 +392,8 @@ CallOverlayModel::eventFilter(QObject* object, QEvent* event)
void
CallOverlayModel::setControlRanges()
{
auto count = secondaryModel_->rowCount();
overflowModel_->setRange(0, overflowIndex_);
overflowVisibleModel_->setRange(overflowIndex_, secondaryModel_->rowCount());
overflowHiddenModel_->setRange(overflowIndex_ + 1, secondaryModel_->rowCount());
overflowVisibleModel_->setRange(overflowIndex_, count);
overflowHiddenModel_->setRange(overflowIndex_ + 1, count);
}

View File

@ -36,12 +36,13 @@
namespace CallControl {
Q_NAMESPACE
enum Role { ItemAction = Qt::UserRole + 1, UrgentCount };
enum Role { ItemAction = Qt::UserRole + 1, UrgentCount, Enabled};
Q_ENUM_NS(Role)
struct Item
{
QObject* itemAction;
bool enabled {true};
int urgentCount {0};
};
} // namespace CallControl
@ -106,6 +107,7 @@ public:
QHash<int, QByteArray> roleNames() const override;
void setUrgentCount(QVariant item, int count);
void setEnabled(QObject* obj, bool enabled);
void addItem(const CallControl::Item& item);
void clearData();
@ -121,9 +123,10 @@ class CallOverlayModel : public QObject
public:
CallOverlayModel(LRCInstance* instance, QObject* parent = nullptr);
Q_INVOKABLE void addPrimaryControl(const QVariant& action);
Q_INVOKABLE void addSecondaryControl(const QVariant& action);
Q_INVOKABLE void addPrimaryControl(const QVariant& action, bool enabled);
Q_INVOKABLE void addSecondaryControl(const QVariant& action, bool enabled);
Q_INVOKABLE void setUrgentCount(QVariant item, int count);
Q_INVOKABLE void setEnabled(QObject* obj, bool enabled);
Q_INVOKABLE void clearControls();
Q_INVOKABLE QVariant primaryModel();

View File

@ -75,7 +75,7 @@ Loader {
// Needed to give proper focus to loaded item
onFocusChanged: {
if (root.focus && root.isPersistent) {
if (item && root.focus && root.isPersistent) {
item.forceActiveFocus();
}
isEditing = !isEditing;

View File

@ -22,6 +22,9 @@
#include "qtutils.h"
#include "systemtray.h"
#include "qmlregister.h"
#include "qtutils.h"
#include "namedirectory.h"
#include <QApplication>
#include <QJsonObject>
@ -232,21 +235,37 @@ ConversationsAdapter::onNewTrustRequest(const QString& accountId,
if (convInfo.uid.isEmpty())
return;
}
auto& accInfo = lrcInstance_->getAccountInfo(accountId);
auto from = accInfo.contactModel->bestNameForContact(peerUri);
auto to = lrcInstance_->accountModel().bestNameForAccount(accountId);
auto preferences = accInfo.conversationModel->getConversationPreferences(convId);
// Ignore notifications for this conversation
if (preferences["ignoreNotifications"] == "true")
return;
auto contactPhoto = Utils::contactPhoto(lrcInstance_, peerUri, QSize(50, 50), accountId);
auto notifId = QString("%1;%2").arg(accountId, conv);
systemTray_->showNotification(notifId,
tr("%1 received a new trust request").arg(to),
"New request from " + from,
SystemTray::NotificationType::REQUEST,
Utils::QImageToByteArray(contactPhoto));
auto cb = [this, to, accountId, conv, peerUri](QString peerBestName) {
auto contactPhoto = Utils::contactPhoto(lrcInstance_, peerUri, QSize(50, 50), accountId);
auto notifId = QString("%1;%2").arg(accountId, conv);
systemTray_->showNotification(notifId,
tr("%1 received a new trust request").arg(to),
"New request from " + peerBestName,
SystemTray::NotificationType::REQUEST,
Utils::QImageToByteArray(contactPhoto));
};
// This peer is not yet a contact, so we don't have a name for it,
// but we can attempt to look it up using the name service before
// falling back to the bestNameForContact.
Utils::oneShotConnect(&NameDirectory::instance(),
&NameDirectory::registeredNameFound,
this,
[this, accountId, peerUri, cb](NameDirectory::LookupStatus status,
const QString& address,
const QString& name) {
if (address == peerUri) {
if (status == NameDirectory::LookupStatus::SUCCESS)
cb(name);
else {
auto& accInfo = lrcInstance_->getAccountInfo(accountId);
cb(accInfo.contactModel->bestNameForContact(peerUri));
}
}
});
std::ignore = NameDirectory::instance().lookupAddress(accountId, peerUri);
}
#else
Q_UNUSED(accountId)

View File

@ -43,6 +43,13 @@ CurrentCall::CurrentCall(LRCInstance* lrcInstance, QObject* parent)
this,
&CurrentCall::onShowIncomingCallView);
try {
auto& accInfo = lrcInstance_->getCurrentAccountInfo();
set_isSIP(accInfo.profileInfo.type == profile::Type::SIP);
} catch (const std::exception& e) {
qWarning() << "Can't update current call type" << e.what();
}
connectModel();
}

View File

@ -18,6 +18,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import SortFilterProxyModel 0.2
import net.jami.Models 1.1
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
@ -299,6 +300,8 @@ Control {
text: !checked ? JamiStrings.muteCamera : JamiStrings.unmuteCamera
checked: !CurrentCall.isCapturing
property var menuAction: videoInputMenuAction
enabled: CurrentAccount.videoEnabled_Video
onEnabledChanged: CallOverlayModel.setEnabled(this, muteVideoAction.enabled)
}
]
@ -319,6 +322,8 @@ Control {
icon.source: JamiResources.add_people_black_24dp_svg
icon.color: "white"
text: JamiStrings.addParticipants
enabled: CurrentCall.isModerator && !CurrentCall.isSIP
onEnabledChanged: CallOverlayModel.setEnabled(this, addPersonAction.enabled)
},
Action {
id: chatAction
@ -333,6 +338,8 @@ Control {
icon.source: CurrentCall.isPaused ? JamiResources.play_circle_outline_24dp_svg : JamiResources.pause_circle_outline_24dp_svg
icon.color: "white"
text: CurrentCall.isPaused ? JamiStrings.resumeCall : JamiStrings.pauseCall
enabled: CurrentCall.isSIP
onEnabledChanged: CallOverlayModel.setEnabled(this, resumePauseCallAction.enabled)
},
Action {
id: inputPanelSIPAction
@ -340,6 +347,8 @@ Control {
icon.source: JamiResources.ic_keypad_svg
icon.color: "white"
text: JamiStrings.sipInputPanel
enabled: CurrentCall.isSIP
onEnabledChanged: CallOverlayModel.setEnabled(this, inputPanelSIPAction.enabled)
},
Action {
id: callTransferAction
@ -347,6 +356,8 @@ Control {
icon.source: JamiResources.phone_forwarded_24dp_svg
icon.color: "white"
text: JamiStrings.transferCall
enabled: CurrentCall.isSIP
onEnabledChanged: CallOverlayModel.setEnabled(this, callTransferAction.enabled)
},
Action {
id: shareAction
@ -361,6 +372,8 @@ Control {
text: CurrentCall.isSharing ? JamiStrings.stopSharing : JamiStrings.shareScreen
property real size: 34
property var menuAction: shareMenuAction
enabled: CurrentAccount.videoEnabled_Video && !CurrentCall.isSIP
onEnabledChanged: CallOverlayModel.setEnabled(this, shareAction.enabled)
},
Action {
id: raiseHandAction
@ -371,6 +384,8 @@ Control {
text: checked ? JamiStrings.lowerHand : JamiStrings.raiseHand
checked: CurrentCall.isHandRaised
property real size: 34
enabled: CurrentCall.isConference
onEnabledChanged: CallOverlayModel.setEnabled(this, raiseHandAction.enabled)
},
Action {
id: layoutAction
@ -406,6 +421,7 @@ Control {
icon.color: "white"
text: JamiStrings.viewPlugin
enabled: PluginAdapter.isEnabled && PluginAdapter.callMediaHandlersListCount
onEnabledChanged: CallOverlayModel.setEnabled(this, pluginsAction.enabled)
},
Action {
id: swarmDetailsAction
@ -414,7 +430,7 @@ Control {
icon.color: "white"
text: JamiStrings.details
enabled: {
if (LRCInstance.currentAccountType === Profile.Type.SIP)
if (CurrentCall.isSIP)
return true;
if (!CurrentConversation.isTemporary && !CurrentConversation.isSwarm)
return false;
@ -422,81 +438,35 @@ Control {
return false;
return true;
}
onEnabledChanged: CallOverlayModel.setEnabled(this, swarmDetailsAction.enabled)
}
]
property var overflowItemCount
Connections {
target: CurrentCall
function onIsActiveChanged() {
if (CurrentCall.isActive)
reset();
}
function onIsRecordingLocallyChanged() {
Qt.callLater(reset);
}
function onIsHandRaisedChanged() {
Qt.callLater(reset);
}
function onIsConferenceChanged() {
Qt.callLater(reset);
}
function onIsModeratorChanged() {
Qt.callLater(reset);
}
function onIsSIPChanged() {
Qt.callLater(reset);
}
function onIsAudioOnlyChanged() {
Qt.callLater(reset);
}
function onIsAudioMutedChanged() {
Qt.callLater(reset);
}
function onIsVideoMutedChanged() {
Qt.callLater(reset);
}
}
Connections {
target: CurrentAccount
function onVideoEnabledVideoChanged() {
reset();
}
}
function reset() {
Component.onCompleted: {
CallOverlayModel.clearControls();
// centered controls
CallOverlayModel.addPrimaryControl(muteAudioAction);
CallOverlayModel.addPrimaryControl(hangupAction);
if (CurrentAccount.videoEnabled_Video)
CallOverlayModel.addPrimaryControl(muteVideoAction);
CallOverlayModel.addPrimaryControl(muteAudioAction, muteAudioAction.enabled);
CallOverlayModel.addPrimaryControl(hangupAction, hangupAction.enabled);
CallOverlayModel.addPrimaryControl(muteVideoAction, muteVideoAction.enabled);
// overflow controls
CallOverlayModel.addSecondaryControl(audioOutputAction);
if (CurrentCall.isConference) {
CallOverlayModel.addSecondaryControl(raiseHandAction);
}
if (CurrentCall.isModerator && !CurrentCall.isSIP)
CallOverlayModel.addSecondaryControl(addPersonAction);
if (CurrentCall.isSIP) {
CallOverlayModel.addSecondaryControl(resumePauseCallAction);
CallOverlayModel.addSecondaryControl(inputPanelSIPAction);
CallOverlayModel.addSecondaryControl(callTransferAction);
}
CallOverlayModel.addSecondaryControl(chatAction);
if (CurrentAccount.videoEnabled_Video && !CurrentCall.isSIP)
CallOverlayModel.addSecondaryControl(shareAction);
CallOverlayModel.addSecondaryControl(layoutAction);
CallOverlayModel.addSecondaryControl(recordAction);
if (pluginsAction.enabled)
CallOverlayModel.addSecondaryControl(pluginsAction);
if (swarmDetailsAction.enabled)
CallOverlayModel.addSecondaryControl(swarmDetailsAction);
CallOverlayModel.addSecondaryControl(audioOutputAction, audioOutputAction.enabled);
CallOverlayModel.addSecondaryControl(raiseHandAction, raiseHandAction.enabled);
CallOverlayModel.addSecondaryControl(addPersonAction, addPersonAction.enabled);
CallOverlayModel.addSecondaryControl(resumePauseCallAction, resumePauseCallAction.enabled);
CallOverlayModel.addSecondaryControl(inputPanelSIPAction, inputPanelSIPAction.enabled);
CallOverlayModel.addSecondaryControl(callTransferAction, callTransferAction.enabled);
CallOverlayModel.addSecondaryControl(chatAction, chatAction.enabled);
CallOverlayModel.addSecondaryControl(shareAction, shareAction.enabled);
CallOverlayModel.addSecondaryControl(layoutAction, layoutAction.enabled);
CallOverlayModel.addSecondaryControl(recordAction, recordAction.enabled);
CallOverlayModel.addSecondaryControl(pluginsAction, pluginsAction.enabled);
CallOverlayModel.addSecondaryControl(swarmDetailsAction, swarmDetailsAction.enabled);
overflowItemCount = CallOverlayModel.secondaryModel().rowCount();
}
@ -519,7 +489,13 @@ Control {
implicitHeight: contentHeight
interactive: false
model: CallOverlayModel.primaryModel()
model: SortFilterProxyModel {
sourceModel: CallOverlayModel.primaryModel()
filters: ValueFilter {
roleName: "Enabled"
value: true
}
}
delegate: buttonDelegate
}
}
@ -547,7 +523,15 @@ Control {
property int overflowIndex: {
var maxItems = Math.floor((overflowRect.remainingSpace) / (root.height + itemSpacing)) - 2;
return Math.min(overflowItemCount, maxItems);
var idx = Math.min(overflowItemCount, maxItems);
idx = Math.max(0, idx);
if (CallOverlayModel.overflowModel().rowCount() > 0 || CallOverlayModel.overflowHiddenModel().rowCount() > 0) {
var visibleIdx = CallOverlayModel.overflowModel().mapToSource(CallOverlayModel.overflowModel().index(idx, 0)).row;
var hiddenIdx = CallOverlayModel.overflowHiddenModel().mapToSource(CallOverlayModel.overflowHiddenModel().index(idx - CallOverlayModel.overflowModel().rowCount(), 0)).row;
if (visibleIdx >= 0 || hiddenIdx >= 0)
idx = Math.max(visibleIdx, hiddenIdx);
}
return idx;
}
property int nOverflowItems: overflowItemCount - overflowIndex
onNOverflowItemsChanged: {

View File

@ -35,7 +35,7 @@ StackLayout {
function restoreState() {
// Only applies to Jami accounts, and we musn't be in a call.
if (detailsShouldOpen && !inCallView) {
if (detailsShouldOpen && !inCallView && !CurrentConversation.needsSyncing && !CurrentConversation.isRequest) {
switchToPanel(ChatView.SwarmDetailsPanel, false);
} else {
closePanel();

View File

@ -63,6 +63,7 @@ RowLayout {
}
visible: !root.isPassword
focus: visible
isSettings: true
Layout.alignment: Qt.AlignCenter
@ -73,7 +74,7 @@ RowLayout {
onAccepted: {
root.dynamicText = dynamicText;
editFinished();
root.editFinished();
}
editMode: false
@ -82,7 +83,7 @@ RowLayout {
onActiveFocusChanged: {
if (!activeFocus) {
root.dynamicText = dynamicText;
editFinished();
root.editFinished();
modalTextEdit.editMode = false;
} else {
modalTextEdit.editMode = true;
@ -94,6 +95,7 @@ RowLayout {
id: passwordTextEdit
visible: root.isPassword
focus: visible
isSettings: true
Layout.alignment: Qt.AlignCenter
@ -103,8 +105,16 @@ RowLayout {
onAccepted: {
root.dynamicText = dynamicText;
editFinished();
root.editFinished();
echoMode = TextInput.Password;
}
onActiveFocusChanged: {
if (!activeFocus) {
root.dynamicText = dynamicText;
root.editFinished();
echoMode = TextInput.Password;
}
}
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2023 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtTest
import "../../../src/app/"
import "../../../src/app/settingsview/components"
ColumnLayout {
id: root
spacing: 0
width: 300
height: 300
Item {
id: dummy
}
SettingsMaterialTextEdit {
id: uut
property bool focusLeft: false
isPassword: true
TestCase {
name: "Test password un-focus"
when: windowShown
function test_unfocusPassword() {
// Open the recorder and take a picture
uut.forceActiveFocus()
dummy.forceActiveFocus()
compare(uut.focusLeft, true)
}
}
onEditFinished: focusLeft = true
Layout.fillWidth: true
Layout.fillHeight: true
}
}