Salome HOME
Issue #2533: Send signal on modified int value
[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
39 bool isVariableSymbol(const QChar& theChar) {
40   if (theChar.isLetterOrNumber())
41     return true;
42   if (theChar == '_')
43     return true;
44   return false;
45 }
46
47 ModuleBase_ParamSpinBox::ModuleBase_ParamSpinBox(QWidget* theParent, int thePrecision)
48   : QAbstractSpinBox(theParent),
49   myPrecision(thePrecision),
50   myIsEquation(false),
51   myAcceptVariables(true),
52   mySingleStep(1),
53   myMinimum(DBL_MIN),
54   myMaximum(DBL_MAX)
55 {
56   myCompleter = new QCompleter(this);
57   myCompleter->setWidget(lineEdit());
58   myCompleter->setCompletionMode(QCompleter::PopupCompletion);
59
60   myCompleterModel = new QStringListModel(this);
61   myCompleter->setModel(myCompleterModel);
62   connect(myCompleter, SIGNAL(highlighted(const QString&)),
63     this, SLOT(insertCompletion(const QString&)));
64
65   QAbstractItemView* aPopup = myCompleter->popup();
66   aPopup->installEventFilter(this);
67
68   //  connectSignalsAndSlots();
69   myEnabledBaseColor = palette().color(QPalette::Active, QPalette::Base);
70   connect(lineEdit(), SIGNAL(textChanged(const QString&)),
71     this, SLOT(onTextChanged(const QString&)));
72
73   setLocale(QLocale::c());
74
75   myValidator = new QDoubleValidator(this);
76   myValidator->setLocale(locale());
77   myValidator->setRange(myMinimum, myMaximum);
78   myValidator->setDecimals(myPrecision);
79 }
80
81 void ModuleBase_ParamSpinBox::setCompletionList(QStringList& theList)
82 {
83   theList.removeDuplicates();
84   theList.sort();
85   myCompleterModel->setStringList(theList);
86 }
87
88 /*!
89  \brief Destructor.
90  */
91 ModuleBase_ParamSpinBox::~ModuleBase_ParamSpinBox()
92 {
93 }
94
95
96 /*!
97  \brief Perform \a steps increment/decrement steps.
98
99  Re-implemented to handle cases when Notebook variable
100  name is specified by the user as the widget text.
101  Otherwise, simply calls the base implementation.
102
103  \param steps number of increment/decrement steps
104  */
105 void ModuleBase_ParamSpinBox::stepBy(int steps)
106 {
107   if (hasVariable())
108     return;
109
110   double aVal = lineEdit()->text().toDouble();
111   aVal += steps * mySingleStep;
112   setValue(aVal);
113   //QAbstractSpinBox::stepBy(steps);
114 }
115
116 void ModuleBase_ParamSpinBox::onTextChanged(const QString& theText)
117 {
118   myIsEquation = hasVariable(theText);
119   emit textChanged(theText);
120 }
121
122
123 /*!
124  \brief This function is used to determine whether input is valid.
125  \param str currently entered value
126  \param pos cursor position in the string
127  \return validating operation result
128  */
129 QValidator::State ModuleBase_ParamSpinBox::validate(QString& str, int& pos) const
130 {
131   // Trying to interpret the current input text as a numeric value
132   if (!hasVariable(str)) {
133     /// If decimals = 0 do not accept '.' (interpret as int)
134     if ((myValidator->decimals() == 0) && str.endsWith('.'))
135       return QValidator::Invalid;
136     return myValidator->validate(str, pos);
137   }
138
139   return isAcceptVariables() ? QValidator::Acceptable : QValidator::Invalid;
140 }
141
142 /*!
143  \brief This function is used to set a current value for this spinbox.
144  \param value current value
145
146  The new value is ignored if the spinbox has a variable.
147  */
148 void ModuleBase_ParamSpinBox::setValue(double value)
149 {
150   myIsEquation = false;
151   double aVal = value;
152   if (aVal < myMinimum)
153     aVal = myMinimum;
154   else if (aVal > myMaximum)
155     aVal = myMaximum;
156   QString aText = QString::number(aVal, 'g', decimals());
157   lineEdit()->blockSignals(true);
158   lineEdit()->setText(aText);
159   lineEdit()->blockSignals(false);
160   emit textChanged(aText);
161 }
162
163 double ModuleBase_ParamSpinBox::value() const
164 {
165   return lineEdit()->text().toDouble();
166 }
167
168 /*!
169  \brief This function is used to set a text for this spinbox.
170  \param value current value
171  */
172 void ModuleBase_ParamSpinBox::setText(const QString& value)
173 {
174   myIsEquation = hasVariable(value);
175   if (myAcceptVariables && myIsEquation) {
176     lineEdit()->setText(value);
177     emit textChanged(value);
178   }
179 }
180
181 /*!
182  \brief Enables or disables variable names in the spin box.
183  By default, variable names are enabled.
184  \param flag If true, variable names are enabled.
185  */
186 void ModuleBase_ParamSpinBox::setAcceptVariables(const bool flag)
187 {
188   myAcceptVariables = flag;
189   if ((!myAcceptVariables) && myIsEquation) {
190     setValue(0);
191   }
192 }
193
194 /*!
195  \brief Returns true if the spin box accepts variable names.
196  */
197 bool ModuleBase_ParamSpinBox::isAcceptVariables() const
198 {
199   return myAcceptVariables;
200 }
201
202 bool ModuleBase_ParamSpinBox::hasVariable() const
203 {
204   return myIsEquation;
205 }
206
207 bool ModuleBase_ParamSpinBox::hasVariable(const QString& theText) const
208 {
209   bool isDouble = false;
210   QLocale::c().toDouble(theText, &isDouble);
211   return !isDouble;
212 }
213
214 void ModuleBase_ParamSpinBox::showCompletion(bool checkPrefix)
215 {
216   myCompletePos = lineEdit()->cursorPosition();
217   int aStart, aEnd;
218   QString aPrefix;
219   aPrefix = getPrefix(aStart, aEnd);
220   if (checkPrefix) {
221     if (aPrefix.length() > 0) {
222       myCompleter->setCompletionPrefix(aPrefix);
223       myCompleter->complete();
224     }
225   } else {
226     myCompleter->setCompletionPrefix(aPrefix);
227     myCompleter->complete();
228   }
229 }
230
231 void ModuleBase_ParamSpinBox::keyReleaseEvent(QKeyEvent* e)
232 {
233   QString aText;
234
235   switch (e->key()) {
236   case Qt::Key_Backspace:
237     if (myCompleter->popup()->isVisible()) {
238       myCompleter->popup()->hide();
239     }
240     showCompletion(true);
241     break;
242   case Qt::Key_Return:
243   case Qt::Key_Enter:
244     if (myCompleter->popup()->isVisible()) {
245       myCompleter->popup()->hide();
246       myIsEquation = true;
247     }
248     emit textChanged(lineEdit()->text());
249     break;
250   case Qt::Key_Space:
251     if (e->modifiers() & Qt::ControlModifier) {
252       showCompletion(false);
253     }
254     break;  default:
255     aText = e->text();
256     if (aText.length() == 1) {
257       QChar aChar = aText.at(0);
258       if (isVariableSymbol(aChar)) {
259         showCompletion(true);
260       }
261     }
262   }
263   QAbstractSpinBox::keyReleaseEvent(e);
264 }
265
266 bool ModuleBase_ParamSpinBox::eventFilter(QObject* theObj, QEvent* theEvent)
267 {
268   if (theEvent->type() == QEvent::KeyRelease) {
269     keyReleaseEvent((QKeyEvent*)theEvent);
270   }
271   return QAbstractSpinBox::eventFilter(theObj, theEvent);
272 }
273
274
275 QString ModuleBase_ParamSpinBox::getPrefix(int& theStart, int& theEnd) const
276 {
277   QString aPrefix;
278   QString aText = lineEdit()->text();
279   theStart = theEnd = myCompletePos;
280   const int aLen = aText.length();
281   if (aLen > 0) {
282     if (myCompletePos > 0) {
283       int aLastChar = myCompletePos - 1;
284       QChar aChar = aText.at(aLastChar);
285       while (isVariableSymbol(aChar)) {
286         aPrefix.prepend(aText.at(aLastChar));
287         aLastChar--;
288         if (aLastChar < 0)
289           break;
290         aChar = aText.at(aLastChar);
291       }
292       theStart = aLastChar + 1;
293     }
294     if (myCompletePos < aLen) {
295       int aLastChar = myCompletePos;
296       QChar aChar = aText.at(aLastChar);
297       while (isVariableSymbol(aChar)) {
298         aPrefix.append(aText.at(aLastChar));
299         aLastChar++;
300         if (aLastChar >= aLen)
301           break;
302         aChar = aText.at(aLastChar);
303       }
304       theEnd = aLastChar;
305     }
306   }
307   return aPrefix;
308 }
309
310
311 void ModuleBase_ParamSpinBox::insertCompletion(const QString& theText)
312 {
313   QString aText = lineEdit()->text();
314   int aStart, aEnd;
315   QString aPrefix = getPrefix(aStart, aEnd);
316
317   QString aResult;
318   int aPrefLen = aPrefix.length();
319   if (aPrefLen == 0)
320     aResult = aText.insert(myCompletePos, theText);
321   else {
322     aResult = aText.left(aStart) + theText + aText.right(aText.length() - aEnd);
323   }
324   lineEdit()->setText(aResult);
325   myIsEquation = true;
326 }
327
328
329 void ModuleBase_ParamSpinBox::setValueEnabled(bool theEnable)
330 {
331   setReadOnly(!theEnable);
332
333   QPalette aPal = palette();
334   aPal.setColor(QPalette::All, QPalette::Base,
335     theEnable ? myEnabledBaseColor : aPal.color(QPalette::Disabled, QPalette::Base));
336   setPalette(aPal);
337 }