Salome HOME
Validators for Parameters values and expressions
[modules/shaper.git] / src / ModuleBase / ModuleBase_WidgetExprEditor.cpp
1 // Copyright (C) 2014-20xx CEA/DEN, EDF R&D
2
3 /*
4  * ModuleBase_WidgetExprEditor.cpp
5  *
6  *  Created on: Aug 28, 2014
7  *      Author: sbh
8  */
9
10 #include <ModuleBase_WidgetExprEditor.h>
11 #include <ModuleBase_Tools.h>
12
13 #include <ModelAPI_Data.h>
14 #include <ModelAPI_Object.h>
15 #include <ModelAPI_Validator.h>
16 #include <ModelAPI_ResultParameter.h>
17 #include <ModelAPI_AttributeString.h>
18 #include <ModelAPI_AttributeDouble.h>
19
20 #include <Config_WidgetAPI.h>
21
22 #include <QVBoxLayout>
23 #include <QLabel>
24 #include <QLineEdit>
25 #include <QObject>
26 #include <QString>
27 #include <QStringListModel>
28 #include <QCompleter>
29 #include <QSize>
30 #include <QShortcut>
31 #include <QScrollBar>
32
33 #include <memory>
34 #include <string>
35
36 ExpressionEditor::ExpressionEditor(QWidget* theParent)
37 : QPlainTextEdit(theParent)
38 {
39   myCompleter = new QCompleter(this);
40   myCompleter->setWidget(this);
41   myCompleter->setCompletionMode(QCompleter::PopupCompletion);
42
43   myCompleterModel = new QStringListModel(this);
44   myCompleter->setModel(myCompleterModel);
45   // Use sorted model to accelerate completion (QCompleter will use binary search)
46   myCompleter->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
47   myCompleter->setCaseSensitivity(Qt::CaseInsensitive);
48
49   connect(myCompleter, SIGNAL(activated(const QString&)),
50           this,        SLOT(insertCompletion(const QString&)));
51   (void) new QShortcut(QKeySequence(tr("Ctrl+Space", "Complete")),
52                        this, SLOT(performCompletion()));
53 }
54
55 ExpressionEditor::~ExpressionEditor()
56 {
57
58 }
59
60 void ExpressionEditor::setCompletionList(QStringList& theList)
61 {
62   theList.sort();
63   theList.removeDuplicates();
64   myCompleterModel->setStringList(theList);
65 }
66
67 void ExpressionEditor::insertCompletion(const QString& theCompletion, bool isSingleWord)
68 {
69   QTextCursor aCursor = textCursor();
70   int numberOfCharsToComplete = theCompletion.length() -
71       myCompleter->completionPrefix().length();
72   int insertionPosition = aCursor.position();
73   aCursor.insertText(theCompletion.right(numberOfCharsToComplete));
74   if (isSingleWord) {
75     aCursor.setPosition(insertionPosition);
76     aCursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
77     myCompletedAndSelected = true;
78   }
79   setTextCursor(aCursor);
80 }
81
82 void ExpressionEditor::performCompletion()
83 {
84   QTextCursor aCursor = textCursor();
85   aCursor.select(QTextCursor::WordUnderCursor);
86   const QString aPrefix = aCursor.selectedText();
87   if (!aPrefix.isEmpty() && aPrefix.at(aPrefix.length() - 1).isLetter()) {
88     performCompletion(aPrefix);
89   }
90 }
91
92 void ExpressionEditor::performCompletion(const QString& theCompletionPrefix)
93 {
94   //populate model?
95   if (theCompletionPrefix != myCompleter->completionPrefix()) {
96     myCompleter->setCompletionPrefix(theCompletionPrefix);
97     myCompleter->popup()->setCurrentIndex(myCompleter->completionModel()->index(0, 0));
98   }
99   if (myCompleter->completionCount() == 1) {
100     insertCompletion(myCompleter->currentCompletion(), true);
101   } else {
102     QRect aRect = cursorRect();
103     aRect.setWidth(myCompleter->popup()->sizeHintForColumn(0)
104                   + myCompleter->popup()->verticalScrollBar()->sizeHint().width());
105     myCompleter->complete(aRect);
106   }
107 }
108
109 void ExpressionEditor::keyPressEvent(QKeyEvent* theEvent)
110 {
111   if (myCompletedAndSelected && handledCompletedAndSelected(theEvent))
112     return;
113   myCompletedAndSelected = false;
114   if (myCompleter->popup()->isVisible()) {
115     switch (theEvent->key()) {
116       case Qt::Key_Up:
117       case Qt::Key_Down:
118       case Qt::Key_Enter:
119       case Qt::Key_Return:
120       case Qt::Key_Escape:
121         theEvent->ignore();
122         return;
123       default:
124         myCompleter->popup()->hide();
125         break;
126     }
127   }
128   QPlainTextEdit::keyPressEvent(theEvent);
129 }
130
131 bool ExpressionEditor::handledCompletedAndSelected(QKeyEvent* theEvent)
132 {
133   myCompletedAndSelected = false;
134   QTextCursor aCursor = textCursor();
135   switch (theEvent->key()) {
136     case Qt::Key_Enter:
137     case Qt::Key_Return: aCursor.clearSelection(); break;
138     case Qt::Key_Escape: aCursor.removeSelectedText(); break;
139     default: return false;
140   }
141   setTextCursor(aCursor);
142   theEvent->accept();
143   return true;
144 }
145
146 ModuleBase_WidgetExprEditor::ModuleBase_WidgetExprEditor(QWidget* theParent,
147                                                      const Config_WidgetAPI* theData,
148                                                      const std::string& theParentId)
149     : ModuleBase_ModelWidget(theParent, theData, theParentId)
150 {
151   QVBoxLayout* aMainLay = new QVBoxLayout(this);
152   ModuleBase_Tools::adjustMargins(aMainLay);
153
154   myResultLabel = new QLabel(this);
155   myResultLabel->setWordWrap(true);
156   aMainLay->addWidget(myResultLabel);
157   myEditor = new ExpressionEditor(this);
158   myEditor->setMinimumHeight(20);
159   aMainLay->addWidget(myEditor);
160   this->setLayout(aMainLay);
161
162   connect(myEditor, SIGNAL(textChanged()), this, SLOT(onTextChanged()));
163 }
164
165 ModuleBase_WidgetExprEditor::~ModuleBase_WidgetExprEditor()
166 {
167 }
168
169 bool ModuleBase_WidgetExprEditor::storeValueCustom() const
170 {
171   // A rare case when plugin was not loaded. 
172   if(!myFeature)
173     return false;
174   DataPtr aData = myFeature->data();
175   AttributeStringPtr aStringAttr = aData->string(attributeID());
176   QString aWidgetValue = myEditor->toPlainText();
177   aStringAttr->setValue(aWidgetValue.toStdString());
178   updateObject(myFeature);
179
180   // Try to get the value
181   QString aStateMsg;
182   std::string anErrorMessage = myFeature->error();
183   if (anErrorMessage.empty()) {
184     ResultParameterPtr aParam =
185       std::dynamic_pointer_cast<ModelAPI_ResultParameter>(myFeature->firstResult());
186     if(aParam.get()) {
187       AttributeDoublePtr aValueAttr =
188         aParam->data()->real(ModelAPI_ResultParameter::VALUE());
189       if (aValueAttr.get()) {
190         double aValue = aValueAttr->value();
191         aStateMsg = "Result: " + QString::number(aValue);
192       }
193     }
194   } else {
195     aStateMsg = QString::fromStdString(anErrorMessage);
196   }
197   myResultLabel->setText(aStateMsg);
198   return true;
199 }
200
201 bool ModuleBase_WidgetExprEditor::restoreValue()
202 {
203   // A rare case when plugin was not loaded. 
204   if(!myFeature)
205     return false;
206   DataPtr aData = myFeature->data();
207   AttributeStringPtr aStringAttr = aData->string(attributeID());
208
209   bool isBlocked = myEditor->blockSignals(true);
210   QTextCursor aCursor = myEditor->textCursor();
211   int pos = aCursor.position();
212   std::string aRestoredStr = aStringAttr->value();
213   myEditor->setPlainText(QString::fromStdString(aRestoredStr));
214   aCursor.setPosition(pos);
215   myEditor->setTextCursor(aCursor);
216   myEditor->blockSignals(isBlocked);
217
218   return true;
219 }
220
221 QList<QWidget*> ModuleBase_WidgetExprEditor::getControls() const
222 {
223   QList<QWidget*> result;
224   result << myEditor;
225   return result;
226 }
227
228 void ModuleBase_WidgetExprEditor::onTextChanged()
229 {
230   storeValue();
231 }