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