File indexing completed on 2024-04-14 03:39:23

0001 /*************************************************************************************
0002  *  Copyright (C) 2007 by Aleix Pol <aleixpol@kde.org>                               *
0003  *  Copyright (C) 2010 by Percy Camilo T. Aucahuasi <percy.camilo.ta@gmail.com>      *
0004  *                                                                                   *
0005  *  This program is free software; you can redistribute it and/or                    *
0006  *  modify it under the terms of the GNU General Public License                      *
0007  *  as published by the Free Software Foundation; either version 2                   *
0008  *  of the License, or (at your option) any later version.                           *
0009  *                                                                                   *
0010  *  This program is distributed in the hope that it will be useful,                  *
0011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of                   *
0012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                    *
0013  *  GNU General Public License for more details.                                     *
0014  *                                                                                   *
0015  *  You should have received a copy of the GNU General Public License                *
0016  *  along with this program; if not, write to the Free Software                      *
0017  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA   *
0018  *************************************************************************************/
0019 
0020 #include "expressionedit.h"
0021 
0022 #include <QMenu>
0023 #include <QKeyEvent>
0024 #include <QHeaderView>
0025 #include <QApplication>
0026 #include <QTimer>
0027 #include <QPropertyAnimation>
0028 #include <QCoreApplication>
0029 
0030 #include <analitzagui/operatorsmodel.h>
0031 #include <analitza/explexer.h>
0032 #include <analitza/expressionparser.h>
0033 #include <analitza/analyzer.h>
0034 #include <analitza/variables.h>
0035 
0036 using namespace Analitza;
0037 
0038 class HelpTip : public QLabel
0039 {
0040     public:
0041         HelpTip(QWidget* parent)
0042             : QLabel(parent, Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::ToolTip | Qt::X11BypassWindowManagerHint)
0043         {
0044             setFrameShape(QFrame::Box);
0045             setFocusPolicy(Qt::NoFocus);
0046             setAutoFillBackground(false);
0047             
0048             QPalette p=palette();
0049             p.setColor(backgroundRole(), p.color(QPalette::Active, QPalette::ToolTipBase));
0050             p.setColor(foregroundRole(), p.color(QPalette::Active, QPalette::ToolTipText));
0051             setPalette(p);
0052         }
0053         
0054         void mousePressEvent(QMouseEvent*) override
0055         {
0056             hide();
0057         }
0058 };
0059 
0060 ExpressionEdit::ExpressionEdit(QWidget *parent, AlgebraHighlighter::Mode inimode)
0061     : QPlainTextEdit(parent), m_histPos(0), a(nullptr), m_correct(true), m_ans(QStringLiteral("ans"))
0062 {
0063     this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0064     this->setTabChangesFocus(true);
0065     m_history.append(QString());
0066     
0067     m_helptip = new HelpTip(this);
0068     m_helptip->hide();
0069     m_hideHelpTip = new QTimer(this);
0070     m_hideHelpTip->setInterval(500);
0071     connect(m_hideHelpTip, &QTimer::timeout, m_helptip, &QWidget::hide);
0072     
0073     m_highlight= new AlgebraHighlighter(this->document(), a);
0074     
0075     m_completer = new QCompleter(this);
0076     m_completer->setWidget(this);
0077     m_completer->setCompletionColumn(0);
0078     m_completer->setCompletionRole(Qt::DisplayRole);
0079     QTreeView* completionView = new QTreeView;
0080     m_completer->setPopup(completionView);
0081     completionView->setRootIsDecorated(false);
0082     completionView->header()->hide();
0083 //     completionView->resizeColumnToContents(1);
0084     completionView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0085     completionView->setMinimumWidth(300);
0086     m_ops = new OperatorsModel(m_completer);
0087     m_completer->setModel(m_ops);
0088     
0089     updateCompleter();
0090     
0091     completionView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
0092     completionView->showColumn(0);
0093     completionView->showColumn(1);
0094     completionView->hideColumn(2);
0095     completionView->hideColumn(3);
0096     
0097     connect(this, &ExpressionEdit::returnPressed, this, &ExpressionEdit::returnP);
0098     connect(this, &QPlainTextEdit::cursorPositionChanged, this, &ExpressionEdit::cursorMov);
0099     connect(this, SIGNAL(signalHelper(QString)), this, SLOT(helper(QString)));
0100     connect(m_completer, SIGNAL(activated(QString)), this, SLOT(completed(QString)));
0101 //     connect(m_completer, SIGNAL(activated(QModelIndex)), this, SLOT(completed(QModelIndex)));
0102     
0103     setMode(inimode);
0104     m_lineHeight = QFontMetrics(currentCharFormat().font()).height();
0105     setFixedHeight(m_lineHeight+15);
0106     
0107     setInputMethodHints(Qt::ImhNoAutoUppercase);
0108 }
0109 
0110 ExpressionEdit::~ExpressionEdit()
0111 {
0112 }
0113 
0114 void ExpressionEdit::setExpression(const Analitza::Expression& e)
0115 {
0116     if(!e.isCorrect())
0117         clear();
0118     else if(isMathML())
0119         setText(e.toMathML());
0120     else
0121         setText(e.toString());
0122     
0123     setCorrect(true);
0124 }
0125 
0126 void ExpressionEdit::updateCompleter()
0127 {
0128     m_ops->updateInformation();
0129 }
0130  
0131 void ExpressionEdit::completed(const QString& newText)
0132 {
0133     int l = OperatorsModel::lastWord(textCursor().selectionStart(), text()).length();
0134     QString toInsert=newText.mid(l);
0135     if(Analitza::Expression::whatType(newText) == Analitza::Object::oper && !isMathML())
0136         toInsert += '(';
0137     insertPlainText(toInsert);
0138 }
0139 
0140 bool ExpressionEdit::isMathML() const
0141 {
0142     switch(m_highlight->mode()) {
0143         case AlgebraHighlighter::MathML:
0144             return true;
0145         case AlgebraHighlighter::Expression:
0146             return false;
0147         default:
0148             return Analitza::Expression::isMathML(this->toPlainText());
0149     }
0150 }
0151 
0152 void ExpressionEdit::setMode(AlgebraHighlighter::Mode en)
0153 {
0154     bool correct=true;
0155     if(!text().isEmpty()) {
0156         if(isMathML() && en==AlgebraHighlighter::Expression) { //We convert it into MathML
0157             Analitza::Expression e(toPlainText(), true);
0158             correct=e.isCorrect();
0159             if(correct) setPlainText(e.toString());
0160         } else if(!isMathML() && en==AlgebraHighlighter::MathML) {
0161             Analitza::Expression e(toPlainText(), false);
0162             correct=e.isCorrect();
0163             if(correct) setPlainText(e.toMathML());
0164         }
0165     }
0166     if(correct)
0167         m_highlight->setMode(en);
0168     
0169     setCorrect(correct);
0170 }
0171 
0172 void ExpressionEdit::returnP()
0173 {
0174 //     removenl();
0175     if(!this->toPlainText().isEmpty()) {
0176         m_history.last() = this->toPlainText();
0177         m_history.append(QString());
0178         m_histPos=m_history.count()-1;
0179     }
0180 }
0181 
0182 void ExpressionEdit::keyPressEvent(QKeyEvent * e)
0183 {
0184     bool ch=false;
0185     QAbstractItemView* completionView = m_completer->popup();
0186     
0187     switch(e->key()){
0188         case Qt::Key_F2:
0189             simplify();
0190             break;
0191         case Qt::Key_Escape:
0192             if(!completionView->isVisible())
0193                 selectAll();
0194             
0195             completionView->hide();
0196             m_helptip->hide();
0197             break;
0198         case Qt::Key_Return:
0199         case Qt::Key_Enter:
0200             if(completionView->isVisible() && !completionView->selectionModel()->selectedRows().isEmpty()) 
0201                 completed(m_completer->currentCompletion());
0202             else if(returnPress())
0203                     QPlainTextEdit::keyPressEvent(e);
0204             completionView->hide();
0205             break;
0206         case Qt::Key_Up:
0207             if(!completionView->isVisible()) {
0208                 m_histPos--;
0209                 ch=true;
0210             }
0211             break;
0212         case Qt::Key_Down:
0213             if(!completionView->isVisible()) {
0214                 m_histPos++;
0215                 ch=true;
0216             }
0217             break;
0218         case Qt::Key_Left:
0219         case Qt::Key_Right:
0220             m_highlight->rehighlight();
0221             QPlainTextEdit::keyPressEvent(e);
0222             break;
0223         case Qt::Key_Plus:
0224         case Qt::Key_Asterisk:
0225         case Qt::Key_Slash:
0226             if(this->toPlainText().length() == (this->textCursor().position()-this->textCursor().anchor())) {
0227                 this->setPlainText(m_ans);
0228                 QTextCursor tc = this->textCursor();
0229                 tc.setPosition(m_ans.length());
0230                 this->setTextCursor(tc);
0231             }
0232             QPlainTextEdit::keyPressEvent(e);
0233             break;
0234         case Qt::Key_Alt:
0235             QPlainTextEdit::keyPressEvent(e);
0236             break;
0237         default:
0238             QPlainTextEdit::keyPressEvent(e);
0239             m_history.last() = this->toPlainText();
0240             QString last = OperatorsModel::lastWord(textCursor().selectionStart(), text());
0241             if(!last.isEmpty()) {
0242                 m_completer->setCompletionPrefix(last);
0243                 m_completer->complete();
0244             } else {
0245                 completionView->hide();
0246             }
0247             break;
0248     }
0249     
0250     if(ch) {
0251         if(m_histPos<0)
0252             m_histPos=0;
0253         if(m_histPos>=m_history.count())
0254             m_histPos=m_history.count()-1;
0255         this->setPlainText(m_history[m_histPos]);
0256     }
0257     
0258     if(m_completer->completionCount()==1 && m_completer->completionPrefix()==m_completer->currentCompletion()) {
0259         completionView->hide();
0260     }
0261     
0262     int lineCount=toPlainText().count('\n')+1;
0263     setFixedHeight(m_lineHeight*lineCount+15);
0264     setCorrect(m_highlight->isCorrect());
0265 }
0266 
0267 void ExpressionEdit::cursorMov()
0268 {
0269     int pos=this->textCursor().position();
0270     m_highlight->setPos(pos);
0271     if(text().isEmpty())
0272         setCorrect(true);
0273     
0274     QString help = helpShow(m_highlight->editingName(),
0275                             m_highlight->editingParameter(),
0276                             m_highlight->editingBounds(),
0277                             a ? a->variables().data() : nullptr);
0278     
0279     if(help.isEmpty()) {
0280         if(isCorrect()) {
0281             QTimer::singleShot(500, this, &ExpressionEdit::showSimplified);
0282         }
0283     } else
0284         helper(help);
0285 }
0286 
0287 
0288 void ExpressionEdit::showSimplified()
0289 {
0290     Analitza::Analyzer a;
0291     a.setExpression(expression());
0292     QString help;
0293     if(a.isCorrect()) {
0294         a.simplify();
0295         help=QCoreApplication::tr("Result: %1").arg(a.expression().toString());
0296     }
0297     helper(help);
0298 }
0299 
0300 
0301 QString ExpressionEdit::helpShow(const QString& funcname, int param, bool bounds, const Analitza::Variables* v) const
0302 {
0303     QString ret;
0304     QModelIndex idx = m_ops->indexForOperatorName(funcname);
0305     
0306     if(idx.isValid())
0307         ret = m_ops->parameterHelp(idx, param, bounds);
0308     else if(v && v->contains(funcname)) { //if it is a function defined by the user
0309         Analitza::Expression val=v->valueExpression(funcname);
0310         if(val.isLambda())
0311             ret = m_ops->standardFunctionCallHelp(funcname, param, val.bvarList().size(), false, false);
0312     }
0313     return ret;
0314 }
0315 
0316 void ExpressionEdit::removenl()
0317 {
0318     this->setPlainText(this->toPlainText().remove('\n'));
0319 }
0320 
0321 void ExpressionEdit::helper(const QString& msg)
0322 {
0323     QPoint pos = mapToGlobal( QPoint( cursorRect().left(), height() ) );
0324     
0325     if(msg.isEmpty()) {
0326         if(!m_hideHelpTip->isActive())
0327             m_hideHelpTip->start();
0328     } else {
0329         helper(msg, pos-QPoint(0, 50));
0330         m_hideHelpTip->stop();
0331     }
0332 }
0333 
0334 void ExpressionEdit::helper(const QString& msg, const QPoint& p)
0335 {
0336     if(isVisible()){
0337         m_helptip->setText(msg);
0338         m_helptip->resize(m_helptip->sizeHint());
0339         if(!m_helptip->isVisible()) {
0340             m_helptip->move(p);
0341             
0342             m_helptip->show();
0343             m_helptip->raise();
0344         } else {
0345             QPropertyAnimation* anim = new QPropertyAnimation(m_helptip, "pos", this);
0346             anim->setEndValue(p);
0347             anim->start(QAbstractAnimation::DeleteWhenStopped);
0348         }
0349         setFocus();
0350     }
0351 }
0352 
0353 void ExpressionEdit::setCorrect(bool correct)
0354 {
0355     QPalette p=qApp->palette();
0356     QColor c;
0357     m_correct=correct;
0358     
0359     if(m_correct && !isMathML())
0360         c = p.base().color();
0361     else if(m_correct)
0362         c = QColor(255,255,200);
0363     else //if mathml
0364         c = QColor(255,222,222);
0365     
0366     p.setColor(QPalette::Active, QPalette::Base, c);
0367     setPalette(p);
0368 }
0369 
0370 void ExpressionEdit::focusInEvent ( QFocusEvent * event )
0371 {
0372     QPlainTextEdit::focusInEvent(event);
0373     switch(event->reason()) {
0374         case Qt::OtherFocusReason:
0375         case Qt::TabFocusReason:
0376             this->selectAll();
0377         default:
0378             break;
0379     }
0380 }
0381 
0382 void ExpressionEdit::focusOutEvent ( QFocusEvent * event)
0383 {
0384     m_helptip->hide();
0385     
0386     QPlainTextEdit::focusOutEvent(event);
0387 }
0388 
0389 void ExpressionEdit::simplify()
0390 {
0391     Analitza::Analyzer a;
0392     a.setExpression(expression());
0393     if(a.isCorrect()) {
0394         a.simplify();
0395         setExpression(a.expression());
0396     }
0397     selectAll();
0398 }
0399 
0400 void ExpressionEdit::contextMenuEvent(QContextMenuEvent * e)
0401 {
0402     QScopedPointer<QMenu> popup(createStandardContextMenu());
0403     popup->addSeparator();
0404     if(isMathML())
0405         popup->addAction(QCoreApplication::tr("To Expression"), this, &ExpressionEdit::toExpression);
0406     else
0407         popup->addAction(QCoreApplication::tr("To MathML"), this, &ExpressionEdit::toMathML);
0408     
0409     popup->addAction(QCoreApplication::tr("Simplify"), this, &ExpressionEdit::simplify);
0410     
0411     QMenu* examples=popup->addMenu(QCoreApplication::tr("Examples"));
0412     examples->setEnabled(!m_examples.isEmpty());
0413     foreach(const QString &example, m_examples) {
0414         QAction* ac=examples->addAction(example);
0415         ac->setData(example);
0416     }
0417     connect(examples, &QMenu::triggered, this, &ExpressionEdit::setActionText);
0418     
0419     popup->exec(e->globalPos());
0420 
0421 }
0422 
0423 void ExpressionEdit::setActionText(QAction* text)
0424 {
0425     setPlainText(text->data().toString());
0426 }
0427 
0428 void ExpressionEdit::setAnalitza(Analitza::Analyzer * in)
0429 {
0430     m_highlight->setAnalitza(in);
0431     a=in;
0432     m_ops->setVariables(a->variables());
0433     updateCompleter();
0434 }
0435 
0436 bool ExpressionEdit::returnPress()
0437 {
0438     bool haveToPress=false;
0439     if(isMathML()) {
0440         emit returnPressed();
0441     } else {
0442         bool complete = Analitza::Expression::isCompleteExpression(toPlainText());
0443         haveToPress = !complete;
0444         setCorrect(complete);
0445         if(complete)
0446             emit returnPressed();
0447     }
0448     m_helptip->hide();
0449     return haveToPress;
0450 }
0451 
0452 void ExpressionEdit::insertText(const QString& text)
0453 {
0454     QTextCursor tc=textCursor();
0455     tc.insertText(text);
0456 }
0457 
0458 Analitza::Expression ExpressionEdit::expression() const
0459 {
0460     return Analitza::Expression(text(), isMathML());
0461 }
0462 
0463 bool ExpressionEdit::isCorrect() const
0464 {
0465     return m_correct && Analitza::Expression::isCompleteExpression(toPlainText());
0466 }
0467 
0468 
0469 
0470 #include "moc_expressionedit.cpp"