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