Fix blinkink text fields.

Remove storing core from GUI on fields. Core should never be stored in GUI because they are managed by CPP and not QML.
Fix crashes on account settings.
Add missing exception verbosing.
This commit is contained in:
Julien Wadel 2024-12-17 15:13:45 +01:00
parent 79b15f4178
commit c908f0d42c
15 changed files with 129 additions and 96 deletions

View file

@ -760,7 +760,7 @@ bool App::notify(QObject *receiver, QEvent *event) {
try {
done = QApplication::notify(receiver, event);
} catch (const std::exception &ex) {
lCritical() << log().arg("Exception has been catch in notify");
lCritical() << log().arg("Exception has been catch in notify: %1").arg(ex.what());
} catch (...) {
lCritical() << log().arg("Generic exeption has been catch in notify");
}

View file

@ -106,7 +106,7 @@ AccountCore::AccountCore(const std::shared_ptr<linphone::Account> &account) : QO
AccountCore::~AccountCore() {
mustBeInMainThread("~" + getClassName());
emit mAccountModel->removeListener();
if (mAccountModel) emit mAccountModel->removeListener();
}
AccountCore::AccountCore(const AccountCore &accountCore) {
@ -752,7 +752,10 @@ void AccountCore::save() {
mustBeInLinphoneThread(getClassName() + Q_FUNC_INFO);
thisCopy->writeIntoModel(mAccountModel);
thisCopy->deleteLater();
mAccountModelConnection->invokeToCore([this]() { setIsSaved(true); });
mAccountModelConnection->invokeToCore([this, thisCopy]() {
setIsSaved(true);
undo(); // Reset new values because some values can be invalid and not changed.
});
});
}
}
@ -769,4 +772,4 @@ void AccountCore::undo() {
});
});
}
}
}

View file

@ -67,6 +67,7 @@ QQuickFramebufferObject::Renderer *PreviewManager::subscribe(const CameraGui *ca
} else {
lDebug() << log().arg("Resubscribing") << itCandidate->first->getQmlName();
}
mCounterMutex.unlock();
App::postModelBlock([&renderer, isFirst = (itCandidate == mCandidates.begin()),
name = itCandidate->first->getQmlName()]() {
renderer =
@ -80,6 +81,7 @@ QQuickFramebufferObject::Renderer *PreviewManager::subscribe(const CameraGui *ca
CoreModel::getInstance()->getCore()->setNativePreviewWindowId(renderer);
}
});
mCounterMutex.lock();
itCandidate->second = renderer;
mCounterMutex.unlock();
return renderer;

View file

@ -193,14 +193,18 @@ QString AccountModel::getMwiServerAddress() const {
void AccountModel::setMwiServerAddress(QString value) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto params = mMonitor->getParams()->clone();
auto address = value.isEmpty()
? nullptr
: CoreModel::getInstance()->getCore()->interpretUrl(Utils::appStringToCoreString(value), false);
if (value.isEmpty() || address) {
params->setMwiServerAddress(address);
mMonitor->setParams(params);
emit mwiServerAddressChanged(value);
auto params = mMonitor->getParams();
auto oldAddress = params->getMwiServerAddress();
if (address != oldAddress && (!address || !address->weakEqual(oldAddress))) {
auto newParams = params->clone();
newParams->setMwiServerAddress(address);
if (!mMonitor->setParams(newParams)) emit mwiServerAddressChanged(value);
}
} else qWarning() << "Unable to set MWI address, failed creating address from" << value;
}
@ -395,7 +399,8 @@ void AccountModel::setVoicemailAddress(QString value) {
}
QString AccountModel::getVoicemailAddress() const {
return Utils::coreStringToAppString(mMonitor->getParams()->getVoicemailAddress()->asString());
auto addr = mMonitor->getParams()->getVoicemailAddress();
return addr ? Utils::coreStringToAppString(addr->asString()) : "";
}
// UserData (see hpp for explanations)

View file

