From 8edc606c63dae502f1f550a04a1e357db60abdfb Mon Sep 17 00:00:00 2001 From: nds Date: Thu, 25 May 2017 17:23:40 +0300 Subject: [PATCH] Issues #2173, #2169: processing focus in widget by SHAPER loop (instead of Qt loop). Reason, to process it in Qt loop we need to have Property Panel always as active but it is not possible as we wish click in the view or python console. --- src/ModuleBase/ModuleBase_DoubleSpinBox.cpp | 47 +---------------- src/ModuleBase/ModuleBase_DoubleSpinBox.h | 14 ------ src/ModuleBase/ModuleBase_ModelWidget.cpp | 27 +++++++++- src/ModuleBase/ModuleBase_ModelWidget.h | 10 ++++ src/ModuleBase/ModuleBase_WidgetEditor.cpp | 30 +++++++---- src/ModuleBase/ModuleBase_WidgetEditor.h | 6 +++ src/PartSet/PartSet_SketcherReentrantMgr.cpp | 4 +- src/XGUI/XGUI_ActionsMgr.cpp | 3 -- src/XGUI/XGUI_OperationMgr.cpp | 53 ++++++++++++-------- src/XGUI/XGUI_OperationMgr.h | 5 -- src/XGUI/XGUI_PropertyPanel.cpp | 15 ++++-- src/XGUI/XGUI_PropertyPanel.h | 7 +-- src/XGUI/XGUI_Workshop.cpp | 8 +-- 13 files changed, 117 insertions(+), 112 deletions(-) diff --git a/src/ModuleBase/ModuleBase_DoubleSpinBox.cpp b/src/ModuleBase/ModuleBase_DoubleSpinBox.cpp index b5af3da54..d1b8efa17 100644 --- a/src/ModuleBase/ModuleBase_DoubleSpinBox.cpp +++ b/src/ModuleBase/ModuleBase_DoubleSpinBox.cpp @@ -59,8 +59,7 @@ const double PSEUDO_ZERO = 1.e-20; */ ModuleBase_DoubleSpinBox::ModuleBase_DoubleSpinBox(QWidget* theParent, int thePrecision) : QDoubleSpinBox(theParent), - myCleared(false), - myIsEmitKeyPressEvent(false) + myCleared(false) { setLocale(ModuleBase_Tools::doubleLocale()); @@ -198,42 +197,6 @@ QString ModuleBase_DoubleSpinBox::removeTrailingZeroes(const QString& src) const return res; } -void ModuleBase_DoubleSpinBox::keyPressEvent(QKeyEvent* theEvent) -{ - switch (theEvent->key()) { - case Qt::Key_Enter: - case Qt::Key_Return: { - // do not react to the Enter key, the property panel processes it - if (!myIsEmitKeyPressEvent) - return; - } - break; - default: - break; - } - QDoubleSpinBox::keyPressEvent(theEvent); -} - -void ModuleBase_DoubleSpinBox::keyReleaseEvent(QKeyEvent* theEvent) -{ - switch (theEvent->key()) { - case Qt::Key_Enter: - case Qt::Key_Return: { - // the enter has already been processed when key is pressed, - // key release should not be processed in operation manager - if (myIsEmitKeyPressEvent) { - theEvent->accept(); - emit enterReleased(); - return; - } - } - break; - default: - break; - } - QDoubleSpinBox::keyReleaseEvent(theEvent); -} - /*! \brief Perform \a steps increment/decrement steps. @@ -348,14 +311,6 @@ void ModuleBase_DoubleSpinBox::onTextChanged(const QString& ) myCleared = false; } -bool ModuleBase_DoubleSpinBox::enableKeyPressEvent(const bool& theEnable) -{ - bool aPreviousValue = myIsEmitKeyPressEvent; - myIsEmitKeyPressEvent = theEnable; - - return aPreviousValue; -} - void ModuleBase_DoubleSpinBox::setValueEnabled(const bool& theEnable) { setReadOnly(!theEnable); diff --git a/src/ModuleBase/ModuleBase_DoubleSpinBox.h b/src/ModuleBase/ModuleBase_DoubleSpinBox.h index 982b1088d..237bd704f 100644 --- a/src/ModuleBase/ModuleBase_DoubleSpinBox.h +++ b/src/ModuleBase/ModuleBase_DoubleSpinBox.h @@ -51,19 +51,10 @@ Q_OBJECT /// Validate current value virtual QValidator::State validate(QString&, int&) const; - /// Change enable/disable internal state to emit key press event - /// \param theEnable if true, the signal is emitted - /// \return the previous value - bool enableKeyPressEvent(const bool& theEnable); - /// Imitation of disable control value. If theEnable is false, the control becomes /// read only and base color is disabled. void setValueEnabled(const bool& theEnable); -signals: - /// The signal about key release on the control, that corresponds to the attribute - void enterReleased(); - protected slots: /// Called on text changed virtual void onTextChanged(const QString&); @@ -71,11 +62,6 @@ signals: protected: /// Removes extra trailing zero symbols QString removeTrailingZeroes(const QString&) const; - /// Called on key press event - virtual void keyReleaseEvent(QKeyEvent* theEvent); - - /// Called on key press event - virtual void keyPressEvent(QKeyEvent* theEvent); private: // boolen flag whether the key event is emitted. The default value is false diff --git a/src/ModuleBase/ModuleBase_ModelWidget.cpp b/src/ModuleBase/ModuleBase_ModelWidget.cpp index f0be4ae9e..01eeccac4 100644 --- a/src/ModuleBase/ModuleBase_ModelWidget.cpp +++ b/src/ModuleBase/ModuleBase_ModelWidget.cpp @@ -5,6 +5,7 @@ // Author: Natalia ERMOLAEVA #include "ModuleBase_ModelWidget.h" +#include "ModuleBase_IPropertyPanel.h" #include "ModuleBase_ViewerPrs.h" #include "ModuleBase_Tools.h" #include "ModuleBase_WidgetValidator.h" @@ -426,8 +427,14 @@ bool ModuleBase_ModelWidget::eventFilter(QObject* theObject, QEvent *theEvent) QFocusEvent* aFocusEvent = dynamic_cast(theEvent); bool isWinFocus = aFocusEvent->reason() == Qt::ActiveWindowFocusReason; #endif - if (getControls().contains(aWidget)) { - emit focusInWidget(this); + Qt::FocusReason aReason = aFocusEvent->reason(); + bool aMouseOrKey = aReason == Qt::MouseFocusReason || + /*aReason == Qt::TabFocusReason || + //aReason == Qt::BacktabFocusReason ||*/ + aReason == Qt::OtherFocusReason; // to process widget->setFocus() + if (aMouseOrKey && getControls().contains(aWidget)) { + //if (getControls().contains(aWidget)) { + emitFocusInWidget(); } } else if (theEvent->type() == QEvent::FocusOut) { @@ -461,7 +468,23 @@ void ModuleBase_ModelWidget::onWidgetValuesModified() setValueState(ModifiedInPP); } +//************************************************************** QString ModuleBase_ModelWidget::translate(const std::string& theStr) const { return ModuleBase_Tools::translate(context(), theStr); } + +//************************************************************** +ModuleBase_ModelWidget* ModuleBase_ModelWidget::findModelWidget(ModuleBase_IPropertyPanel* theProp, + QWidget* theWidget) +{ + ModuleBase_ModelWidget* aModelWidget; + QObject* aParent = theWidget->parent(); + while (aParent) { + aModelWidget = qobject_cast(aParent); + if (aModelWidget) + break; + aParent = aParent->parent(); + } + return aModelWidget; +} diff --git a/src/ModuleBase/ModuleBase_ModelWidget.h b/src/ModuleBase/ModuleBase_ModelWidget.h index 0b409e6fe..603e0d97a 100644 --- a/src/ModuleBase/ModuleBase_ModelWidget.h +++ b/src/ModuleBase/ModuleBase_ModelWidget.h @@ -17,6 +17,7 @@ class Config_WidgetAPI; class Events_InfoMessage; +class ModuleBase_IPropertyPanel; class ModuleBase_IWorkshop; class ModuleBase_ViewerPrs; class ModuleBase_WidgetValidator; @@ -242,6 +243,15 @@ Q_OBJECT /// Translate passed string with widget context() virtual QString translate(const std::string& theStr) const; + /// Emit focus in widget to set this control as active in propety panel + void emitFocusInWidget() { emit focusInWidget(this); } + + /// Finds model widget parent of the given sub widget + /// \param theWidget a candidate to be a child of the model widget + /// \param theProp a property panel instance + /// \return a model widget or NULL + static ModuleBase_ModelWidget* findModelWidget(ModuleBase_IPropertyPanel* theProp, + QWidget* theWidget); signals: /// The signal about widget values are to be changed void beforeValuesChanged(); diff --git a/src/ModuleBase/ModuleBase_WidgetEditor.cpp b/src/ModuleBase/ModuleBase_WidgetEditor.cpp index edaecab32..2725012c3 100644 --- a/src/ModuleBase/ModuleBase_WidgetEditor.cpp +++ b/src/ModuleBase/ModuleBase_WidgetEditor.cpp @@ -35,7 +35,7 @@ ModuleBase_WidgetEditor::ModuleBase_WidgetEditor(QWidget* theParent, const Config_WidgetAPI* theData) : ModuleBase_WidgetDoubleValue(theParent, theData), - myXPosition(-1), myYPosition(-1) + myXPosition(-1), myYPosition(-1), myEditorDialog(0) { } @@ -47,13 +47,12 @@ bool ModuleBase_WidgetEditor::editedValue(double& outValue, QString& outText) { bool isValueAccepted = false; - QDialog aDlg(QApplication::desktop(), Qt::FramelessWindowHint); - QHBoxLayout* aLay = new QHBoxLayout(&aDlg); - aLay->setContentsMargins(2, 2, 2, 2); + myEditorDialog = new QDialog(QApplication::desktop(), Qt::FramelessWindowHint); - ModuleBase_ParamSpinBox* anEditor = new ModuleBase_ParamSpinBox(&aDlg); - anEditor->enableKeyPressEvent(true); + QHBoxLayout* aLay = new QHBoxLayout(myEditorDialog); + aLay->setContentsMargins(2, 2, 2, 2); + ModuleBase_ParamSpinBox* anEditor = new ModuleBase_ParamSpinBox(myEditorDialog); anEditor->setMinimum(0); anEditor->setMaximum(DBL_MAX); if (outText.isEmpty()) @@ -65,14 +64,13 @@ bool ModuleBase_WidgetEditor::editedValue(double& outValue, QString& outText) ModuleBase_Tools::setFocus(anEditor, "ModuleBase_WidgetEditor::editedValue"); anEditor->selectAll(); - QObject::connect(anEditor, SIGNAL(enterReleased()), &aDlg, SLOT(accept())); QPoint aPoint = QCursor::pos(); if (myXPosition >= 0 && myYPosition >= 0) aPoint = QPoint(myXPosition, myYPosition); - aDlg.move(aPoint); - isValueAccepted = aDlg.exec() == QDialog::Accepted; + myEditorDialog->move(aPoint); + isValueAccepted = myEditorDialog->exec() == QDialog::Accepted; if (isValueAccepted) { outText = anEditor->text(); bool isDouble; @@ -82,6 +80,8 @@ bool ModuleBase_WidgetEditor::editedValue(double& outValue, QString& outText) outText = ""; // return empty string, if it's can be converted to a double } } + delete myEditorDialog; + myEditorDialog = 0; return isValueAccepted; } @@ -98,7 +98,7 @@ bool ModuleBase_WidgetEditor::showPopupEditor(const bool theSendSignals) // in the property panel before the mouse leave event happens in the viewer. The module // ask an active widget and change the feature visualization if the widget is not the current one. if (theSendSignals) - emit focusInWidget(this); + emitFocusInWidget(); // nds: it seems, that the envents processing is not necessary anymore // White while all events will be processed @@ -141,3 +141,13 @@ void ModuleBase_WidgetEditor::setCursorPosition(const int theX, const int theY) myXPosition = theX; myYPosition = theY; } + +bool ModuleBase_WidgetEditor::processEnter() +{ + if (myEditorDialog) { + myEditorDialog->accept(); + return true; + } + + return ModuleBase_WidgetDoubleValue::processEnter(); +} diff --git a/src/ModuleBase/ModuleBase_WidgetEditor.h b/src/ModuleBase/ModuleBase_WidgetEditor.h index 51d7eff60..1232a1dec 100644 --- a/src/ModuleBase/ModuleBase_WidgetEditor.h +++ b/src/ModuleBase/ModuleBase_WidgetEditor.h @@ -14,6 +14,7 @@ #include class ModelAPI_Feature; +class QDialog; class QLineEdit; /**\class ModuleBase_WidgetEditor @@ -53,6 +54,9 @@ Q_OBJECT /// \param theY the Y coordinate void setCursorPosition(const int theX, const int theY); + /// Returns true if the event is processed. + virtual bool processEnter(); + private: /// Show editor /// \param theOutValue a result value @@ -68,6 +72,8 @@ private: QStringList myFeatureKinds; int myXPosition, myYPosition; + + QDialog* myEditorDialog; }; #endif diff --git a/src/PartSet/PartSet_SketcherReentrantMgr.cpp b/src/PartSet/PartSet_SketcherReentrantMgr.cpp index 35fbf3e41..a98087127 100644 --- a/src/PartSet/PartSet_SketcherReentrantMgr.cpp +++ b/src/PartSet/PartSet_SketcherReentrantMgr.cpp @@ -175,9 +175,8 @@ bool PartSet_SketcherReentrantMgr::processMouseMoved(ModuleBase_IViewWindow* the myPreviousFeature = FeaturePtr(); anActiveWidget = module()->activeWidget(); - aCurrentFeature = anActiveWidget->feature(); aProcessed = true; - if (anActiveWidget->attributeID() == anAttributeOnStart) { + if (anActiveWidget && anActiveWidget->attributeID() == anAttributeOnStart) { // it was not deactivated by preselection processing aPanel->activateNextWidget(anActiveWidget); } @@ -501,6 +500,7 @@ bool PartSet_SketcherReentrantMgr::startInternalEdit(const std::string& thePrevi if (aPreviousAttributeWidget) { if (!aPreviousAttributeWidget->isViewerSelector()) { aPreviousAttributeWidget->focusTo(); + aPreviousAttributeWidget->emitFocusInWidget(); aPreviousAttributeWidget->selectContent(); } else { diff --git a/src/XGUI/XGUI_ActionsMgr.cpp b/src/XGUI/XGUI_ActionsMgr.cpp index d9b264bab..ae30f613d 100644 --- a/src/XGUI/XGUI_ActionsMgr.cpp +++ b/src/XGUI/XGUI_ActionsMgr.cpp @@ -241,9 +241,6 @@ QAction* XGUI_ActionsMgr::operationStateAction(OperationStateActionId theId) case AbortAll: { aResult = ModuleBase_Tools::createAction(QIcon(":pictures/button_cancel.png"), "Cancel", aParent); - if (theId == Abort) { - aResult->setShortcut(QKeySequence(Qt::Key_Escape)); - } } break; case Help: { diff --git a/src/XGUI/XGUI_OperationMgr.cpp b/src/XGUI/XGUI_OperationMgr.cpp index d7aa1f858..17e7ae51f 100644 --- a/src/XGUI/XGUI_OperationMgr.cpp +++ b/src/XGUI/XGUI_OperationMgr.cpp @@ -60,11 +60,15 @@ public: bool isAccepted = false; if (myIsActive && theEvent->type() == QEvent::KeyRelease) { QKeyEvent* aKeyEvent = dynamic_cast(theEvent); - if(aKeyEvent) { + if (aKeyEvent) { switch (aKeyEvent->key()) { - case Qt::Key_Delete: { + case Qt::Key_Delete: isAccepted = myOperationMgr->onProcessDelete(theObject); - } + break; + default: + myOperationMgr->onKeyReleased(theObject, aKeyEvent); + isAccepted = true; + break; } } } @@ -171,20 +175,6 @@ ModuleBase_Operation* XGUI_OperationMgr::previousOperation(ModuleBase_Operation* return myOperations.at(idx - 1); } -bool XGUI_OperationMgr::eventFilter(QObject *theObject, QEvent *theEvent) -{ - bool isAccepted = false; - if (theEvent->type() == QEvent::KeyRelease) { - QKeyEvent* aKeyEvent = dynamic_cast(theEvent); - if(aKeyEvent) - isAccepted = onKeyReleased(theObject, aKeyEvent); - } - if (!isAccepted) - isAccepted = QObject::eventFilter(theObject, theEvent); - - return isAccepted; -} - bool XGUI_OperationMgr::startOperation(ModuleBase_Operation* theOperation) { if (hasOperation()) @@ -600,6 +590,28 @@ bool XGUI_OperationMgr::onKeyReleased(QObject *theObject, QKeyEvent* theEvent) ModuleBase_Operation* anOperation = currentOperation(); bool isAccepted = false; switch (theEvent->key()) { + case Qt::Key_Escape: { + ModuleBase_Operation* aOperation = currentOperation(); + if (aOperation) { + onAbortOperation(); + isAccepted = true; + } + } + break; + case Qt::Key_Tab: + case Qt::Key_Backtab: + { + ModuleBase_Operation* aOperation = currentOperation(); + if (aOperation) { + ModuleBase_IPropertyPanel* aPanel = anOperation->propertyPanel(); + if (aPanel) { // check for case when the operation is started but property panel is not filled + XGUI_PropertyPanel* aPP = dynamic_cast(aPanel); + aPP->focusNextPrevChild_(theEvent->key() == Qt::Key_Tab); + isAccepted = true; + } + } + } + break; case Qt::Key_Return: case Qt::Key_Enter: { isAccepted = onProcessEnter(theObject); @@ -640,10 +652,11 @@ bool XGUI_OperationMgr::onProcessEnter(QObject* theObject) if (!aOperation) return isAccepted; ModuleBase_IPropertyPanel* aPanel = aOperation->propertyPanel(); + // the next code is obsolete as we want to process Enter in property panel always // only property panel enter is processed in order to do not process enter in application dialogs - bool isPPChild = isChildObject(theObject, aPanel); - if (!isPPChild) - return isAccepted; + //bool isPPChild = isChildObject(theObject, aPanel); + //if (!isPPChild) + // return isAccepted; ModuleBase_ModelWidget* anActiveWgt = aPanel->activeWidget(); bool isAborted = false; diff --git a/src/XGUI/XGUI_OperationMgr.h b/src/XGUI/XGUI_OperationMgr.h index c8ccf9d08..f1106c342 100755 --- a/src/XGUI/XGUI_OperationMgr.h +++ b/src/XGUI/XGUI_OperationMgr.h @@ -88,11 +88,6 @@ Q_OBJECT /// else, or if there is no parent - returns NULL ModuleBase_Operation* previousOperation(ModuleBase_Operation* theOperation) const; - /// Redefinition of virtual function - /// \param theObject a sender of the event - /// \param theEvent the event - virtual bool eventFilter(QObject *theObject, QEvent *theEvent); - /// Start the operation and append it to the stack of operations /// \param theOperation the started operation /// \return the state whether the current operation is started diff --git a/src/XGUI/XGUI_PropertyPanel.cpp b/src/XGUI/XGUI_PropertyPanel.cpp index be453d93c..3ccea3360 100755 --- a/src/XGUI/XGUI_PropertyPanel.cpp +++ b/src/XGUI/XGUI_PropertyPanel.cpp @@ -244,7 +244,7 @@ void XGUI_PropertyPanel::activateNextWidget(ModuleBase_ModelWidget* theWidget, QList::const_iterator anIt = myWidgets.begin(), aLast = myWidgets.end(); bool isFoundWidget = false; - ModuleBase_Tools::activateWindow(this, "XGUI_PropertyPanel::activateNextWidget()"); + //ModuleBase_Tools::activateWindow(this, "XGUI_PropertyPanel::activateNextWidget()"); for (; anIt != aLast; anIt++) { ModuleBase_ModelWidget* aCurrentWidget = *anIt; if (isFoundWidget || !theWidget) { @@ -262,6 +262,7 @@ void XGUI_PropertyPanel::activateNextWidget(ModuleBase_ModelWidget* theWidget, continue; // do not set focus if it can not be accepted, case: optional choice if (aCurrentWidget->focusTo()) { + aCurrentWidget->emitFocusInWidget(); return; } } @@ -341,7 +342,7 @@ void findDirectChildren(QWidget* theParent, QList& theWidgets, const b #endif } -bool XGUI_PropertyPanel::focusNextPrevChild(bool theIsNext) +bool XGUI_PropertyPanel::focusNextPrevChild_(bool theIsNext) { // it wraps the Tabs clicking to follow in the chain: // controls, last control, Apply, Cancel, first control, controls @@ -354,6 +355,10 @@ bool XGUI_PropertyPanel::focusNextPrevChild(bool theIsNext) qDebug(anInfo.toStdString().c_str()); } #endif + ModuleBase_ModelWidget* aFocusMWidget = ModuleBase_ModelWidget::findModelWidget(this, + aFocusWidget); + if (aFocusMWidget) + aFocusMWidget->setHighlighted(false); QWidget* aNewFocusWidget = 0; if (aFocusWidget) { @@ -398,7 +403,6 @@ bool XGUI_PropertyPanel::focusNextPrevChild(bool theIsNext) } if (aNewFocusWidget) { if (myActiveWidget) { - myActiveWidget->getControls(); bool isFirstControl = !theIsNext; QWidget* aLastFocusControl = myActiveWidget->getControlAcceptingFocus(isFirstControl); if (aFocusWidget == aLastFocusControl) { @@ -408,6 +412,11 @@ bool XGUI_PropertyPanel::focusNextPrevChild(bool theIsNext) //ModuleBase_Tools::setFocus(aNewFocusWidget, "XGUI_PropertyPanel::focusNextPrevChild()"); aNewFocusWidget->setFocus(theIsNext ? Qt::TabFocusReason : Qt::BacktabFocusReason); + + ModuleBase_ModelWidget* aNewFocusMWidget = ModuleBase_ModelWidget::findModelWidget(this, + aNewFocusWidget); + if (aNewFocusMWidget) + aNewFocusMWidget->emitFocusInWidget(); isChangedFocus = true; } return isChangedFocus; diff --git a/src/XGUI/XGUI_PropertyPanel.h b/src/XGUI/XGUI_PropertyPanel.h index 7743a0ffc..0b508df96 100644 --- a/src/XGUI/XGUI_PropertyPanel.h +++ b/src/XGUI/XGUI_PropertyPanel.h @@ -151,12 +151,13 @@ protected: /// Makes the widget active, deactivate the previous, activate and hightlight the given one /// \param theWidget a widget bool setActiveWidget(ModuleBase_ModelWidget* theWidget); - +public: /// The parent method that processes the "Tab"/"SHIF + Tab" keyboard events /// Emits a signal about focus change /// If theIsNext is true, this function searches forward, if next is false, it searches backward. - virtual bool focusNextPrevChild(bool theIsNext); - + virtual bool focusNextPrevChild_(bool theIsNext); +protected: + virtual bool focusNextPrevChild(bool theIsNext) { return true; } /// Activate the next widget in the property panel /// \param theWidget a widget. The next widget should be activated /// \param isCheckVisibility flag whether the next widget visibility is checked diff --git a/src/XGUI/XGUI_Workshop.cpp b/src/XGUI/XGUI_Workshop.cpp index 855abacc1..abbc09841 100755 --- a/src/XGUI/XGUI_Workshop.cpp +++ b/src/XGUI/XGUI_Workshop.cpp @@ -1275,7 +1275,7 @@ void XGUI_Workshop::showPropertyPanel() // in order to operation manager could process key events of the panel. // otherwise they are ignored. It happens only if the same(activateWindow) is // not happened by property panel activation(e.g. resume operation of Sketch) - ModuleBase_Tools::activateWindow(myPropertyPanel, "XGUI_Workshop::showPropertyPanel()"); + //ModuleBase_Tools::activateWindow(myPropertyPanel, "XGUI_Workshop::showPropertyPanel()"); ModuleBase_Tools::setFocus(myPropertyPanel, "XGUI_Workshop::showPropertyPanel()"); } @@ -1294,9 +1294,9 @@ void XGUI_Workshop::hidePropertyPanel() // set the focus on it. As a result, shortcuts of the application, like // are processed by this console. For example Undo actions. // It is possible that this code is to be moved to SHAPER package - QMainWindow* aDesktop = desktop(); - ModuleBase_Tools::activateWindow(aDesktop, "XGUI_Workshop::hidePropertyPanel()"); - ModuleBase_Tools::setFocus(aDesktop, "XGUI_Workshop::showPropertyPanel()"); + //QMainWindow* aDesktop = desktop(); + //ModuleBase_Tools::activateWindow(aDesktop, "XGUI_Workshop::hidePropertyPanel()"); + //ModuleBase_Tools::setFocus(aDesktop, "XGUI_Workshop::showPropertyPanel()"); } //****************************************************** -- 2.39.2