File indexing completed on 2024-04-21 03:41:34

0001 /*************************************************************************************
0002  *  Copyright (C) 2007-2009 by Aleix Pol <aleixpol@kde.org>                          *
0003  *                                                                                   *
0004  *  This program is free software; you can redistribute it and/or                    *
0005  *  modify it under the terms of the GNU General Public License                      *
0006  *  as published by the Free Software Foundation; either version 2                   *
0007  *  of the License, or (at your option) any later version.                           *
0008  *                                                                                   *
0009  *  This program is distributed in the hope that it will be useful,                  *
0010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of                   *
0011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                    *
0012  *  GNU General Public License for more details.                                     *
0013  *                                                                                   *
0014  *  You should have received a copy of the GNU General Public License                *
0015  *  along with this program; if not, write to the Free Software                      *
0016  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA   *
0017  *************************************************************************************/
0018 
0019 #include "functionedit.h"
0020 
0021 #include <QHBoxLayout>
0022 #include <QVBoxLayout>
0023 
0024 #include <KColorScheme>
0025 
0026 #include <analitza/analyzer.h>
0027 #include <analitza/expression.h>
0028 #include <analitza/value.h>
0029 #include <analitza/variables.h>
0030 #include <analitzagui/algebrahighlighter.h>
0031 #include <analitzagui/expressionedit.h>
0032 #include <analitzagui/plotsview2d.h>
0033 #include <analitzaplot/planecurve.h>
0034 #include <analitzaplot/plotsfactory.h>
0035 #include <analitzaplot/plotsmodel.h>
0036 #include <klocalizedstring.h>
0037 
0038 using namespace Analitza;
0039 
0040 namespace
0041 {
0042 static const int resolution = 200;
0043 }
0044 
0045 FunctionEdit::FunctionEdit(QWidget *parent)
0046     : QWidget(parent)
0047     , m_calcUplimit(0)
0048     , m_calcDownlimit(0)
0049     , m_modmode(false)
0050 {
0051     setWindowTitle(i18n("Add/Edit a function"));
0052 
0053     QVBoxLayout *topLayout = new QVBoxLayout(this);
0054     topLayout->setContentsMargins(2, 2, 2, 2);
0055     topLayout->setSpacing(5);
0056 
0057     m_name = new QLineEdit(this);
0058 
0059     m_func = new ExpressionEdit(this);
0060     m_func->setExamples(PlotsFactory::self()->examples(Dim2D));
0061     m_func->setAns(QStringLiteral("x"));
0062     connect(m_func, &QPlainTextEdit::textChanged, this, &FunctionEdit::edit);
0063     connect(m_func, &ExpressionEdit::returnPressed, this, &FunctionEdit::ok);
0064 
0065     m_valid = new QLabel(this);
0066     m_valid->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
0067 
0068     QPalette p = palette();
0069     p.setColor(QPalette::Active, QPalette::Base, Qt::white);
0070     m_valid->setPalette(p);
0071 
0072     m_validIcon = new QLabel(this);
0073     m_validIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0074     QLayout *validLayout = new QHBoxLayout;
0075     validLayout->addWidget(m_validIcon);
0076     validLayout->addWidget(m_valid);
0077 
0078     m_color = new KColorCombo(this);
0079     m_color->setColor(QColor(0, 150, 0));
0080     connect(m_color, SIGNAL(currentIndexChanged(int)), this, SLOT(colorChange(int)));
0081 
0082     m_funcsModel = new PlotsModel(this);
0083     m_funcsModel->setResolution(resolution);
0084 
0085     m_viewTabs = new QTabWidget(this);
0086 
0087     m_graph = new PlotsView2D(m_viewTabs);
0088     m_graph->setModel(m_funcsModel);
0089     m_graph->setViewport(QRectF(QPointF(-10.0, 10.0), QSizeF(20.0, -20.0)));
0090     m_graph->setFocusPolicy(Qt::NoFocus);
0091     m_graph->setMouseTracking(false);
0092     m_graph->setFramed(true);
0093     m_graph->setReadOnly(true);
0094     m_graph->setTicksShown(Qt::Orientation(0));
0095 
0096     m_viewTabs->addTab(m_graph, QIcon::fromTheme(QStringLiteral("document-preview")), i18n("Preview"));
0097     QWidget *options = new QWidget(m_viewTabs);
0098     options->setLayout(new QVBoxLayout);
0099     m_uplimit = new ExpressionEdit(options);
0100     m_downlimit = new ExpressionEdit(options);
0101     m_uplimit->setText(QStringLiteral("2*pi"));
0102     m_downlimit->setText(QStringLiteral("0"));
0103     options->layout()->addWidget(new QLabel(i18n("From:"), options));
0104     options->layout()->addWidget(m_downlimit);
0105     options->layout()->addWidget(new QLabel(i18n("To:"), options));
0106     options->layout()->addWidget(m_uplimit);
0107     options->layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding));
0108     m_viewTabs->addTab(options, QIcon::fromTheme(QStringLiteral("configure")), i18n("Options"));
0109     connect(m_uplimit, &QPlainTextEdit::textChanged, this, &FunctionEdit::updateUplimit);
0110     connect(m_downlimit, &QPlainTextEdit::textChanged, this, &FunctionEdit::updateDownlimit);
0111 
0112     QHBoxLayout *m_butts = new QHBoxLayout;
0113     m_ok = new QPushButton(i18n("OK"), this);
0114     m_ok->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok")));
0115     m_remove = new QPushButton(i18nc("@action:button", "Remove"), this);
0116     m_remove->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0117     connect(m_ok, &QAbstractButton::clicked, this, &FunctionEdit::ok);
0118     connect(m_remove, &QAbstractButton::clicked, this, &FunctionEdit::removeEditingPlot);
0119 
0120     topLayout->addWidget(m_name);
0121     topLayout->addWidget(m_func);
0122     topLayout->addWidget(m_color);
0123     topLayout->addLayout(validLayout);
0124     topLayout->addWidget(m_viewTabs);
0125     topLayout->addLayout(m_butts);
0126 
0127     m_name->hide(); // FIXME: Remove this when the name has any sense
0128 
0129     m_butts->addWidget(m_ok);
0130     m_butts->addWidget(m_remove);
0131 
0132     m_func->setFocus();
0133     m_ok->setEnabled(false);
0134 
0135     QFont errorFont = m_valid->font();
0136     errorFont.setBold(true);
0137     m_valid->setFont(errorFont);
0138 }
0139 
0140 FunctionEdit::~FunctionEdit()
0141 {
0142 }
0143 
0144 void FunctionEdit::clear()
0145 {
0146     m_func->setText(QString());
0147     m_funcsModel->clear();
0148     edit();
0149 }
0150 
0151 void FunctionEdit::setFunction(const QString &newText)
0152 {
0153     m_func->setText(newText);
0154     m_func->document()->setModified(true);
0155 }
0156 
0157 void FunctionEdit::setColor(const QColor &newColor)
0158 {
0159     m_color->setColor(newColor);
0160     if (m_funcsModel->rowCount() > 0)
0161         m_funcsModel->setData(m_funcsModel->index(0), newColor);
0162 }
0163 
0164 void FunctionEdit::colorChange(int)
0165 {
0166     setColor(m_color->color());
0167 }
0168 
0169 static double calcExp(const Analitza::Expression &exp, const QSharedPointer<Analitza::Variables> &v, bool *corr)
0170 {
0171     Q_ASSERT(exp.isCorrect());
0172     Analitza::Analyzer d(v);
0173     d.setExpression(exp);
0174     Analitza::Expression r = d.calculate();
0175 
0176     *corr = r.isCorrect() && r.isReal();
0177 
0178     if (*corr)
0179         return r.toReal().value();
0180     else
0181         return 0.;
0182 }
0183 
0184 void FunctionEdit::updateUplimit()
0185 {
0186     bool corr = m_uplimit->isCorrect();
0187     if (corr) {
0188         Analitza::Expression e = m_uplimit->expression();
0189         m_calcUplimit = calcExp(e, m_vars, &corr);
0190         m_uplimit->setCorrect(corr);
0191         if (corr)
0192             edit();
0193     }
0194 }
0195 
0196 void FunctionEdit::updateDownlimit()
0197 {
0198     bool corr = m_downlimit->isCorrect();
0199     if (corr) {
0200         Analitza::Expression e = m_downlimit->expression();
0201         m_calcDownlimit = calcExp(e, m_vars, &corr);
0202         m_downlimit->setCorrect(corr);
0203         if (corr)
0204             edit();
0205     }
0206 }
0207 
0208 void FunctionEdit::setState(const QString &text, bool negative)
0209 {
0210     QFontMetrics fm(m_valid->font());
0211     m_valid->setText(fm.elidedText(text, Qt::ElideRight, m_valid->width()));
0212     m_valid->setToolTip(text);
0213 
0214     KColorScheme scheme(QPalette::Normal);
0215     KColorScheme::ForegroundRole role = negative ? KColorScheme::NegativeText : KColorScheme::PositiveText;
0216 
0217     QPalette p = m_valid->palette();
0218     p.setColor(foregroundRole(), scheme.foreground(role).color());
0219     m_valid->setPalette(p);
0220 
0221     if (negative)
0222         m_validIcon->setPixmap(QIcon::fromTheme(QStringLiteral("flag-red")).pixmap(QSize(16, 16)));
0223     else
0224         m_validIcon->setPixmap(QIcon::fromTheme(QStringLiteral("flag-green")).pixmap(QSize(16, 16)));
0225 }
0226 
0227 /// Let's see if the exp is correct
0228 void FunctionEdit::edit()
0229 {
0230     if (m_func->text().isEmpty()) {
0231         m_func->setCorrect(true);
0232         m_ok->setEnabled(false);
0233         m_valid->clear();
0234         m_valid->setToolTip(QString());
0235         m_validIcon->setPixmap(QIcon::fromTheme(QStringLiteral("flag-yellow")).pixmap(QSize(16, 16)));
0236 
0237         m_funcsModel->clear();
0238         m_graph->forceRepaint();
0239         return;
0240     }
0241 
0242     if (!m_uplimit->isCorrect() || !m_downlimit->isCorrect()) {
0243         setState(i18n("The options you specified are not correct"), true);
0244         return;
0245     }
0246 
0247     if (m_calcDownlimit > m_calcUplimit) {
0248         setState(i18n("Downlimit cannot be greater than uplimit"), true);
0249         return;
0250     }
0251     bool added = false;
0252 
0253     PlaneCurve *f = 0;
0254     PlotBuilder req = PlotsFactory::self()->requestPlot(expression(), Dim2D, m_vars);
0255     if (req.canDraw())
0256         f = createFunction();
0257 
0258     if (f && f->isCorrect())
0259         f->update(QRect(-10, 10, 20, -20));
0260 
0261     m_funcsModel->clear();
0262     if (f && f->isCorrect()) {
0263         m_funcsModel->addPlot(f);
0264         added = true;
0265         setState(QStringLiteral("%1:=%2").arg(m_name->text(), f->expression().toString()), false);
0266     } else {
0267         QStringList errors = req.errors();
0268         if (f)
0269             errors = f->errors();
0270         Q_ASSERT(!errors.isEmpty());
0271 
0272         setState(errors.first(), true);
0273         m_valid->setToolTip(errors.join(QStringLiteral("<br />")));
0274         delete f;
0275     }
0276     m_func->setCorrect(added);
0277     m_ok->setEnabled(added);
0278 }
0279 
0280 void FunctionEdit::ok()
0281 {
0282     if (m_ok->isEnabled())
0283         Q_EMIT accept();
0284 }
0285 
0286 void FunctionEdit::focusInEvent(QFocusEvent *)
0287 {
0288     m_func->setFocus();
0289 }
0290 
0291 PlaneCurve *FunctionEdit::createFunction() const
0292 {
0293     PlotBuilder req = PlotsFactory::self()->requestPlot(expression(), Dim2D, m_vars);
0294     PlaneCurve *curve = static_cast<PlaneCurve *>(req.create(color(), name()));
0295     curve->setResolution(resolution);
0296     if (m_calcUplimit != m_calcDownlimit) {
0297         const auto parameters = curve->parameters();
0298         for (const QString &var : parameters)
0299             curve->setInterval(var, m_calcUplimit, m_calcDownlimit);
0300     }
0301     return curve;
0302 }
0303 
0304 Analitza::Expression FunctionEdit::expression() const
0305 {
0306     return m_func->expression();
0307 }
0308 
0309 void FunctionEdit::setOptionsShown(bool shown)
0310 {
0311     m_viewTabs->setVisible(shown);
0312 }
0313 
0314 void FunctionEdit::resizeEvent(QResizeEvent *)
0315 {
0316     QFontMetrics fm(m_valid->font());
0317     m_valid->setText(fm.elidedText(m_valid->toolTip(), Qt::ElideRight, m_valid->width()));
0318 }
0319 
0320 void FunctionEdit::setEditing(bool m)
0321 {
0322     m_modmode = m;
0323     m_remove->setVisible(m);
0324 }