!XT (BREAK-16) (Sandbox) Remove double-newlines at the end of files.
[CRYENGINE.git] / Code / Sandbox / Plugins / EditorCommon / Controls / QNumericBox.cpp
blobdc343eadc8d56cce7de2f560126cf719f2272629
1 // Copyright 2001-2018 Crytek GmbH / Crytek Group. All rights reserved.
2 #include <StdAfx.h>
3 #include "QNumericBox.h"
5 #include <BoostPythonMacros.h>
7 #include <QLineEdit>
8 #include <QVBoxLayout>
9 #include <QHBoxLayout>
10 #include <QPushButton>
11 #include <QPainter>
12 #include <QProxyStyle>
13 #include <QFocusEvent>
14 #include <QApplication>
15 #include <QStyleOption>
16 #include <CryIcon.h>
18 class QNumericEditProxyStyle : public QProxyStyle
20 public:
21 QNumericEditProxyStyle() : m_cursorWidth(0) {}
22 virtual ~QNumericEditProxyStyle() override {}
24 void setCursorWidth(int width) { m_cursorWidth = width; }
26 virtual int pixelMetric(PixelMetric metric, const QStyleOption* option = 0, const QWidget* widget = 0) const
28 if (metric == QStyle::PM_TextCursorWidth)
30 return m_cursorWidth;
33 return QProxyStyle::pixelMetric(metric, option, widget);
36 private:
37 int m_cursorWidth;
40 class QNumericEdit : public QLineEdit
42 public:
43 QNumericEdit(QWidget* parent)
44 : QLineEdit(parent)
45 , m_lastCursor(-1)
46 , m_editMode(false)
47 , m_selectAllFromMouse(true)
48 , m_editStyle(new QNumericEditProxyStyle())
49 , m_mouseMoved(false)
51 setStyleSheet("background-color: transparent");
52 setEditMode(false);
53 setStyle(m_editStyle);
54 setText("0");
57 inline QString text() { return QLineEdit::text().replace(',', '.'); }
59 virtual ~QNumericEdit() override {}
61 virtual void mouseDoubleClickEvent(QMouseEvent* e) override
63 selectAll();
66 virtual void focusInEvent(QFocusEvent* e) override
68 m_selectAllFromMouse = false;
69 if (e->reason() == Qt::TabFocusReason ||
70 e->reason() == Qt::BacktabFocusReason)
72 setEditMode(true);
75 QLineEdit::focusInEvent(e);
78 virtual void focusOutEvent(QFocusEvent* e) override
80 if (e->reason() != Qt::PopupFocusReason)
82 setEditMode(false);
85 QLineEdit::focusOutEvent(e);
88 virtual void mousePressEvent(QMouseEvent* e) override
90 if (!getEditMode() && e->button() == Qt::LeftButton)
92 m_mouseDownLocation = QCursor::pos();
93 m_mouseMoved = false;
94 setCursor(Qt::BlankCursor);
96 else
98 if (m_selectAllFromMouse)
100 m_selectAllFromMouse = false;
101 selectAll();
103 else
105 QLineEdit::mousePressEvent(e);
110 virtual void mouseMoveEvent(QMouseEvent* e) override
112 if (!getEditMode() && QApplication::mouseButtons() == Qt::LeftButton)
114 QNumericBox* numbox = qobject_cast<QNumericBox*>(parentWidget());
115 QPoint mouse = QCursor::pos();
116 if (numbox && mouse != m_mouseDownLocation)
118 m_mouseMoved = true;
119 double add = 0.0;
120 if (numbox->hasMinMax())
122 add = (numbox->maximum() - numbox->minimum()) * double((mouse.x() - m_mouseDownLocation.x())) / double(numbox->width());
124 else
126 add = numbox->singleStep() * double((mouse.x() - m_mouseDownLocation.x()));
129 if (Qt::ControlModifier == QApplication::keyboardModifiers())
131 add *= 0.01;
133 else if (Qt::ShiftModifier == QApplication::keyboardModifiers())
135 add *= 100.0;
138 numbox->setValue(numbox->value() + add);
139 QCursor::setPos(m_mouseDownLocation);
142 else
144 QLineEdit::mouseMoveEvent(e);
148 virtual void mouseReleaseEvent(QMouseEvent* e) override
150 releaseMouse();
151 if (!getEditMode() && e->button() == Qt::LeftButton)
153 setCursor(Qt::ArrowCursor);
155 if (!m_mouseMoved)
157 setEditMode(true);
158 selectAllFromMouse();
159 QMouseEvent pe(QEvent::MouseButtonPress, e->pos(), e->button(), e->buttons(), e->modifiers());
160 QMouseEvent re(QEvent::MouseButtonRelease, e->pos(), e->button(), e->buttons(), e->modifiers());
161 QApplication::sendEvent(this, &pe);
162 QApplication::sendEvent(this, &re);
165 else
167 QLineEdit::mouseReleaseEvent(e);
171 virtual bool eventFilter(QObject* object, QEvent* e) override
173 if (object != this && e->type() == QEvent::MouseButtonPress)
175 QWidget* widget = qobject_cast<QWidget*>(object);
176 if (widget && widget->parentWidget() != this)
178 QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(e);
179 QNumericBox* numbox = qobject_cast<QNumericBox*>(parentWidget());
180 if (numbox && mouseEvent->buttons() == Qt::LeftButton)
182 QPoint numboxPoint = numbox->mapFromGlobal(widget->mapToGlobal(mouseEvent->pos()));
183 QRect numboxRect = numbox->rect();
184 if (!numboxRect.contains(numboxPoint))
186 setEditMode(false);
192 return false;
195 void setEditMode(bool edit)
197 m_editMode = edit;
198 m_editStyle->setCursorWidth(edit ? 1 : 0);
199 setCursor(edit ? Qt::IBeamCursor : Qt::ArrowCursor);
201 QApplication::instance()->removeEventFilter(this);
202 if (edit)
204 QApplication::instance()->installEventFilter(this);
206 else
208 deselect();
211 update();
214 bool getEditMode() { return m_editMode; }
216 void selectAllFromMouse() { m_selectAllFromMouse = true; }
218 void step(double multiplier)
220 QByteArray expression = text().toUtf8();
221 int revCursor = text().length() - cursorPosition();
222 int cursor = stepCursor();
224 if (-1 != cursor)
226 int digitStart = cursor;
227 int digitLen = 1;
228 bool valid = true;
230 for (int i = 0; i < digitStart; ++i)
232 if (' ' == text().at(i))
234 continue;
237 QString strValue = text().mid(i, 1 + digitStart - i);
238 double value = strValue.toDouble(&valid);
239 double avalue = atof(strValue.toUtf8().constData());
241 if (valid && avalue == value)
243 digitLen = 1 + digitStart - i;
244 digitStart = i;
245 break;
249 QString numString = text().mid(digitStart, digitLen);
250 QString addString;
251 int digitEnd = digitStart + digitLen;
253 for (QChar c : numString)
255 addString.append(isxdigit(c.toLatin1()) ? '0' : c);
258 QByteArray tmpAddStr = addString.toUtf8();
259 tmpAddStr[tmpAddStr.length() - 1] = '1';
260 addString = QString(tmpAddStr);
262 double result = numString.toDouble();
263 double add = addString.toDouble();
265 if (add < 0.0f)
267 add = -add;
270 result += add * multiplier;
272 int decPoint = numString.indexOf('.');
273 int precision = numString.length() - (decPoint + 1);
274 QString outNumber = -1 != decPoint ? QString::number(result, 'f', precision) : QString::number(result, 'f', 0);
275 QString outText = text().mid(0, digitStart) + outNumber + text().mid(digitEnd);
276 setText(outText);
277 setCursorPosition(text().length() - revCursor);
281 virtual void keyPressEvent(QKeyEvent* e) override
283 if (e->key() == Qt::Key_Escape ||
284 e->key() == Qt::Key_Enter ||
285 e->key() == Qt::Key_Return)
287 QLineEdit::editingFinished();
288 if (hasFocus())
290 selectAll();
293 //Lets the keypress go to parents, i.e. the parent dialog may close on Enter/ESC
294 //Note that this behavior can be observed on QLineEdit
295 e->ignore();
296 return;
299 if (e->key() == Qt::Key_Up ||
300 e->key() == Qt::Key_Down)
302 step(e->key() == Qt::Key_Up ? 1.0f : -1.0f);
303 QNumericBox* numbox = qobject_cast<QNumericBox*>(parentWidget());
304 if (numbox)
306 numbox->valueChanged(numbox->value());
309 else if (getEditMode())
311 QLineEdit::keyPressEvent(e);
315 int stepCursor()
317 if (hasSelectedText() || !hasFocus() || !getEditMode())
319 return -1;
322 int cursor = cursorPosition() - 1;
323 if (cursor >= 0 && cursor < text().length())
325 char currentChar = text().at(cursor).toLatin1();
326 if (isdigit(currentChar))
328 return cursor;
331 return -1;
334 QRect stepCursorRect(int cursor)
336 int currentCursor = cursorPosition();
337 if (m_lastText != text() || m_lastCursor != currentCursor)
339 int start = selectionStart();
340 int length = selectedText().length();
341 setCursorPosition(cursor);
342 m_stepRect = cursorRect();
343 setCursorPosition(currentCursor);
344 setSelection(start, length);
345 m_lastText = text();
346 m_lastCursor = currentCursor;
348 return m_stepRect;
351 private:
352 inline bool isValidDigit(char c) { return isdigit(c) || '.' == c; }
354 QRect m_stepRect;
355 QString m_lastText;
356 int m_lastCursor;
357 int m_editMode;
358 QNumericEditProxyStyle* m_editStyle;
359 bool m_selectAllFromMouse;
360 bool m_mouseMoved;
361 QPoint m_mouseDownLocation;
364 QNumericButton::QNumericButton(QWidget* parent)
365 : QWidget(parent)
366 , m_spinClickTimerId(-1)
367 , m_spinClickTimerInterval(100)
368 , m_spinClickThresholdTimerId(-1)
369 , m_spinClickThresholdTimerInterval(-1)
370 , m_effectiveSpinRepeatRate(1)
371 , m_acceleration(0)
372 , m_accelerated(false)
374 setStyleSheet("background-color: transparent");
375 setAutoFillBackground(false);
376 setAttribute(Qt::WA_TranslucentBackground);
377 setAttribute(Qt::WA_NoSystemBackground);
380 void QNumericButton::paintEvent(QPaintEvent* e)
382 QPainter painter(this);
383 m_icon.paint(&painter, rect());
386 void QNumericButton::mousePressEvent(QMouseEvent* e)
388 reset();
389 if (Qt::LeftButton == e->button())
391 singleStep();
392 clicked();
393 m_spinClickThresholdTimerId = startTimer(m_spinClickThresholdTimerInterval);
395 e->accept();
398 void QNumericButton::changeEvent(QEvent* e)
400 switch (e->type())
402 case QEvent::StyleChange:
403 m_spinClickTimerInterval = style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatRate, 0, this);
404 m_spinClickThresholdTimerInterval = style()->styleHint(QStyle::SH_SpinBox_ClickAutoRepeatThreshold, 0, this);
405 reset();
406 break;
408 case QEvent::EnabledChange:
409 if (!isEnabled())
411 reset();
413 break;
415 case QEvent::ActivationChange:
416 if (!isActiveWindow())
418 reset();
420 break;
422 default:
423 break;
426 QWidget::changeEvent(e);
429 void QNumericButton::mouseReleaseEvent(QMouseEvent* e)
431 reset();
432 e->accept();
434 if (Qt::LeftButton == e->button())
436 clicked();
440 void QNumericButton::focusOutEvent(QFocusEvent* e)
442 reset();
443 QWidget::focusOutEvent(e);
446 void QNumericButton::timerEvent(QTimerEvent* e)
448 if (e->timerId() == m_spinClickThresholdTimerId)
450 killTimer(m_spinClickThresholdTimerId);
451 m_spinClickThresholdTimerId = -1;
452 m_effectiveSpinRepeatRate = m_spinClickTimerInterval;
453 m_spinClickTimerId = startTimer(m_effectiveSpinRepeatRate);
454 singleStep();
456 else if (e->timerId() == m_spinClickTimerId)
458 if (m_accelerated)
460 m_acceleration = m_acceleration + (int)(m_effectiveSpinRepeatRate * 0.05);
461 if (m_effectiveSpinRepeatRate - m_acceleration >= 10)
463 killTimer(m_spinClickTimerId);
464 m_spinClickTimerId = startTimer(m_effectiveSpinRepeatRate - m_acceleration);
467 singleStep();
470 QWidget::timerEvent(e);
473 void QNumericButton::reset()
475 if (m_spinClickTimerId != -1)
477 killTimer(m_spinClickTimerId);
479 if (m_spinClickThresholdTimerId != -1)
481 killTimer(m_spinClickThresholdTimerId);
483 m_spinClickTimerId = m_spinClickThresholdTimerId = -1;
484 m_acceleration = 0;
487 QNumericBox::QNumericBox(QWidget* parent)
488 : QWidget(parent)
489 , m_min(-DBL_MAX)
490 , m_max(DBL_MAX)
491 , m_step(0.1)
492 , m_value(0)
493 , m_precision(6)
495 m_pEdit = new QNumericEdit(this);
497 connect(m_pEdit, &QLineEdit::editingFinished, this, &QNumericBox::textEdited);
499 QHBoxLayout* pHLayout = new QHBoxLayout(this);
500 pHLayout->addWidget(m_pEdit);
501 pHLayout->setContentsMargins(0, 0, 0, 0);
502 pHLayout->setSpacing(0);
504 QVBoxLayout* pVLayout = new QVBoxLayout();
505 pHLayout->addLayout(pVLayout);
507 m_pUpButton = new QNumericButton();
508 m_pUpButton->setMaximumSize(QSize(20, 10));
509 m_pUpButton->setMinimumSize(QSize(20, 10));
510 m_pUpButton->setIcon(CryIcon("icons:General/Pointer_Up_Numeric.ico"));
511 connect(m_pUpButton, &QNumericButton::singleStep, [=]() { step(1.0); });
512 connect(m_pUpButton, &QNumericButton::clicked, [=]() { valueSubmitted(value()); });
514 m_pDownButton = new QNumericButton();
515 m_pDownButton->setMaximumSize(QSize(20, 10));
516 m_pDownButton->setMinimumSize(QSize(20, 10));
517 m_pDownButton->setIcon(CryIcon("icons:General/Pointer_Down_Numeric.ico"));
518 connect(m_pDownButton, &QNumericButton::clicked, [=]() { step(-1.0); });
519 connect(m_pDownButton, &QNumericButton::clicked, [=]() { valueSubmitted(value()); });
521 pVLayout->addWidget(m_pUpButton);
522 pVLayout->addWidget(m_pDownButton);
523 pVLayout->setContentsMargins(0, 0, 0, 0);
524 pVLayout->setSpacing(0);
526 setLayout(pHLayout);
527 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
530 QNumericBox::~QNumericBox()
532 m_pEdit->removeEventFilter(this);
535 void QNumericBox::setRestrictToInt()
537 setPrecision(0);
538 setSingleStep(1);
541 void QNumericBox::setAlignment(Qt::Alignment alignment)
543 m_pEdit->setAlignment(alignment);
546 void QNumericBox::setAccelerated(bool accelerated)
548 m_pUpButton->setAccelerated(accelerated);
549 m_pDownButton->setAccelerated(accelerated);
552 double QNumericBox::value() const
554 if (!GetIEditor() || !GetIEditor()->GetIPythonManager())
555 return 0.0;
557 if (m_pEdit->text() == "")
559 return m_value;
561 GetIEditor()->GetIPythonManager()->Execute((QString("numeric_expression_result = ") + m_pEdit->text()).toUtf8().constData());
562 double result = (double)GetIEditor()->GetIPythonManager()->GetAsFloat("numeric_expression_result");
563 return result;
566 double QNumericBox::getSliderPosition() const
568 if (!hasMinMax())
570 return 0.0;
573 if (m_pEdit->getEditMode())
575 return 0.0;
578 return clamp_tpl(double(value() - m_min) / (m_max - m_min), 0.0, 1.0);
581 void QNumericBox::setValue(double value)
583 value = clamp_tpl(value, m_min, m_max);
585 QString outText = QString::number(value, 'f', m_precision);
586 int revCursor = m_pEdit->text().length() - m_pEdit->cursorPosition();
587 int decPoint = outText.lastIndexOf('.');
589 if (-1 != decPoint)
591 for (int i = outText.length() - 1; i > decPoint; --i)
593 if ('0' == outText.at(i))
595 outText.remove(i, 1);
597 else
599 break;
604 if ('.' == outText.at(outText.length() - 1))
606 outText.remove(outText.length() - 1, 1);
609 m_value = value;
610 m_pEdit->setText(outText);
611 m_pEdit->setCursorPosition(m_pEdit->text().length() - revCursor);
613 valueChanged(value);
616 void QNumericBox::setPrecision(int precision)
618 if (m_precision != precision)
620 m_precision = precision;
621 setValue(value());
625 void QNumericBox::step(double multiplier)
627 if (Qt::ControlModifier == QApplication::keyboardModifiers())
629 multiplier *= 0.01;
631 else if (Qt::ShiftModifier == QApplication::keyboardModifiers())
633 multiplier *= 100.0;
636 setValue(value() + m_step * multiplier);
639 void QNumericBox::textEdited()
641 double val = value();
642 setValue(val);
643 valueSubmitted(val);
646 bool QNumericBox::hasMinMax() const
648 return -DBL_MAX != m_min && DBL_MAX != m_max;
651 void QNumericBox::paintEvent(QPaintEvent* e)
653 QPainter painter(this);
654 QStyleOptionFrameV2 option;
655 option.state = QStyle::State_Sunken;
656 option.lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, 0);
657 option.midLineWidth = 0;
658 option.features = QStyleOptionFrameV2::None;
659 if (m_pEdit->isEnabled())
661 option.state |= QStyle::State_Enabled;
663 option.rect = rect();
664 option.palette = m_pEdit->palette();
665 option.fontMetrics = m_pEdit->fontMetrics();
667 painter.setPen(QPen(m_pEdit->palette().color(QPalette::WindowText)));
668 painter.setBrush(QBrush(m_pEdit->palette().color(QPalette::Base)));
670 static QLineEdit s_lineEdit;
671 s_lineEdit.style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &painter, &s_lineEdit);
673 double sliderPos = getSliderPosition();
674 if (sliderPos != 0.0)
676 QRect r = rect();
677 QRect sliderOverlayRect(r.left(), r.top(), int(r.width() * sliderPos), r.height());
678 QColor sliderOverlayColor = m_sliderColor;
680 painter.setBrush(QBrush(sliderOverlayColor));
681 painter.setPen(Qt::NoPen);
682 painter.drawRoundedRect(sliderOverlayRect, 2, 2);
685 m_pDownButton->update();
686 m_pUpButton->update();
689 void QNumericBox::setEditMode(bool edit)
691 m_pEdit->setFocus(Qt::TabFocusReason);
694 void QNumericBox::alignText()
696 m_pEdit->home(false);
699 void QNumericBox::setText(const QString& text)
701 m_pEdit->setText(text);
704 QString QNumericBox::text() const
706 return m_pEdit->text();
709 void QNumericBox::setPlaceholderText(const QString& text)
711 m_pEdit->setPlaceholderText(text);
714 void QNumericBox::grabFocus()
716 m_pEdit->setFocus();
719 void QNumericBox::wheelEvent(QWheelEvent* e)
721 step(sgn(e->delta()));
724 void QNumericBox::showEvent(QShowEvent* e)
726 QWidget::showEvent(e);
727 shown();
730 void QNumericBox::resizeEvent(QResizeEvent* event)
732 QWidget::resizeEvent(event);
734 //Hide the buttons if the width is less than 8 characters
735 const bool buttonsVisible = event->size().width() > m_pEdit->fontMetrics().averageCharWidth() * 10;
736 m_pUpButton->setVisible(buttonsVisible);
737 m_pDownButton->setVisible(buttonsVisible);
740 void QNumericBox::focusInEvent(QFocusEvent* e)
742 m_pEdit->setFocus(e->reason());