Salome HOME
4438684664dc273d3b71b17d9c12aaaed609cfd8
[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 <ModelAPI_Session.h>
24 #include <ModelAPI_Document.h>
25 #include <ModelAPI_Feature.h>
26 #include <ModelAPI_ResultParameter.h>
27 #include <ModelAPI_AttributeDouble.h>
28 #include <ModelAPI_Tools.h>
29
30 #include <QKeyEvent>
31 #include <QLocale>
32 #include <QRegExp>
33 #include <QToolTip>
34 #include <QApplication>
35
36 #include <QStringListModel>
37 #include <QCompleter>
38 #include <QAbstractItemView>
39 #include <QShortcut>
40
41 #include <string>
42 #include <iostream>
43
44 ModuleBase_ParamSpinBox::ModuleBase_ParamSpinBox(QWidget* theParent, int thePrecision)
45   : QAbstractSpinBox(theParent),
46   myPrecision(thePrecision),
47   myIsEquation(false),
48   myAcceptVariables(true),
49   myDecimals(3),
50   mySingleStep(1),
51   myMinimum(DBL_MIN),
52   myMaximum(DBL_MAX)
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   // Use sorted model to accelerate completion (QCompleter will use binary search)
61   myCompleter->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
62   myCompleter->setCaseSensitivity(Qt::CaseInsensitive);
63   connect(myCompleter, SIGNAL(highlighted(const QString&)),
64     this, SLOT(insertCompletion(const QString&)));
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   myValidator = new QDoubleValidator(this);
72   myValidator->setLocale(locale());
73   myValidator->setRange(myMinimum, myMaximum);
74 }
75
76 void ModuleBase_ParamSpinBox::setCompletionList(QStringList& theList)
77 {
78   theList.sort();
79   theList.removeDuplicates();
80   myCompleterModel->setStringList(theList);
81 }
82
83 /*!
84  \brief Destructor.
85  */
86 ModuleBase_ParamSpinBox::~ModuleBase_ParamSpinBox()
87 {
88 }
89
90 /*!
91  \brief Perform \a steps increment/decrement steps.
92
93  Re-implemented to handle cases when Notebook variable
94  name is specified by the user as the widget text.
95  Otherwise, simply calls the base implementation.
96
97  \param steps number of increment/decrement steps
98  */
99 void ModuleBase_ParamSpinBox::stepBy(int steps)
100 {
101   if (hasVariable())
102     return;
103
104   double aVal = lineEdit()->text().toDouble();
105   aVal += steps * mySingleStep;
106   setValue(aVal);
107   QAbstractSpinBox::stepBy(steps);
108 }
109
110 void ModuleBase_ParamSpinBox::onTextChanged(const QString& theText)
111 {
112   myIsEquation = hasVariable(theText);
113   emit textChanged(theText);
114 }
115
116
117 ///*!
118 // \brief Connect signals and slots.
119 // */
120 //void ModuleBase_ParamSpinBox::connectSignalsAndSlots()
121 //{
122 //  connect(this, SIGNAL(valueChanged(const QString&)),
123 //          this, SLOT(onTextChanged(const QString&)));
124 //}
125 //
126 //void ModuleBase_ParamSpinBox::onTextChanged(const QString& text)
127 //{
128 //  myTextValue = text;
129 //  emit textChanged(text);
130 //}
131 //
132 //double ModuleBase_ParamSpinBox::valueFromText(const QString& theText) const
133 //{
134 //  if (!hasVariable(theText))
135 //    return ModuleBase_DoubleSpinBox::valueFromText(theText);
136 //
137 //  // small hack: return hash of the string to initiate valuesChanged signal
138 //  return qHash(theText);
139 //}
140 //
141 //QString ModuleBase_ParamSpinBox::textFromValue (double theValue) const
142 //{
143 //  if ((!myTextValue.isEmpty()) && hasVariable(myTextValue)){
144 //    return myTextValue;
145 //  }
146 //  return ModuleBase_DoubleSpinBox::textFromValue(theValue);
147 //}
148
149 /*!
150  \brief This function is used to determine whether input is valid.
151  \param str currently entered value
152  \param pos cursor position in the string
153  \return validating operation result
154  */
155 QValidator::State ModuleBase_ParamSpinBox::validate(QString& str, int& pos) const
156 {
157   // Trying to interpret the current input text as a numeric value
158   if (!hasVariable(str))
159     return myValidator->validate(str, pos);
160
161   return isAcceptVariables() ? QValidator::Acceptable : QValidator::Invalid;
162 }
163
164 /*!
165  \brief This function is used to set a current value for this spinbox.
166  \param value current value
167
168  The new value is ignored if the spinbox has a variable.
169  */
170 void ModuleBase_ParamSpinBox::setValue(double value)
171 {
172   myIsEquation = false;
173   double aVal = value;
174   if (aVal < myMinimum)
175     aVal = myMinimum;
176   else if (aVal > myMaximum)
177     aVal = myMaximum;
178   QString aText = QString::number(aVal, 'g', myDecimals);
179   lineEdit()->setText(aText);
180   emit textChanged(aText);
181 }
182
183 double ModuleBase_ParamSpinBox::value() const
184 {
185   //if (myIsEquation) {
186
187   //}
188   return lineEdit()->text().toDouble();
189 }
190
191 /*!
192  \brief This function is used to set a text for this spinbox.
193  \param value current value
194  */
195 void ModuleBase_ParamSpinBox::setText(const QString& value)
196 {
197   myIsEquation = hasVariable(value);
198   if (myAcceptVariables && myIsEquation) {
199     lineEdit()->setText(value);
200     emit textChanged(value);
201   }
202 }
203
204 /*!
205  \brief Enables or disables variable names in the spin box.
206  By default, variable names are enabled.
207  \param flag If true, variable names are enabled.
208  */
209 void ModuleBase_ParamSpinBox::setAcceptVariables(const bool flag)
210 {
211   myAcceptVariables = flag;
212   if ((!myAcceptVariables) && myIsEquation) {
213     setValue(0);
214   }
215 }
216
217 /*!
218  \brief Returns true if the spin box accepts variable names.
219  */
220 bool ModuleBase_ParamSpinBox::isAcceptVariables() const
221 {
222   return myAcceptVariables;
223 }
224
225 bool ModuleBase_ParamSpinBox::hasVariable() const
226 {
227   return myIsEquation;
228 }
229
230 bool ModuleBase_ParamSpinBox::hasVariable(const QString& theText) const
231 {
232   bool isDouble = false;
233   QLocale::c().toDouble(theText, &isDouble);
234   return !isDouble;
235 }
236
237 ///*!
238 // \brief This function is used to determine whether input is valid.
239 // \return validating operation result
240 // */
241 //ModuleBase_ParamSpinBox::State ModuleBase_ParamSpinBox::isValid(const QString& theText,
242 //                                                                double& theValue) const
243 //{
244 //  if (hasVariable() && !findVariable(theText, theValue)) {
245 //    bool ok = false;
246 //    theValue = locale().toDouble(theText, &ok);
247 //    if (!ok) {
248 //      return NoVariable;
249 //    }
250 //  }
251 //  if (!checkRange(theValue)) {
252 //    return Invalid;
253 //  }
254 //
255 //  return Acceptable;
256 //}
257 //
258 ///*!
259 // \brief This function is used to check that string value lies within predefined range.
260 // \return check status
261 // */
262 //bool ModuleBase_ParamSpinBox::checkRange(const double theValue) const
263 //{
264 //  return theValue >= minimum() && theValue <= maximum();
265 //}
266 //
267 ///*!
268 // \brief This function is used to determine whether input is a variable name and to get its value.
269 // \return status of search operation
270 // */
271 //bool ModuleBase_ParamSpinBox::findVariable(const QString& theName,
272 //                                           double& outValue) const
273 //{
274 //  ResultParameterPtr aParam;
275 //  return ModelAPI_Tools::findVariable(FeaturePtr(), theName.toStdString(), outValue, aParam);
276 //}
277
278 /*!
279  \brief This function is called when the spinbox receives key press event.
280  */
281 void ModuleBase_ParamSpinBox::keyReleaseEvent(QKeyEvent* e)
282 {
283   switch (e->key()) {
284   case Qt::Key_Return:
285   case Qt::Key_Enter:
286     if (myCompleter->popup()->isVisible()) {
287       myCompleter->popup()->hide();
288       myIsEquation = true;
289     }
290     emit textChanged(lineEdit()->text());
291     return;
292   case Qt::Key_Space:
293     if (e->modifiers() & Qt::ControlModifier) {
294       myCompletePos = lineEdit()->cursorPosition();
295       int aStart, aEnd;
296       QString aPrefix = getPrefix(aStart, aEnd);
297       myCompleter->setCompletionPrefix(aPrefix);
298       myCompleter->complete();
299     }
300     break;
301   default:
302     QAbstractSpinBox::keyReleaseEvent(e);
303   }
304 }
305
306 QString ModuleBase_ParamSpinBox::getPrefix(int& theStart, int& theEnd) const
307 {
308   QString aPrefix;
309   QString aText = lineEdit()->text();
310   theStart = theEnd = myCompletePos;
311   const int aLen = aText.length();
312   if (aLen > 0) {
313     if (myCompletePos > 0) {
314       int aLastChar = myCompletePos - 1;
315       QChar aChar = aText.at(aLastChar);
316       while (aChar.isLetter() || aChar.isDigit()) {
317         aPrefix.prepend(aText.at(aLastChar));
318         aLastChar--;
319         if (aLastChar < 0)
320           break;
321         aChar = aText.at(aLastChar);
322       }
323       theStart = aLastChar + 1;
324     }
325     if (myCompletePos < aLen) {
326       int aLastChar = myCompletePos;
327       QChar aChar = aText.at(aLastChar);
328       while (aChar.isLetter() || aChar.isDigit()) {
329         aPrefix.append(aText.at(aLastChar));
330         aLastChar++;
331         if (aLastChar >= aLen)
332           break;
333         aChar = aText.at(aLastChar);
334       }
335       theEnd = aLastChar;
336     }
337   }
338   return aPrefix;
339 }
340
341
342 void ModuleBase_ParamSpinBox::insertCompletion(const QString& theText)
343 {
344   QString aText = lineEdit()->text();
345   int aStart, aEnd;
346   QString aPrefix = getPrefix(aStart, aEnd);
347
348   QString aResult;
349   int aPrefLen = aPrefix.length();
350   if (aPrefLen == 0)
351     aResult = aText.insert(myCompletePos, theText);
352   else {
353     aResult = aText.left(aStart) + theText + aText.right(aText.length() - aEnd);
354   }
355   lineEdit()->setText(aResult);
356   myIsEquation = true;
357
358   qDebug("### aPos=%i", myCompletePos);
359   qDebug("### text=%s", qPrintable(aText));
360   qDebug("### prefix=%s", qPrintable(aPrefix));
361   qDebug("### result=%s", qPrintable(aResult));
362 }
363
364
365 ///*!
366 // \brief This function is called when the spinbox receives show event.
367 // */
368 //void ModuleBase_ParamSpinBox::showEvent(QShowEvent* theEvent)
369 //{
370 //  ModuleBase_DoubleSpinBox::showEvent(theEvent);
371 //  if ((!myTextValue.isEmpty()) && hasVariable(myTextValue)) {
372 //    setText(myTextValue);
373 //  }
374 //}
375
376 void ModuleBase_ParamSpinBox::setValueEnabled(bool theEnable)
377 {
378   setReadOnly(!theEnable);
379
380   QPalette aPal = palette();
381   aPal.setColor(QPalette::All, QPalette::Base,
382     theEnable ? myEnabledBaseColor : aPal.color(QPalette::Disabled, QPalette::Base));
383   setPalette(aPal);
384 }