Files
qad/libs/widgets/evalspinbox.cpp

365 lines
8.8 KiB
C++

#include "evalspinbox.h"
#include "qad_types.h"
#include "qpievaluator_p.h"
#include <QDebug>
#include <QLabel>
#include <QLineEdit>
#include <QMouseEvent>
#include <QPainter>
#include <QStyle>
#include <QStyleOptionSpinBox>
#include <QTimer>
#if QT_VERSION_MAJOR <= 5
# include <QRegExp>
#else
# include <QRegularExpression>
#endif
EvalSpinBox::EvalSpinBox(QWidget * parent): QAbstractSpinBox(parent) {
status = new QWidget(lineEdit());
cw = new QWidget(lineEdit());
label = new QLabel(lineEdit());
eval = new QPIEvaluator();
clear_im.load(":/icons/edit-clear-locationbar-rtl.png");
icon_ok.load(":/icons/dialog-ok-apply.png");
icon_fail.load(":/icons/dialog-warning.png");
icon_calc.load(":/icons/tools-wizard.png");
icon = icon_ok;
status->setCursor(Qt::ArrowCursor);
status->setToolTip("OK -> 0");
status->hide();
cw->setCursor(Qt::ArrowCursor);
cw->setToolTip(tr("Clear"));
cw->hide();
cw->installEventFilter(this);
status->installEventFilter(this);
connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(textChanged_(QString)));
connect(this, SIGNAL(editingFinished()), this, SLOT(setExpressionSlot()));
label->setText("0");
}
EvalSpinBox::~EvalSpinBox() {
delete eval;
delete label;
delete cw;
delete status;
}
bool EvalSpinBox::eventFilter(QObject * o, QEvent * e) {
switch (e->type()) {
case QEvent::MouseButtonRelease:
if (o == cw) {
clearMouseRelease(static_cast<QMouseEvent *>(e));
}
break;
case QEvent::Paint:
if (o == status) {
statusPaintEvent();
}
if (o == cw) {
cwPaintEvent();
}
break;
default: break;
}
return QAbstractSpinBox::eventFilter(o, e);
}
void EvalSpinBox::resizeIcons() {
int is = fontHeight(this);
int tm = (lineEdit()->height() - is + 1) / 2;
QStyleOptionFrame so;
so.initFrom(lineEdit());
QRect r = style()->subElementRect(QStyle::SE_LineEditContents, &so, lineEdit());
QMargins m = lineEdit()->textMargins();
int lwh = label->sizeHint().width();
label->setGeometry(lineEdit()->width() - m.left() - lwh + r.x() - 2,
m.top() + r.y() + (r.height() - fontMetrics().height() + 1) / 2,
lwh, // - 2*tm - (is * 1.2) * ((status->isVisible() ? 1 : 0) + (cw->isVisible() ? 1 : 0)),
lineEdit()->height() - 2 * tm);
status->setGeometry(lineEdit()->width() - (is + tm) * (cw->isVisible() ? 2 : 1), tm, is, is);
cw->setGeometry(lineEdit()->width() - (is + tm) * 1, tm, is, is);
m.setRight((is * 1.2) * ((status->isVisible() ? 1 : 0) + (cw->isVisible() ? 1 : 0)));
lineEdit()->setTextMargins(m);
}
void EvalSpinBox::resizeEvent(QResizeEvent * e) {
QAbstractSpinBox::resizeEvent(e);
resizeIcons();
}
void EvalSpinBox::changeEvent(QEvent * e) {
QAbstractSpinBox::changeEvent(e);
if (e->type() == QEvent::LanguageChange) {
cw->setToolTip(tr("Clear"));
return;
}
}
void EvalSpinBox::cwPaintEvent() {
QPainter p(cw);
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.drawImage(cw->rect(), clear_im);
}
void EvalSpinBox::statusPaintEvent() {
QPainter p(status);
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.drawImage(status->rect(), icon);
}
void EvalSpinBox::clearMouseRelease(QMouseEvent * e) {
if (cw->rect().contains(e->pos())) clear();
}
void EvalSpinBox::textChanged_(const QString & text) {
double pv = value();
QString t = text;
cw->setVisible(text != dt && cw_visible);
bool td = false;
if (t.endsWith('=')) {
td = true;
t.chop(1);
}
bool ok = eval->check(t);
if (ok) {
eval->evaluate();
if (td) {
icon = icon_calc;
status->setToolTip("Enter to calc -> " + QString::number(value(), 'G', 10));
} else {
icon = icon_ok;
status->setToolTip("OK -> " + QString::number(value(), 'G', 10));
}
if (pv != value()) {
emit valueChanged(value());
}
} else {
icon = icon_fail;
status->setToolTip(eval->error());
}
resizeIcons();
}
void EvalSpinBox::setExpressionSlot() {
bool td = false;
double pv = value();
QString t = text();
if (t.endsWith('=')) {
td = true;
t.chop(1);
}
if (eval->check(t)) {
lineEdit()->setText(eval->expression());
eval->evaluate();
if (td) lineEdit()->setText(QString::number(value(), 'G', precision_ > 0 ? precision_ : 16));
status->setToolTip("OK -> " + QString::number(value(), 'G', 10));
icon = icon_ok;
} else {
icon = icon_fail;
status->setToolTip(eval->error());
}
if (!label->isHidden()) {
if (eval->expression() != QString::number(value(), 'G', 10) && eval->expression() != QString::number(value(), 'G', 11) &&
eval->isCorrect())
label->setText("<html><head/><body><p><span style=\"color:#005500;\">-&gt; " + QString::number(value(), 'G', 10) +
"</span></p></body></html>");
else
label->setText("");
lineEdit()->blockSignals(true);
if (!eval->isCorrect()) {
lineEdit()->setStyleSheet("color: darkred;");
status->show();
} else {
lineEdit()->setStyleSheet("");
status->hide();
}
lineEdit()->blockSignals(false);
}
if (pv != value()) emit valueChanged(value());
}
void EvalSpinBox::setExpression(const QString & expr) {
lineEdit()->setText(expr);
cw->setVisible(text() != dt && cw_visible);
setExpressionSlot();
}
void EvalSpinBox::setValue(double val) {
lineEdit()->setText(QString::number(val, 'G', precision_ > 0 ? precision_ : 16));
cw->setVisible(text() != dt && cw_visible);
setExpressionSlot();
}
void EvalSpinBox::stepBy(int steps) {
stepByDouble(steps * m_singleStep);
}
void EvalSpinBox::clear() {
lineEdit()->setText(dt);
setExpressionSlot();
cw->hide();
resizeIcons();
emit cleared();
}
double EvalSpinBox::value() const {
if (eval->isCorrect()) {
return eval->lastResult().real();
}
return 0.;
}
const QString & EvalSpinBox::expression() const {
return eval->expression();
}
bool EvalSpinBox::isCleared() const {
return (dt == eval->expression() || (value() == 0 && dt.isEmpty()));
}
QSize EvalSpinBox::sizeHint() const {
QSize s = QAbstractSpinBox::sizeHint();
s.setWidth(120);
return s;
}
QAbstractSpinBox::StepEnabled EvalSpinBox::stepEnabled() const {
return StepUpEnabled | StepDownEnabled;
}
void EvalSpinBox::focusInEvent(QFocusEvent * event) {
label->hide();
status->show();
lineEdit()->blockSignals(true);
lineEdit()->setStyleSheet("");
if (eval->expression() == "0") lineEdit()->clear();
lineEdit()->blockSignals(false);
QAbstractSpinBox::focusInEvent(event);
resizeIcons();
}
void EvalSpinBox::focusOutEvent(QFocusEvent * event) {
QAbstractSpinBox::focusOutEvent(event);
if (eval->expression() != QString::number(value(), 'G', 10) && eval->expression() != QString::number(value(), 'G', 11) &&
eval->isCorrect()) {
label->setText("<html><head/><body><p><span style=\"color:#005500;\">-&gt; " + QString::number(value(), 'G', 10) +
"</span></p></body></html>");
} else {
label->setText("");
}
if (calc_visible) {
label->show();
}
lineEdit()->blockSignals(true);
if (!eval->isCorrect())
lineEdit()->setStyleSheet("color: darkred;");
else
status->hide();
lineEdit()->blockSignals(false);
resizeIcons();
}
void EvalSpinBox::wheelEvent(QWheelEvent * event) {
if (event->modifiers().testFlag(Qt::ShiftModifier)) {
stepByDouble((
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
event->delta()
#else
event->angleDelta().y()
#endif
> 0
? 0.1
: -0.1) *
m_singleStep);
} else {
QAbstractSpinBox::wheelEvent(event);
}
}
void EvalSpinBox::stepByDouble(double steps) {
if (isReadOnly()) return;
QString t = text();
if (eval->check(t)) {
t = eval->expression();
#if QT_VERSION_MAJOR <= 5
QRegExp re("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)");
int pos = 0;
if ((pos = re.indexIn(t)) != -1) {
double v = t.mid(pos, re.matchedLength()).toDouble();
v += steps;
t.remove(pos, re.matchedLength());
t.insert(pos, QString::number(v));
} else {
double v = steps;
t = QString::number(v) + t;
}
#else
QRegularExpression re("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)");
QRegularExpressionMatchIterator i = re.globalMatch(t);
if (i.hasNext()) {
QRegularExpressionMatch match = i.next();
double v = t.mid(match.capturedStart(), match.capturedLength()).toDouble();
v += steps;
t.remove(match.capturedStart(), match.capturedLength());
t.insert(match.capturedStart(), QString::number(v));
} else {
double v = steps;
t = QString::number(v) + t;
}
#endif
eval->check(t);
lineEdit()->setText(eval->expression());
}
}
void EvalSpinBox::setDefaultText(const QString & t) {
dt = t;
cw->setVisible((eval->expression() != dt || (dt.isEmpty() && eval->expression() == "0")) && cw_visible);
resizeIcons();
}
void EvalSpinBox::setClearButtonVisible(bool visible) {
cw_visible = visible;
cw->setVisible((eval->expression() != dt || (dt.isEmpty() && eval->expression() == "0")) && cw_visible);
resizeIcons();
}
void EvalSpinBox::setCalculationVisible(bool visible) {
calc_visible = visible;
setExpressionSlot();
if (!calc_visible) {
label->hide();
}
}