Salome HOME
Merge branch 'csgroup_IS2'
[modules/shaper.git] / src / ModuleBase / ModuleBase_ParamSpinBox.cpp
1 // Copyright (C) 2014-2021  CEA/DEN, EDF R&D
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 #include "ModuleBase_ParamSpinBox.h"
21
22 #include <QKeyEvent>
23 #include <QLocale>
24 #include <QRegExp>
25 #include <QToolTip>
26 #include <QApplication>
27
28 #include <QStringListModel>
29 #include <QCompleter>
30 #include <QAbstractItemView>
31 #include <QShortcut>
32
33 #include <string>
34 #include <iostream>
35 #include <cfloat>
36
37
38 bool isVariableSymbol(const QChar& theChar) {
39   if (theChar.isLetterOrNumber())
40     return true;
41   if (theChar == '_')
42     return true;
43   return false;
44 }
45
46 ModuleBase_ParamSpinBox::ModuleBase_ParamSpinBox(QWidget* theParent, int thePrecision)
47   : QAbstractSpinBox(theParent),
48   myIsEquation(false),
49   myAcceptVariables(true),
50   myMinimum(-DBL_MAX),
51   myMaximum(DBL_MAX),
52   mySingleStep(1)
53 {
54   myCompleter = new QCompleter(this);
55   myCompleter->setWidget(lineEdit());
56   myCompleter->setCompletionMode(QCompleter::PopupCompletion);
57
58   myCompleterModel = new QStringListModel(this);
59   myCompleter->setModel(myCompleterModel);
60   connect(myCompleter, SIGNAL(highlighted(const QString&)),
61     this, SLOT(insertCompletion(const QString&)));
62
63   QAbstractItemView* aPopup = myCompleter->popup();
64   aPopup->installEventFilter(this);
65
66   //  connectSignalsAndSlots();
67   myEnabledBaseColor = palette().color(QPalette::Active, QPalette::Base);
68   connect(lineEdit(), SIGNAL(textChanged(const QString&)),
69     this, SLOT(onTextChanged(const QString&)));
70
71   setLocale(QLocale::c());
72
73   myValidator = new QDoubleValidator(this);
74   myValidator->setLocale(locale());
75   myValidator->setRange(myMinimum, myMaximum);
76   myValidator->setDecimals(thePrecision);
77 }
78
79 void ModuleBase_ParamSpinBox::setCompletionList(QStringList& theList)
80 {
81   theList.removeDuplicates();
82   theList.sort();
83   myCompleterModel->setStringList(theList);
84
85   QAbstractItemView* aPopup = myCompleter->popup();
86   QFontMetrics aMetric = aPopup->fontMetrics();
87   int aWidth = 0;
88   QRect aRect;
89   foreach(QString aStr, theList) {
90     aRect = aMetric.boundingRect(aStr);
91     if (aRect.width() > aWidth)
92       aWidth = aRect.width();
93   }
94   aPopup->setMinimumWidth(aWidth + 25);
95 }
96
97 /*!
98  \brief Destructor.
99  */
100 ModuleBase_ParamSpinBox::~ModuleBase_ParamSpinBox()
101 {
102 }
103
104
105 /*!
106  \brief Perform \a steps increment/decrement steps.
107
108  Re-implemented to handle cases when Notebook variable
109  name is specified by the user as the widget text.
110  Otherwise, simply calls the base implementation.
111
112  \param steps number of increment/decrement steps
113  */
114 void ModuleBase_ParamSpinBox::stepBy(int steps)
115 {
116   if (hasVariable())
117     return;
118
119   double aVal = lineEdit()->text().toDouble();
120   aVal += steps * mySingleStep;
121   setValue(aVal);
122   //QAbstractSpinBox::stepBy(steps);
123 }
124
125 void ModuleBase_ParamSpinBox::onTextChanged(const QString& theText)
126 {
127   myIsEquation = hasVariable(theText);
128   emit textChanged(theText);
129 }
130
131
132 /*!
133  \brief This function is used to determine whether input is valid.
134  \param str currently entered value
135  \param pos cursor position in the string
136  \return validating operation result
137  */
138 QValidator::State ModuleBase_ParamSpinBox::validate(QString& str, int& pos) const
139 {
140   // Trying to interpret the current input text as a numeric value
141   if (!hasVariable(str)) {
142     /// If decimals = 0 do not accept '.' (interpret as int)
143     if ((myValidator->decimals() == 0) && str.endsWith('.'))
144       return QValidator::Invalid;
145     return myValidator->validate(str, pos);
146   }
147
148   return isAcceptVariables() ? QValidator::Acceptable : QValidator::Invalid;
149 }
150
151 /*!
152  \brief This function is used to set a current value for this spinbox.
153  \param value current value
154
155  The new value is ignored if the spinbox has a variable.
156  */
157 void ModuleBase_ParamSpinBox::setValue(double value)
158 {
159   myIsEquation = false;
160   double aVal = value;
161   if (aVal < myMinimum)
162     aVal = myMinimum;
163   else if (aVal > myMaximum)
164     aVal = myMaximum;
165   QString aText = (myValidator->decimals() == 0) ? QString::number((int)aVal) :
166     QString::number(aVal, 'g', decimals());
167   lineEdit()->blockSignals(true);
168   lineEdit()->setText(aText);
169   lineEdit()->blockSignals(false);
170   emit textChanged(aText);
171 }
172
173 double ModuleBase_ParamSpinBox::value() const
174 {
175   return lineEdit()->text().toDouble();
176 }
177
178 /*!
179  \brief This function is used to set a text for this spinbox.
180  \param value current value
181  */
182 void ModuleBase_ParamSpinBox::setText(const QString& value)
183 {
184   myIsEquation = hasVariable(value);
185   if (myAcceptVariables && myIsEquation) {
186     lineEdit()->setText(value);
187     emit textChanged(value);
188   }
189   else {
190     bool isConv = false;
191     double aVal = value.toDouble(&isConv);
192     if (isConv)
193       setValue(aVal);
194   }
195 }
196
197 /*!
198  \brief Enables or disables variable names in the spin box.
199  By default, variable names are enabled.
200  \param flag If true, variable names are enabled.
201  */
202 void ModuleBase_ParamSpinBox::setAcceptVariables(const bool flag)
203 {
204   myAcceptVariables = flag;
205   if ((!myAcceptVariables) && myIsEquation) {
206     setValue(0);
207   }
208 }
209
210 /*!
211  \brief Returns true if the spin box accepts variable names.
212  */
213 bool ModuleBase_ParamSpinBox::isAcceptVariables() const
214 {
215   return myAcceptVariables;
216 }
217
218 bool ModuleBase_ParamSpinBox::hasVariable() const
219 {
220   return myIsEquation;
221 }
222
223 bool ModuleBase_ParamSpinBox::hasVariable(const QString& theText) const
224 {
225   bool isDouble = false;
226   QLocale::c().toDouble(theText, &isDouble);
227   return !isDouble;
228 }
229
230 void ModuleBase_ParamSpinBox::showCompletion(bool checkPrefix)
231 {
232   myCompletePos = lineEdit()->cursorPosition();
233   int aStart, aEnd;
234   QString aPrefix;
235   aPrefix = getPrefix(aStart, aEnd);
236   if (checkPrefix) {
237     if (aPrefix.length() > 0) {
238       myCompleter->setCompletionPrefix(aPrefix);
239       myCompleter->complete();
240     }
241   } else {
242     myCompleter->setCompletionPrefix(aPrefix);
243     myCompleter->complete();
244   }
245 }
246
247 void ModuleBase_ParamSpinBox::keyReleaseEvent(QKeyEvent* e)
248 {
249   QString aText;
250
251   switch (e->key()) {
252   case Qt::Key_Backspace:
253     if (myCompleter->popup()->isVisible()) {
254       myCompleter->popup()->hide();
255     }
256     showCompletion(true);
257     break;
258   case Qt::Key_Return:
259   case Qt::Key_Enter:
260     if (myCompleter->popup()->isVisible()) {
261       myCompleter->popup()->hide();
262       myIsEquation = true;
263     }
264     emit textChanged(lineEdit()->text());
265     break;
266   case Qt::Key_Space:
267     if (e->modifiers() & Qt::ControlModifier) {
268       showCompletion(false);
269     }
270     break;  default:
271     aText = e->text();
272     if (aText.length() == 1) {
273       QChar aChar = aText.at(0);
274       if (isVariableSymbol(aChar)) {
275         showCompletion(true);
276       }
277     }
278   }
279   QAbstractSpinBox::keyReleaseEvent(e);
280 }
281
282 bool ModuleBase_ParamSpinBox::eventFilter(QObject* theObj, QEvent* theEvent)
283 {
284   if (theEvent->type() == QEvent::KeyRelease) {
285     keyReleaseEvent((QKeyEvent*)theEvent);
286   }
287   return QAbstractSpinBox::eventFilter(theObj, theEvent);
288 }
289
290
291 QString ModuleBase_ParamSpinBox::getPrefix(int& theStart, int& theEnd) const
292 {
293   QString aPrefix;
294   QString aText = lineEdit()->text();
295   theStart = theEnd = myCompletePos;
296   const int aLen = aText.length();
297   if (aLen > 0) {
298     if (myCompletePos > 0) {
299       int aLastChar = myCompletePos - 1;
300       QChar aChar = aText.at(aLastChar);
301       while (isVariableSymbol(aChar)) {
302         aPrefix.prepend(aText.at(aLastChar));
303         aLastChar--;
304         if (aLastChar < 0)
305           break;
306         aChar = aText.at(aLastChar);
307       }
308       theStart = aLastChar + 1;
309     }
310     if (myCompletePos < aLen) {
311       int aLastChar = myCompletePos;
312       QChar aChar = aText.at(aLastChar);
313       while (isVariableSymbol(aChar)) {
314         aPrefix.append(aText.at(aLastChar));
315         aLastChar++;
316         if (aLastChar >= aLen)
317           break;
318         aChar = aText.at(aLastChar);
319       }
320       theEnd = aLastChar;
321     }
322   }
323   return aPrefix;
324 }
325
326
327 void ModuleBase_ParamSpinBox::insertCompletion(const QString& theText)
328 {
329   QString aText = lineEdit()->text();
330   int aStart, aEnd;
331   QString aPrefix = getPrefix(aStart, aEnd);
332
333   QString aResult;
334   int aPrefLen = aPrefix.length();
335   if (aPrefLen == 0)
336     aResult = aText.insert(myCompletePos, theText);
337   else {
338     aResult = aText.left(aStart) + theText + aText.right(aText.length() - aEnd);
339   }
340   lineEdit()->setText(aResult);
341   myIsEquation = true;
342 }
343
344
345 void ModuleBase_ParamSpinBox::setValueEnabled(bool theEnable)
346 {
347   setReadOnly(!theEnable);
348
349   QPalette aPal = palette();
350   aPal.setColor(QPalette::All, QPalette::Base,
351     theEnable ? myEnabledBaseColor : aPal.color(QPalette::Disabled, QPalette::Base));
352   setPalette(aPal);
353 }