1 // Copyright 2001-2018 Crytek GmbH / Crytek Group. All rights reserved.
3 #include "QNumericBox.h"
5 #include <BoostPythonMacros.h>
10 #include <QPushButton>
12 #include <QProxyStyle>
13 #include <QFocusEvent>
14 #include <QApplication>
15 #include <QStyleOption>
18 class QNumericEditProxyStyle
: public QProxyStyle
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
)
33 return QProxyStyle::pixelMetric(metric
, option
, widget
);
40 class QNumericEdit
: public QLineEdit
43 QNumericEdit(QWidget
* parent
)
47 , m_selectAllFromMouse(true)
48 , m_editStyle(new QNumericEditProxyStyle())
51 setStyleSheet("background-color: transparent");
53 setStyle(m_editStyle
);
57 inline QString
text() { return QLineEdit::text().replace(',', '.'); }
59 virtual ~QNumericEdit() override
{}
61 virtual void mouseDoubleClickEvent(QMouseEvent
* e
) override
66 virtual void focusInEvent(QFocusEvent
* e
) override
68 m_selectAllFromMouse
= false;
69 if (e
->reason() == Qt::TabFocusReason
||
70 e
->reason() == Qt::BacktabFocusReason
)
75 QLineEdit::focusInEvent(e
);
78 virtual void focusOutEvent(QFocusEvent
* e
) override
80 if (e
->reason() != Qt::PopupFocusReason
)
85 QLineEdit::focusOutEvent(e
);
88 virtual void mousePressEvent(QMouseEvent
* e
) override
90 if (!getEditMode() && e
->button() == Qt::LeftButton
)
92 m_mouseDownLocation
= QCursor::pos();
94 setCursor(Qt::BlankCursor
);
98 if (m_selectAllFromMouse
)
100 m_selectAllFromMouse
= false;
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
)
120 if (numbox
->hasMinMax())
122 add
= (numbox
->maximum() - numbox
->minimum()) * double((mouse
.x() - m_mouseDownLocation
.x())) / double(numbox
->width());
126 add
= numbox
->singleStep() * double((mouse
.x() - m_mouseDownLocation
.x()));
129 if (Qt::ControlModifier
== QApplication::keyboardModifiers())
133 else if (Qt::ShiftModifier
== QApplication::keyboardModifiers())
138 numbox
->setValue(numbox
->value() + add
);
139 QCursor::setPos(m_mouseDownLocation
);
144 QLineEdit::mouseMoveEvent(e
);
148 virtual void mouseReleaseEvent(QMouseEvent
* e
) override
151 if (!getEditMode() && e
->button() == Qt::LeftButton
)
153 setCursor(Qt::ArrowCursor
);
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
);
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
))
195 void setEditMode(bool edit
)
198 m_editStyle
->setCursorWidth(edit
? 1 : 0);
199 setCursor(edit
? Qt::IBeamCursor
: Qt::ArrowCursor
);
201 QApplication::instance()->removeEventFilter(this);
204 QApplication::instance()->installEventFilter(this);
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();
226 int digitStart
= cursor
;
230 for (int i
= 0; i
< digitStart
; ++i
)
232 if (' ' == text().at(i
))
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
;
249 QString numString
= text().mid(digitStart
, digitLen
);
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();
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
);
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();
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
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());
306 numbox
->valueChanged(numbox
->value());
309 else if (getEditMode())
311 QLineEdit::keyPressEvent(e
);
317 if (hasSelectedText() || !hasFocus() || !getEditMode())
322 int cursor
= cursorPosition() - 1;
323 if (cursor
>= 0 && cursor
< text().length())
325 char currentChar
= text().at(cursor
).toLatin1();
326 if (isdigit(currentChar
))
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
);
346 m_lastCursor
= currentCursor
;
352 inline bool isValidDigit(char c
) { return isdigit(c
) || '.' == c
; }
358 QNumericEditProxyStyle
* m_editStyle
;
359 bool m_selectAllFromMouse
;
361 QPoint m_mouseDownLocation
;
364 QNumericButton::QNumericButton(QWidget
* parent
)
366 , m_spinClickTimerId(-1)
367 , m_spinClickTimerInterval(100)
368 , m_spinClickThresholdTimerId(-1)
369 , m_spinClickThresholdTimerInterval(-1)
370 , m_effectiveSpinRepeatRate(1)
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
)
389 if (Qt::LeftButton
== e
->button())
393 m_spinClickThresholdTimerId
= startTimer(m_spinClickThresholdTimerInterval
);
398 void QNumericButton::changeEvent(QEvent
* e
)
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);
408 case QEvent::EnabledChange
:
415 case QEvent::ActivationChange
:
416 if (!isActiveWindow())
426 QWidget::changeEvent(e
);
429 void QNumericButton::mouseReleaseEvent(QMouseEvent
* e
)
434 if (Qt::LeftButton
== e
->button())
440 void QNumericButton::focusOutEvent(QFocusEvent
* e
)
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
);
456 else if (e
->timerId() == m_spinClickTimerId
)
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
);
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;
487 QNumericBox::QNumericBox(QWidget
* parent
)
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);
527 setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Fixed
);
530 QNumericBox::~QNumericBox()
532 m_pEdit
->removeEventFilter(this);
535 void QNumericBox::setRestrictToInt()
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())
557 if (m_pEdit
->text() == "")
561 GetIEditor()->GetIPythonManager()->Execute((QString("numeric_expression_result = ") + m_pEdit
->text()).toUtf8().constData());
562 double result
= (double)GetIEditor()->GetIPythonManager()->GetAsFloat("numeric_expression_result");
566 double QNumericBox::getSliderPosition() const
573 if (m_pEdit
->getEditMode())
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('.');
591 for (int i
= outText
.length() - 1; i
> decPoint
; --i
)
593 if ('0' == outText
.at(i
))
595 outText
.remove(i
, 1);
604 if ('.' == outText
.at(outText
.length() - 1))
606 outText
.remove(outText
.length() - 1, 1);
610 m_pEdit
->setText(outText
);
611 m_pEdit
->setCursorPosition(m_pEdit
->text().length() - revCursor
);
616 void QNumericBox::setPrecision(int precision
)
618 if (m_precision
!= precision
)
620 m_precision
= precision
;
625 void QNumericBox::step(double multiplier
)
627 if (Qt::ControlModifier
== QApplication::keyboardModifiers())
631 else if (Qt::ShiftModifier
== QApplication::keyboardModifiers())
636 setValue(value() + m_step
* multiplier
);
639 void QNumericBox::textEdited()
641 double val
= value();
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)
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()
719 void QNumericBox::wheelEvent(QWheelEvent
* e
)
721 step(sgn(e
->delta()));
724 void QNumericBox::showEvent(QShowEvent
* e
)
726 QWidget::showEvent(e
);
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());