@ -30,7 +30,7 @@ DEFINE_ABSTRACT_OBJECT(CallModel)
CallModel::CallModel(const std::shared_ptr<linphone::Call> &call, QObject *parent)
: ::Listener<linphone::Call, linphone::CallListener>(call, parent) {
lDebug() << "[CallModel] new" << this;
lDebug() << "[CallModel] new" << this << " / SDKModel=" << call.get();
mustBeInLinphoneThread(getClassName());
mDurationTimer.setInterval(1000);
mDurationTimer.setSingleShot(false);

View file

@ -38,7 +38,7 @@ std::shared_ptr<ConferenceModel> ConferenceModel::create(const std::shared_ptr<l
ConferenceModel::ConferenceModel(const std::shared_ptr<linphone::Conference> &conference, QObject *parent)
: ::Listener<linphone::Conference, linphone::ConferenceListener>(conference, parent) {
mustBeInLinphoneThread(getClassName());
lDebug() << "[ConferenceModel] new" << this << conference.get();
lDebug() << "[ConferenceModel] new " << this << ", SDKModel=" << conference.get();
connect(this, &ConferenceModel::isScreenSharingEnabledChanged, this,
&ConferenceModel::onIsScreenSharingEnabledChanged);
}
@ -149,8 +149,13 @@ void ConferenceModel::toggleScreenSharing() {
? linphone::MediaDirection::SendRecv
: linphone::MediaDirection::SendOnly);
}
if (params->isValid()) mMonitor->getCall()->update(params);
else lCritical() << log().arg("Cannot toggle screen sharing because parameters are invalid");
if (params->isValid()) {
lInfo() << log()
.arg("Toggling screen sharing %1, direction=%2")
.arg(enable)
.arg((int)params->getVideoDirection());
mMonitor->getCall()->update(params);
} else lCritical() << log().arg("Cannot toggle screen sharing because parameters are invalid");
}
}
@ -276,6 +281,7 @@ void ConferenceModel::onAudioDeviceChanged(const std::shared_ptr<linphone::Confe
void ConferenceModel::onIsScreenSharingEnabledChanged() {
auto call = mMonitor->getCall();
std::shared_ptr<linphone::CallParams> params = CoreModel::getInstance()->getCore()->createCallParams(call);
lDebug() << log().arg("Old Layout=%1").arg((int)params->getConferenceVideoLayout());
if (params->getConferenceVideoLayout() == linphone::Conference::Layout::Grid && params->videoEnabled()) {
params->setConferenceVideoLayout(linphone::Conference::Layout::ActiveSpeaker);
}

View file

@ -10,17 +10,25 @@ ComboBox {
property string propertyName
property var propertyOwner
property var propertyOwnerGui
property alias entries: mainItem.model
oneLine: true
currentIndex: Utils.findIndex(model, function (entry) {
return Utils.equalObject(entry,propertyOwner[propertyName])
if(propertyOwnerGui)
return Utils.equalObject(entry,propertyOwnerGui.core[propertyName])
else
return Utils.equalObject(entry,propertyOwner[propertyName])
})
onCurrentValueChanged: {
binding.when = !Utils.equalObject(currentValue,propertyOwner[propertyName])
if(propertyOwnerGui) {
binding.when = !Utils.equalObject(currentValue,propertyOwnerGui.core[propertyName])
}else{
binding.when = !Utils.equalObject(currentValue,propertyOwner[propertyName])
}
}
Binding {
id: binding
target: propertyOwner
target: propertyOwnerGui ? propertyOwnerGui : propertyOwner
property: propertyName
value: mainItem.currentValue
when: false

View file

@ -9,6 +9,7 @@ RowLayout {
property string subTitleText
property string propertyName
property var propertyOwner
property var propertyOwnerGui
property bool enabled: true
spacing : 20 * DefaultStyle.dp
Layout.minimumHeight: 32 * DefaultStyle.dp
@ -38,14 +39,15 @@ RowLayout {
Switch {
id: switchButton
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
checked: propertyOwner ? propertyOwner[mainItem.propertyName] : false
checked: propertyOwnerGui ? propertyOwnerGui.core[mainItem.propertyName]
: propertyOwner ? propertyOwner[mainItem.propertyName] : false
enabled: mainItem.enabled
onCheckedChanged: mainItem.checkedChanged(checked)
onToggled: binding.when = true
}
Binding {
id: binding
target: propertyOwner ? propertyOwner : null
target: propertyOwnerGui ? propertyOwnerGui : propertyOwner ? propertyOwner : null
property: mainItem.propertyName
value: switchButton.checked
when: false

View file

@ -12,6 +12,7 @@ FormItemLayout {
enableErrorText: true
property string propertyName: "value"
property var propertyOwner: new Array
property var propertyOwnerGui
property var title
property var placeHolder
property bool useTitleAsPlaceHolder: true
@ -19,7 +20,7 @@ FormItemLayout {
property bool toValidate: false
function value() {
return propertyOwner[propertyName]
return propertyOwnerGui ? propertyOwnerGui.core[propertyName] : propertyOwner[propertyName]
}
property alias hidden: textField.hidden
@ -33,10 +34,11 @@ FormItemLayout {
id: textField
Layout.preferredWidth: 360 * DefaultStyle.dp
placeholderText: useTitleAsPlaceHolder ? mainItem.title : mainItem.placeHolder
initialText: mainItem.propertyOwner[mainItem.propertyName] || ''
initialText: (mainItem.propertyOwnerGui ? mainItem.propertyOwnerGui.core[mainItem.propertyName] : mainItem.propertyOwner[mainItem.propertyName]) || ''
customWidth: mainItem.parent.width
propertyName: mainItem.propertyName
propertyOwner: mainItem.propertyOwner
propertyOwnerGui: mainItem.propertyOwnerGui
canBeEmpty: mainItem.canBeEmpty
isValid: mainItem.isValid
toValidate: mainItem.toValidate

View file

@ -22,6 +22,7 @@ Control.TextField {
selectByMouse: true
activeFocusOnTab: true
KeyNavigation.right: eyeButton
text: initialText
property bool controlIsDown: false
property bool hidden: false
@ -37,26 +38,64 @@ Control.TextField {
// fill propertyName and propertyOwner to check text validity
property string propertyName
property var propertyOwner
property var propertyOwnerGui
property var initialReading: true
property var isValid: function(text) {
return true
}
property bool toValidate: false
property int idleTimeOut: 200
property bool empty: mainItem.propertyOwner!= undefined && mainItem.propertyOwner[mainItem.propertyName]?.length == 0
property bool empty: propertyOwnerGui ? mainItem.propertyOwnerGui.core != undefined && mainItem.propertyOwnerGui.core[mainItem.propertyName]?.length == 0
: mainItem.propertyOwner != undefined && mainItem.propertyOwner[mainItem.propertyName]?.length == 0
property bool canBeEmpty: true
signal validationChecked(bool valid)
Component.onCompleted: {
text = initialText
}
function resetText() {
text = initialText
}
signal enterPressed()
onAccepted: {// No need to process changing focus because of TextEdited callback.
idleTimer.stop()
updateText()
}
onTextEdited: {
if(mainItem.toValidate) {
idleTimer.restart()
}
}
function updateText() {
mainItem.empty = text.length == 0
if (initialReading) {
initialReading = false
}
if (mainItem.empty && !mainItem.canBeEmpty) {
mainItem.validationChecked(false)
return
}
if (mainItem.propertyName && isValid(text)) {
if(mainItem.propertyOwnerGui){
if (mainItem.propertyOwnerGui.core[mainItem.propertyName] != text)
mainItem.propertyOwnerGui.core[mainItem.propertyName] = text
}else{
if (mainItem.propertyOwner[mainItem.propertyName] != text)
mainItem.propertyOwner[mainItem.propertyName] = text
}
mainItem.validationChecked(true)
} else mainItem.validationChecked(false)
}
// Validation textfield functions
Timer {
id: idleTimer
running: false
interval: mainItem.idleTimeOut
repeat: false
onTriggered: {
mainItem.accepted()
}
}
background: Rectangle {
id: inputBackground
@ -138,39 +177,5 @@ Control.TextField {
anchors.right: parent.right
anchors.rightMargin: rightMargin
}
// Validation textfield functions
Timer {
id: idleTimer
running: false
interval: mainItem.idleTimeOut
repeat: false
onTriggered: mainItem.editingFinished()
}
onEditingFinished: {
updateText()
}
onTextChanged: {
if(mainItem.toValidate) {
// Restarting
idleTimer.restart()
}
// updateText()
}
function updateText() {
mainItem.empty = text.length == 0
if (initialReading) {
initialReading = false
}
if (mainItem.empty && !mainItem.canBeEmpty) {
mainItem.validationChecked(false)
return
}
if (isValid(text) && mainItem.propertyOwner && mainItem.propertyName) {
if (mainItem.propertyOwner[mainItem.propertyName] != text)
mainItem.propertyOwner[mainItem.propertyName] = text
mainItem.validationChecked(true)
} else mainItem.validationChecked(false)
}
}

View file

@ -151,7 +151,7 @@ AbstractSettingsLayout {
Layout.topMargin: -15 * DefaultStyle.dp
entries: account.core.dialPlans
propertyName: "dialPlan"
propertyOwner: account.core
propertyOwnerGui: account
textRole: 'text'
flagRole: 'flag'
}
@ -159,7 +159,7 @@ AbstractSettingsLayout {
titleText: account?.core.humaneReadableRegistrationState
subTitleText: account?.core.humaneReadableRegistrationStateExplained
propertyName: "registerEnabled"
propertyOwner: account?.core
propertyOwnerGui: account
}
RowLayout {
id:mainItem

View file

@ -47,7 +47,7 @@ AbstractSettingsLayout {
spacing: 20 * DefaultStyle.dp
DecoratedTextField {
propertyName: "mwiServerAddress"
propertyOwner: account.core
propertyOwnerGui: account
title: qsTr("URI du serveur de messagerie vocale")
Layout.fillWidth: true
isValid: function(text) { return text.length == 0 || !text.endsWith(".") } // work around sdk crash when adress ends with .
@ -55,7 +55,7 @@ AbstractSettingsLayout {
}
DecoratedTextField {
propertyName: "voicemailAddress"
propertyOwner: account.core
propertyOwnerGui: account
title: qsTr("URI de messagerie vocale")
Layout.fillWidth: true
}
@ -80,44 +80,44 @@ AbstractSettingsLayout {
Layout.topMargin: -15 * DefaultStyle.dp
entries: account.core.transports
propertyName: "transport"
propertyOwner: account.core
propertyOwnerGui: account
}
DecoratedTextField {
Layout.fillWidth: true
title: qsTr("URL du serveur mandataire")
propertyName: "serverAddress"
propertyOwner: account.core
propertyOwnerGui: account
}
SwitchSetting {
titleText: qsTr("Serveur mandataire sortant")
propertyName: "outboundProxyEnabled"
propertyOwner: account.core
propertyOwnerGui: account
}
DecoratedTextField {
Layout.fillWidth: true
propertyName: "stunServer"
propertyOwner: account.core
propertyOwnerGui: account
title: qsTr("Adresse du serveur STUN")
}
SwitchSetting {
titleText: qsTr("Activer ICE")
propertyName: "iceEnabled"
propertyOwner: account.core
propertyOwnerGui: account
}
SwitchSetting {
titleText: qsTr("AVPF")
propertyName: "avpfEnabled"
propertyOwner: account.core
propertyOwnerGui: account
}
SwitchSetting {
titleText: qsTr("Mode bundle")
propertyName: "bundleModeEnabled"
propertyOwner: account.core
propertyOwnerGui: account
}
DecoratedTextField {
Layout.fillWidth: true
propertyName: "expire"
propertyOwner: account.core
propertyOwnerGui: account
title: qsTr("Expiration (en seconde)")
canBeEmpty: false
isValid: function(text) { return !isNaN(Number(text)) }
@ -127,20 +127,20 @@ AbstractSettingsLayout {
Layout.fillWidth: true
title: qsTr("URI de lusine à conversations")
propertyName: "conferenceFactoryAddress"
propertyOwner: account.core
propertyOwnerGui: account
}
DecoratedTextField {
Layout.fillWidth: true
title: qsTr("URI de lusine à réunions")
propertyName: "audioVideoConferenceFactoryAddress"
propertyOwner: account.core
propertyOwnerGui: account
visible: !SettingsCpp.disableMeetingsFeature
}
DecoratedTextField {
Layout.fillWidth: true
title: qsTr("URL du serveur déchange de clés de chiffrement")
propertyName: "limeServerUrl"
propertyOwner: account.core
propertyOwnerGui: account
}
}
}

View file

@ -139,7 +139,7 @@ AbstractSettingsLayout {
titleText: Utils.capitalizeFirstLetter(modelData.core.mimeType)
subTitleText: modelData.core.clockRate + " Hz"
propertyName: "enabled"
propertyOwner: modelData.core
propertyOwnerGui: modelData
}
}
}
@ -162,7 +162,7 @@ AbstractSettingsLayout {
titleText: Utils.capitalizeFirstLetter(modelData.core.mimeType)
subTitleText: modelData.core.encoderDescription
propertyName: "enabled"
propertyOwner: modelData.core
propertyOwnerGui: modelData
}
}
Repeater {

View file

@ -74,7 +74,7 @@ AbstractSettingsLayout {
Layout.leftMargin: 64 * DefaultStyle.dp
DecoratedTextField {
propertyName: "displayName"
propertyOwner: carddavGui.core
propertyOwnerGui: carddavGui
title: qsTr("Nom daffichage")
canBeEmpty: false
toValidate: true
@ -82,7 +82,7 @@ AbstractSettingsLayout {
}
DecoratedTextField {
propertyName: "uri"
propertyOwner: carddavGui.core
propertyOwnerGui: carddavGui
title: qsTr("URL du serveur")
canBeEmpty: false
toValidate: true
@ -90,7 +90,7 @@ AbstractSettingsLayout {
}
DecoratedTextField {
propertyName: "username"
propertyOwner: carddavGui.core
propertyOwnerGui: carddavGui
title: qsTr("Nom dutilisateur")
toValidate: true
Layout.fillWidth: true
@ -98,14 +98,14 @@ AbstractSettingsLayout {
DecoratedTextField {
propertyName: "password"
hidden: true
propertyOwner: carddavGui.core
propertyOwnerGui: carddavGui
title: qsTr("Mot de passe")
toValidate: true
Layout.fillWidth: true
}
DecoratedTextField {
propertyName: "realm"
propertyOwner: carddavGui.core
propertyOwnerGui: carddavGui
title: qsTr("Domaine dauthentification")
toValidate: true
Layout.fillWidth: true
@ -113,7 +113,7 @@ AbstractSettingsLayout {
SwitchSetting {
titleText: qsTr("Stocker ici les contacts nouvellement crées")
propertyName: "storeNewFriendsInIt"
propertyOwner: carddavGui.core
propertyOwnerGui: carddavGui
}
}
}

View file

@ -70,14 +70,14 @@ AbstractSettingsLayout {
DecoratedTextField {
id: server
propertyName: "serverUrl"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
title: qsTr("URL du serveur (ne peut être vide)")
toValidate: true
Layout.fillWidth: true
}
DecoratedTextField {
propertyName: "bindDn"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
title: qsTr("Bind DN")
toValidate: true
Layout.fillWidth: true
@ -85,7 +85,7 @@ AbstractSettingsLayout {
DecoratedTextField {
propertyName: "password"
hidden: true
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
title: qsTr("Mot de passe")
toValidate: true
Layout.fillWidth: true
@ -93,25 +93,25 @@ AbstractSettingsLayout {
SwitchSetting {
titleText: qsTr("Utiliser TLS")
propertyName: "tls"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
}
DecoratedTextField {
propertyName: "baseObject"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
title: qsTr("Base de recherche (ne peut être vide)")
toValidate: true
Layout.fillWidth: true
}
DecoratedTextField {
propertyName: "filter"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
title: qsTr("Filtre")
toValidate: true
Layout.fillWidth: true
}
DecoratedTextField {
propertyName: "limit"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
validator: RegularExpressionValidator { regularExpression: /[0-9]+/ }
title: qsTr("Nombre maximum de résultats")
toValidate: true
@ -119,7 +119,7 @@ AbstractSettingsLayout {
}
DecoratedTextField {
propertyName: "delay"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
validator: RegularExpressionValidator { regularExpression: /[0-9]+/ }
title: qsTr("Délai entre 2 requêtes (en millisecondes)")
toValidate: true
@ -127,7 +127,7 @@ AbstractSettingsLayout {
}
DecoratedTextField {
propertyName: "timeout"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
title: qsTr("Durée maximun (en secondes)")
validator: RegularExpressionValidator { regularExpression: /[0-9]+/ }
toValidate: true
@ -135,7 +135,7 @@ AbstractSettingsLayout {
}
DecoratedTextField {
propertyName: "minCharacters"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
title: qsTr("Nombre minimum de caractères pour la requête")
validator: RegularExpressionValidator { regularExpression: /[0-9]+/ }
toValidate: true
@ -143,21 +143,21 @@ AbstractSettingsLayout {
}
DecoratedTextField {
propertyName: "nameAttribute"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
title: qsTr("Attributs de nom")
toValidate: true
Layout.fillWidth: true
}
DecoratedTextField {
propertyName: "sipAttribute"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
title: qsTr("Attributs SIP")
toValidate: true
Layout.fillWidth: true
}
DecoratedTextField {
propertyName: "sipDomain"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
title: qsTr("Domaine SIP")
toValidate: true
Layout.fillWidth: true
@ -165,7 +165,7 @@ AbstractSettingsLayout {
SwitchSetting {
titleText: qsTr("Débogage")
propertyName: "debug"
propertyOwner: ldapGui.core
propertyOwnerGui: ldapGui
}
}
}