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"