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