Salome HOME
Implementation of point 2.3.1 of CEA specification
[modules/shaper.git] / src / ModuleBase / ModuleBase_ParamSpinBox.cpp
1 // Copyright (C) 2014-2017  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
18 // email : webmaster.salome@opencascade.com<mailto:webmaster.salome@opencascade.com>
19 //
20
21 #include "ModuleBase_ParamSpinBox.h"
22
23 #include <QKeyEvent>
24 #include <QLocale>
25 #include <QRegExp>
26 #include <QToolTip>
27 #include <QApplication>
28
29 #include <QStringListModel>
30 #include <QCompleter>
31 #include <QAbstractItemView>
32 #include <QShortcut>
33
34 #include <string>
35 #include <iostream>
36 #include <cfloat>
37
38 ModuleBase_ParamSpinBox::ModuleBase_ParamSpinBox(QWidget* theParent, int thePrecision)
39   : QAbstractSpinBox(theParent),
40   myPrecision(thePrecision),
41   myIsEquation(false),
42   myAcceptVariables(true),
43   mySingleStep(1),
44   myMinimum(DBL_MIN),
45   myMaximum(DBL_MAX)
46 {
47   myCompleter = new QCompleter(this);
48   myCompleter->setWidget(lineEdit());
49   myCompleter->setCompletionMode(QCompleter::PopupCompletion);
50
51   myCompleterModel = new QStringListModel(this);
52   myCompleter->setModel(myCompleterModel);
53   // Use sorted model to accelerate completion (QCompleter will use binary search)
54   myCompleter->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
55   myCompleter->setCaseSensitivity(Qt::CaseInsensitive);
56   connect(myCompleter, SIGNAL(highlighted(const QString&)),
57     this, SLOT(insertCompletion(const QString&)));
58
59   //  connectSignalsAndSlots();
60   myEnabledBaseColor = palette().color(QPalette::Active, QPalette::Base);
61   connect(lineEdit(), SIGNAL(textChanged(const QString&)),
62     this, SLOT(onTextChanged(const QString&)));
63
64   setLocale(QLocale::c());
65
66   myValidator = new QDoubleValidator(this);
67   myValidator->setLocale(locale());
68   myValidator->setRange(myMinimum, myMaximum);
69   myValidator->setDecimals(myPrecision);
70 }
71
72 void ModuleBase_ParamSpinBox::setCompletionList(QStringList& theList)
73 {
74   theList.sort();
75   theList.removeDuplicates();
76   myCompleterModel->setStringList(theList);
77 }
78
79 /*!
80  \brief Destructor.
81  */
82 ModuleBase_ParamSpinBox::~ModuleBase_ParamSpinBox()
83 {
84 }
85
86
87 /*!
88  \brief Perform \a steps increment/decrement steps.
89
90  Re-implemented to handle cases when Notebook variable
91  name is specified by the user as the widget text.
92  Otherwise, simply calls the base implementation.
93
94  \param steps number of increment/decrement steps
95  */
96 void ModuleBase_ParamSpinBox::stepBy(int steps)
97 {
98   if (hasVariable())
99     return;
100
101   double aVal = lineEdit()->text().toDouble();
102   aVal += steps * mySingleStep;
103   setValue(aVal);
104   QAbstractSpinBox::stepBy(steps);
105 }
106
107 void ModuleBase_ParamSpinBox::onTextChanged(const QString& theText)
108 {
109   myIsEquation = hasVariable(theText);
110   emit textChanged(theText);
111 }
112
113
114 /*!
115  \brief This function is used to determine whether input is valid.
116  \param str currently entered value
117  \param pos cursor position in the string
118  \return validating operation result
119  */
120 QValidator::State ModuleBase_ParamSpinBox::validate(QString& str, int& pos) const
121 {
122   // Trying to interpret the current input text as a numeric value
123   if (!hasVariable(str)) {
124     /// If decimals = 0 do not accept '.' (interpret as int)
125     if ((myValidator->decimals() == 0) && str.endsWith('.'))
126       return QValidator::Invalid;
127     return myValidator->validate(str, pos);
128   }
129
130   return isAcceptVariables() ? QValidator::Acceptable : QValidator::Invalid;
131 }
132
133 /*!
134  \brief This function is used to set a current value for this spinbox.
135  \param value current value
136
137  The new value is ignored if the spinbox has a variable.
138  */
139 void ModuleBase_ParamSpinBox::setValue(double value)
140 {
141   myIsEquation = false;
142   double aVal = value;
143   if (aVal < myMinimum)
144     aVal = myMinimum;
145   else if (aVal > myMaximum)
146     aVal = myMaximum;
147   QString aText = QString::number(aVal, 'g', decimals());
148   lineEdit()->setText(aText);
149   emit textChanged(aText);
150 }
151
152 double ModuleBase_ParamSpinBox::value() const
153 {
154   std::string aa = lineEdit()->text().toStdString();
155   return lineEdit()->text().toDouble();
156 }
157
158 /*!
159  \brief This function is used to set a text for this spinbox.
160  \param value current value
161  */
162 void ModuleBase_ParamSpinBox::setText(const QString& value)
163 {
164   myIsEquation = hasVariable(value);
165   if (myAcceptVariables && myIsEquation) {
166     lineEdit()->setText(value);
167     emit textChanged(value);
168   }
169 }
170
171 /*!
172  \brief Enables or disables variable names in the spin box.
173  By default, variable names are enabled.
174  \param flag If true, variable names are enabled.
175  */
176 void ModuleBase_ParamSpinBox::setAcceptVariables(const bool flag)
177 {
178   myAcceptVariables = flag;
179   if ((!myAcceptVariables) && myIsEquation) {
180     setValue(0);
181   }
182 }
183
184 /*!
185  \brief Returns true if the spin box accepts variable names.
186  */
187 bool ModuleBase_ParamSpinBox::isAcceptVariables() const
188 {
189   return myAcceptVariables;
190 }
191
192 bool ModuleBase_ParamSpinBox::hasVariable() const
193 {
194   return myIsEquation;
195 }
196
197 bool ModuleBase_ParamSpinBox::hasVariable(const QString& theText) const
198 {
199   bool isDouble = false;
200   QLocale::c().toDouble(theText, &isDouble);
201   return !isDouble;
202 }
203
204 void ModuleBase_ParamSpinBox::keyReleaseEvent(QKeyEvent* e)
205 {
206   switch (e->key()) {
207   case Qt::Key_Return:
208   case Qt::Key_Enter:
209   {
210     if (myCompleter->popup()->isVisible()) {
211       myCompleter->popup()->hide();
212       myIsEquation = true;
213     }
214     emit textChanged(lineEdit()->text());
215     return;
216   }
217   case Qt::Key_Space:
218     if (e->modifiers() & Qt::ControlModifier) {
219       myCompletePos = lineEdit()->cursorPosition();
220       int aStart, aEnd;
221       QString aPrefix = getPrefix(aStart, aEnd);
222       myCompleter->setCompletionPrefix(aPrefix);
223       myCompleter->complete();
224     }
225     break;
226   default:
227     QAbstractSpinBox::keyReleaseEvent(e);
228   }
229 }
230
231
232 QString ModuleBase_ParamSpinBox::getPrefix(int& theStart, int& theEnd) const
233 {
234   QString aPrefix;
235   QString aText = lineEdit()->text();
236   theStart = theEnd = myCompletePos;
237   const int aLen = aText.length();
238   if (aLen > 0) {
239     if (myCompletePos > 0) {
240       int aLastChar = myCompletePos - 1;
241       QChar aChar = aText.at(aLastChar);
242       while (aChar.isLetter() || aChar.isDigit()) {
243         aPrefix.prepend(aText.at(aLastChar));
244         aLastChar--;
245         if (aLastChar < 0)
246           break;
247         aChar = aText.at(aLastChar);
248       }
249       theStart = aLastChar + 1;
250     }
251     if (myCompletePos < aLen) {
252       int aLastChar = myCompletePos;
253       QChar aChar = aText.at(aLastChar);
254       while (aChar.isLetter() || aChar.isDigit()) {
255         aPrefix.append(aText.at(aLastChar));
256         aLastChar++;
257         if (aLastChar >= aLen)
258           break;
259         aChar = aText.at(aLastChar);
260       }
261       theEnd = aLastChar;
262     }
263   }
264   return aPrefix;
265 }
266
267
268 void ModuleBase_ParamSpinBox::insertCompletion(const QString& theText)
269 {
270   QString aText = lineEdit()->text();
271   int aStart, aEnd;
272   QString aPrefix = getPrefix(aStart, aEnd);
273
274   QString aResult;
275   int aPrefLen = aPrefix.length();
276   if (aPrefLen == 0)
277     aResult = aText.insert(myCompletePos, theText);
278   else {
279     aResult = aText.left(aStart) + theText + aText.right(aText.length() - aEnd);
280   }
281   lineEdit()->setText(aResult);
282   myIsEquation = true;
283 }
284
285
286 void ModuleBase_ParamSpinBox::setValueEnabled(bool theEnable)
287 {
288   setReadOnly(!theEnable);
289
290   QPalette aPal = palette();
291   aPal.setColor(QPalette::All, QPalette::Base,
292     theEnable ? myEnabledBaseColor : aPal.color(QPalette::Disabled, QPalette::Base));
293   setPalette(aPal);
294 }