diff --git a/kmplot/equationeditorwidget.cpp b/kmplot/equationeditorwidget.cpp index 7289b61..e355338 100644 --- a/kmplot/equationeditorwidget.cpp +++ b/kmplot/equationeditorwidget.cpp @@ -1,135 +1,135 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "equationeditorwidget.h" #include #include "equationeditwidget.h" #include "maindlg.h" #include "xparser.h" EquationEditorWidget::EquationEditorWidget(QWidget* parent) : QWidget(parent) { setupUi(this); QFont font; double pointSize = font.pointSizeF() * 1.1; font.setPointSizeF(pointSize); edit->m_equationEditWidget->document()->setDefaultFont(font); edit->m_equationEditWidget->recalculateGeometry(); QFont buttonFont; buttonFont.setPointSizeF(font.pointSizeF() * 1.1); - QList buttons = findChildren(); - foreach (QToolButton* w, buttons) { + const QList buttons = findChildren(); + for (QToolButton* w : buttons) { KAcceleratorManager::setNoAccel(w); connect(w, &QToolButton::clicked, this, &EquationEditorWidget::characterButtonClicked); // Also increase the font size, since the fractions, etc are probably not that visible // at the default font size w->setFont(buttonFont); } connect(constantsButton, &QPushButton::clicked, this, &EquationEditorWidget::editConstants); connect(functionList, QOverload::of(&QComboBox::activated), this, &EquationEditorWidget::insertFunction); connect(constantList, QOverload::of(&QComboBox::activated), this, &EquationEditorWidget::insertConstant); QStringList functions = XParser::self()->predefinedFunctions(false); functions.sort(); functionList->addItems(functions); connect(XParser::self()->constants(), &Constants::constantsChanged, this, &EquationEditorWidget::updateConstantList); updateConstantList(); } void EquationEditorWidget::updateConstantList() { QStringList items; // The first item text is "Insert constant..." items << constantList->itemText(0); ConstantList constants = XParser::self()->constants()->list(Constant::All); for (ConstantList::iterator it = constants.begin(); it != constants.end(); ++it) { QString text = it.key() + " = " + it.value().value.expression(); items << text; } constantList->clear(); constantList->addItems(items); } void EquationEditorWidget::insertFunction(const QString& function) { if (functionList->currentIndex() == 0) return; functionList->setCurrentIndex(0); edit->wrapSelected(function + '(', ")"); edit->setFocus(); } void EquationEditorWidget::editConstants() { MainDlg::self()->editConstantsModal(this); } void EquationEditorWidget::insertConstant(int index) { if (index == 0) return; ConstantList constants = XParser::self()->constants()->list(Constant::All); if (constants.size() < index) return; // Don't forget that index==0 corresponds to "Insert constant..." ConstantList::iterator it = constants.begin(); int at = 0; while (++at < index) ++it; QString constant = it.key(); constantList->setCurrentIndex(0); edit->insertText(constant); edit->setFocus(); } void EquationEditorWidget::characterButtonClicked() { const QToolButton* tb = static_cast(sender()); // Something (I can't work out what) is 'helpfully' inserting an ampersand (for keyboard acceleration). // Get rid of it. edit->insertText(tb->text().remove('&')); } diff --git a/kmplot/equationhighlighter.cpp b/kmplot/equationhighlighter.cpp index f5fc145..374d1b2 100644 --- a/kmplot/equationhighlighter.cpp +++ b/kmplot/equationhighlighter.cpp @@ -1,161 +1,161 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "equationhighlighter.h" #include #include "equationedit.h" #include "equationeditwidget.h" #include "xparser.h" EquationHighlighter::EquationHighlighter(EquationEdit* parent) : QSyntaxHighlighter(parent->m_equationEditWidget), m_parent(parent) { m_errorPosition = -1; } EquationHighlighter::~ EquationHighlighter() { } void EquationHighlighter::highlightBlock(const QString& text) { m_parent->checkTextValidity(); if (text.isEmpty()) return; QTextCharFormat number; QTextCharFormat function; QTextCharFormat variable; QTextCharFormat matchedParenthesis; QPalette palette; if (qGray(palette.color(QPalette::Base).rgb()) > 160) { // Good color defaults borrowed from Abakus - thanks! :) number.setForeground(QColor(0, 0, 127)); function.setForeground(QColor(85, 0, 0)); variable.setForeground(QColor(0, 85, 0)); matchedParenthesis.setBackground(QColor(255, 255, 183)); } else { number.setForeground(QColor(160, 160, 255)); function.setForeground(QColor(255, 160, 160)); variable.setForeground(QColor(160, 255, 160)); matchedParenthesis.setBackground(QColor(85, 85, 0)); } QTextCharFormat other; - QStringList variables = m_parent->m_equation->variables(); - QStringList functions = XParser::self()->predefinedFunctions(true) + XParser::self()->userFunctions(); + const QStringList variables = m_parent->m_equation->variables(); + const QStringList functions = XParser::self()->predefinedFunctions(true) + XParser::self()->userFunctions(); for (int i = 0; i < text.length(); ++i) { QString remaining = text.right(text.length() - i); bool found = false; - foreach (const QString& var, variables) { + for (const QString& var : variables) { if (remaining.startsWith(var)) { setFormat(i, var.length(), variable); i += var.length() - 1; found = true; break; } } if (found) continue; - foreach (const QString& f, functions) { + for (const QString& f : functions) { if (remaining.startsWith(f)) { setFormat(i, f.length(), function); i += f.length() - 1; found = true; break; } } if (found) continue; ushort u = text[i].unicode(); bool isFraction = (u >= 0xbc && u <= 0xbe) || (u >= 0x2153 && u <= 0x215e); bool isPower = (u >= 0xb2 && u <= 0xb3) || (u == 0x2070) || (u >= 0x2074 && u <= 0x2079); bool isDigit = text[i].isDigit(); bool isDecimalPoint = text[i] == QLocale().decimalPoint(); if (isFraction || isPower || isDigit || isDecimalPoint) setFormat(i, 1, number); else setFormat(i, 1, other); } //BEGIN highlight matched brackets int cursorPos = m_parent->m_equationEditWidget->textCursor().position(); if (cursorPos < 0) cursorPos = 0; // Adjust cursorpos to allow for a bracket before the cursor position if (cursorPos >= text.size()) cursorPos = text.size() - 1; else if (cursorPos > 0 && (text[cursorPos-1] == '(' || text[cursorPos-1] == ')')) cursorPos--; bool haveOpen = text[cursorPos] == '('; bool haveClose = text[cursorPos] == ')'; if ((haveOpen || haveClose) && m_parent->hasFocus()) { // Search for the other bracket int inc = haveOpen ? 1 : -1; // which direction to search in int level = 0; for (int i = cursorPos; i >= 0 && i < text.size(); i += inc) { if (text[i] == ')') level--; else if (text[i] == '(') level++; if (level == 0) { // Matched! setFormat(cursorPos, 1, matchedParenthesis); setFormat(i, 1, matchedParenthesis); break; } } } //END highlight matched brackets if (m_errorPosition != -1) { QTextCharFormat error; error.setForeground(Qt::red); setFormat(m_errorPosition, 1, error); } } void EquationHighlighter::setErrorPosition(int position) { m_errorPosition = position; } diff --git a/kmplot/function.cpp b/kmplot/function.cpp index eec18c9..051ce53 100644 --- a/kmplot/function.cpp +++ b/kmplot/function.cpp @@ -1,1209 +1,1207 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 1998, 1999, 2000, 2002 Klaus-Dieter Möller * 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "function.h" #include "ksliderwindow.h" #include "settings.h" #include "view.h" #include "xparser.h" #include #include #include #include #include int MAX_PM = 4; //BEGIN class Value Value::Value( const QString & expression ) { m_value = 0.0; if ( expression.isEmpty() ) m_expression = '0'; else updateExpression( expression ); } Value::Value( double value ) { updateExpression( value ); } bool Value::updateExpression( const QString & expression ) { Parser::Error error; double newValue = XParser::self()->eval( expression, & error ); if ( error != Parser::ParseSuccess ) return false; m_value = newValue; m_expression = expression; return true; } void Value::updateExpression( double value ) { m_value = value; m_expression = Parser::number( value ); } bool Value::operator == ( const Value & other ) const { return m_expression == other.expression(); } //END class Value //BEGIN class PlotAppearance PlotAppearance::PlotAppearance( ) { lineWidth = 0.3; color = Qt::black; useGradient = false; visible = false; style = Qt::SolidLine; showExtrema = false; showTangentField = false; showPlotName = false; } bool PlotAppearance::operator !=( const PlotAppearance & other ) const { return (lineWidth != other.lineWidth) || (color != other.color) || (useGradient != other.useGradient) || (gradient.stops() != other.gradient.stops()) || (visible != other.visible) || (style != other.style) || (showExtrema != other.showExtrema) || (showTangentField != other.showTangentField) || (showPlotName != other.showPlotName); } QString PlotAppearance::penStyleToString( Qt::PenStyle style ) { switch ( style ) { case Qt::NoPen: return "NoPen"; case Qt::SolidLine: return "SolidLine"; case Qt::DashLine: return "DashLine"; case Qt::DotLine: return "DotLine"; case Qt::DashDotLine: return "DashDotLine"; case Qt::DashDotDotLine: return "DashDotDotLine"; case Qt::MPenStyle: case Qt::CustomDashLine: qWarning() << "Unsupported pen style\n"; break; } qWarning() << "Unknown style " << style ; return "SolidLine"; } Qt::PenStyle PlotAppearance::stringToPenStyle( const QString & style ) { if ( style == "NoPen" ) return Qt::NoPen; if ( style == "SolidLine" ) return Qt::SolidLine; if ( style == "DashLine" ) return Qt::DashLine; if ( style == "DotLine" ) return Qt::DotLine; if ( style == "DashDotLine" ) return Qt::DashDotLine; if ( style == "DashDotDotLine" ) return Qt::DashDotDotLine; qWarning() << "Unknown style " << style ; return Qt::SolidLine; } //END class PlotAppearance //BEGIN class DifferentialState DifferentialState::DifferentialState() { x = 0; } DifferentialState::DifferentialState( int order ) { x = 0; setOrder( order ); } void DifferentialState::setOrder( int order ) { bool orderWasZero = (y0.size() == 0); y.resize( order ); y0.resize( order ); if ( orderWasZero && order >= 1 ) y0[0].updateExpression( "1" ); resetToInitial(); } bool DifferentialStates::setStep( const Value & step ) { if ( step.value() <= 0 ) return false; m_step = step; return true; } void DifferentialState::resetToInitial() { x = x0.value(); y = y0; } bool DifferentialState::operator == ( const DifferentialState & other ) const { return (x0 == other.x0) && (x == other.x) && (y0 == other.y0) && (y == other.y); } //END class DifferentialState //BEGIN class DifferentialStates DifferentialStates::DifferentialStates() { m_uniqueState = false; m_order = 0; m_step.updateExpression( 0.05 ); } void DifferentialStates::setOrder( int order ) { m_order = order; for ( int i = 0; i < m_data.size(); ++i ) m_data[i].setOrder( order ); } DifferentialState * DifferentialStates::add() { if ( !m_uniqueState || m_data.isEmpty() ) m_data << DifferentialState( order() ); else qDebug() << "Unable to add another state!\n"; return & m_data[ size() - 1 ]; } void DifferentialStates::setUniqueState( bool unique ) { m_uniqueState = unique; if ( m_uniqueState && m_data.size() > 1 ) { // Remove any states other than the first m_data.resize( 1 ); } } void DifferentialStates::resetToInitial( ) { for ( int i = 0; i < m_data.size(); ++i ) m_data[i].resetToInitial(); } //END class DifferentialStates //BEGIN class Equation Equation::Equation( Type type, Function * parent ) : m_type( type ), m_parent( parent ) { m_usesParameter = false; mptr = 0; if ( type == Differential || type == Cartesian ) { differentialStates.setUniqueState( type == Cartesian ); differentialStates.setOrder( order() ); differentialStates.add(); } } Equation::~ Equation() { } int Equation::order( ) const { if ( type() == Cartesian ) { // For drawing integrals return 1; } else return name(false).count( '\'' ); } int Equation::pmCount() const { return m_fstr.count( PmSymbol ); } QString Equation::name( bool removePrimes ) const { if ( m_fstr.isEmpty() ) return QString(); int open = m_fstr.indexOf( '(' ); int equals = m_fstr.indexOf( '=' ); if ( (equals == -1) && (open == -1) ) return QString(); int pos; if ( ((equals > open) && (open != -1)) || (equals == -1) ) pos = open; else pos = equals; QString n = m_fstr.left( pos ).trimmed(); if ( removePrimes ) n.remove( '\'' ); return n; } bool Equation::looksLikeFunction( ) const { int open = m_fstr.indexOf( '(' ); int equals = m_fstr.indexOf( '=' ); if ( (open != -1) && (open < equals) ) return true; switch ( type() ) { case Cartesian: case Differential: case ParametricY: return (name() != "y"); case Polar: return (name() != "r"); case ParametricX: return (name() != "x"); case Implicit: return false; case Constant: return false; } return true; } void Equation::updateVariables() { if ( type() == Constant ) { return; } m_variables.clear(); if ( looksLikeFunction() ) { int p1 = m_fstr.indexOf( '(' ); int p2 = m_fstr.indexOf( ')' ); - QStringList listSplit; - if ( (p1 != -1) && (p2 != -1) ) - listSplit = m_fstr.mid( p1+1, p2-p1-1 ).split( ',', QString::SkipEmptyParts ); + const QStringList listSplit = ( (p1 != -1) && (p2 != -1) ) ? m_fstr.mid( p1+1, p2-p1-1 ).split( ',', QString::SkipEmptyParts ) : QStringList(); // Variables shouldn't contain spaces! - foreach ( QString s, listSplit ) { //krazy:exclude=foreach + for ( QString s : listSplit ) { s = s.remove(' '); if ( !s.isEmpty() ) m_variables << s; } } else switch ( type() ) { case Cartesian: case Differential: m_variables << "x" << "k"; break; case Polar: m_variables << QChar( 0x3b8 ) << "k"; // (theta) break; case ParametricX: case ParametricY: m_variables << "t" << "k"; break; case Implicit: m_variables << "x" << "y" << "k"; break; case Constant: break; } // If we are a differential equation, then add on y, y', etc if ( type() == Differential && !name().isEmpty() ) { QString n = name(); int order = this->order(); for ( int i = 0; i < order; ++i ) { m_variables << n; n += '\''; } } //BEGIN Update whether we accept a parameter or not int expectedNumVariables = 0; switch ( m_type ) { case Cartesian: case ParametricX: case ParametricY: case Polar: expectedNumVariables = 1; break; case Implicit: expectedNumVariables = 2; break; case Differential: expectedNumVariables = order()+1; break; case Constant: expectedNumVariables = 0; break; } m_usesParameter = (variables().size() > expectedNumVariables); //END Update whether we accept a parameter or not } QString Equation::parameterName( ) const { if ( !usesParameter() ) return QString(); int parAt = (type() == Implicit) ? 2 : 1; return variables()[parAt]; } bool Equation::setFstr( const QString & fstr, int * error, int * errorPosition, bool force ) { #define HANDLE_ERROR \ if ( !force ) \ { \ m_fstr = prevFstr; \ updateVariables(); \ } \ else \ { \ qDebug() << "fstr "< maxArg ) { *error = Parser::TooManyArguments; HANDLE_ERROR; /// \todo indicate the position of the invalid argument? return false; } XParser::self()->initEquation( this, (Parser::Error*)error, errorPosition ); if ( *error != Parser::ParseSuccess ) { HANDLE_ERROR; if ( !force ) XParser::self()->initEquation( this ); return false; } differentialStates.setOrder( order() ); return true; } void Equation::setPMSignature( QVector< bool > pmSignature ) { differentialStates.resetToInitial(); m_pmSignature = pmSignature; } bool Equation::operator !=( const Equation & other ) { return (fstr() != other.fstr()) || (differentialStates != other.differentialStates); } Equation & Equation::operator =( const Equation & other ) { setFstr( other.fstr() ); differentialStates = other.differentialStates; return * this; } //END class Equation //BEGIN class Function Function::Function( Type type ) : m_type( type ) { x = y = 0; m_implicitMode = UnfixedXY; usecustomxmin = false; usecustomxmax = false; dmin.updateExpression( QChar('0') ); if ( Settings::anglemode() == Parser::Radians ) dmax.updateExpression( QString(QChar('2')) + PiSymbol ); else dmax.updateExpression( "360" ); switch ( m_type ) { case Cartesian: eq << new Equation( Equation::Cartesian, this ); break; case Polar: eq << new Equation( Equation::Polar, this ); usecustomxmin = true; usecustomxmax = true; break; case Parametric: eq << new Equation( Equation::ParametricX, this ); eq << new Equation( Equation::ParametricY, this ); usecustomxmin = true; usecustomxmax = true; break; case Implicit: eq << new Equation( Equation::Implicit, this ); break; case Differential: eq << new Equation( Equation::Differential, this ); break; } m_id = 0; f0.visible = true; k = 0; } Function::~Function() { - foreach ( Equation * e, eq ) + for ( Equation * e : qAsConst(eq) ) delete e; } bool Function::copyFrom( const Function & function ) { bool changed = false; int i = 0; #define COPY_AND_CHECK(s) \ { \ if ( s != function.s ) \ { \ s = function.s; \ changed = true; \ } \ } \ i++; COPY_AND_CHECK( f0 ); // 0 if ( type() == Cartesian ) { COPY_AND_CHECK( f1 ); // 1 COPY_AND_CHECK( f2 ); // 2 COPY_AND_CHECK( f3 ); // 3 COPY_AND_CHECK( integral ); // 4 } COPY_AND_CHECK( dmin ); // 5,1 COPY_AND_CHECK( dmax ); // 6,2 COPY_AND_CHECK( usecustomxmin ); // 7,3 COPY_AND_CHECK( usecustomxmax ); // 8,4 COPY_AND_CHECK( m_parameters ); // 9,5 // handle equations separately for ( int i = 0; i < eq.size(); ++i ) { if ( *eq[i] != *function.eq[i] ) { changed = true; *eq[i] = *function.eq[i]; } } return changed; } QString Function::name() const { QString n = eq[0]->fstr(); for ( int i = 1; i < eq.size(); ++i ) n += '\n' + eq[i]->fstr(); return n; } PlotAppearance & Function::plotAppearance( PMode plot ) { // NOTE: This function is identical to the const one, so changes to this should be applied to both switch ( plot ) { case Function::Derivative0: return f0; case Function::Derivative1: return f1; case Function::Derivative2: return f2; case Function::Derivative3: return f3; case Function::Integral: return integral; } qCritical() << "Unknown plot " << plot << endl; return f0; } PlotAppearance Function::plotAppearance( PMode plot ) const { // NOTE: This function is identical to the none-const one, so changes to this should be applied to both switch ( plot ) { case Function::Derivative0: return f0; case Function::Derivative1: return f1; case Function::Derivative2: return f2; case Function::Derivative3: return f3; case Function::Integral: return integral; } qCritical() << "Unknown plot " << plot << endl; return f0; } bool Function::allPlotsAreHidden( ) const { return !f0.visible && !f1.visible && !f2.visible && !integral.visible; } QString Function::typeToString( Type type ) { switch ( type ) { case Cartesian: return "cartesian"; case Parametric: return "parametric"; case Polar: return "polar"; case Implicit: return "implicit"; case Differential: return "differential"; } qWarning() << "Unknown type " << type ; return "unknown"; } Function::Type Function::stringToType( const QString & type ) { if ( type == "cartesian" ) return Cartesian; if ( type == "parametric" ) return Parametric; if ( type == "polar" ) return Polar; if ( type == "implicit" ) return Implicit; if ( type == "differential" ) return Differential; qWarning() << "Unknown type " << type ; return Cartesian; } QList< Plot > Function::plots( PlotCombinations combinations ) const { QList< Plot > list; if ( allPlotsAreHidden() ) return list; Plot plot; plot.setFunctionID( id() ); plot.plotNumberCount = m_parameters.useList ? m_parameters.list.size() + (m_parameters.useSlider?1:0) : 1; bool singlePlot = (!m_parameters.useList && !m_parameters.useSlider) || m_parameters.animating || (~combinations & DifferentParameters) || (!m_parameters.useSlider && m_parameters.useList && m_parameters.list.isEmpty()); if ( singlePlot ) { if ( m_parameters.animating ) plot.parameter = Parameter( Parameter::Animated ); list << plot; } else { int i = 0; if ( m_parameters.useSlider ) { Parameter param( Parameter::Slider ); param.setSliderID( m_parameters.sliderID ); plot.parameter = param; plot.plotNumber = i++; list << plot; } if ( m_parameters.useList ) { const int listsize = m_parameters.list.size(); for ( int pos = 0; pos < listsize; ++pos ) { Parameter param( Parameter::List ); param.setListPos( pos ); plot.parameter = param; plot.plotNumber = i++; list << plot; } } } // Copy each plot in the list for other variations if ( (type() == Cartesian) && (combinations & DifferentDerivatives) ) { QList< Plot > duplicated; for ( PMode p = Derivative0; p <= Integral; p = PMode(p+1) ) { - foreach ( Plot plot, list ) { //krazy:exclude=foreach + for ( Plot plot : qAsConst(list) ) { if ( !plotAppearance(p).visible ) continue; plot.plotMode = p; duplicated << plot; } } list = duplicated; } if ( (type() == Differential) && (combinations & DifferentInitialStates) ) { QList< Plot > duplicated; for ( int i = 0; i < eq[0]->differentialStates.size(); ++i ) { - foreach ( Plot plot, list ) { //krazy:exclude=foreach + for ( Plot plot : qAsConst(list) ) { plot.stateNumber = i; duplicated << plot; } } list = duplicated; } if ( combinations & DifferentPMSignatures ) { int size = 0; - foreach ( Equation * equation, eq ) + for ( Equation * equation : qAsConst(eq) ) size += equation->pmCount(); unsigned max = unsigned( std::pow( 2.0, (double)size ) ); QVector< QVector > signatures( max ); for ( unsigned i = 0; i < max; ++i ) { QVector sig( size ); for ( int j = 0; j < size; ++j ) sig[ j ] = i & (1< duplicated; - foreach ( const QVector &signature, signatures ) + for ( const QVector &signature : qAsConst(signatures) ) { int at = 0; QList< QVector > pmSignature; - foreach ( Equation * equation, eq ) + for ( Equation * equation : qAsConst(eq) ) { int pmCount = equation->pmCount(); QVector sig( pmCount ); for ( int i = 0; i < pmCount; ++i ) sig[i] = signature[ i + at]; at += pmCount; pmSignature << sig; } - foreach ( Plot plot, list ) { //krazy:exclude=foreach + for ( Plot plot : qAsConst(list) ) { plot.pmSignature = pmSignature; duplicated << plot; } } list = duplicated; } return list; } void Function::addFunctionDependency( Function * function ) { if ( !function || m_dependencies.contains( function->id() ) ) return; Q_ASSERT_X( !function->dependsOn( this ), "addFunctionDependency", "circular dependency" ); m_dependencies << function->id(); } bool Function::dependsOn( Function * function ) const { if ( !function ) return false; if ( m_dependencies.contains( function->id() ) ) return true; - foreach ( int functionId, m_dependencies ) + for ( int functionId : qAsConst(m_dependencies) ) { Function * f = XParser::self()->functionWithID( functionId ); if ( f->dependsOn( function ) ) return true; } return false; } //END class Function //BEGIN class ParameterSettings ParameterSettings::ParameterSettings() { animating = false; useSlider = false; sliderID = 0; useList = false; } bool ParameterSettings::operator == ( const ParameterSettings & other ) const { return ( useSlider == other.useSlider ) && ( sliderID == other.sliderID ) && ( useList == other.useList ) && ( list == other.list ); } //END class ParameterSettings //BEGIN class Parameter Parameter::Parameter( Type type ) : m_type( type ) { m_sliderID = -1; m_listPos = -1; } bool Parameter::operator == ( const Parameter & other ) const { return ( type() == other.type() ) && ( listPos() == other.listPos() ) && ( sliderID() == other.sliderID() ); } //END class Parameter //BEGIN class Plot Plot::Plot( ) { stateNumber = -1; plotNumberCount = 1; plotNumber = 0; m_function = 0; m_functionID = -1; plotMode = Function::Derivative0; } bool Plot::operator ==( const Plot & other ) const { return ( m_functionID == other.functionID() ) && ( plotMode == other.plotMode ) && ( parameter == other.parameter ) && ( stateNumber == other.stateNumber ); } void Plot::setFunctionID( int id ) { m_functionID = id; updateCached(); } void Plot::updateCached() { m_function = XParser::self()->functionWithID( m_functionID ); } QString Plot::name( ) const { if ( !m_function ) return QString(); QString n = m_function->name(); if ( m_function->eq[0]->usesParameter() ) n += QString( "\n%1 = %2" ).arg( m_function->eq[0]->parameterName() ).arg( Parser::number( parameterValue() ) ); if ( plotMode == Function::Derivative1 ) n = n.section('=', 0, 0).replace('(', "\'("); if ( plotMode == Function::Derivative2 ) n = n.section('=', 0, 0).replace('(', "\'\'("); if ( plotMode == Function::Integral ) { QString functionName = n.section('=', 0, 0); n = QChar(0x222B) + ' ' + functionName + 'd' + functionName.section('(', 1, 1).remove(')').section(',', 0, 0); } return n; } void Plot::updateFunction() const { if ( !m_function ) return; // Update the plus-minus signature assert( pmSignature.size() <= m_function->eq.size() ); for ( int i = 0; i < pmSignature.size(); ++i ) m_function->eq[i]->setPMSignature( pmSignature[i] ); if ( parameter.type() != Parameter::Animated ) m_function->setParameter( parameterValue() ); } double Plot::parameterValue() const { switch ( parameter.type() ) { case Parameter::Unknown: return 0; case Parameter::Slider: { KSliderWindow * sw = View::self()->m_sliderWindow; if ( !sw ) { // Slider window isn't open. Ask View to open it View::self()->updateSliders(); // It should now be open sw = View::self()->m_sliderWindow; assert( sw ); } return sw->value( parameter.sliderID() ); } case Parameter::List: { if ( (parameter.listPos() >= 0) && (parameter.listPos() < m_function->m_parameters.list.size()) ) return m_function->m_parameters.list[ parameter.listPos() ].value(); return 0; } case Parameter::Animated: { qWarning() << "Shouldn't use this function for animated parameter!\n"; return 0; } } return 0; } void Plot::differentiate() { switch ( plotMode ) { case Function::Integral: plotMode = Function::Derivative0; break; case Function::Derivative0: plotMode = Function::Derivative1; break; case Function::Derivative1: plotMode = Function::Derivative2; break; case Function::Derivative2: plotMode = Function::Derivative3; break; case Function::Derivative3: qWarning() << "Can't handle this yet!\n"; break; } } void Plot::integrate() { switch ( plotMode ) { case Function::Integral: qWarning() << "Can't handle this yet!\n"; break; case Function::Derivative0: plotMode = Function::Integral; break; case Function::Derivative1: plotMode = Function::Derivative0; break; case Function::Derivative2: plotMode = Function::Derivative1; break; case Function::Derivative3: plotMode = Function::Derivative2; break; } } QColor Plot::color( ) const { Function * f = function(); assert(f); // Shouldn't call color without a function PlotAppearance appearance = f->plotAppearance( plotMode ); if ( (plotNumberCount <= 1) || !appearance.useGradient ) return appearance.color; // Is a gradient int x = plotNumber; int l = plotNumberCount; QLinearGradient lg( 0, 0, l-1, 0 ); lg.setStops( appearance.gradient.stops() ); QImage im( l, 1, QImage::Format_RGB32 ); QPainter p(&im); p.setPen( QPen( lg, 1 ) ); p.drawLine( 0, 0, l, 0 ); return im.pixel( x, 0 ); } int Plot::derivativeNumber( ) const { switch ( plotMode ) { case Function::Integral: return -1; case Function::Derivative0: return 0; case Function::Derivative1: return 1; case Function::Derivative2: return 2; case Function::Derivative3: return 3; } qWarning() << "Unknown derivative number.\n"; return 0; } DifferentialState * Plot::state( ) const { if ( !function() || (stateNumber < 0) ) return 0; if ( function()->eq[0]->differentialStates.size() <= stateNumber ) return 0; return & function()->eq[0]->differentialStates[stateNumber]; } //END class Plot diff --git a/kmplot/functioneditor.cpp b/kmplot/functioneditor.cpp index a604ea3..bce9ad2 100644 --- a/kmplot/functioneditor.cpp +++ b/kmplot/functioneditor.cpp @@ -1,849 +1,849 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 1998-2002 Klaus-Dieter Möller * 2004 Fredrik Edemar * 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "functioneditor.h" #include "equationedit.h" #include "kgradientdialog.h" #include "kmplotio.h" #include "maindlg.h" #include "parameterswidget.h" #include "ui_functioneditorwidget.h" #include "view.h" #include "xparser.h" #include #include #include #include #include #include class FunctionEditorWidget : public QWidget, public Ui::FunctionEditorWidget { public: FunctionEditorWidget(QWidget *parent = 0) : QWidget(parent) { setupUi(this); } }; //BEGIN class FunctionEditor FunctionEditor::FunctionEditor( QMenu * createNewPlotsMenu, QWidget * parent ) : QDockWidget( i18n("Functions"), parent ) { m_functionID = -1; // need a name for saving and restoring the position of this dock widget setObjectName( QStringLiteral("FunctionEditor") ); setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea ); setFeatures( QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable ); for ( int i = 0; i < 5; ++i ) { m_saveTimer[i] = new QTimer( this ); m_saveTimer[i]->setSingleShot( true ); } m_syncFunctionListTimer = new QTimer( this ); m_syncFunctionListTimer->setSingleShot( true ); connect( m_saveTimer[Function::Cartesian], &QTimer::timeout, this, &FunctionEditor::saveCartesian ); connect( m_saveTimer[Function::Polar], &QTimer::timeout, this, &FunctionEditor::savePolar ); connect( m_saveTimer[Function::Parametric], &QTimer::timeout, this, &FunctionEditor::saveParametric ); connect( m_saveTimer[Function::Implicit], &QTimer::timeout, this, &FunctionEditor::saveImplicit ); connect( m_saveTimer[Function::Differential], &QTimer::timeout, this, &FunctionEditor::saveDifferential ); connect(m_syncFunctionListTimer, &QTimer::timeout, this, &FunctionEditor::syncFunctionList); m_editor = new FunctionEditorWidget; m_functionList = m_editor->functionList; m_editor->createNewPlot->setIcon( QIcon::fromTheme(QStringLiteral("document-new")) ); m_editor->deleteButton->setIcon( QIcon::fromTheme(QStringLiteral("edit-delete")) ); //BEGIN initialize equation edits m_editor->cartesianEquation->setInputType( EquationEdit::Function ); m_editor->cartesianEquation->setEquationType( Equation::Cartesian ); m_editor->cartesianParameters->associateEquationEdit( m_editor->cartesianEquation ); m_editor->polarEquation->setInputType( EquationEdit::Function ); m_editor->polarEquation->setEquationType( Equation::Polar ); m_editor->polarParameters->associateEquationEdit( m_editor->polarEquation ); m_editor->parametricX->setInputType( EquationEdit::Function ); m_editor->parametricX->setEquationType( Equation::ParametricX ); m_editor->parametricParameters->associateEquationEdit( m_editor->parametricX ); m_editor->parametricY->setInputType( EquationEdit::Function ); m_editor->parametricY->setEquationType( Equation::ParametricY ); m_editor->parametricParameters->associateEquationEdit( m_editor->parametricY ); m_editor->implicitEquation->setInputType( EquationEdit::Function ); m_editor->implicitEquation->setEquationType( Equation::Implicit ); m_editor->differentialEquation->setInputType( EquationEdit::Function ); m_editor->differentialEquation->setEquationType( Equation::Differential ); m_editor->differentialParameters->associateEquationEdit( m_editor->differentialEquation ); //END initialize equation edits for ( unsigned i = 0; i < 5; ++i ) m_editor->stackedWidget->widget(i)->layout()->setMargin( 0 ); connect(m_editor->deleteButton, &QPushButton::clicked, this, &FunctionEditor::deleteCurrent); connect(m_functionList, &FunctionListWidget::currentItemChanged, this, &FunctionEditor::functionSelected); connect(m_functionList, &FunctionListWidget::itemClicked, this, &FunctionEditor::saveItem); //BEGIN connect up all editing widgets #define CONNECT_WIDGETS( name, signal ) \ { \ QList widgets = m_editor->findChildren(); \ - foreach ( name * w, widgets ) \ + for ( name * w : qAsConst(widgets) ) \ connect( w, SIGNAL(signal), this, SLOT(save()) ); \ } CONNECT_WIDGETS( QLineEdit, editingFinished() ); CONNECT_WIDGETS( EquationEdit, editingFinished() ); CONNECT_WIDGETS( QCheckBox, stateChanged(int) ); CONNECT_WIDGETS( KColorButton, changed(const QColor &) ); CONNECT_WIDGETS( QRadioButton, toggled(bool) ); CONNECT_WIDGETS( QComboBox, currentIndexChanged(int) ); CONNECT_WIDGETS( ParametersWidget, parameterListChanged() ); CONNECT_WIDGETS( KGradientButton, gradientChanged(const QGradient &) ); connect(m_editor->initialConditions, &InitialConditionsEditor::dataChanged, this, &FunctionEditor::save); //END connect up all editing widgets connect(XParser::self(), &XParser::functionAdded, this, &FunctionEditor::functionsChanged); connect(XParser::self(), &XParser::functionRemoved, this, &FunctionEditor::functionsChanged); m_editor->createNewPlot->setMenu( createNewPlotsMenu ); resetFunctionEditing(); setWidget( m_editor ); } FunctionEditor::~ FunctionEditor() { } void FunctionEditor::deleteCurrent() { m_editor->initialConditions->init( 0 ); FunctionListItem * functionItem = static_cast(m_functionList->currentItem()); if ( !functionItem ) { qDebug() << "Nothing currently selected!\n"; return; } if ( !XParser::self()->removeFunction( functionItem->function() ) ) { qDebug() << "Couldn't delete function.\n"; // couldn't delete it, as e.g. another function depends on it return; } MainDlg::self()->requestSaveCurrentState(); View::self()->drawPlot(); } void FunctionEditor::functionsChanged() { m_syncFunctionListTimer->start( 0 ); } void FunctionEditor::syncFunctionList() { int oldFunctionCount = m_functionList->count(); QListWidgetItem * currentItem = m_functionList->currentItem(); QString currentText = currentItem ? currentItem->text() : QString(); // build up a list of IDs that we have QMap< int, FunctionListItem * > currentIDs; QList< FunctionListItem * > currentFunctionItems; for ( int row = 0; row < m_functionList->count(); ++row ) { FunctionListItem * item = static_cast(m_functionList->item( row )); currentFunctionItems << item; currentIDs[ item->function() ] = item; // also update what is displayed item->update(); } FunctionListItem * toSelect = 0l; int newFunctionCount = 0; for ( QMap::iterator it = XParser::self()->m_ufkt.begin(); it != XParser::self()->m_ufkt.end(); ++it) { Function * function = *it; if ( currentIDs.contains( function->id() ) ) { // already have the function currentFunctionItems.removeAll( currentIDs[ function->id() ] ); currentIDs.remove( function->id() ); continue; } toSelect = new FunctionListItem( m_functionList, function->id() ); newFunctionCount++; } if ( newFunctionCount != 1 ) { // only select a new functionlistitem if there was precisely one added toSelect = 0l; } // Now, any IDs left in currentIDs are of functions that have been deleted - foreach ( FunctionListItem * item, currentFunctionItems ) + for ( FunctionListItem * item : qAsConst(currentFunctionItems) ) { if ( m_functionID == item->function() ) m_functionID = -1; delete m_functionList->takeItem( m_functionList->row( item ) ); } m_functionList->sortItems(); // Try and see if there is an item with the same text as was initially selected, if we have // the same number of functions if ( (oldFunctionCount == m_functionList->count()) && !currentText.isEmpty() ) { QList matchedItems = m_functionList->findItems( currentText, Qt::MatchExactly ); if ( matchedItems.count() == 1 ) toSelect = static_cast(matchedItems.first()); } if ( toSelect ) m_functionList->setCurrentItem( toSelect ); if ( m_functionList->count() == 0 ) resetFunctionEditing(); } void FunctionEditor::setCurrentFunction( int functionID ) { for ( int row = 0; row < m_functionList->count(); ++row ) { FunctionListItem * item = static_cast(m_functionList->item( row )); if ( item->function() != functionID ) continue; m_functionList->setCurrentRow( row ); return; } } void FunctionEditor::functionSelected( QListWidgetItem * item ) { m_editor->deleteButton->setEnabled( item != 0 ); if ( !item ) return; // If there are any pending save events, then cancel them for ( int i = 0; i < 5; ++i ) m_saveTimer[i]->stop(); FunctionListItem * functionItem = static_cast(item); m_functionID = functionItem->function(); Function * f = XParser::self()->functionWithID( m_functionID ); if ( !f ) return; switch ( f->type() ) { case Function::Cartesian: initFromCartesian(); break; case Function::Polar: initFromPolar(); break; case Function::Parametric: initFromParametric(); break; case Function::Implicit: initFromImplicit(); break; case Function::Differential: initFromDifferential(); } functionItem->update(); } void FunctionEditor::initFromCartesian() { Function * f = XParser::self()->functionWithID(m_functionID); if ( !f ) { qWarning() << "No f! (id="<cartesianEquation->setText( f->eq[0]->fstr() ); m_editor->cartesian_f0->init( f->plotAppearance( Function::Derivative0 ), Function::Cartesian ); m_editor->cartesian_f1->init( f->plotAppearance( Function::Derivative1 ), Function::Cartesian ); m_editor->cartesian_f2->init( f->plotAppearance( Function::Derivative2 ), Function::Cartesian ); m_editor->cartesian_integral->init( f->plotAppearance( Function::Integral ), Function::Cartesian ); m_editor->showDerivative1->setChecked( f->plotAppearance( Function::Derivative1 ).visible ); m_editor->showDerivative2->setChecked( f->plotAppearance( Function::Derivative2 ).visible ); m_editor->cartesianCustomMin->setChecked( f->usecustomxmin ); m_editor->cartesianMin->setText( f->dmin.expression() ); m_editor->cartesianCustomMax->setChecked( f->usecustomxmax ); m_editor->cartesianMax->setText( f->dmax.expression() ); m_editor->cartesianParameters->init( f->m_parameters ); m_editor->showIntegral->setChecked( f->plotAppearance( Function::Integral ).visible ); m_editor->integralStep->setText( f->eq[0]->differentialStates.step().expression() ); DifferentialState state = f->eq[0]->differentialStates[0]; m_editor->txtInitX->setText( state.x0.expression() ); m_editor->txtInitY->setText( state.y0[0].expression() ); m_editor->stackedWidget->setCurrentIndex( 0 ); m_editor->tabWidget->setCurrentIndex( 0 ); m_editor->cartesianEquation->setFocus(); } void FunctionEditor::initFromPolar() { Function * f = XParser::self()->functionWithID(m_functionID); if ( !f ) return; QString function = f->eq[0]->fstr(); m_editor->polarEquation->setText( function ); m_editor->polarMin->setText( f->dmin.expression() ); m_editor->polarMax->setText( f->dmax.expression() ); m_editor->polar_f0->init( f->plotAppearance( Function::Derivative0 ), Function::Polar ); m_editor->polarParameters->init( f->m_parameters ); m_editor->stackedWidget->setCurrentIndex( 2 ); m_editor->polarEquation->setFocus(); } void FunctionEditor::initFromParametric() { Function * f = XParser::self()->functionWithID(m_functionID); if ( !f ) return; m_editor->parametricX->setText( f->eq[0]->fstr() ); m_editor->parametricY->setText( f->eq[1]->fstr() ); m_editor->parametricMin->setText( f->dmin.expression() ); m_editor->parametricMax->setText( f->dmax.expression() ); m_editor->parametricParameters->init( f->m_parameters ); m_editor->parametric_f0->init( f->plotAppearance( Function::Derivative0 ), Function::Parametric ); m_editor->stackedWidget->setCurrentIndex( 1 ); m_editor->parametricX->setFocus(); } void FunctionEditor::initFromImplicit() { Function * f = XParser::self()->functionWithID(m_functionID); if ( !f ) return; QString name, expression; splitImplicitEquation( f->eq[0]->fstr(), & name, & expression ); m_editor->implicitEquation->setValidatePrefix( name + '=' ); m_editor->implicitName->setText( name ); m_editor->implicitEquation->setText( expression ); m_editor->implicit_f0->init( f->plotAppearance( Function::Derivative0 ), Function::Implicit ); m_editor->implicitParameters->init( f->m_parameters ); m_editor->stackedWidget->setCurrentIndex( 3 ); m_editor->implicitEquation->setFocus(); } void FunctionEditor::initFromDifferential() { Function * f = XParser::self()->functionWithID(m_functionID); if ( !f ) return; m_editor->differentialEquation->setText( f->eq[0]->fstr()); m_editor->differentialStep->setText( f->eq[0]->differentialStates.step().expression() ); m_editor->differential_f0->init( f->plotAppearance( Function::Derivative0 ), Function::Differential ); m_editor->differentialParameters->init( f->m_parameters ); m_editor->initialConditions->init( f ); m_editor->differentialTabWidget->setCurrentIndex( 0 ); m_editor->stackedWidget->setCurrentIndex( 4 ); m_editor->differentialEquation->setFocus(); } void FunctionEditor::splitImplicitEquation( const QString &equation, QString * name, QString * expression ) { int equalsPos = equation.indexOf( '=' ); assert( equalsPos >= 0 ); *name = equation.left( equalsPos ).trimmed(); *expression = equation.right( equation.length() - equalsPos - 1 ).trimmed(); } void FunctionEditor::resetFunctionEditing() { m_functionID = -1; // page 5 is an empty page m_editor->stackedWidget->setCurrentIndex( 5 ); // assume that if there are functions in the list, then one will be selected m_editor->deleteButton->setEnabled( m_functionList->count() != 0 ); } void FunctionEditor::createCartesian() { QString name; if ( Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function ) name = XParser::self()->findFunctionName( QStringLiteral("f"), -1 ) + "(x)"; else name = 'y'; createFunction( name + " = 0", QString(), Function::Cartesian ); } void FunctionEditor::createParametric() { QString name = XParser::self()->findFunctionName( QStringLiteral("f"), -1, QStringList() << QStringLiteral("%1") << QStringLiteral("%1_x") << QStringLiteral("%1_y") ); QString name_x, name_y; if ( Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function ) { name_x = QStringLiteral("%1_x(t)").arg( name ); name_y = QStringLiteral("%1_y(t)").arg( name ); } else { name_x = 'x'; name_y = 'y'; } createFunction( name_x + " = 0", name_y + " = 0", Function::Parametric ); } void FunctionEditor::createPolar() { QString name; if ( Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function ) name = XParser::self()->findFunctionName( QStringLiteral("f"), -1 ) + "(x)"; else name = 'r'; createFunction( name + " = 0", QString(), Function::Polar ); } void FunctionEditor::createImplicit() { QString name = XParser::self()->findFunctionName( QStringLiteral("f"), -1 ); if ( Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function ) name += QLatin1String("(x,y)"); createFunction( name + " = y² = x³ − x + 1", QString(), Function::Implicit ); } void FunctionEditor::createDifferential() { QString name; if ( Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function ) name = QStringLiteral( "%1''(x) = -%1" ).arg( XParser::self()->findFunctionName( QStringLiteral("f"), -1 ) ); else name = QLatin1String("y'' = -y"); createFunction( name, QString(), Function::Differential ); } void FunctionEditor::createFunction( const QString & eq0, const QString & eq1, Function::Type type ) { m_functionID = XParser::self()->Parser::addFunction( eq0, eq1, type ); assert( m_functionID != -1 ); MainDlg::self()->requestSaveCurrentState(); } void FunctionEditor::save() { Function * f = XParser::self()->functionWithID( m_functionID ); if ( !f ) return; m_saveTimer[ f->type() ]->start( 0 ); } // TODO This should be a part of a model. The proper model should be created later. void FunctionEditor::saveItem(QListWidgetItem *item) { if (item != m_functionList->currentItem()) { m_functionList->setCurrentItem(item); if (item->checkState() == Qt::Checked) { item->setCheckState(Qt::Unchecked); } else { item->setCheckState(Qt::Checked); } } save(); } void FunctionEditor::saveCartesian() { FunctionListItem * functionListItem = static_cast(m_functionList->currentItem()); if ( !functionListItem ) return; QString f_str( m_editor->cartesianEquation->text() ); XParser::self()->fixFunctionName(f_str, Equation::Cartesian, m_functionID ); Function tempFunction( Function::Cartesian ); tempFunction.setId( m_functionID ); tempFunction.usecustomxmin = m_editor->cartesianCustomMin->isChecked(); if ( !tempFunction.dmin.updateExpression( m_editor->cartesianMin->text() ) ) return; tempFunction.usecustomxmax = m_editor->cartesianCustomMax->isChecked(); if ( !tempFunction.dmax.updateExpression( m_editor->cartesianMax->text() ) ) return; tempFunction.plotAppearance( Function::Derivative0 ) = m_editor->cartesian_f0->plot( (functionListItem->checkState() == Qt::Checked) ); tempFunction.plotAppearance( Function::Derivative1 ) = m_editor->cartesian_f1->plot( m_editor->showDerivative1->isChecked() ); tempFunction.plotAppearance( Function::Derivative2 ) = m_editor->cartesian_f2->plot( m_editor->showDerivative2->isChecked() ); tempFunction.plotAppearance( Function::Integral ) = m_editor->cartesian_integral->plot( m_editor->showIntegral->isChecked() ); DifferentialState * state = & tempFunction.eq[0]->differentialStates[0]; state->setOrder( 1 ); state->x0.updateExpression( m_editor->txtInitX->text() ); state->y0[0].updateExpression( m_editor->txtInitY->text() ); if ( !tempFunction.eq[0]->differentialStates.setStep( m_editor->integralStep->text() ) ) return; tempFunction.m_parameters = m_editor->cartesianParameters->parameterSettings(); if ( !tempFunction.eq[0]->setFstr( f_str ) ) return; saveFunction( & tempFunction ); } void FunctionEditor::savePolar() { FunctionListItem * functionListItem = static_cast(m_functionList->currentItem()); if ( !functionListItem ) return; QString f_str = m_editor->polarEquation->text(); XParser::self()->fixFunctionName( f_str, Equation::Polar, m_functionID ); Function tempFunction( Function::Polar ); // all settings are saved here until we know that no errors have appeared tempFunction.setId( m_functionID ); if ( !tempFunction.dmin.updateExpression( m_editor->polarMin->text() ) ) return; if ( !tempFunction.dmax.updateExpression( m_editor->polarMax->text() ) ) return; tempFunction.m_parameters = m_editor->polarParameters->parameterSettings(); tempFunction.plotAppearance( Function::Derivative0 ) = m_editor->polar_f0->plot( (functionListItem->checkState() == Qt::Checked) ); if ( !tempFunction.eq[0]->setFstr( f_str ) ) return; saveFunction( & tempFunction ); } void FunctionEditor::saveParametric() { FunctionListItem * functionListItem = static_cast(m_functionList->currentItem()); if ( !functionListItem ) return; Function tempFunction( Function::Parametric ); tempFunction.setId( m_functionID ); QString f_str = m_editor->parametricX->text(); XParser::self()->fixFunctionName( f_str, Equation::ParametricX, m_functionID ); if ( !tempFunction.eq[0]->setFstr( f_str ) ) return; f_str = m_editor->parametricY->text(); XParser::self()->fixFunctionName( f_str, Equation::ParametricY, m_functionID ); if ( !tempFunction.eq[1]->setFstr( f_str ) ) return; if ( !tempFunction.dmin.updateExpression( m_editor->parametricMin->text() ) ) return; if ( !tempFunction.dmax.updateExpression( m_editor->parametricMax->text() ) ) return; tempFunction.m_parameters = m_editor->parametricParameters->parameterSettings(); tempFunction.plotAppearance( Function::Derivative0 ) = m_editor->parametric_f0->plot( (functionListItem->checkState() == Qt::Checked) ); saveFunction( & tempFunction ); } void FunctionEditor::saveImplicit() { FunctionListItem * functionListItem = static_cast(m_functionList->currentItem()); if ( !functionListItem ) return; // find a name not already used if ( m_editor->implicitName->text().isEmpty() ) { QString fname; XParser::self()->fixFunctionName(fname, Equation::Implicit, m_functionID ); int const pos = fname.indexOf('('); m_editor->implicitName->setText(fname.mid(1,pos-1)); } QString prefix = m_editor->implicitName->text() + " = "; QString f_str = prefix + m_editor->implicitEquation->text(); m_editor->implicitEquation->setValidatePrefix( prefix ); Function tempFunction( Function::Implicit ); // all settings are saved here until we know that no errors have appeared tempFunction.setId( m_functionID ); tempFunction.m_parameters = m_editor->implicitParameters->parameterSettings(); tempFunction.plotAppearance( Function::Derivative0 ) = m_editor->implicit_f0->plot( (functionListItem->checkState() == Qt::Checked) ); if ( !tempFunction.eq[0]->setFstr( f_str ) ) return; saveFunction( & tempFunction ); } void FunctionEditor::saveDifferential() { FunctionListItem * functionListItem = static_cast(m_functionList->currentItem()); if ( !functionListItem ) return; Function tempFunction( Function::Differential ); // all settings are saved here until we know that no errors have appeared tempFunction.setId( m_functionID ); QString f_str = m_editor->differentialEquation->text(); if ( !tempFunction.eq[0]->setFstr( f_str ) ) return; tempFunction.m_parameters = m_editor->differentialParameters->parameterSettings(); tempFunction.plotAppearance( Function::Derivative0 ) = m_editor->differential_f0->plot( (functionListItem->checkState() == Qt::Checked) ); m_editor->initialConditions->setOrder( tempFunction.eq[0]->order() ); tempFunction.eq[0]->differentialStates = *m_editor->initialConditions->differentialStates(); if ( !tempFunction.eq[0]->differentialStates.setStep( m_editor->differentialStep->text() ) ) return; saveFunction( & tempFunction ); } void FunctionEditor::saveFunction( Function * tempFunction ) { FunctionListItem * functionListItem = static_cast(m_functionList->currentItem()); Function * f = XParser::self()->functionWithID( m_functionID ); if ( !f || !functionListItem ) return; - foreach ( Equation * eq, f->eq ) + for ( Equation * eq : qAsConst(f->eq) ) eq->differentialStates.resetToInitial(); //save all settings in the function now when we know no errors have appeared bool changed = f->copyFrom( *tempFunction ); if ( !changed ) return; qDebug() << "Changed\n"; if ( f->eq[0]->looksLikeFunction() ) Settings::setDefaultEquationForm( Settings::EnumDefaultEquationForm::Function ); else Settings::setDefaultEquationForm( Settings::EnumDefaultEquationForm::Implicit ); Settings::self()->save(); MainDlg::self()->requestSaveCurrentState(); functionListItem->update(); View::self()->drawPlot(); } //END class FunctionEditor //BEGIN class FunctionListWidget FunctionListWidget::FunctionListWidget( QWidget * parent ) : QListWidget( parent ) { setAcceptDrops(true); setDragEnabled(true); show(); } QMimeData * FunctionListWidget::mimeData( const QList items ) const { QDomDocument doc( QStringLiteral("kmpdoc") ); QDomElement root = doc.createElement( QStringLiteral("kmpdoc") ); doc.appendChild( root ); KmPlotIO io; - foreach ( QListWidgetItem * item, items ) + for ( QListWidgetItem * item : qAsConst(items) ) { int f = static_cast(item)->function(); if ( Function * function = XParser::self()->functionWithID( f ) ) io.addFunction( doc, root, function ); } QMimeData * md = new QMimeData; md->setData( QStringLiteral("text/kmplot"), doc.toByteArray() ); return md; } QStringList FunctionListWidget::mimeTypes() const { QStringList mt; mt << QStringLiteral("text/kmplot"); return mt; } void FunctionListWidget::dragEnterEvent( QDragEnterEvent * event ) { const QMimeData * md = event->mimeData(); if ( md->hasFormat( QStringLiteral("text/kmplot") ) ) event->acceptProposedAction(); } void FunctionListWidget::dropEvent( QDropEvent * event ) { const QMimeData * md = event->mimeData(); QDomDocument doc( QStringLiteral("kmpdoc") ); doc.setContent( md->data( QStringLiteral("text/kmplot") ) ); QDomElement element = doc.documentElement(); KmPlotIO io; for ( QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { if ( n.nodeName() == QLatin1String("function") ) io.parseFunction( n.toElement(), true ); else qWarning() << "Unexpected node with name " << n.nodeName() ; } } //END class FunctionListWidget //BEGIN class FunctionListItem FunctionListItem::FunctionListItem( QListWidget * parent, int function ) : QListWidgetItem( parent ) { m_function = function; assert( m_function != -1 ); update(); } void FunctionListItem::update() { Function * f = XParser::self()->functionWithID( m_function ); if ( !f ) { // The function was probably deleted return; } setText( f->name() ); setCheckState( f->plotAppearance( Function::Derivative0 ).visible ? Qt::Checked : Qt::Unchecked ); setForeground( f->plotAppearance( Function::Derivative0 ).color ); } //END class FunctionListItem diff --git a/kmplot/functiontools.cpp b/kmplot/functiontools.cpp index 0d6d068..a2fb100 100644 --- a/kmplot/functiontools.cpp +++ b/kmplot/functiontools.cpp @@ -1,238 +1,238 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 2004 Fredrik Edemar * 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "functiontools.h" #include "ui_functiontools.h" #include "view.h" #include "xparser.h" #include class FunctionToolsWidget : public QWidget, public Ui::FunctionTools { public: FunctionToolsWidget( QWidget * parent = 0 ) : QWidget( parent ) { setupUi(this); } }; //BEGIN class FunctionTools FunctionTools::FunctionTools(QWidget *parent ) : QDialog( parent ) { m_widget = new FunctionToolsWidget( this ); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::rejected, this, &FunctionTools::reject); QVBoxLayout *dialogLayout = new QVBoxLayout (this); dialogLayout->addWidget (m_widget); dialogLayout->addWidget (buttonBox); init( CalculateArea ); connect(m_widget->min, &EquationEdit::editingFinished, this, &FunctionTools::rangeEdited); connect(m_widget->max, &EquationEdit::editingFinished, this, &FunctionTools::rangeEdited); connect(m_widget->list, &QListWidget::currentRowChanged, this, &FunctionTools::equationSelected); } FunctionTools::~FunctionTools() { } void FunctionTools::init( Mode m ) { m_mode = m; switch ( m_mode ) { case FindMinimum: { m_widget->rangeTitle->setText( i18n("Search between:") ); setWindowTitle(i18n("Find Minimum Point")); break; } case FindMaximum: { m_widget->rangeTitle->setText( i18n("Search between:") ); setWindowTitle(i18n("Find Maximum Point")); break; } case CalculateArea: { m_widget->rangeTitle->setText( i18n("Calculate the area between:") ); setWindowTitle(i18n("Area Under Graph")); break; } } m_widget->min->setText( XParser::self()->number( View::self()->m_xmin ) ); m_widget->max->setText( XParser::self()->number( View::self()->m_xmax ) ); m_widget->min->setFocus(); updateEquationList(); setEquation( EquationPair( View::self()->m_currentPlot, 0 ) ); } void FunctionTools::updateEquationList() { EquationPair previousEquation = equation(); m_widget->list->clear(); m_equations.clear(); - foreach ( Function * function, XParser::self()->m_ufkt ) + for ( Function * function : qAsConst(XParser::self()->m_ufkt) ) { if ( function->type() != Function::Cartesian && function->type() != Function::Differential ) continue; QList plots = function->plots(); for ( int i = 0; i < function->eq.size(); ++i ) { - foreach ( const Plot &plot, plots ) + for ( const Plot &plot : qAsConst(plots) ) m_equations << EquationPair( plot, i ); } } - foreach ( const EquationPair &eq, m_equations ) + for ( const EquationPair &eq : qAsConst(m_equations) ) { Equation * equation = eq.first.function()->eq[ eq.second ]; QListWidgetItem * item = new QListWidgetItem( equation->fstr(), m_widget->list ); item->setForeground( eq.first.color() ); } setEquation( previousEquation ); } EquationPair FunctionTools::equation( ) const { int row = m_widget->list->currentRow(); if ( row < 0 || row >= m_equations.size() ) return EquationPair(); else return m_equations[ row ]; } void FunctionTools::setEquation( const EquationPair & equation ) { int row = m_equations.indexOf( equation); if ( row < 0 ) row = 0; m_widget->list->setCurrentRow( row ); equationSelected( row ); } void FunctionTools::equationSelected( int equation ) { if ( equation < 0 || equation >= m_equations.size() ) return; EquationPair current = m_equations[ equation ]; switch ( m_mode ) { case FindMinimum: findMinimum( current ); break; case FindMaximum: findMaximum( current ); break; case CalculateArea: calculateArea( current ); break; } } void FunctionTools::rangeEdited() { switch ( m_mode ) { case FindMinimum: findMinimum( equation() ); break; case FindMaximum: findMaximum( equation() ); break; case CalculateArea: calculateArea( equation() ); break; } } void FunctionTools::findMinimum( const EquationPair & equation ) { if ( !equation.first.function() ) return; QPointF extremum = View::self()->findMinMaxValue( equation.first, View::Minimum, m_widget->min->value(), m_widget->max->value() ); m_widget->rangeResult->setText( i18n("Minimum is at x = %1, %2(x) = %3", extremum.x(), equation.first.function()->eq[0]->name(), extremum.y() ) ); } void FunctionTools::findMaximum( const EquationPair & equation ) { if ( !equation.first.function() ) return; QPointF extremum = View::self()->findMinMaxValue( equation.first, View::Maximum, m_widget->min->value(), m_widget->max->value() ); m_widget->rangeResult->setText( i18n("Maximum is at x = %1, %2(x) = %3", extremum.x(), equation.first.function()->eq[0]->name(), extremum.y() ) ); } void FunctionTools::calculateArea( const EquationPair & equation ) { if ( !equation.first.function() ) return; IntegralDrawSettings s; s.plot = equation.first; s.dmin = m_widget->min->value(); s.dmax = m_widget->max->value(); double area = View::self()->areaUnderGraph( s ); m_widget->rangeResult->setText( i18n("Area is %1", area ) ); } //END class FunctionTools diff --git a/kmplot/initialconditionseditor.cpp b/kmplot/initialconditionseditor.cpp index f524fb8..c2b7ba7 100644 --- a/kmplot/initialconditionseditor.cpp +++ b/kmplot/initialconditionseditor.cpp @@ -1,335 +1,335 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "initialconditionseditor.h" #include "equationedit.h" #include "parser.h" #include #include //BEGIN helper functions /** * \return a pointer to the differential state for the given function and row. */ DifferentialState * differentialState( DifferentialStates * states, int row ) { if ( !states ) return 0; if ( row < 0 || row >= states->size() ) return 0; return & (*states)[row]; } /** * \return a pointer to the Value for the given function, row and column */ Value * value( DifferentialStates * states, int row, int column ) { DifferentialState * state = differentialState( states, row ); if ( !state ) return 0; if ( column == 0 ) return & state->x0; else return & state->y0[ column- 1 ]; } //END helper functions //BEGIN class InitialConditionsModel InitialConditionsModel::InitialConditionsModel( InitialConditionsEditor * parent ) : QAbstractTableModel( parent ) { m_parent = parent; } int InitialConditionsModel::rowCount( const QModelIndex & /*parent*/ ) const { return m_parent->differentialStates()->size(); } int InitialConditionsModel::columnCount( const QModelIndex & /*parent*/ ) const { return m_parent->differentialStates()->order()+1; } QVariant InitialConditionsModel::data( const QModelIndex & index, int role ) const { Value * v = value( m_parent->differentialStates(), index.row(), index.column() ); if ( !v ) return QVariant(); switch ( (Qt::ItemDataRole)role ) { case Qt::DisplayRole: case Qt::EditRole: case Qt::AccessibleTextRole: return v->expression(); case Qt::ToolTipRole: /// \todo return a description of the initial condition return QVariant(); case Qt::DecorationRole: case Qt::StatusTipRole: return QVariant(); case Qt::TextAlignmentRole: return Qt::AlignLeft; case Qt::TextColorRole: return QColor(Qt::black); case Qt::WhatsThisRole: case Qt::AccessibleDescriptionRole: case Qt::CheckStateRole: case Qt::BackgroundColorRole: case Qt::SizeHintRole: case Qt::FontRole: case Qt::UserRole: return QVariant(); default: return QVariant(); } } bool InitialConditionsModel::setData( const QModelIndex & index, const QVariant & variant, int role ) { if ( role != Qt::EditRole ) return false; Value * v = value( m_parent->differentialStates(), index.row(), index.column() ); if ( !v ) return false; v->updateExpression( variant.toString() ); emit dataChanged( index, index ); return true; } QVariant InitialConditionsModel::headerData( int section, Qt::Orientation orientation, int role ) const { Equation * eq = m_parent->equation(); if ( role != Qt::DisplayRole || !eq ) return QAbstractTableModel::headerData( section, orientation, role ); // Don't display row headers if ( orientation == Qt::Vertical ) return QVariant(); QString param; QStringList variables = eq->variables(); if ( variables.isEmpty() ) param = 'x'; else param = variables.first(); param += SubscriptZeroSymbol; if ( section == 0 ) return param; else return QStringLiteral( "%1%2(%3)" ) .arg( eq->name( true ) ) .arg( QString(), section-1, '\'' ) .arg( param ); } Qt::ItemFlags InitialConditionsModel::flags( const QModelIndex & /*index*/ ) const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } bool InitialConditionsModel::insertRows(int position, int rows, const QModelIndex & /*parent*/ ) { if ( !m_parent->differentialStates() ) return false; beginInsertRows(QModelIndex(), position, position+rows-1); for (int row = 0; row < rows; ++row) m_parent->differentialStates()->add(); endInsertRows(); return true; } bool InitialConditionsModel::removeRows(int position, int rows, const QModelIndex & /*parent*/ ) { beginRemoveRows(QModelIndex(), position, position+rows-1); m_parent->differentialStates()->remove( position, rows ); endRemoveRows(); return true; } //END class InitialConditionsModel //BEGIN class InitialConditionsView InitialConditionsView::InitialConditionsView( QWidget * parent ) : QTableView( parent ) { setSelectionMode( QAbstractItemView::ExtendedSelection ); setSelectionBehavior( QAbstractItemView::SelectRows ); horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch ); horizontalHeader()->setSectionsClickable( false ); verticalHeader()->hide(); } //END class InitialConditionsView //BEGIN class InitialConditionsDelegate InitialConditionsDelegate::InitialConditionsDelegate( InitialConditionsEditor * parent ) : QItemDelegate( parent ) { m_parent = parent; m_lastEditor = 0; } QWidget * InitialConditionsDelegate::createEditor( QWidget * parent, const QStyleOptionViewItem & /*option*/, const QModelIndex & index ) const { Value * v = value( m_parent->differentialStates(), index.row(), index.column() ); if ( !v ) return 0; m_lastEditor = new EquationEdit( parent ); connect(m_lastEditor, &EquationEdit::returnPressed, this, &InitialConditionsDelegate::equationEditDone); m_lastEditor->setFocus(); return m_lastEditor; } void InitialConditionsDelegate::equationEditDone() { emit commitData( m_lastEditor ); emit closeEditor( m_lastEditor ); } void InitialConditionsDelegate::setEditorData( QWidget * editor, const QModelIndex & index ) const { QString expression = index.model()->data( index, Qt::DisplayRole ).toString(); static_cast(editor)->setText( expression ); } void InitialConditionsDelegate::setModelData( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const { EquationEdit * edit = static_cast(editor); model->setData( index, edit->text() ); } void InitialConditionsDelegate::updateEditorGeometry( QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */ ) const { editor->setGeometry(option.rect); } //END class InitialConditionsDelegate //BEGIN class InitialConditionsEditor InitialConditionsEditor::InitialConditionsEditor( QWidget * parent ) : QWidget( parent ) { m_equation = 0; setupUi( this ); layout()->setMargin( 0 ); connect(addButton, &QPushButton::clicked, this, &InitialConditionsEditor::add); connect(removeButton, &QPushButton::clicked, this, &InitialConditionsEditor::remove); m_model = new InitialConditionsModel( this ); view->setModel( m_model ); view->setItemDelegate( new InitialConditionsDelegate( this ) ); connect(m_model, &InitialConditionsModel::dataChanged, this, &InitialConditionsEditor::dataChanged); } void InitialConditionsEditor::setOrder( int order ) { m_model->beginResetModel(); m_states.setOrder( order ); m_model->endResetModel(); } void InitialConditionsEditor::init( Function * function ) { m_model->beginResetModel(); if ( function ) { m_equation = function->eq[0]; m_states = m_equation->differentialStates; } else { m_equation = 0; } m_model->endResetModel(); } void InitialConditionsEditor::add() { m_model->insertRows( 0, 1, QModelIndex() ); emit dataChanged(); } void InitialConditionsEditor::remove() { - QModelIndexList selected = view->selectionModel()->selectedIndexes(); + const QModelIndexList selected = view->selectionModel()->selectedIndexes(); QMap< int, void * > sorted; - foreach ( const QModelIndex &index, selected ) + for ( const QModelIndex &index : selected ) sorted.insert( -index.row(), 0l ); - QList indexes = sorted.keys(); + const QList indexes = sorted.keys(); - foreach ( int row, indexes ) + for ( int row : indexes ) m_model->removeRows( -row, 1, QModelIndex() ); emit dataChanged(); } //END class InitialConditionsEditor diff --git a/kmplot/kgradientdialog.cpp b/kmplot/kgradientdialog.cpp index 8b9f90b..f3051e8 100644 --- a/kmplot/kgradientdialog.cpp +++ b/kmplot/kgradientdialog.cpp @@ -1,553 +1,553 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "kgradientdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include const double SQRT_3 = 1.732050808; const double ArrowLength = 8; const double ArrowHalfWidth = ArrowLength/SQRT_3; //BEGIN class KGradientEditor KGradientEditor::KGradientEditor( QWidget * parent ) : QWidget( parent ) { m_haveArrow = false; m_clickOffset = 0; m_orientation = Qt::Horizontal; findGradientStop(); } KGradientEditor::~KGradientEditor() { } void KGradientEditor::setGradient( const QGradient & gradient ) { if ( m_gradient == gradient ) return; setGradient( gradient.stops() ); findGradientStop(); } void KGradientEditor::setColor( const QColor & color ) { // Hmm...why doesn't qvector have some sortof search / replace functionality? QGradientStops stops = m_gradient.stops(); for ( int i = 0; i < stops.size(); ++i ) { if ( stops[i] != m_currentStop ) continue; if ( stops[i].second == color ) return; m_currentStop.second = color; stops[i] = m_currentStop; break; } setGradient( stops ); } QSize KGradientEditor::minimumSizeHint() const { double w = 3 * ArrowHalfWidth; double h = 12 + ArrowLength; if ( m_orientation == Qt::Vertical ) qSwap( w, h ); return QSizeF( w, h ).toSize(); } void KGradientEditor::paintEvent( QPaintEvent * ) { QPainter painter( this ); //BEGIN draw gradient QRectF r; QLinearGradient lg; if ( m_orientation == Qt::Horizontal ) { lg = QLinearGradient( 0, 0, width(), 0 ); r = QRectF( ArrowHalfWidth-1, 0, width() - 2*ArrowHalfWidth + 1, height()-ArrowLength ); } else { lg = QLinearGradient( 0, 0, 0, height() ); r = QRectF( 0, ArrowHalfWidth-1, width()-ArrowLength, height() - 2*ArrowHalfWidth + 1 ); } lg.setStops( m_gradient.stops() ); painter.setBrush( lg ); painter.setPen( QPen( Qt::black, 1 ) ); painter.drawRect( r ); //END draw gradient //BEGIN draw arrows painter.setRenderHint( QPainter::Antialiasing, true ); - QGradientStops stops = m_gradient.stops(); - foreach ( const QGradientStop &stop, stops ) + const QGradientStops stops = m_gradient.stops(); + for ( const QGradientStop &stop : stops ) drawArrow( & painter, stop ); //END draw arrows } void KGradientEditor::drawArrow( QPainter * painter, const QGradientStop & stop ) { QPolygonF arrow(3); double mid = toArrowPos( stop.first ); if ( m_orientation == Qt::Horizontal ) { arrow[0] = QPointF( mid, height()-ArrowLength+0.5 ); arrow[1] = QPointF( mid+ArrowHalfWidth, height()-0.5 ); arrow[2] = QPointF( mid-ArrowHalfWidth, height()-0.5 ); } else { arrow[0] = QPointF( width()-ArrowLength+0.5, mid ); arrow[1] = QPointF( width()-0.5, mid+ArrowHalfWidth ); arrow[2] = QPointF( width()-0.5, mid-ArrowHalfWidth ); } bool selected = (stop == m_currentStop); QColor color( selected ? palette().color( QPalette::Dark ) : Qt::black ); painter->setPen( color ); painter->setBrush( stop.second ); painter->drawPolygon( arrow ); } void KGradientEditor::contextMenuEvent( QContextMenuEvent * e ) { // Prevent the "QWhatsThis" menu from popping up when right-clicking e->accept(); } void KGradientEditor::removeStop() { QGradientStops stops = m_gradient.stops(); for ( int i = 0; i < stops.size(); ++i ) { if ( stops[i] != m_currentStop ) continue; stops.remove( i ); break; } setGradient( stops ); findGradientStop(); } void KGradientEditor::mousePressEvent( QMouseEvent * e ) { if ( !getGradientStop( e->pos() ) ) return; e->accept(); if ( e->button() == Qt::RightButton ) removeStop(); else m_haveArrow = true; } bool KGradientEditor::getGradientStop( const QPoint & point ) { double dl; // the vertical (for horizontal layout) distance from the tip of the arrows if ( m_orientation == Qt::Horizontal ) dl = point.y() - (height() - ArrowLength); else dl = point.x() - (width() - ArrowLength); // Is the arrow in the strip? if ( dl < 0 ) return false; QGradientStops stops = m_gradient.stops(); // Iterate over stops in reverse as the last stops are displayed on top of // the first stops. for ( int i = stops.size()-1; i >= 0; --i ) { QGradientStop stop = stops[i]; double pos = toArrowPos( stop.first ); // Is the click inside the arrow? double lower = pos - dl*(ArrowHalfWidth/ArrowLength); double upper = pos + dl*(ArrowHalfWidth/ArrowLength); double x = (m_orientation == Qt::Horizontal) ? point.x() : point.y(); if ( x < lower || x > upper ) continue; // Is inside arrow! :) m_clickOffset = x - pos; setCurrentStop( stop ); return true; } return false; } void KGradientEditor::mouseMoveEvent( QMouseEvent * e ) { if ( !m_haveArrow ) return; e->accept(); QPoint point = e->pos(); // Hmm...why doesn't qvector have some sortof search / replace functionality? QGradientStops stops = m_gradient.stops(); for ( int i = 0; i < stops.size(); ++i ) { if ( stops[i] != m_currentStop ) continue; double x = (m_orientation == Qt::Horizontal) ? point.x() : point.y(); m_currentStop.first = fromArrowPos( x-m_clickOffset ); stops[i] = m_currentStop; break; } setGradient( stops ); } void KGradientEditor::mouseReleaseEvent( QMouseEvent * ) { m_haveArrow = false; } void KGradientEditor::mouseDoubleClickEvent( QMouseEvent * e ) { e->accept(); if ( getGradientStop( e->pos() ) ) return; // Create new stop QPoint point = e->pos(); double pos = fromArrowPos( (m_orientation == Qt::Horizontal) ? point.x() : point.y() ); QGradientStop stop; stop.first = pos; stop.second = Qt::red; QGradientStops stops = m_gradient.stops(); stops << stop; setGradient( stops ); setCurrentStop( stop ); } void KGradientEditor::setOrientation( Qt::Orientation orientation ) { m_orientation = orientation; update(); } void KGradientEditor::findGradientStop() { QGradientStops stops = m_gradient.stops(); // The QGradientStops should always have at least one stop in, since // QGradient returns a Black->White gradient if its stops are empty. Q_ASSERT( !stops.isEmpty() ); // Pick a stop in the center setCurrentStop( stops[ stops.size()/2 ] ); } void KGradientEditor::setCurrentStop( const QGradientStop & stop ) { if ( m_currentStop == stop ) return; bool colorChanged = stop.second != m_currentStop.second; m_currentStop = stop; update(); if ( colorChanged ) emit colorSelected( stop.second ); } void KGradientEditor::setGradient( const QGradientStops & stops ) { if ( stops == m_gradient.stops() ) return; m_gradient.setStops( stops ); update(); emit gradientChanged( m_gradient ); } double KGradientEditor::toArrowPos( double stop ) const { double l = (m_orientation == Qt::Horizontal) ? width() : height(); l -= 2*ArrowHalfWidth; return stop*l + ArrowHalfWidth; } double KGradientEditor::fromArrowPos( double pos ) const { double l = (m_orientation == Qt::Horizontal) ? width() : height(); l -= 2*ArrowHalfWidth; double stop = (pos - ArrowHalfWidth) / l; if ( stop < 0 ) stop = 0; else if ( stop > 1 ) stop = 1; return stop; } //END class KGradientEditor //BEGIN class KGradientDialog KGradientDialog::KGradientDialog( QWidget * parent, bool modal ) : QDialog( parent ) { QWidget * widget = new QWidget( this ); m_gradient = new KGradientEditor( widget ); m_colorDialog = new QColorDialog( widget ); m_colorDialog->setWindowFlags( Qt::Widget ); m_colorDialog->setOptions( QColorDialog::DontUseNativeDialog | QColorDialog::NoButtons ); QLabel * label = new QLabel( i18n("(Double-click on the gradient to add a stop)"), widget ); QPushButton * button = new QPushButton( i18n("Remove stop"), widget ); connect( button, &QPushButton::clicked, m_gradient, &KGradientEditor::removeStop ); QDialogButtonBox *buttonBox = new QDialogButtonBox(modal ? QDialogButtonBox::Ok|QDialogButtonBox::Cancel : QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &KGradientDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &KGradientDialog::reject); //BEGIN layout widgets QVBoxLayout * layout = new QVBoxLayout( this ); layout->setMargin( 0 ); m_gradient->setFixedHeight( 24 ); layout->addWidget( m_gradient ); QHBoxLayout * hLayout = new QHBoxLayout; hLayout->addWidget( label ); hLayout->addStretch( 1 ); hLayout->addWidget( button ); layout->addLayout( hLayout ); layout->addWidget( m_colorDialog ); layout->addWidget( buttonBox ); resize( layout->minimumSize() ); //END layout widgets setWindowTitle( i18n("Choose a Gradient") ); setModal( modal ); connect( m_gradient, &KGradientEditor::colorSelected, m_colorDialog, &QColorDialog::setCurrentColor ); connect( m_colorDialog, &QColorDialog::currentColorChanged, m_gradient, &KGradientEditor::setColor ); connect( m_gradient, &KGradientEditor::gradientChanged, this, &KGradientDialog::gradientChanged ); m_colorDialog->setCurrentColor( m_gradient->color() ); } KGradientDialog::~KGradientDialog() { } // static int KGradientDialog::getGradient( QGradient & gradient, QWidget * parent ) { QPointer dlg = new KGradientDialog( parent, true ); dlg->setGradient( gradient ); int result = dlg->exec(); if ( result == Accepted ) gradient = dlg->gradient(); delete dlg; return result; } void KGradientDialog::setGradient( const QGradient & gradient ) { m_gradient->setGradient( gradient ); } QGradient KGradientDialog::gradient() const { return m_gradient->gradient(); } //END class KGradientDialog //BEGIN class KGradientButton KGradientButton::KGradientButton( QWidget * parent ) : QPushButton( parent ) { connect( this, &KGradientButton::clicked, this, &KGradientButton::chooseGradient ); } KGradientButton::~KGradientButton() { } void KGradientButton::initStyleOption( QStyleOptionButton * opt ) const { opt->init(this); opt->text.clear(); opt->icon = QIcon(); opt->features = QStyleOptionButton::None; } QSize KGradientButton::sizeHint() const { QStyleOptionButton opt; initStyleOption(&opt); return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(40, 15), this). expandedTo(QApplication::globalStrut()); } void KGradientButton::setGradient( const QGradient & gradient ) { if ( m_gradient.stops() == gradient.stops() ) return; m_gradient.setStops( gradient.stops() ); emit gradientChanged( m_gradient ); } void KGradientButton::chooseGradient() { int result = KGradientDialog::getGradient( m_gradient, this ); if ( result == KGradientDialog::Accepted ) emit gradientChanged( m_gradient ); } void KGradientButton::paintEvent( QPaintEvent * ) { // Mostly copied verbatim from KColorButton - thanks! :) QPainter painter(this); // First, we need to draw the bevel. QStyleOptionButton butOpt; initStyleOption(&butOpt); style()->drawControl( QStyle::CE_PushButtonBevel, &butOpt, &painter, this ); // OK, now we can muck around with drawing out pretty little color box // First, sort out where it goes QRect labelRect = style()->subElementRect( QStyle::SE_PushButtonContents, &butOpt, this ); int shift = style()->pixelMetric( QStyle::PM_ButtonMargin ); labelRect.adjust(shift, shift, -shift, -shift); int x, y, w, h; labelRect.getRect(&x, &y, &w, &h); if (isChecked() || isDown()) { x += style()->pixelMetric( QStyle::PM_ButtonShiftHorizontal ); y += style()->pixelMetric( QStyle::PM_ButtonShiftVertical ); } qDrawShadePanel( &painter, x, y, w, h, palette(), true, 1, NULL); if ( isEnabled() ) { QLinearGradient lg( x+1, 0, x+w-1, 0 ); lg.setStops( m_gradient.stops() ); painter.setBrush( lg ); } else painter.setBrush( palette().color(backgroundRole()) ); painter.drawRect( x+1, y+1, w-2, h-2 ); if ( hasFocus() ) { QRect focusRect = style()->subElementRect( QStyle::SE_PushButtonFocusRect, &butOpt, this ); QStyleOptionFocusRect focusOpt; focusOpt.init(this); focusOpt.rect = focusRect; focusOpt.backgroundColor = palette().window().color(); style()->drawPrimitive( QStyle::PE_FrameFocusRect, &focusOpt, &painter, this ); } } //END class KGradientButton diff --git a/kmplot/kmplot.cpp b/kmplot/kmplot.cpp index 38cfbe9..1d851ce 100644 --- a/kmplot/kmplot.cpp +++ b/kmplot/kmplot.cpp @@ -1,289 +1,290 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 2004 Fredrik Edemar * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "kmplot.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "maindlg.h" #include #include "kmplotadaptor.h" #include "view.h" static QUrl urlFromArg(const QString &arg) { #if QT_VERSION >= 0x050400 return QUrl::fromUserInput(arg, QDir::currentPath(), QUrl::AssumeLocalFile); #else // Logic from QUrl::fromUserInput(QString, QString, UserInputResolutionOptions) return (QUrl(arg, QUrl::TolerantMode).isRelative() && !QDir::isAbsolutePath(arg)) ? QUrl::fromLocalFile(QDir::current().absoluteFilePath(arg)) : QUrl::fromUserInput(arg); #endif } KmPlot::KmPlot( const QCommandLineParser& parser ) : KParts::MainWindow() { setObjectName( QStringLiteral("KmPlot") ); // set the shell's ui resource file setXMLFile(QStringLiteral("kmplot_shell.rc")); // then, setup our actions setupActions(); // setup the status bar setupStatusBar(); // this routine will find and load our Part. it finds the Part by // name which is a bad idea usually.. but it's alright in this // case since our Part is made for this Shell KPluginFactory *factory = KPluginLoader(QStringLiteral("kmplotpart")).factory(); if (factory) { // now that the Part is loaded, we cast it to a Part to get // our hands on it m_part = factory->create(this); if (m_part) { // tell the KParts::MainWindow that this is indeed the main widget setCentralWidget(m_part->widget()); //m_part->widget()->setFocus(); // and integrate the part's GUI with the shell's setupGUI(Keys | ToolBar | Save); createGUI(m_part); } } else { // if we couldn't find our Part, we exit since the Shell by // itself can't do anything useful KMessageBox::error(this, i18n("Could not find KmPlot's part.")); qApp->quit(); // we return here, cause qApp->quit() only means "exit the // next time we enter the event loop... return; } // apply the saved mainwindow settings, if any, and ask the mainwindow // to automatically save settings if changed: window size, toolbar // position, icon size, etc. setAutoSaveSettings(); { bool exit = false; - bool first = true; - foreach(const QString& arg, parser.positionalArguments()) + bool first = true; + const auto arguments = parser.positionalArguments(); + for (const QString& arg : arguments) { QUrl url = urlFromArg(arg); if (first) { exit = !load(url); } else openFileInNewWindow( url ); } if (exit) deleteLater(); // couldn't open the file, and therefore exit first = false; } show(); new KmPlotAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/kmplot"), this); if ( parser.isSet(QStringLiteral("function")) ) { QString f = parser.value(QStringLiteral("function")); QDBusReply reply = QDBusInterface( QDBusConnection::sessionBus().baseService(), QStringLiteral("/parser"), QStringLiteral("org.kde.kmplot.Parser")).call( QDBus::BlockWithGui, QStringLiteral("addFunction"), f, "" ); } } KmPlot::~KmPlot() {} void KmPlot::slotUpdateFullScreen( bool checked) { if (checked) { KToggleFullScreenAction::setFullScreen( this, true ); //m_fullScreen->plug( toolBar( "mainToolBar" ) ); deprecated annma 2006-03-01 } else { KToggleFullScreenAction::setFullScreen( this, false ); //m_fullScreen->unplug( toolBar( "mainToolBar" ) ); deprecated annma 2006-03-01 } } bool KmPlot::load(const QUrl& url) { m_part->openUrl( url ); if (m_part->url().isEmpty()) return false; setWindowTitle(url.toDisplayString()); return true; } void KmPlot::setupActions() { KStandardAction::openNew(this, SLOT(fileNew()), actionCollection()); KStandardAction::open(this, SLOT(fileOpen()), actionCollection()); KStandardAction::quit(this, SLOT(close()), actionCollection()); createStandardStatusBarAction(); setStandardToolBarMenuEnabled(true); m_fullScreen = KStandardAction::fullScreen( NULL, NULL, this, actionCollection()); actionCollection()->addAction(QStringLiteral("fullscreen"), m_fullScreen); connect(m_fullScreen, &KToggleFullScreenAction::toggled, this, &KmPlot::slotUpdateFullScreen); } void KmPlot::fileNew() { // About this function, the style guide ( // http://developer.kde.org/documentation/standards/kde/style/basics/index.html ) // says that it should open a new window if the document is _not_ // in its initial state. This is what we do here.. if ( !m_part->url().isEmpty() || isModified() ) //KToolInvocation::startServiceByDesktopName("kmplot"); KToolInvocation::kdeinitExec(QStringLiteral("kmplot")); } void KmPlot::applyNewToolbarConfig() { applyMainWindowSettings(KSharedConfig::openConfig()->group( QString() )); } void KmPlot::fileOpen() { // this slot is called whenever the File->Open menu is selected, // the Open shortcut is pressed (usually CTRL+O) or the Open toolbar // button is clicked QUrl const url = QFileDialog::getOpenFileUrl(this, i18n( "Open" ), QUrl::fromLocalFile(QDir::currentPath()), i18n( "KmPlot Files (*.fkt);;All Files (*)")); if ( !url.isEmpty()) { // About this function, the style guide ( // http://developer.kde.org/documentation/standards/kde/style/basics/index.html ) // says that it should open a new window if the document is _not_ // in its initial state. This is what we do here.. if ( m_part->url().isEmpty() && !isModified() ) load( url ); // we open the file in this window... else openFileInNewWindow(url); // we open the file in a new window... } } void KmPlot::fileOpen(const QUrl &url) { if ( !url.isEmpty()) { // About this function, the style guide ( // http://developer.kde.org/documentation/standards/kde/style/basics/index.html ) // says that it should open a new window if the document is _not_ // in its initial state. This is what we do here.. if ( m_part->url().isEmpty() && !isModified() ) load(url); // we open the file in this window... else openFileInNewWindow(url); // we open the file in a new window... } } void KmPlot::openFileInNewWindow(const QUrl &url) { KToolInvocation::kdeinitExec(QStringLiteral("kmplot"), QStringList() << url.url()); } bool KmPlot::isModified() { QDBusReply reply = QDBusInterface( QDBusConnection::sessionBus().baseService(), QStringLiteral("/maindlg"), QStringLiteral("org.kde.kmplot.MainDlg")).call( QDBus::BlockWithGui, QStringLiteral("isModified") ); return reply.value(); } bool KmPlot::queryClose() { return m_part->queryClose(); } void KmPlot::setStatusBarText(const QString &text, int id) { static_cast(statusBarLabels.at(id))->setText(text); } void KmPlot::setupStatusBar() { QStatusBar *statusBar = new QStatusBar(this); setStatusBar(statusBar); for (int i = 0; i < View::SectionCount; ++i) { QLabel *label = new QLabel (statusBar); label->setFixedHeight (label->fontMetrics ().height () + 2); /// Labels for coordinates should be of fixed width 16 chars to be the same as for good old KmPlot if ( i < 2) { label->setFixedWidth ( label->fontMetrics().boundingRect(QLatin1Char('8')).width() * 16 ); label->setAlignment( Qt::AlignCenter ); } else { label->setAlignment( Qt::AlignLeft ); } statusBar->addWidget (label); statusBarLabels.append (label); } m_progressBar = new KmPlotProgress( statusBar ); m_progressBar->setMaximumHeight( statusBar->height()-10 ); connect(m_progressBar, &KmPlotProgress::cancelDraw, this, &KmPlot::cancelDraw); statusBar->addWidget(m_progressBar); } void KmPlot::setDrawProgress( double progress ) { m_progressBar->setProgress( progress ); } void KmPlot::cancelDraw() { QDBusInterface( QDBusConnection::sessionBus().baseService(), QStringLiteral("/kmplot"), QStringLiteral("org.kde.kmplot.KmPlot") ).call( QDBus::NoBlock, QStringLiteral("stopDrawing") ); } diff --git a/kmplot/kmplotio.cpp b/kmplot/kmplotio.cpp index 734d417..caafa31 100644 --- a/kmplot/kmplotio.cpp +++ b/kmplot/kmplotio.cpp @@ -1,861 +1,861 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 1998, 1999, 2000, 2002 Klaus-Dieter Möller * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "kmplotio.h" // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include // ANSI-C includes #include // local includes #include "maindlg.h" #include "settings.h" #include "xparser.h" static QString CurrentVersionString( "4" ); class XParser; KmPlotIO::KmPlotIO() { KmPlotIO::version = CurrentVersionString.toInt(); lengthScaler = 1.0; } KmPlotIO::~KmPlotIO() { } QDomDocument KmPlotIO::currentState() { // saving as xml by a QDomDocument QDomDocument doc( "kmpdoc" ); // the root tag QDomElement root = doc.createElement( "kmpdoc" ); root.setAttribute( "version", CurrentVersionString ); doc.appendChild( root ); // the axes tag QDomElement tag = doc.createElement( "axes" ); tag.setAttribute( "color", Settings::axesColor().name() ); tag.setAttribute( "width", Settings::axesLineWidth() ); tag.setAttribute( "tic-width", Settings::ticWidth() ); tag.setAttribute( "tic-legth", Settings::ticLength() ); addTag( doc, tag, "show-axes", Settings::showAxes() ? "1" : "-1" ); addTag( doc, tag, "show-arrows", Settings::showArrows() ? "1" : "-1" ); addTag( doc, tag, "show-label", Settings::showLabel() ? "1" : "-1" ); addTag( doc, tag, "xmin", Settings::xMin() ); addTag( doc, tag, "xmax", Settings::xMax() ); addTag( doc, tag, "ymin", Settings::yMin() ); addTag( doc, tag, "ymax", Settings::yMax() ); root.appendChild( tag ); tag = doc.createElement( "grid" ); tag.setAttribute( "color", Settings::gridColor().name() ); tag.setAttribute( "width", Settings::gridLineWidth() ); addTag( doc, tag, "mode", QString::number( Settings::gridStyle() ) ); root.appendChild( tag ); tag = doc.createElement( "scale" ); addTag( doc, tag, "tic-x-mode", QString::number( Settings::xScalingMode() ) ); addTag( doc, tag, "tic-y-mode", QString::number( Settings::yScalingMode() ) ); addTag( doc, tag, "tic-x", Settings::xScaling() ); addTag( doc, tag, "tic-y", Settings::yScaling() ); root.appendChild( tag ); - foreach ( Function *f, XParser::self()->m_ufkt ) + for ( Function *f : qAsConst(XParser::self()->m_ufkt) ) addFunction( doc, root, f ); addConstants( doc, root ); tag = doc.createElement( "fonts" ); addTag( doc, tag, "axes-font", Settings::axesFont().family() ); addTag( doc, tag, "label-font", Settings::labelFont().family() ); addTag( doc, tag, "header-table-font", Settings::headerTableFont().family() ); root.appendChild( tag ); return doc; } bool KmPlotIO::save( const QUrl &url ) { QDomDocument doc = currentState(); if (!url.isLocalFile() ) { QTemporaryFile tmpfile; if ( !tmpfile.open() ) { qWarning() << "Could not open " << QUrl( tmpfile.fileName() ).toLocalFile() << " for writing.\n"; return false; } QTextStream ts( &tmpfile ); doc.save( ts, 4 ); ts.flush(); Q_CONSTEXPR int permission = -1; QFile file(tmpfile.fileName()); file.open(QIODevice::ReadOnly); KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite); if (!putjob->exec()) { qWarning() << "Could not open " << url.toString() << " for writing ("<errorString()<<").\n"; return false; } file.close(); } else { QFile xmlfile (url.toLocalFile()); if (!xmlfile.open( QIODevice::WriteOnly ) ) { qWarning() << "Could not open " << url.path() << " for writing.\n"; return false; } QTextStream ts( &xmlfile ); doc.save( ts, 4 ); xmlfile.close(); return true; } return true; } void KmPlotIO::addConstants( QDomDocument & doc, QDomElement & root ) { ConstantList constants = XParser::self()->constants()->list( Constant::Document ); for ( ConstantList::iterator it = constants.begin(); it != constants.end(); ++it ) { QDomElement tag = doc.createElement( "constant" ); root.appendChild( tag ); tag.setAttribute( "name", it.key() ); tag.setAttribute( "value", it.value().value.expression() ); } } void KmPlotIO::addFunction( QDomDocument & doc, QDomElement & root, Function * function ) { QDomElement tag = doc.createElement( "function" ); QString names[] = { "f0", "f1", "f2", "integral" }; PlotAppearance * plots[] = { & function->plotAppearance( Function::Derivative0 ), & function->plotAppearance( Function::Derivative1 ), & function->plotAppearance( Function::Derivative2 ), & function->plotAppearance( Function::Integral ) }; for ( int i = 0; i < 4; ++i ) { tag.setAttribute( QString("%1-width").arg( names[i] ), plots[i]->lineWidth ); tag.setAttribute( QString("%1-color").arg( names[i] ), plots[i]->color.name() ); tag.setAttribute( QString("%1-use-gradient").arg( names[i] ), plots[i]->useGradient ); tag.setAttribute( QString("%1-gradient").arg( names[i] ), gradientToString( plots[i]->gradient.stops() ) ); tag.setAttribute( QString("%1-show-tangent-field").arg( names[i] ), plots[i]->showTangentField ); tag.setAttribute( QString("%1-visible").arg( names[i] ), plots[i]->visible ); tag.setAttribute( QString("%1-style").arg( names[i] ), PlotAppearance::penStyleToString( plots[i]->style ) ); tag.setAttribute( QString("%1-show-extrema").arg( names[i] ), plots[i]->showExtrema ); tag.setAttribute( QString("%1-show-plot-name").arg( names[i] ), plots[i]->showPlotName ); } //BEGIN parameters tag.setAttribute( "use-parameter-slider", function->m_parameters.useSlider ); tag.setAttribute( "parameter-slider", function->m_parameters.sliderID ); tag.setAttribute( "use-parameter-list", function->m_parameters.useList ); QStringList str_parameters; - foreach ( const Value &k, function->m_parameters.list ) + for ( const Value &k : qAsConst(function->m_parameters.list) ) str_parameters << k.expression(); if( !str_parameters.isEmpty() ) addTag( doc, tag, "parameter-list", str_parameters.join( ";" ) ); //END parameters tag.setAttribute( "type", Function::typeToString( function->type() ) ); for ( int i=0; i< function->eq.size(); ++i ) { Equation * equation = function->eq[i]; QString fstr = equation->fstr(); if ( fstr.isEmpty() ) continue; QDomElement element = addTag( doc, tag, QString("equation-%1").arg(i), fstr ); element.setAttribute( "step", equation->differentialStates.step().expression() ); for ( int i = 0; i < equation->differentialStates.size(); ++i ) { DifferentialState * state = & equation->differentialStates[i]; QDomElement differential = doc.createElement( "differential" ); element.appendChild( differential ); bool first = true; QString ys; - foreach ( const Value &y, state->y0 ) + for ( const Value &y : qAsConst(state->y0) ) { if ( !first ) ys += ';'; first = false; ys += y.expression(); } differential.setAttribute( "x", state->x0.expression() ); differential.setAttribute( "y", ys ); } } addTag( doc, tag, "arg-min", function->dmin.expression() ).setAttribute( "use", function->usecustomxmin ); addTag( doc, tag, "arg-max", function->dmax.expression() ).setAttribute( "use", function->usecustomxmax ); root.appendChild( tag ); } QDomElement KmPlotIO::addTag( QDomDocument &doc, QDomElement &parentTag, const QString &tagName, const QString &tagValue ) { QDomElement tag = doc.createElement( tagName ); QDomText value = doc.createTextNode( tagValue ); tag.appendChild( value ); parentTag.appendChild( tag ); return tag; } bool KmPlotIO::restore( const QDomDocument & doc ) { // temporary measure: for now, delete all previous functions XParser::self()->removeAllFunctions(); QDomElement element = doc.documentElement(); QString versionString = element.attribute( "version" ); if ( versionString.isNull()) //an old kmplot-file { MainDlg::oldfileversion = true; for ( QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { version = 0; lengthScaler = 0.1; if ( n.nodeName() == "axes" ) parseAxes( n.toElement() ); if ( n.nodeName() == "grid" ) parseGrid( n.toElement() ); if ( n.nodeName() == "scale" ) parseScale( n.toElement() ); if ( n.nodeName() == "function" ) oldParseFunction( n.toElement() ); } } else if ( versionString == "1" || versionString == "2" || versionString == "3" || versionString == "4" ) { MainDlg::oldfileversion = false; version = versionString.toInt(); lengthScaler = (version < 3) ? 0.1 : 1.0; for ( QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) { if ( n.nodeName() == "axes" ) parseAxes( n.toElement() ); else if ( n.nodeName() == "grid" ) parseGrid( n.toElement() ); else if ( n.nodeName() == "scale" ) parseScale( n.toElement() ); else if ( n.nodeName() == "constant" ) parseConstant( n.toElement() ); else if ( n.nodeName() == "function") { if ( version < 3 ) oldParseFunction2( n.toElement() ); else parseFunction( n.toElement() ); } } } else { KMessageBox::sorry(0,i18n("The file had an unknown version number")); return false; } // Because we may not have loaded the constants / functions in the right order // to account for dependencies XParser::self()->reparseAllFunctions(); return true; } bool KmPlotIO::load( const QUrl &url ) { QDomDocument doc( "kmpdoc" ); QFile f; bool downloadedFile = false; if ( !url.isLocalFile() ) { if( !MainDlg::fileExists( url ) ) { KMessageBox::sorry(0,i18n("The file does not exist.")); return false; } downloadedFile = true; KIO::StoredTransferJob *transferjob = KIO::storedGet (url); KJobWidgets::setWindow(transferjob, 0); if ( !transferjob->exec() ) { KMessageBox::sorry(0,i18n("An error appeared when opening this file (%1)", transferjob->errorString() )); return false; } QTemporaryFile file; file.setAutoRemove(false); file.open(); file.write(transferjob->data()); f.setFileName(file.fileName()); file.close(); } else f.setFileName( url.toLocalFile() ); if ( !f.open( QIODevice::ReadOnly ) ) { KMessageBox::sorry(0,i18n("%1 could not be opened", f.fileName() ) ); return false; } QString errorMessage; int errorLine, errorColumn; if ( !doc.setContent( &f, & errorMessage, & errorLine, & errorColumn ) ) { KMessageBox::sorry(0,i18n("%1 could not be loaded (%2 at line %3, column %4)", f.fileName(), errorMessage, errorLine, errorColumn ) ); f.close(); return false; } f.close(); if ( !restore( doc ) ) return false; if ( downloadedFile ) QFile::remove( f.fileName() ); return true; } void KmPlotIO::parseConstant( const QDomElement & n ) { QString name = n.attribute( "name" ); QString value = n.attribute( "value" ); /// \todo how to handle overwriting constants, etc? Constant c; c.value.updateExpression( value ); c.type = Constant::Document; if ( XParser::self()->constants()->list( Constant::Global ).contains( name ) ) c.type |= Constant::Global; XParser::self()->constants()->add( name, c ); } void KmPlotIO::parseAxes( const QDomElement &n ) { Settings::setAxesLineWidth( n.attribute( "width", (version<3) ? "2" : "0.2" ).toDouble() * lengthScaler ); Settings::setAxesColor( QColor( n.attribute( "color", "#000000" ) ) ); Settings::setTicWidth( n.attribute( "tic-width", (version<3) ? "3" : "0.3" ).toDouble() * lengthScaler ); Settings::setTicLength( n.attribute( "tic-length", (version<3) ? "5" : "0.5" ).toDouble() * lengthScaler ); if ( version < 1 ) { Settings::setShowAxes( true ); Settings::setShowArrows( true ); Settings::setShowLabel( true ); } else { Settings::setShowAxes( n.namedItem( "show-axes" ).toElement().text().toInt() == 1 ); Settings::setShowArrows( n.namedItem( "show-arrows" ).toElement().text().toInt() == 1 ); Settings::setShowLabel( n.namedItem( "show-label" ).toElement().text().toInt() == 1 ); } Settings::setXMin( n.namedItem( "xmin" ).toElement().text() ); Settings::setXMax( n.namedItem( "xmax" ).toElement().text() ); Settings::setYMin( n.namedItem( "ymin" ).toElement().text() ); Settings::setYMax( n.namedItem( "ymax" ).toElement().text() ); } void KmPlotIO::parseGrid( const QDomElement & n ) { Settings::setGridColor( QColor( n.attribute( "color", "#c0c0c0" ) ) ); Settings::setGridLineWidth( n.attribute( "width", (version<3) ? "1" : "0.1" ).toDouble() * lengthScaler ); Settings::setGridStyle( n.namedItem( "mode" ).toElement().text().toInt() ); } int unit2index( const QString &unit ) { QString units[ 9 ] = { "10", "5", "2", "1", "0.5", "pi/2", "pi/3", "pi/4",i18n("automatic") }; int index = 0; while( ( index < 9 ) && ( unit!= units[ index ] ) ) index ++; if( index == 9 ) index = -1; return index; } void KmPlotIO::parseScale(const QDomElement & n ) { #if 0 if ( version < 1 ) { Settings::setXScaling( unit2index( n.namedItem( "tic-x" ).toElement().text() ) ); Settings::setYScaling( unit2index( n.namedItem( "tic-y" ).toElement().text() ) ); Settings::setXPrinting( unit2index( n.namedItem( "print-tic-x" ).toElement().text() ) ); Settings::setYPrinting( unit2index( n.namedItem( "print-tic-y" ).toElement().text() ) ); } else { Settings::setXScaling( n.namedItem( "tic-x" ).toElement().text().toInt() ); Settings::setYScaling( n.namedItem( "tic-y" ).toElement().text().toInt() ); Settings::setXPrinting( n.namedItem( "print-tic-x" ).toElement().text().toInt() ); Settings::setYPrinting( n.namedItem( "print-tic-y" ).toElement().text().toInt() ); } #endif if ( version >= 4 ) { Settings::setXScalingMode( n.namedItem( "tic-x-mode" ).toElement().text().toInt() ); Settings::setYScalingMode( n.namedItem( "tic-y-mode" ).toElement().text().toInt() ); Settings::setXScaling( n.namedItem( "tic-x" ).toElement().text() ); Settings::setYScaling( n.namedItem( "tic-y" ).toElement().text() ); } } void KmPlotIO::parseFunction( const QDomElement & n, bool allowRename ) { QDomElement equation0 = n.namedItem( "equation-0" ).toElement(); QDomElement equation1 = n.namedItem( "equation-1" ).toElement(); QString eq0 = equation0.text(); QString eq1 = equation1.text(); Function::Type type = Function::stringToType( n.attribute( "type" ) ); if ( allowRename ) { switch ( type ) { case Function::Polar: XParser::self()->fixFunctionName( eq0, Equation::Polar, -1 ); break; case Function::Parametric: XParser::self()->fixFunctionName( eq0, Equation::ParametricX, -1 ); XParser::self()->fixFunctionName( eq1, Equation::ParametricY, -1 ); break; case Function::Cartesian: XParser::self()->fixFunctionName( eq0, Equation::Cartesian, -1 ); break; case Function::Implicit: XParser::self()->fixFunctionName( eq0, Equation::Implicit, -1 ); break; case Function::Differential: XParser::self()->fixFunctionName( eq0, Equation::Differential, -1 ); break; } } int functionID = XParser::self()->Parser::addFunction( eq0, eq1, type, true ); if ( functionID == -1 ) { qWarning() << "Could not create function!\n"; return; } Function * function = XParser::self()->functionWithID( functionID ); parseDifferentialStates( equation0, function->eq[0] ); if ( function->eq.size() > 1 ) parseDifferentialStates( equation1, function->eq[1] ); PlotAppearance * plots[] = { & function->plotAppearance( Function::Derivative0 ), & function->plotAppearance( Function::Derivative1 ), & function->plotAppearance( Function::Derivative2 ), & function->plotAppearance( Function::Integral ) }; QString names[] = { "f0", "f1", "f2", "integral" }; for ( int i = 0; i < 4; ++i ) { plots[i]->lineWidth = n.attribute( QString("%1-width").arg( names[i] ) ).toDouble() * lengthScaler; plots[i]->color = n.attribute( QString("%1-color").arg( names[i] ) ); plots[i]->useGradient = n.attribute( QString("%1-use-gradient").arg( names[i] ) ).toInt(); plots[i]->gradient.setStops( stringToGradient( n.attribute( QString("%1-gradient").arg( names[i] ) ) ) ); plots[i]->visible = n.attribute( QString("%1-visible").arg( names[i] ) ).toInt(); plots[i]->style = PlotAppearance::stringToPenStyle( n.attribute( QString("%1-style").arg( names[i] ) ) ); plots[i]->showExtrema = n.attribute( QString("%1-show-extrema").arg( names[i] ) ).toInt(); plots[i]->showTangentField = n.attribute( QString("%1-show-tangent-field").arg( names[i] ) ).toInt(); plots[i]->showPlotName = n.attribute( QString("%1-show-plot-name").arg( names[i] ) ).toInt(); } //BEGIN parameters parseParameters( n, function ); function->m_parameters.useSlider = n.attribute( "use-parameter-slider" ).toInt(); function->m_parameters.sliderID = n.attribute( "parameter-slider" ).toInt(); function->m_parameters.useList = n.attribute( "use-parameter-list" ).toInt(); //END parameters QDomElement minElement = n.namedItem( "arg-min" ).toElement(); QString expression = minElement.text(); if ( expression.isEmpty() ) function->usecustomxmin = false; else { function->dmin.updateExpression( expression ); function->usecustomxmin = minElement.attribute( "use", "1" ).toInt(); } QDomElement maxElement = n.namedItem( "arg-max" ).toElement(); expression = maxElement.text(); if ( expression.isEmpty() ) function->usecustomxmax = false; else { function->dmax.updateExpression( expression ); function->usecustomxmax = maxElement.attribute( "use", "1" ).toInt(); } } void KmPlotIO::parseParameters( const QDomElement &n, Function * function ) { QChar separator = (version < 1) ? ',' : ';'; QString tagName = (version < 4) ? "parameterlist" : "parameter-list"; const QStringList str_parameters = n.namedItem( tagName ).toElement().text().split( separator, QString::SkipEmptyParts ); for( QStringList::const_iterator it = str_parameters.constBegin(); it != str_parameters.constEnd(); ++it ) function->m_parameters.list.append( Value( *it )); } void KmPlotIO::parseDifferentialStates( const QDomElement & n, Equation * equation ) { equation->differentialStates.setStep( n.attribute( "step" ) ); QDomNode node = n.firstChild(); while (!node.isNull()) { if (node.isElement()) { QDomElement e = node.toElement(); QString x = e.attribute( "x" ); - QStringList y = e.attribute( "y" ).split( ';' ); + const QStringList y = e.attribute( "y" ).split( ';' ); DifferentialState * state = equation->differentialStates.add(); if ( state->y0.size() != y.size() ) { qWarning() << "Invalid y count!\n"; return; } state->x0.updateExpression( x ); int at = 0; - foreach ( const QString &f, y ) + for ( const QString &f : y ) state->y0[at++] = f; } node = node.nextSibling(); } } void KmPlotIO::oldParseFunction2( const QDomElement & n ) { Function::Type type; QString eq0, eq1; eq0 = n.namedItem( "equation" ).toElement().text(); switch ( eq0[0].unicode() ) { case 'r': type = Function::Polar; break; case 'x': parametricXEquation = eq0; return; case 'y': type = Function::Parametric; eq1 = eq0; eq0 = parametricXEquation; break; default: type = Function::Cartesian; break; } Function ufkt( type ); ufkt.eq[0]->setFstr( eq0, 0, 0, true ); if ( !eq1.isEmpty() ) ufkt.eq[1]->setFstr( eq1, 0, 0, true ); PlotAppearance * plots[] = { & ufkt.plotAppearance( Function::Derivative0 ), & ufkt.plotAppearance( Function::Derivative1 ), & ufkt.plotAppearance( Function::Derivative2 ), & ufkt.plotAppearance( Function::Integral ) }; plots[ 0 ]->visible = n.attribute( "visible" ).toInt(); plots[ 0 ]->color = QColor( n.attribute( "color" ) ); plots[ 0 ]->lineWidth = n.attribute( "width" ).toDouble() * lengthScaler; plots[ 1 ]->visible = n.attribute( "visible-deriv", "0" ).toInt(); plots[ 1 ]->color = QColor(n.attribute( "deriv-color" )); plots[ 1 ]->lineWidth = n.attribute( "deriv-width" ).toDouble() * lengthScaler; plots[ 2 ]->visible = n.attribute( "visible-2nd-deriv", "0" ).toInt(); plots[ 2 ]->color = QColor(n.attribute( "deriv2nd-color" )); plots[ 2 ]->lineWidth = n.attribute( "deriv2nd-width" ).toDouble() * lengthScaler; plots[ 3 ]->visible = n.attribute( "visible-integral", "0" ).toInt(); plots[ 3 ]->color = QColor(n.attribute( "integral-color" )); plots[ 3 ]->lineWidth = n.attribute( "integral-width" ).toDouble() * lengthScaler; //BEGIN parameters parseParameters( n, & ufkt ); int use_slider = n.attribute( "use-slider" ).toInt(); ufkt.m_parameters.useSlider = (use_slider >= 0); ufkt.m_parameters.sliderID = use_slider; ufkt.m_parameters.useList = !ufkt.m_parameters.list.isEmpty(); //END parameters if ( type == Function::Cartesian ) { DifferentialState * state = & ufkt.eq[0]->differentialStates[0]; state->x0.updateExpression( n.attribute( "integral-startx" ) ); state->y0[0].updateExpression( n.attribute( "integral-starty" ) ); } QDomElement minElement = n.namedItem( "arg-min" ).toElement(); QString expression = minElement.text(); if ( expression.isEmpty() ) ufkt.usecustomxmin = false; else { ufkt.dmin.updateExpression( expression ); ufkt.usecustomxmin = minElement.attribute( "use", "1" ).toInt(); } QDomElement maxElement = n.namedItem( "arg-max" ).toElement(); expression = maxElement.text(); if ( expression.isEmpty() ) ufkt.usecustomxmax = false; else { ufkt.dmax.updateExpression( expression ); ufkt.usecustomxmax = maxElement.attribute( "use", "1" ).toInt(); } QString fstr = ufkt.eq[0]->fstr(); if ( !fstr.isEmpty() ) { int const i = fstr.indexOf( ';' ); QString str; if ( i == -1 ) str = fstr; else str = fstr.left( i ); int id = XParser::self()->Parser::addFunction( str, eq1, type, true ); Function * added_function = XParser::self()->m_ufkt[id]; added_function->copyFrom( ufkt ); } } void KmPlotIO::oldParseFunction( const QDomElement & n ) { QString tmp_fstr = n.namedItem( "equation" ).toElement().text(); if ( tmp_fstr.isEmpty() ) { qWarning() << "tmp_fstr is empty!\n"; return; } Function::Type type; switch ( tmp_fstr[0].unicode() ) { case 'r': type = Function::Polar; break; case 'x': parametricXEquation = tmp_fstr; return; case 'y': type = Function::Parametric; break; default: type = Function::Cartesian; break; } Function ufkt( type ); ufkt.plotAppearance( Function::Derivative0 ).visible = n.attribute( "visible" ).toInt(); ufkt.plotAppearance( Function::Derivative1 ).visible = n.attribute( "visible-deriv" ).toInt(); ufkt.plotAppearance( Function::Derivative2 ).visible = n.attribute( "visible-2nd-deriv" ).toInt(); ufkt.plotAppearance( Function::Derivative0 ).lineWidth = n.attribute( "width" ).toDouble() * lengthScaler; ufkt.plotAppearance( Function::Derivative0 ).color = ufkt.plotAppearance( Function::Derivative1 ).color = ufkt.plotAppearance( Function::Derivative2 ).color = ufkt.plotAppearance( Function::Integral ).color = QColor( n.attribute( "color" ) ); QString expression = n.namedItem( "arg-min" ).toElement().text(); ufkt.dmin.updateExpression( expression ); ufkt.usecustomxmin = !expression.isEmpty(); expression = n.namedItem( "arg-max" ).toElement().text(); ufkt.dmax.updateExpression( expression ); ufkt.usecustomxmax = !expression.isEmpty(); if (ufkt.usecustomxmin && ufkt.usecustomxmax && ufkt.dmin.expression()==ufkt.dmax.expression()) { ufkt.usecustomxmin = false; ufkt.usecustomxmax = false; } const int pos = tmp_fstr.indexOf(';'); if ( pos == -1 ) ufkt.eq[0]->setFstr( tmp_fstr, 0, 0, true ); else { ufkt.eq[0]->setFstr( tmp_fstr.left(pos), 0, 0, true ); if ( !XParser::self()->getext( &ufkt, tmp_fstr) ) { KMessageBox::sorry(0,i18n("The function %1 could not be loaded", ufkt.eq[0]->fstr())); return; } } QString fstr = ufkt.eq[0]->fstr(); if ( !fstr.isEmpty() ) { int const i = fstr.indexOf( ';' ); QString str; if ( i == -1 ) str = fstr; else str = fstr.left( i ); int id; if ( type == Function::Parametric ) id = XParser::self()->Parser::addFunction( str, parametricXEquation, type, true ); else id = XParser::self()->Parser::addFunction( str, 0, type, true ); Function *added_function = XParser::self()->m_ufkt[id]; added_function->copyFrom( ufkt ); } } // static QString KmPlotIO::gradientToString( const QGradientStops & stops ) { QString string; - foreach ( const QGradientStop &stop, stops ) + for ( const QGradientStop &stop : qAsConst(stops) ) string += QString( "%1;%2," ).arg( stop.first ).arg( stop.second.name() ); return string; } // static QGradientStops KmPlotIO::stringToGradient( const QString & string ) { - QStringList stopStrings = string.split( ',', QString::SkipEmptyParts ); + const QStringList stopStrings = string.split( ',', QString::SkipEmptyParts ); QGradientStops stops; - foreach ( const QString &stopString, stopStrings ) + for ( const QString &stopString : stopStrings ) { QString pos = stopString.section( ';', 0, 0 ); QString color = stopString.section( ';', 1, 1 ); QGradientStop stop; stop.first = pos.toDouble(); stop.second = color; stops << stop; } return stops; } diff --git a/kmplot/kparametereditor.cpp b/kmplot/kparametereditor.cpp index c632df9..a9b08eb 100644 --- a/kmplot/kparametereditor.cpp +++ b/kmplot/kparametereditor.cpp @@ -1,364 +1,364 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 2004 Fredrik Edemar * 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "kparametereditor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "maindlg.h" #include class ParameterValueList; KParameterEditor::KParameterEditor( QList *l, QWidget *parent ) : QDialog( parent ), m_parameter(l) { m_mainWidget = new QParameterEditor( this ); setWindowTitle( i18n( "Parameter Editor" ) ); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(m_mainWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &KParameterEditor::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &KParameterEditor::reject); mainLayout->addWidget(buttonBox); mainLayout->setMargin( 0 ); m_mainWidget->cmdNew->setIcon( QIcon::fromTheme("document-new" ) ); m_mainWidget->cmdDelete->setIcon( QIcon::fromTheme("edit-delete" ) ); m_mainWidget->moveUp->setIcon( QIcon::fromTheme("go-up") ); m_mainWidget->moveDown->setIcon( QIcon::fromTheme("go-down") ); m_mainWidget->cmdImport->setIcon( QIcon::fromTheme("document-open") ); m_mainWidget->cmdExport->setIcon( QIcon::fromTheme("document-save") ); m_mainWidget->list->setFocusPolicy( Qt::NoFocus ); connect(m_mainWidget->value, &EquationEdit::upPressed, this, &KParameterEditor::prev); connect(m_mainWidget->value, &EquationEdit::downPressed, this, &KParameterEditor::next); - foreach ( const Value &v, *m_parameter ) + for ( const Value &v : qAsConst(*m_parameter) ) m_mainWidget->list->addItem( v.expression() ); connect(m_mainWidget->cmdNew, &QPushButton::clicked, this, &KParameterEditor::cmdNew_clicked); connect(m_mainWidget->cmdDelete, &QPushButton::clicked, this, &KParameterEditor::cmdDelete_clicked); connect(m_mainWidget->moveUp, &QPushButton::clicked, this, &KParameterEditor::moveUp); connect(m_mainWidget->moveDown, &QPushButton::clicked, this, &KParameterEditor::moveDown); connect(m_mainWidget->cmdImport, &QPushButton::clicked, this, &KParameterEditor::cmdImport_clicked); connect(m_mainWidget->cmdExport, &QPushButton::clicked, this, &KParameterEditor::cmdExport_clicked); connect(m_mainWidget->list, &QListWidget::currentItemChanged, this, &KParameterEditor::selectedConstantChanged); connect(m_mainWidget->value, &EquationEdit::textEdited, this, &KParameterEditor::saveCurrentValue); connect(m_mainWidget->value, &EquationEdit::textChanged, this, &KParameterEditor::checkValueValid); connect(m_mainWidget->value, &EquationEdit::returnPressed, m_mainWidget->cmdNew, &QAbstractButton::click); checkValueValid(); m_mainWidget->value->setFocus(); } KParameterEditor::~KParameterEditor() { } void KParameterEditor::accept() { qDebug() << "saving\n"; m_parameter->clear(); QString item_text; for ( int i = 0; i < m_mainWidget->list->count(); i++ ) { item_text = m_mainWidget->list->item(i)->text(); if ( !item_text.isEmpty() ) { Value value; if ( value.updateExpression( item_text ) ) m_parameter->append( value ); } } QDialog::accept(); } void KParameterEditor::moveUp() { int current = m_mainWidget->list->currentRow(); if ( current == 0 ) return; QListWidgetItem * item = m_mainWidget->list->takeItem( current-1 ); m_mainWidget->list->insertItem( current, item ); } void KParameterEditor::moveDown() { int current = m_mainWidget->list->currentRow(); if ( current == m_mainWidget->list->count() - 1 ) return; QListWidgetItem * item = m_mainWidget->list->takeItem( current+1 ); m_mainWidget->list->insertItem( current, item ); } void KParameterEditor::cmdNew_clicked() { QListWidgetItem * item = new QListWidgetItem( m_mainWidget->list ); item->setText( "0" ); m_mainWidget->list->setCurrentItem( item ); m_mainWidget->value->setFocus(); m_mainWidget->value->selectAll(); } void KParameterEditor::prev() { int current = m_mainWidget->list->currentRow(); if ( current > 0 ) m_mainWidget->list->setCurrentRow( current-1 ); } void KParameterEditor::next() { int current = m_mainWidget->list->currentRow(); if ( current < m_mainWidget->list->count()-1 ) m_mainWidget->list->setCurrentRow( current+1 ); else cmdNew_clicked(); } void KParameterEditor::selectedConstantChanged( QListWidgetItem * current ) { m_mainWidget->cmdDelete->setEnabled( current != 0 ); m_mainWidget->value->setText( current ? current->text() : QString() ); m_mainWidget->value->selectAll(); } void KParameterEditor::cmdDelete_clicked() { QListWidgetItem * item = m_mainWidget->list->currentItem(); if ( !item ) return; m_mainWidget->value->clear(); m_mainWidget->list->takeItem( m_mainWidget->list->currentRow() ); delete item; m_mainWidget->cmdDelete->setEnabled( m_mainWidget->list->currentItem() != 0 ); } void KParameterEditor::saveCurrentValue() { QListWidgetItem * current = m_mainWidget->list->currentItem(); if ( !current ) current = new QListWidgetItem( m_mainWidget->list ); current->setText( m_mainWidget->value->text() ); m_mainWidget->list->setCurrentItem( current ); } bool KParameterEditor::checkValueValid() { QString valueText = m_mainWidget->value->text(); Parser::Error error; (double) XParser::self()->eval( valueText, & error ); bool valid = (error == Parser::ParseSuccess); m_mainWidget->valueInvalidLabel->setVisible( !valueText.isEmpty() && !valid ); return valid; } void KParameterEditor::cmdImport_clicked() { QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Open Parameter File"), QUrl(), i18n("Plain Text File (*.txt)")); if ( url.isEmpty() ) return; if (!MainDlg::fileExists( url ) ) { KMessageBox::sorry(0,i18n("The file does not exist.")); return; } bool verbose = false; QFile file; if ( !url.isLocalFile() ) { KIO::StoredTransferJob *transferjob = KIO::storedGet (url); KJobWidgets::setWindow(transferjob, 0); if ( !transferjob->exec() ) { KMessageBox::sorry(0,i18n("An error appeared when opening this file: %1", transferjob->errorString() )); return; } QTemporaryFile tmpfile; tmpfile.setAutoRemove(false); tmpfile.open(); tmpfile.write(transferjob->data()); file.setFileName(tmpfile.fileName()); tmpfile.close(); } else file.setFileName(url.toLocalFile() ); if ( file.open(QIODevice::ReadOnly) ) { QTextStream stream(&file); QString line; for( int i=1; !stream.atEnd();i++ ) { line = stream.readLine(); if (line.isEmpty()) continue; Parser::Error error; XParser::self()->eval( line, & error ); if ( error == Parser::ParseSuccess ) { if ( !checkTwoOfIt(line) ) { m_mainWidget->list->addItem(line); } } else if ( !verbose) { if ( KMessageBox::warningContinueCancel(this,i18n("Line %1 is not a valid parameter value and will therefore not be included. Do you want to continue?", i) ) == KMessageBox::Cancel) { file.close(); QFile::remove( file.fileName() ); return; } else if (KMessageBox::warningYesNo(this,i18n("Would you like to be informed about other lines that cannot be read?"), QString(), KGuiItem(i18n("Get Informed")), KGuiItem(i18n("Ignore Information")) ) == KMessageBox::No) verbose = true; } } file.close(); } else KMessageBox::sorry(0,i18n("An error appeared when opening this file")); if ( !url.isLocalFile() ) QFile::remove( file.fileName() ); } void KParameterEditor::cmdExport_clicked() { if ( !m_mainWidget->list->count() ) return; QUrl url = QFileDialog::getSaveFileUrl(this, i18n("Save File"), QUrl(), i18n("Plain Text File (*.txt)")); if ( url.isEmpty() ) return; if( !MainDlg::fileExists( url ) || KMessageBox::warningContinueCancel( this, i18n( "A file named \"%1\" already exists. Are you sure you want to continue and overwrite this file?", url.toDisplayString()), i18n( "Overwrite File?" ), KStandardGuiItem::overwrite() ) == KMessageBox::Continue ) { if ( !url.isLocalFile() ) { QTemporaryFile tmpfile; if (tmpfile.open() ) { QTextStream stream(&tmpfile); for ( int i = 0; i < m_mainWidget->list->count(); i++ ) { QListWidgetItem * it = m_mainWidget->list->item( i ); stream << it->text(); if ( i < m_mainWidget->list->count()-1 ) stream << endl; //only write a new line if there are more text } stream.flush(); } else KMessageBox::sorry(0,i18n("An error appeared when saving this file")); Q_CONSTEXPR int permission = -1; QFile file(tmpfile.fileName()); file.open(QIODevice::ReadOnly); KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite); if (!putjob->exec()) { KMessageBox::sorry(0,i18n("An error appeared when saving this file")); return; } file.close(); } else { QFile file; qDebug() << "url.path()="<list->count(); i++ ) { QListWidgetItem * it = m_mainWidget->list->item( i ); stream << it->text(); if ( i < m_mainWidget->list->count()-1 ) stream << endl; //only write a new line if there are more text } file.close(); } else KMessageBox::sorry(0,i18n("An error appeared when saving this file")); } } } bool KParameterEditor::checkTwoOfIt(const QString & text) { return !m_mainWidget->list->findItems(text,Qt::MatchExactly).isEmpty(); } diff --git a/kmplot/maindlg.cpp b/kmplot/maindlg.cpp index 8882961..7d8c370 100644 --- a/kmplot/maindlg.cpp +++ b/kmplot/maindlg.cpp @@ -1,888 +1,888 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 1998, 1999, 2000, 2002 Klaus-Dieter Möller * 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "maindlg.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include #include #include #include #include // local includes #include "calculator.h" #include "functiontools.h" #include "functioneditor.h" #include "kconstanteditor.h" #include "xparser.h" #include "settings.h" #include "ui_settingspagecolor.h" #include "ui_settingspagefonts.h" #include "ui_settingspagegeneral.h" #include "ui_settingspagediagram.h" #include "ksliderwindow.h" #include "maindlgadaptor.h" #include class XParser; class KmPlotIO; class SettingsPageColor : public QWidget, public Ui::SettingsPageColor { public: SettingsPageColor( QWidget * parent = 0 ) : QWidget( parent ) { setupUi(this); layout()->setContentsMargins(0, 0, 0, 0); } }; class SettingsPageFonts : public QWidget, public Ui::SettingsPageFonts { public: SettingsPageFonts( QWidget * parent = 0 ) : QWidget( parent ) { setupUi(this); layout()->setContentsMargins(0, 0, 0, 0); } }; class SettingsPageGeneral : public QWidget, public Ui::SettingsPageGeneral { public: SettingsPageGeneral( QWidget * parent = 0 ) : QWidget( parent ) { setupUi(this); layout()->setContentsMargins(0, 0, 0, 0); } }; class SettingsPageDiagram : public QWidget, public Ui::SettingsPageDiagram { public: SettingsPageDiagram( QWidget * parent = 0 ) : QWidget( parent ) { setupUi(this); layout()->setContentsMargins(0, 0, 0, 0); } }; bool MainDlg::oldfileversion; MainDlg * MainDlg::m_self = 0; K_PLUGIN_FACTORY( KmPlotPartFactory, registerPlugin(); ) //BEGIN class MainDlg MainDlg::MainDlg(QWidget *parentWidget, QObject *parent, const QVariantList& ) : KParts::ReadWritePart( parent ), m_recentFiles( 0 ), m_modified(false), m_parent(parentWidget), m_rootValue( 0 ) { assert( !m_self ); // this class should only be constructed once m_self = this; qDebug() << "parentWidget->objectName():" << parentWidget->objectName(); if ( QString(parentWidget->objectName()).startsWith("KmPlot") ) { setXMLFile("kmplot_part.rc"); m_readonly = false; } else { setXMLFile("kmplot_part_readonly.rc"); m_readonly = true; new BrowserExtension(this); // better integration with Konqueror } m_coordsDialog = 0; m_constantEditor = 0; m_popupmenu = new QMenu( parentWidget ); m_newPlotMenu = new QMenu( parentWidget ); (void) new View( m_readonly, m_popupmenu, parentWidget ); connect( View::self(), &View::setStatusBarText, this, &MainDlg::setReadOnlyStatusBarText ); m_functionEditor = 0; if ( !m_readonly ) { m_functionEditor = new FunctionEditor( m_newPlotMenu, parentWidget ); static_cast(parentWidget)->addDockWidget( Qt::LeftDockWidgetArea, m_functionEditor ); } setWidget( View::self() ); View::self()->setFocusPolicy(Qt::ClickFocus); m_functionTools = new FunctionTools(m_parent); m_calculator = new Calculator( m_parent ); setupActions(); XParser::self()->constants()->load(); kmplotio = new KmPlotIO(); m_config = KSharedConfig::openConfig(); m_recentFiles->loadEntries( m_config->group( QString() ) ); //BEGIN undo/redo stuff m_currentState = kmplotio->currentState(); m_saveCurrentStateTimer = new QTimer( this ); m_saveCurrentStateTimer->setSingleShot( true ); connect( m_saveCurrentStateTimer, &QTimer::timeout, this, &MainDlg::saveCurrentState ); //END undo/redo stuff // Allow config manager to read from equation edits KConfigDialogManager::changedMap()->insert( "EquationEdit", SIGNAL(textEdited(QString)) ); // Let's create a Configure Dialog m_settingsDialog = new KConfigDialog( parentWidget, "settings", Settings::self() ); QMetaObject::invokeMethod(m_settingsDialog, "setHelp", Qt::DirectConnection, Q_ARG(QString, "configuration"), Q_ARG(QString, "kmplot")); // create and add the page(s) m_generalSettings = new SettingsPageGeneral( View::self() ); m_colorSettings = new SettingsPageColor( View::self() ); m_fontsSettings = new SettingsPageFonts( View::self() ); m_diagramSettings = new SettingsPageDiagram( View::self() ); // Make sure the dialog is at a good default size (hmm QDialog should do this automatically?) QSize minSize = m_generalSettings->layout()->minimumSize() .expandedTo( m_colorSettings->layout()->minimumSize() ) .expandedTo( m_fontsSettings->layout()->minimumSize() ) .expandedTo( m_diagramSettings->layout()->minimumSize() ); m_generalSettings->setMinimumSize( minSize ); m_settingsDialog->addPage( m_generalSettings, i18n("General"), "kmplot", i18n("General Settings") ); m_settingsDialog->addPage( m_diagramSettings, i18n("Diagram"), "coords", i18n("Diagram Appearance") ); m_settingsDialog->addPage( m_colorSettings, i18n("Colors"), "preferences-desktop-color", i18n("Colors") ); m_settingsDialog->addPage( m_fontsSettings, i18n("Fonts"), "preferences-desktop-font", i18n("Fonts") ); // User edited the configuration - update your local copies of the // configuration data connect( m_settingsDialog, &KConfigDialog::settingsChanged, View::self(), QOverload<>::of(&View::drawPlot) ); new MainDlgAdaptor(this); QDBusConnection::sessionBus().registerObject("/maindlg", this); } MainDlg::~MainDlg() { m_recentFiles->saveEntries( m_config->group( QString() ) ); XParser::self()->constants()->save(); delete kmplotio; } void MainDlg::setupActions() { // standard actions m_recentFiles = KStandardAction::openRecent( this, SLOT(slotOpenRecent(QUrl)), this ); actionCollection()->addAction( "file_open_recent", m_recentFiles ); actionCollection()->addAction( KStandardAction::Print, "file_print", this, SLOT(slotPrint()) ); actionCollection()->addAction( KStandardAction::PrintPreview, "file_print_preview", this, SLOT(slotPrintPreview()) ); KStandardAction::save( this, SLOT(slotSave()), actionCollection() ); KStandardAction::saveAs( this, SLOT(slotSaveas()), actionCollection() ); QAction *prefs = KStandardAction::preferences( this, SLOT(slotSettings()), actionCollection()); prefs->setText( i18n( "Configure KmPlot..." ) ); // KmPlot specific actions //BEGIN file menu QAction * exportAction = actionCollection()->addAction( "export" ); exportAction->setText( i18n( "E&xport..." ) ); exportAction->setIcon( QIcon::fromTheme( "document-export" ) ); connect( exportAction, &QAction::triggered, this, &MainDlg::slotExport ); //END file menu //BEGIN edit menu m_undoAction = KStandardAction::undo( this, SLOT(undo()), actionCollection() ); m_undoAction->setEnabled( false ); m_redoAction = KStandardAction::redo( this, SLOT(redo()), actionCollection() ); m_redoAction->setEnabled( false ); QAction * editAxes = actionCollection()->addAction( "editaxes" ); editAxes->setText( i18n( "&Coordinate System..." ) ); editAxes->setIcon( QIcon::fromTheme("coords.png") ); connect( editAxes, &QAction::triggered, this, &MainDlg::editAxes ); QAction * editConstants = actionCollection()->addAction( "editconstants" ); editConstants->setText( i18n( "&Constants..." ) ); editConstants->setIcon( QIcon::fromTheme("editconstants.png") ); connect( editConstants, &QAction::triggered, this, &MainDlg::editConstants ); //END edit menu //BEGIN view menu /// \todo check that new shortcuts work QAction * zoomIn = actionCollection()->addAction( "zoom_in" ); zoomIn->setText( i18n("Zoom &In") ); actionCollection()->setDefaultShortcut( zoomIn, QKeySequence(Qt::ControlModifier | Qt::Key_1) ); zoomIn->setIcon( QIcon::fromTheme("zoom-in") ); connect( zoomIn, &QAction::triggered, View::self(), QOverload<>::of(&View::zoomIn) ); QAction * zoomOut = actionCollection()->addAction( "zoom_out" ); zoomOut->setText(i18n("Zoom &Out")); actionCollection()->setDefaultShortcut( zoomOut, QKeySequence(Qt::ControlModifier | Qt::Key_2) ); zoomOut->setIcon( QIcon::fromTheme("zoom-out") ); connect( zoomOut, &QAction::triggered, View::self(), QOverload<>::of(&View::zoomOut) ); QAction * zoomTrig = actionCollection()->addAction( "zoom_trig" ); zoomTrig->setText( i18n("&Fit Widget to Trigonometric Functions") ); connect( zoomTrig, &QAction::triggered, View::self(), &View::zoomToTrigonometric ); QAction * resetView = actionCollection()->addAction( "reset_view" ); resetView->setText( i18n( "Reset View" ) ); resetView->setIcon( QIcon::fromTheme("resetview") ); connect( resetView, &QAction::triggered, this, &MainDlg::slotResetView ); View::self()->m_menuSliderAction = actionCollection()->add( "options_configure_show_sliders" ); View::self()->m_menuSliderAction->setText( i18n( "Show Sliders" ) ); connect( View::self()->m_menuSliderAction, &QAction::toggled, this, &MainDlg::toggleShowSliders ); //END view menu //BEGIN tools menu QAction *mnuCalculator = actionCollection()->addAction( "calculator" ); mnuCalculator->setText( i18n( "Calculator") ); mnuCalculator->setIcon( QIcon::fromTheme("system-run") ); connect( mnuCalculator, &QAction::triggered, this, &MainDlg::calculator ); QAction *mnuArea = actionCollection()->addAction( "grapharea" ); mnuArea->setText( i18n( "Plot &Area..." ) ); connect( mnuArea, &QAction::triggered, this, &MainDlg::graphArea ); QAction *mnuMaxValue = actionCollection()->addAction( "maximumvalue" ); mnuMaxValue->setText( i18n( "Find Ma&ximum..." ) ); mnuMaxValue->setIcon( QIcon::fromTheme("maximum") ); connect( mnuMaxValue, &QAction::triggered, this, &MainDlg::findMaximumValue ); QAction *mnuMinValue = actionCollection()->addAction( "minimumvalue" ); mnuMinValue->setText( i18n( "Find Mi&nimum..." ) ); mnuMinValue->setIcon( QIcon::fromTheme("minimum") ); connect( mnuMinValue, &QAction::triggered, this, &MainDlg::findMinimumValue ); //END tools menu //BEGIN help menu QAction * namesAction = actionCollection()->addAction( "names" ); namesAction->setText( i18n( "Predefined &Math Functions" ) ); namesAction->setIcon( QIcon::fromTheme("functionhelp") ); connect( namesAction, &QAction::triggered, this, &MainDlg::slotNames ); //END help menu //BEGIN new plots menu QAction * newFunction = actionCollection()->addAction( "newcartesian" ); newFunction->setText( i18n( "Cartesian Plot" ) ); newFunction->setIcon( QIcon::fromTheme("newfunction") ); connect( newFunction, &QAction::triggered, m_functionEditor, &FunctionEditor::createCartesian ); m_newPlotMenu->addAction( newFunction ); QAction * newParametric = actionCollection()->addAction( "newparametric" ); newParametric->setText( i18n( "Parametric Plot" ) ); newParametric->setIcon( QIcon::fromTheme("newparametric") ); connect( newParametric, &QAction::triggered, m_functionEditor, &FunctionEditor::createParametric ); m_newPlotMenu->addAction( newParametric ); QAction * newPolar = actionCollection()->addAction( "newpolar" ); newPolar->setText( i18n( "Polar Plot" ) ); newPolar->setIcon( QIcon::fromTheme("newpolar") ); connect( newPolar, &QAction::triggered, m_functionEditor, &FunctionEditor::createPolar ); m_newPlotMenu->addAction( newPolar ); QAction * newImplicit = actionCollection()->addAction( "newimplicit" ); newImplicit->setText( i18n( "Implicit Plot" ) ); newImplicit->setIcon( QIcon::fromTheme("newimplicit") ); connect( newImplicit, &QAction::triggered, m_functionEditor, &FunctionEditor::createImplicit ); m_newPlotMenu->addAction( newImplicit ); QAction * newDifferential = actionCollection()->addAction( "newdifferential" ); newDifferential->setText( i18n( "Differential Plot" ) ); newDifferential->setIcon( QIcon::fromTheme("newdifferential") ); connect( newDifferential, &QAction::triggered, m_functionEditor, &FunctionEditor::createDifferential ); m_newPlotMenu->addAction( newDifferential ); //END new plots menu //BEGIN function popup menu QAction *mnuEdit = actionCollection()->addAction( "mnuedit" ); mnuEdit->setText(i18n("&Edit")); m_firstFunctionAction = mnuEdit; mnuEdit->setIcon( QIcon::fromTheme("editplots") ); connect(mnuEdit, &QAction::triggered, View::self(), &View::editCurrentPlot ); m_popupmenu->addAction( mnuEdit ); QAction *mnuHide = actionCollection()->addAction( "mnuhide" ); mnuHide->setText( i18n("&Hide") ); connect( mnuHide, &QAction::triggered, View::self(), &View::hideCurrentFunction ); m_popupmenu->addAction( mnuHide ); QAction *mnuRemove = actionCollection()->addAction( "mnuremove" ); mnuRemove->setText(i18n("&Remove")); mnuRemove->setIcon( QIcon::fromTheme("edit-delete") ); connect( mnuRemove, &QAction::triggered, View::self(), &View::removeCurrentPlot ); m_popupmenu->addAction( mnuRemove ); m_popupmenu->addSeparator(); QAction * animateFunction = actionCollection()->addAction( "animateFunction" ); animateFunction->setText(i18n("Animate Plot...")); connect( animateFunction, &QAction::triggered, View::self(), &View::animateFunction ); m_popupmenu->addAction( animateFunction ); m_popupmenu->addSeparator(); m_popupmenu->addAction( mnuCalculator ); m_popupmenu->addAction( mnuMinValue ); m_popupmenu->addAction( mnuMaxValue ); m_popupmenu->addAction( mnuArea ); QAction * copyXY = actionCollection()->addAction( "copyXY" ); copyXY->setText(i18n("Copy (x, y)")); connect( copyXY, &QAction::triggered, []{ QClipboard * cb = QApplication::clipboard(); QPointF currentXY = View::self()->getCrosshairPosition(); cb->setText( i18nc("Copied pair of coordinates (x, y)", "(%1, %2)", QLocale().toString( currentXY.x(), 'f', 5 ), QLocale().toString( currentXY.y(), 'f', 5 )), QClipboard::Clipboard ); } ); m_popupmenu->addAction( copyXY ); QAction * copyRootValue = actionCollection()->addAction( "copyRootValue" ); copyRootValue->setText(i18n("Copy Root Value")); connect( View::self(), &View::updateRootValue, [this, copyRootValue]( bool haveRoot, double rootValue ){ copyRootValue->setVisible(haveRoot); m_rootValue = rootValue; } ); connect( copyRootValue, &QAction::triggered, [this]{ QClipboard * cb = QApplication::clipboard(); cb->setText( QLocale().toString( m_rootValue, 'f', 5 ), QClipboard::Clipboard ); } ); m_popupmenu->addAction( copyRootValue ); //END function popup menu } void MainDlg::undo() { qDebug() ; if ( m_undoStack.isEmpty() ) return; m_redoStack.push( m_currentState ); m_currentState = m_undoStack.pop(); kmplotio->restore( m_currentState ); View::self()->drawPlot(); m_undoAction->setEnabled( !m_undoStack.isEmpty() ); m_redoAction->setEnabled( true ); } void MainDlg::redo() { qDebug() ; if ( m_redoStack.isEmpty() ) return; m_undoStack.push( m_currentState ); m_currentState = m_redoStack.pop(); kmplotio->restore( m_currentState ); View::self()->drawPlot(); m_undoAction->setEnabled( true ); m_redoAction->setEnabled( !m_redoStack.isEmpty() ); } void MainDlg::requestSaveCurrentState() { m_saveCurrentStateTimer->start( 0 ); } void MainDlg::saveCurrentState( ) { m_redoStack.clear(); m_undoStack.push( m_currentState ); m_currentState = kmplotio->currentState(); // limit stack size to 100 items while ( m_undoStack.count() > 100 ) m_undoStack.pop_front(); m_undoAction->setEnabled( true ); m_redoAction->setEnabled( false ); m_modified = true; } void MainDlg::resetUndoRedo() { m_redoStack.clear(); m_undoStack.clear(); m_currentState = kmplotio->currentState(); m_undoAction->setEnabled( false ); m_redoAction->setEnabled( false ); } bool MainDlg::checkModified() { if( m_modified ) { int saveit = KMessageBox::warningYesNoCancel( m_parent, i18n( "The plot has been modified.\n" "Do you want to save it?" ), QString(), KStandardGuiItem::save(), KStandardGuiItem::discard() ); switch( saveit ) { case KMessageBox::Yes: slotSave(); if ( m_modified) // the user didn't saved the file return false; break; case KMessageBox::Cancel: return false; } } return true; } void MainDlg::slotSave() { if ( !m_modified || m_readonly) //don't save if no changes are made or readonly is enabled return; if ( url().isEmpty() ) // if there is no file name set yet slotSaveas(); else { if ( !m_modified) //don't save if no changes are made return; if ( oldfileversion) { if ( KMessageBox::warningContinueCancel( m_parent, i18n( "This file is saved with an old file format; if you save it, you cannot open the file with older versions of KmPlot. Are you sure you want to continue?" ), QString(), KGuiItem(i18n("Save New Format")) ) == KMessageBox::Cancel) return; } kmplotio->save( this->url() ); qDebug() << "saved"; m_modified = false; } } void MainDlg::slotSaveas() { if (m_readonly) return; const QUrl url = QFileDialog::getSaveFileUrl(m_parent, i18n( "Save As" ), QUrl::fromLocalFile(QDir::currentPath()), i18n( "KmPlot Files (*.fkt);;All Files (*)" ) ); if ( url.isEmpty() ) return; if ( !kmplotio->save( url ) ) KMessageBox::error(m_parent, i18n("The file could not be saved") ); else { setUrl(url); m_recentFiles->addUrl( url ); setWindowCaption( QUrl(this->url()).toString() ); m_modified = false; } } void MainDlg::slotExport() { QString filters; QMimeDatabase mimeDatabase; - foreach (const QByteArray &mimeType, QImageWriter::supportedMimeTypes()) { + for (const QByteArray &mimeType : QImageWriter::supportedMimeTypes()) { const QString filter = mimeDatabase.mimeTypeForName(QLatin1String(mimeType)).filterString(); if (!filter.isEmpty()) { if (mimeType == QByteArrayLiteral("image/png")) { if (!filters.isEmpty()) { filters.prepend(QStringLiteral(";;")); } filters.prepend(filter); } else { if (!filters.isEmpty()) { filters.append(QStringLiteral(";;")); } filters.append(filter); } } } if (!filters.isEmpty()) { filters.append(QStringLiteral(";;")); } filters.append(i18n("Scalable Vector Graphics (*.svg)")); QUrl url = QFileDialog::getSaveFileUrl(m_parent, i18nc("@title:window", "Export as Image"), QUrl::fromLocalFile(QDir::currentPath()), filters); if ( !url.isValid() ) return; QMimeType mimeType = mimeDatabase.mimeTypeForUrl( url ); qDebug() << "mimetype: " << mimeType.name(); bool isSvg = mimeType.name() == "image/svg+xml"; bool saveOk = true; if ( isSvg ) { QSvgGenerator img; img.setViewBox( QRect( QPoint(0, 0), View::self()->size() ) ); QFile file; QTemporaryFile tmp; if ( url.isLocalFile() ) { file.setFileName( url.toLocalFile() ); img.setOutputDevice( &file ); } else { tmp.setFileTemplate(QDir::tempPath() + QLatin1String("/kmplot_XXXXXX") + QLatin1String(".svg")); img.setOutputDevice( &tmp ); } View::self()->draw( &img, View::SVG ); if ( !url.isLocalFile() ) { Q_CONSTEXPR int permission = -1; QFile file(tmp.fileName()); file.open(QIODevice::ReadOnly); KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite); saveOk &= putjob->exec(); file.close(); } } else { QPixmap img( View::self()->size() ); View::self()->draw( & img, View::Pixmap ); QStringList types = mimeType.suffixes(); if ( types.isEmpty() ) return; // TODO error dialog? if ( url.isLocalFile() ) saveOk = img.save( url.toLocalFile(), types.at(0).toLatin1() ); else { QTemporaryFile tmp; tmp.open(); img.save( tmp.fileName(), types.at(0).toLatin1() ); Q_CONSTEXPR int permission = -1; QFile file(tmp.fileName()); file.open(QIODevice::ReadOnly); KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite); saveOk = putjob->exec(); file.close(); } } if ( !saveOk ) KMessageBox::error(m_parent, i18n("Sorry, something went wrong while saving to image \"%1\"", url.toString())); } bool MainDlg::openFile() { if (url()==m_currentfile || !kmplotio->load( url() ) ) { m_recentFiles->removeUrl( url() ); //remove the file from the recent-opened-file-list setUrl(QUrl()); return false; } m_currentfile = url(); m_recentFiles->addUrl( url() ); setWindowCaption( url().toDisplayString() ); resetUndoRedo(); View::self()->updateSliders(); View::self()->drawPlot(); return true; } bool MainDlg::saveFile() { slotSave(); return !isModified(); } void MainDlg::slotOpenRecent( const QUrl &url ) { if( isModified() || !this->url().isEmpty() ) // open the file in a new window { QDBusReply reply = QDBusInterface( QDBusConnection::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::Block, "openFileInNewWindow", url.url() ); return; } if ( !kmplotio->load( url ) ) //if the loading fails { m_recentFiles->removeUrl(url ); //remove the file from the recent-opened-file-list return; } m_currentfile = url; setUrl(url); m_recentFiles->setCurrentItem(-1); //don't select the item in the open-recent menu setWindowCaption( QUrl(this->url()).toString() ); resetUndoRedo(); View::self()->updateSliders(); View::self()->drawPlot(); } void MainDlg::slotPrint() { QPrinter prt( QPrinter::PrinterResolution ); prt.setResolution( 72 ); KPrinterDlg* printdlg = new KPrinterDlg( m_parent ); printdlg->setObjectName( "KmPlot page" ); QPointer printDialog = new QPrintDialog( &prt, m_parent ); printDialog->setOptionTabs( QList() << printdlg ); printDialog->setWindowTitle( i18n("Print Plot") ); if (printDialog->exec()) { setupPrinter(printdlg, &prt); } delete printDialog; } void MainDlg::slotPrintPreview() { QPrinter prt( QPrinter::PrinterResolution ); QPointer preview = new QPrintPreviewDialog( &prt ); QPointer printdlg = new KPrinterDlg( m_parent ); QList toolbarlist = preview->findChildren(); if(!toolbarlist.isEmpty()) { QAction *printSettings = toolbarlist.first()->addAction( QIcon::fromTheme( "configure" ), i18n("Print Settings") ); QList previewWidgetsList = preview->findChildren(); QPrintPreviewWidget *previewWidget = previewWidgetsList.first(); connect( printSettings, &QAction::triggered, [preview, previewWidget, printdlg]{ QDialog *printSettingsDialog = new QDialog( preview, Qt::WindowFlags() ); printSettingsDialog->setWindowTitle( i18n("Print Settings") ); QVBoxLayout *mainLayout = new QVBoxLayout; printSettingsDialog->setLayout(mainLayout); mainLayout->addWidget(printdlg); QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok|QDialogButtonBox::Cancel ); connect(buttonBox, &QDialogButtonBox::accepted, [previewWidget, printSettingsDialog]{ previewWidget->updatePreview(); printSettingsDialog->close(); } ); connect(buttonBox, &QDialogButtonBox::rejected, printSettingsDialog, &QDialog::reject); mainLayout->addWidget(buttonBox); printSettingsDialog->show(); }); } connect(preview, &QPrintPreviewDialog::paintRequested, [this, &printdlg, &prt]{ setupPrinter(printdlg, &prt); } ); preview->exec(); delete printdlg; delete preview; } void MainDlg::setupPrinter(KPrinterDlg *printDialog, QPrinter *printer) { View::self()->setPrintHeaderTable( printDialog->printHeaderTable() ); View::self()->setPrintBackground( printDialog->printBackground() ); View::self()->setPrintWidth( printDialog->printWidth() ); View::self()->setPrintHeight( printDialog->printHeight() ); View::self()->draw(printer, View::Printer); } void MainDlg::editAxes() { coordsDialog()->show(); } void MainDlg::editConstants() { if ( !m_constantEditor) m_constantEditor = new KConstantEditor(m_parent); m_constantEditor->show(); } void MainDlg::editConstantsModal(QWidget *parent) { if (m_constantEditor) { m_constantEditor->hide(); } else { m_constantEditor = new KConstantEditor(parent); } m_constantEditor->setModal(true); m_constantEditor->show(); } bool MainDlg::fileExists(const QUrl &url) { bool fileExists = false; if (url.isValid()) { short int detailLevel = 0; // Lowest level: file/dir/symlink/none KIO::StatJob* statjob = KIO::stat(url, KIO::StatJob::SourceSide, detailLevel); bool noerror = statjob->exec(); if (noerror) { // We want a file fileExists = !statjob->statResult().isDir(); } } return fileExists; } void MainDlg::slotNames() { KHelpClient::invokeHelp( "func-predefined", "kmplot" ); } void MainDlg::slotResetView() { View::self()->animateZoom( QRectF( -8, -8, 16, 16 ) ); } void MainDlg::slotSettings() { // An instance of your dialog has already been created and has been cached, // so we want to display the cached dialog instead of creating // another one KConfigDialog::showDialog( "settings" ); } void MainDlg::calculator() { m_calculator->show(); } void MainDlg::findMinimumValue() { m_functionTools->init( FunctionTools::FindMinimum ); m_functionTools->show(); } void MainDlg::findMaximumValue() { m_functionTools->init( FunctionTools::FindMaximum ); m_functionTools->show(); } void MainDlg::graphArea() { m_functionTools->init( FunctionTools::CalculateArea ); m_functionTools->show(); } void MainDlg::toggleShowSliders() { View::self()->updateSliders(); } void MainDlg::setReadOnlyStatusBarText(const QString &text) { setStatusBarText(text); } bool MainDlg::queryClose() { return checkModified(); } CoordsConfigDialog * MainDlg::coordsDialog( ) { if ( !m_coordsDialog) { m_coordsDialog = new CoordsConfigDialog(m_parent); connect( m_coordsDialog, &CoordsConfigDialog::settingsChanged, View::self(), QOverload<>::of(&View::drawPlot) ); } return m_coordsDialog; } //END class MainDlg /// BrowserExtension class BrowserExtension::BrowserExtension(MainDlg* parent) : KParts::BrowserExtension( parent ) { emit enableAction("print", true); setURLDropHandlingEnabled(true); } void BrowserExtension::print() { static_cast(parent())->slotPrint(); } #include "maindlg.moc" diff --git a/kmplot/parameterswidget.cpp b/kmplot/parameterswidget.cpp index 829cef1..11ac753 100644 --- a/kmplot/parameterswidget.cpp +++ b/kmplot/parameterswidget.cpp @@ -1,107 +1,107 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parameterswidget.h" #include #include "function.h" #include "kparametereditor.h" #include "xparser.h" //BEGIN class ParametersWidget ParametersWidget::ParametersWidget( QWidget * parent ) : QGroupBox( parent ) { setupUi(this); for( int number = 0; number < SLIDER_COUNT; number++ ) listOfSliders->addItem( i18n( "Slider No. %1", number +1) ); connect(editParameterListButton, &QPushButton::clicked, this, &ParametersWidget::editParameterList); connect(useSlider, &QCheckBox::toggled, this, &ParametersWidget::updateEquationEdits); connect(useList, &QCheckBox::toggled, this, &ParametersWidget::updateEquationEdits); } void ParametersWidget::init( const ParameterSettings & settings ) { useSlider->setChecked( settings.useSlider ); useList->setChecked( settings.useList ); listOfSliders->setCurrentIndex( settings.sliderID ); m_parameters = settings.list; } ParameterSettings ParametersWidget::parameterSettings() const { ParameterSettings s; s.useSlider = useSlider->isChecked(); s.useList = useList->isChecked(); s.sliderID = listOfSliders->currentIndex(); s.list = m_parameters; return s; } void ParametersWidget::editParameterList() { QPointer dlg = new KParameterEditor( & m_parameters, 0 ); dlg->exec(); delete dlg; emit parameterListChanged(); } void ParametersWidget::updateEquationEdits( ) { if ( !useSlider->isChecked() && !useList->isChecked() ) { // Don't need to add any parameter variables return; } - foreach ( EquationEdit * edit, m_equationEdits ) + for ( EquationEdit * edit : qAsConst(m_equationEdits) ) { if ( edit->equation()->usesParameter() || !edit->equation()->looksLikeFunction() ) continue; QString text = edit->text(); int bracket = text.indexOf( ')' ); if ( bracket < 0 ) continue; text.replace( bracket, 1, ",k)" ); edit->setText( text ); } } void ParametersWidget::associateEquationEdit( EquationEdit * edit ) { m_equationEdits << edit; } //END class ParametersWidget diff --git a/kmplot/parser.cpp b/kmplot/parser.cpp index 6e42e6a..0316ba2 100644 --- a/kmplot/parser.cpp +++ b/kmplot/parser.cpp @@ -1,1790 +1,1790 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 1998, 1999, 2000, 2002 Klaus-Dieter Möller * 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ // local includes #include "parser.h" #include "parseradaptor.h" #include "settings.h" #include "xparser.h" //KDE includes #include #include #include #include #include // standard c(++) includes #include #include #include #include #include #include #include double Parser::m_radiansPerAngleUnit = 0; /** * List of predefined functions. * \note Some function names include other function names (e.g. "sinh" has the * string "sin" in it). The Parser will stop once it has found a matching * function name, so such functions must be in order of longest first. */ ScalarFunction Parser::scalarFunctions[ ScalarCount ]= { // Hyperbolic trig {"sinh", QString(), sinh}, // Sinus hyperbolicus {"cosh", QString(), cosh}, // Cosinus hyperbolicus {"tanh", QString(), tanh}, // Tangens hyperbolicus {"arcsinh", "arsinh", asinh}, // Area-sinus hyperbolicus = inverse of sinh {"arccosh", "arcosh", acosh}, // Area-cosinus hyperbolicus = inverse of cosh {"arctanh", "artanh", atanh}, // Area-tangens hyperbolicus = inverse of tanh // Reciprocal-hyperbolic {"cosech", QString(), cosech}, // Co-Secans hyperbolicus {"sech", QString(), sech}, // Secans hyperbolicus {"coth", QString(), coth}, // Co-Tangens hyperbolicus {"arccosech", "arcosech", arcosech},// Area-co-secans hyperbolicus = inverse of cosech {"arcsech", "arsech", arsech}, // Area-secans hyperbolicus = inverse of sech {"arccoth", "arcoth", arcoth}, // Area-co-tangens hyperbolicus = inverse of coth // Reciprocal-trig {"cosec", QString(), lcosec}, // Co-Secans = 1/sin {"sec", QString(), lsec}, // Secans = 1/cos {"cot", QString(), lcot}, // Co-Tangens = 1/tan {"arccosec", "arcosech", larccosec},// Arcus co-secans = inverse of cosec {"arcsec", "arsec", larcsec}, // Arcus secans = inverse of sec {"arccot", "arcot", larccot}, // Arcus co-tangens = inverse of cotan // Trigonometric functions {"sin", QString(), lsin}, // Sinus {"cos", QString(), lcos}, // Cosinus {"tan", QString(), ltan}, // Tangens {"arcsin", QString(), larcsin}, // Arcus sinus = inverse of sin {"arccos", QString(), larccos}, // Arcus cosinus = inverse of cos {"arctan", QString(), larctan}, // Arcus tangens = inverse of tan // Other {"sqrt", QString(), sqrt}, // Square root {"sqr", QString(), sqr}, // Square {"sign", QString(), sign}, // Signum {"H", QString(), heaviside}, // Heaviside step function {"log", QString(), log10}, // Logarithm base 10 {"ln", QString(), log}, // Logarithm base e {"exp", QString(), exp}, // Exponential function base e {"abs", QString(), fabs}, // Absolute value {"floor", QString(), floor}, // round down to nearest integer {"ceil", QString(), ceil}, // round up to nearest integer {"round", QString(), round}, // round to nearest integer {"gamma", QString(), tgamma}, // gamma function {"lgamma", QString(), lgamma}, // log-gamma function {"factorial", QString(), factorial}, // factorial {"erfc", QString(), lerfc}, // error function {"erf", QString(), lerf}, // complementary error function // legendre {"P_0", QString(), legendre0}, // lengedre polynomial (n=0) {"P_1", QString(), legendre1}, // lengedre polynomial (n=1) {"P_2", QString(), legendre2}, // lengedre polynomial (n=2) {"P_3", QString(), legendre3}, // lengedre polynomial (n=3) {"P_4", QString(), legendre4}, // lengedre polynomial (n=4) {"P_5", QString(), legendre5}, // lengedre polynomial (n=5) {"P_6", QString(), legendre6}, // lengedre polynomial (n=6) }; VectorFunction Parser::vectorFunctions[ VectorCount ]= { {"min", min}, // minimum of a set of reals {"max", max}, // maximum of a set of reals {"mod", mod}, // l2 modulus of a set of reals }; /** * Order by longest string first, useful in parsing since we want to at each point * match the longest string first, so e.g. "sinh(x)" shouldn't be read as "sin(h) * x" */ class LengthOrderedString : public QString { public: LengthOrderedString() {} LengthOrderedString( const QString & s ) : QString(s) {} bool operator <( const LengthOrderedString & other ) const { return (length() > other.length()) || ((length() == other.length()) && (static_cast(*this) < static_cast(other))); } }; //BEGIN class Parser Parser::Parser() : m_sanitizer( this ) { m_evalPos = 0; m_nextFunctionID = 0; m_stack = new double [STACKSIZE]; stkptr = m_stack; m_constants = new Constants; m_error = 0; m_ownEquation = 0; m_currentEquation = 0; } Parser::~Parser() { - foreach ( Function * function, m_ufkt ) + for ( Function * function : qAsConst(m_ufkt) ) delete function; delete m_ownEquation; delete m_constants; delete [] m_stack; } QStringList Parser::predefinedFunctions( bool includeAliases ) const { QStringList names; for ( int func = 0; func < ScalarCount; ++func ) { names << scalarFunctions[func].name1; if ( includeAliases && !scalarFunctions[func].name2.isEmpty() ) names << scalarFunctions[func].name2; } for ( int func = 0; func < VectorCount; ++func ) names << vectorFunctions[func].name; return names; } QStringList Parser::userFunctions( ) const { QStringList names; - foreach ( Function * f, m_ufkt ) + for ( Function * f : qAsConst(m_ufkt) ) { - foreach ( Equation * eq, f->eq ) + for ( Equation * eq : qAsConst(f->eq) ) { if ( !eq->name().isEmpty() ) names << eq->name(); } } names.sort(); return names; } void Parser::reparseAllFunctions() { - foreach ( Function * f, m_ufkt ) + for ( Function * f : m_ufkt ) { - foreach ( Equation * eq, f->eq ) + for ( Equation * eq : f->eq ) initEquation( eq ); } } void Parser::setAngleMode( AngleMode mode ) { switch ( mode ) { case Radians: m_radiansPerAngleUnit = 1.0; break; case Degrees: m_radiansPerAngleUnit = M_PI/180; break; } } uint Parser::getNewId() { uint i = m_nextFunctionID; while (1) { if ( !m_ufkt.contains( i ) ) { m_nextFunctionID = i+1; return i; } ++i; } } double Parser::eval( const QString & str, Error * error, int * errorPosition ) { Error t1; if ( ! error ) error = & t1; int t2; if ( ! errorPosition ) errorPosition = & t2; if ( !m_ownEquation ) m_ownEquation = new Equation( Equation::Constant, 0 ); QString fName = XParser::self()->findFunctionName( QStringLiteral("f"), -1 ); QString eq = QStringLiteral( "%1=%2" ).arg( fName ).arg( str ); if ( !m_ownEquation->setFstr( eq, (int*)error, errorPosition ) ) { if ( errorPosition ) *errorPosition -= fName.length()+1; return 0; } return fkt( m_ownEquation, Vector() ); } double Parser::fkt(uint id, int eq, double x ) { if ( !m_ufkt.contains( id ) || m_ufkt[id]->eq.size() <= eq ) { *m_error = NoSuchFunction; return 0; } return fkt( m_ufkt[id]->eq[eq], x ); } double Parser::fkt( Equation * eq, double x ) { Function * function = eq->parent(); Q_ASSERT_X( function->type() != Function::Differential, "Parser::fkt", "Do not use this function directly! Instead, call XParser::differential" ); switch ( function->type() ) { case Function::Cartesian: case Function::Parametric: case Function::Polar: { Vector var(2); var[0] = x; var[1] = function->k; return fkt( eq, var ); } case Function::Implicit: { Vector var(3); // Can only calculate when one of x, y is fixed assert( function->m_implicitMode != Function::UnfixedXY ); if ( function->m_implicitMode == Function::FixedX ) { var[0] = function->x; var[1] = x; } else { // fixed y var[0] = x; var[1] = function->y; } var[2] = function->k; return fkt( eq, var ); } case Function::Differential: return 0; } qWarning() << "Unknown function type!\n"; return 0; } double Parser::fkt( Equation * eq, const Vector & x ) { if ( eq->mem.isEmpty() ) return 0; // Consistency check: Make sure that we leave the stkptr at the same place // that we started it double * stkInitial = stkptr; double *pDouble; double (**pScalarFunction)(double); double (**pVectorFunction)(const Vector &); uint *pUint; eq->mptr = eq->mem.data(); // Start with zero in our stackpointer // *stkptr = 0; while(1) { // qDebug() << "*eq->mptr: "<mptr); switch(*eq->mptr++) { case KONST: { pDouble=(double*)eq->mptr; *stkptr=*pDouble++; eq->mptr=(char*)pDouble; break; } case VAR: { pUint = (uint*)eq->mptr; uint var = *pUint++; if ( int(var) >= x.size() ) { // Assume variable has value zero *stkptr = 0; } else *stkptr = x[var]; eq->mptr = (char*)pUint; break; } case PUSH: { ++stkptr; break; } case PLUS: { stkptr[-1]+=*stkptr; --stkptr; break; } case MINUS: { stkptr[-1]-=*stkptr; --stkptr; break; } case GT: { stkptr[-1] = (*(stkptr-1) > *stkptr) ? 1 : 0; stkptr--; break; } case GE: { stkptr[-1] = (*(stkptr-1) >= *stkptr) ? 1 : 0; stkptr--; break; } case LT: { stkptr[-1] = (*(stkptr-1) < *stkptr) ? 1 : 0; stkptr--; break; } case LE: { stkptr[-1] = (*(stkptr-1) <= *stkptr) ? 1 : 0; stkptr--; break; } case PM: { pUint = (uint*)eq->mptr; uint whichPM = *pUint++; eq->mptr = (char*)pUint; assert( int(whichPM) < eq->pmSignature().size() ); bool plus = eq->pmSignature()[ whichPM ]; if ( plus ) stkptr[-1] += *stkptr; else stkptr[-1] -= *stkptr; --stkptr; break; } case MULT: { stkptr[-1]*=*stkptr; --stkptr; break; } case DIV: { if(*stkptr==0.) *(--stkptr)=HUGE_VAL; else { stkptr[-1]/=*stkptr; --stkptr; } break; } case POW: { stkptr[-1]=pow(*(stkptr-1), *stkptr); --stkptr; break; } case NEG: { *stkptr=-*stkptr; break; } case SQRT: { *stkptr = sqrt(*stkptr); break; } case FACT: { *stkptr = factorial(*stkptr); break; } case FKT_1: { pScalarFunction=(double(**)(double))eq->mptr; *stkptr=(*pScalarFunction++)(*stkptr); eq->mptr=(char*)pScalarFunction; break; } case FKT_N: { pUint = (uint*)eq->mptr; int numArgs = *pUint++; eq->mptr = (char*)pUint; pVectorFunction = (double(**)(const Vector &))eq->mptr; Vector args( numArgs ); for ( int i=0; i < int(numArgs); ++i ) args[i] = *(stkptr-numArgs+1+i); if ( numArgs > 0 ) stkptr += 1-numArgs; *stkptr = (*pVectorFunction++)(args); eq->mptr = (char*)pVectorFunction; break; } case UFKT: { pUint=(uint*)eq->mptr; uint id = *pUint++; uint id_eq = *pUint++; // The number of arguments being passed to the function int numArgs = *pUint++; Vector args( numArgs ); for ( int i=0; i 0 ) stkptr += 1-numArgs; *stkptr = fkt( m_ufkt[id]->eq[id_eq], args ); } eq->mptr=(char*)pUint; break; } case ENDE: { // If the stack isn't where we started at, then we've gone // up / down the wrong number of places - definitely a bug (and // will lead to crashes over time as memory rapidly runs out). assert( stkptr == stkInitial ); return *stkptr; } case ERROR: { // something went wrong due to a incorrect formular or // missing const. qDebug() << "Error in equation " << eq->fstr(); // Adjust stack again. Stack is wrong, only if we have a PUSH token // just before the ERROR token, afaik. while( stkptr != stkInitial ) { stkptr--; } return *stkptr; } } } } int Parser::addFunction( const QString & str1, const QString & str2, Function::Type type, bool force ) { QString str[2] = { str1, str2 }; Function * temp = new Function( type ); temp->setId( getNewId() ); for ( int i = 0; i < 2; ++i ) { if ( str[i].isEmpty() || temp->eq.size() <= i ) continue; int error; if ( !temp->eq[i]->setFstr( str[i], & error, 0, force ) && !force ) { qDebug() << "could not set fstr to \""<eq[i]->name() ) != -1); if ( temp->eq[i]->looksLikeFunction() && duplicate && !force ) { qDebug() << "function name reused.\n"; *m_error = FunctionNameReused; delete temp; return -1; } } m_ufkt[ temp->id() ] = temp; temp->plotAppearance( Function::Derivative0 ).color = XParser::self()->defaultColor( temp->id() ); // Better use QRandomGenerator::global()->generate() instead of qrand() since Qt 5.10 temp->plotAppearance( Function::Derivative1 ).color = QColor::fromRgb( qrand() ); temp->plotAppearance( Function::Derivative2 ).color = QColor::fromRgb( qrand() ); temp->plotAppearance( Function::Integral ).color = QColor::fromRgb( qrand() ); emit functionAdded( temp->id() ); return temp->id(); //return the unique ID-number for the function } void Parser::initEquation( Equation * eq, Error * error, int * errorPosition ) { Error t1; if ( ! error ) error = & t1; int t2; if ( ! errorPosition ) errorPosition = & t2; if ( eq->parent() ) eq->parent()->clearFunctionDependencies(); m_error = error; *m_error = ParseSuccess; *errorPosition = -1; m_currentEquation = eq; mem = & eq->mem; mptr = mem->data(); m_pmAt = 0; m_eval = eq->fstr(); m_sanitizer.fixExpression( & m_eval ); m_evalRemaining = m_eval; m_evalPos = m_eval.indexOf( '=' ) + 1; heir0(); if ( !evalRemaining().isEmpty() && *m_error == ParseSuccess ) *m_error = SyntaxError; if ( *m_error != ParseSuccess ) { *errorPosition = m_sanitizer.realPos( m_evalPos ); qDebug() << "add an error token for " << eq->fstr(); // add an error token and let the user decide addToken( ERROR ); } addToken( ENDE ); } bool Parser::removeFunction( Function * item ) { // Build up a list of functions that need to be removed is this function is removed QList toRemove; QStringList otherRemoveNames; QList newFunctions; // Added since the last iteration toRemove << item; newFunctions << item; while ( ! newFunctions.isEmpty() ) { - QList currentFunctions = newFunctions; + const QList currentFunctions = newFunctions; newFunctions.clear(); - foreach ( Function *f, currentFunctions ) + for ( Function *f : currentFunctions ) { - foreach ( Function *other, m_ufkt ) + for ( Function *other : qAsConst(m_ufkt) ) { if ( (other==f) || toRemove.contains(other) ) continue; if ( other->dependsOn( f ) ) { toRemove << other; otherRemoveNames << other->name(); newFunctions << other; } } } } if ( toRemove.size() > 1 ) { KGuiItem buttonContinue = KStandardGuiItem::cont(); buttonContinue.setText( i18n("Remove all") ); int answer = KMessageBox::warningContinueCancel( 0, i18n( "The function %1 is depended upon by the following functions: %2. These must be removed in addition.", item->name(), otherRemoveNames.join(", ") ), QString(), buttonContinue ); if ( answer == KMessageBox::Cancel ) return false; } - foreach ( Function *f, toRemove ) + for ( Function *f : qAsConst(toRemove) ) { uint id = f->id(); m_ufkt.remove( id ); delete f; emit functionRemoved( id ); } return true; } bool Parser::removeFunction(uint id) { return m_ufkt.contains( id ) && removeFunction( m_ufkt[id] ); } void Parser::removeAllFunctions() { while ( !m_ufkt.isEmpty() ) { Function *f = *m_ufkt.begin(); int id = f->id(); m_ufkt.remove( id ); delete f; emit functionRemoved( id ); } } uint Parser::countFunctions() { return m_ufkt.count(); } void Parser::heir0() { heir1(); if (*m_error!=ParseSuccess) return; while(1) { if ( m_eval.length() <= m_evalPos ) return; QChar c = m_eval[m_evalPos]; switch ( c.unicode() ) { default: return; case '<': case '>': case 0x2264: // less than or equal case 0x2265: // greater than or equal ++m_evalPos; addToken(PUSH); heir1(); if ( *m_error != ParseSuccess ) return; } switch ( c.unicode() ) { case '<': addToken(LT); break; case '>': addToken(GT); break; case 0x2264: // less than or equal addToken(LE); break; case 0x2265: // greater than or equal addToken(GE); break; } } } void Parser::heir1() { heir2(); if (*m_error!=ParseSuccess) return; while(1) { if ( m_eval.length() <= m_evalPos ) return; QChar c = m_eval[m_evalPos]; switch ( c.unicode() ) { default: return; case 0xb1: if ( m_pmAt >= MAX_PM ) { *m_error = TooManyPM; return; } if ( m_currentEquation == m_ownEquation ) { *m_error = InvalidPM; return; } // no break Q_FALLTHROUGH(); case '+': case '-': ++m_evalPos; addToken(PUSH); heir2(); if(*m_error!=ParseSuccess) return; } switch ( c.unicode() ) { case '+': addToken(PLUS); break; case '-': addToken(MINUS); break; case 0xb1: addToken(PM); adduint( m_pmAt++ ); break; } } } void Parser::heir2() { if ( match( SqrtSymbol ) ) // square root symbol { heir2(); if(*m_error!=ParseSuccess) return; addToken(SQRT); } else heir3(); } void Parser::heir3() { QChar c; heir4(); if(*m_error!=ParseSuccess) return; while(1) { if ( m_eval.length() <= m_evalPos ) return; c = m_eval[m_evalPos]; switch ( c.unicode() ) { default: return; case '*': case '/': ++m_evalPos; addToken(PUSH); heir4(); if(*m_error!=ParseSuccess) return ; } switch ( c.unicode() ) { case '*': addToken(MULT); break; case '/': addToken(DIV); break; } } } void Parser::heir4() { if (match(QStringLiteral("-"))) { heir4(); if (*m_error != ParseSuccess) return; addToken(NEG); } else if (match(QStringLiteral("+"))) { heir4(); } else { heir5(); } } void Parser::heir5() { primary(); if(*m_error!=ParseSuccess) return; while ( true ) { if ( match(QStringLiteral("^")) ) { addToken(PUSH); heir4(); if(*m_error!=ParseSuccess) return; addToken(POW); } else if ( match(QStringLiteral("!")) ) addToken(FACT); else return; } } void Parser::primary() { // Notes: // - tryUserFunction has to go after tryVariable since differential // equations treat the function name as a variable // - tryConstant has to go before tryUserFunction. This solves a problem, // when a function and a constant share the same name tryFunction() || tryPredefinedFunction() || tryVariable() || tryConstant() || tryUserFunction() || tryNumber(); } bool Parser::tryFunction() { if ( !match(QStringLiteral("(")) && !match(QStringLiteral(",")) ) return false; heir0(); if ( !match(QStringLiteral(")")) && !match(QStringLiteral(",")) ) *m_error = MissingBracket; return true; } bool Parser::tryPredefinedFunction() { for ( int i=0; i < ScalarCount; ++i ) { if ( match(scalarFunctions[i].name1) || match(scalarFunctions[i].name2) ) { primary(); addToken(FKT_1); addfptr(scalarFunctions[i].mfadr); return true; } } for ( int i=0; i < VectorCount; ++i ) { if ( match(vectorFunctions[i].name) ) { int argCount = readFunctionArguments(); addToken(FKT_N); addfptr( vectorFunctions[i].mfadr, argCount ); return true; } } return false; } bool Parser::tryVariable() { - QStringList variables = m_currentEquation->variables(); + const QStringList variables = m_currentEquation->variables(); // Sort the parameters by size, so that when identifying parameters, want to // match e.g. "ab" before "a" typedef QMultiMap ISMap; ISMap sorted; - foreach ( const QString &var, variables ) + for ( const QString &var : variables ) sorted.insert( -var.length(), var ); - foreach ( const QString &var, sorted ) + for ( const QString &var : qAsConst(sorted) ) { if ( match( var ) ) { addToken( VAR ); adduint( variables.indexOf( var ) ); return true; } } return false; } bool Parser::tryUserFunction() { - foreach ( Function * it, m_ufkt ) + for ( Function * it : qAsConst(m_ufkt) ) { for ( int i = 0; i < it->eq.size(); ++i ) { if ( !match( it->eq[i]->name()) ) continue; if ( it->eq[i] == m_currentEquation || (m_currentEquation && it->dependsOn( m_currentEquation->parent() )) ) { *m_error = RecursiveFunctionCall; return true; } int argCount = readFunctionArguments(); if ( argCount != it->eq[i]->variables().size() ) { *m_error = IncorrectArgumentCount; return true; } addToken(UFKT); addfptr( it->id(), i, argCount ); if ( m_currentEquation->parent() ) m_currentEquation->parent()->addFunctionDependency( it ); return true; } } return false; } bool Parser::tryConstant() { #define CHECK_CONSTANT( a, b ) \ if ( match(a) ) \ { \ addConstant( b ); \ return true; \ } ConstantList constants = m_constants->list( Constant::All ); QMap orderedConstants; for ( ConstantList::iterator i = constants.begin(); i != constants.end(); ++i ) orderedConstants[ i.key() ] = i.value(); for ( QMap::iterator i = orderedConstants.begin(); i != orderedConstants.end(); ++i ) CHECK_CONSTANT( i.key(), i.value().value.value() ); // Or a predefined constant? CHECK_CONSTANT( "pi", M_PI ); CHECK_CONSTANT( PiSymbol, M_PI ); CHECK_CONSTANT( "e", M_E ); CHECK_CONSTANT( InfinitySymbol, std::numeric_limits::infinity()); return false; } bool Parser::tryNumber() { QByteArray remaining = evalRemaining().toLatin1(); char * lptr = remaining.data(); char * p = 0; // we converted all to "C" format in fixExpression char *oldLocale = setlocale(LC_NUMERIC, "C"); double const w = strtod(lptr, &p); setlocale(LC_NUMERIC, oldLocale); if( lptr != p ) { m_evalPos += p-lptr; addConstant(w); return true; } return false; } int Parser::readFunctionArguments() { if ( !evalRemaining().startsWith( '(' ) ) return 0; int argCount = 0; bool argLeft = true; do { argCount++; primary(); argLeft = m_eval.at(m_evalPos-1) == ','; if (argLeft) { addToken(PUSH); m_evalPos--; } } while ( *m_error == ParseSuccess && argLeft && !evalRemaining().isEmpty() ); return argCount; } void Parser::growEqMem( int growth ) { int pos = mptr - mem->data(); mem->resize( mem->size() + growth ); mptr = mem->data() + pos; } void Parser::addToken( Token token ) { growEqMem( sizeof(Token) ); *mptr++=token; } void Parser::addConstant(double x) { addToken(KONST); growEqMem( sizeof(double) ); double *pd=(double*)mptr; *pd++=x; mptr=(char*)pd; } void Parser::adduint(uint x) { growEqMem( sizeof(uint) ); uint *p=(uint*)mptr; *p++=x; mptr=(char*)p; } void Parser::addfptr( double(*fadr)(double) ) { typedef double(**sfPtr)(double); growEqMem( sizeof(sfPtr) ); // double (**pf)(double)=(double(**)(double))mptr; sfPtr pf = (sfPtr)mptr; *pf++=fadr; mptr=(char*)pf; } void Parser::addfptr( double(*fadr)(const Vector &), int argCount ) { typedef double(**vfPtr)(const Vector &); growEqMem( sizeof(uint) ); uint *p = (uint*)mptr; *p++ = argCount; mptr = (char*)p; growEqMem( sizeof(vfPtr) ); vfPtr pf = (vfPtr)mptr; *pf++=fadr; mptr=(char*)pf; } void Parser::addfptr( uint id, uint eq_id, uint args ) { growEqMem( 3*sizeof(uint) ); uint *p=(uint*)mptr; *p++=id; *p++=eq_id; *p++=args; mptr=(char*)p; } int Parser::fnameToID(const QString &name) { - foreach ( Function * it, m_ufkt ) + for ( Function * it : qAsConst(m_ufkt) ) { - foreach ( Equation * eq, it->eq ) + for ( Equation * eq : qAsConst(it->eq) ) { if ( eq->looksLikeFunction() && (name == eq->name()) ) return it->id(); } } return -1; // Name not found } // static QString Parser::errorString( Error error ) { switch ( error ) { case ParseSuccess: return QString(); case SyntaxError: return i18n("Syntax error"); case MissingBracket: return i18n("Missing parenthesis"); case StackOverflow: return i18n("Stack overflow"); case FunctionNameReused: return i18n("Name of function is not free"); case RecursiveFunctionCall: return i18n("recursive function not allowed"); case EmptyFunction: return i18n("Empty function"); case NoSuchFunction: return i18n("Function could not be found"); case ZeroOrder: return i18n("The differential equation must be at least first-order"); case TooManyPM: return i18n("Too many plus-minus symbols"); case InvalidPM: return i18n("Invalid plus-minus symbol (expression must be constant)"); case TooManyArguments: return i18n("The function has too many arguments"); case IncorrectArgumentCount: return i18n("The function does not have the correct number of arguments"); } return QString(); } void Parser::displayErrorDialog( Error error ) { QString message( errorString(error) ); if ( !message.isEmpty() ) KMessageBox::sorry(0, message, QStringLiteral("KmPlot")); } QString Parser::evalRemaining() { /// note changing this code may need to change code in match() as well; similar int newLength = qMax( 0, m_eval.length() - m_evalPos ); if ( newLength != m_evalRemaining.length() ) m_evalRemaining = m_eval.right( newLength ); return m_evalRemaining; } bool Parser::match( const QString & lit ) { if ( lit.isEmpty() ) return false; /// note changing this code may need to change code in evalRemaining() as well; similar // Do we need to update m_evalRemaining ? int newLength = qMax( 0, m_eval.length() - m_evalPos ); if ( newLength != m_evalRemaining.length() ) evalRemaining(); if ( !m_evalRemaining.startsWith( lit ) ) return false; m_evalPos += lit.length(); return true; } Function * Parser::functionWithID( int id ) const { return m_ufkt.contains( id ) ? m_ufkt[id] : 0; } // static QString Parser::number( double value ) { QString str = QString::number( value, 'g', 16 ); str.replace( 'e', QLatin1String("*10^") ); return str; } //END class Parser //BEGIN predefined mathematical functions double sqr(double x) { return x*x; } double lsec(double x) { return (1 / cos(x*Parser::radiansPerAngleUnit())); } double lcosec(double x) { return (1 / sin(x*Parser::radiansPerAngleUnit())); } double lcot(double x) { return (1 / tan(x*Parser::radiansPerAngleUnit())); } double larcsec(double x) { return acos(1/x) / Parser::radiansPerAngleUnit(); } double larccosec(double x) { return asin(1/x) / Parser::radiansPerAngleUnit(); } double larccot(double x) { return (M_PI/2 - atan(x)) / Parser::radiansPerAngleUnit(); } double sech(double x) { return (1 / cosh(x)); } double cosech(double x) { return (1 / sinh(x)); } double coth(double x) { return (1 / tanh(x)); } double arsech(double x) { return acosh(1/x); } double arcosech(double x) { return asinh(1/x); } double arcoth(double x) { return atanh(1/x); } double lcos(double x) { return cos(x*Parser::radiansPerAngleUnit()); } double lsin(double x) { return sin(x*Parser::radiansPerAngleUnit()); } double ltan(double x) { return tan(x*Parser::radiansPerAngleUnit()); } double larccos(double x) { return acos(x) / Parser::radiansPerAngleUnit(); } double larcsin(double x) { return asin(x) / Parser::radiansPerAngleUnit(); } double larctan(double x) { return atan(x) / Parser::radiansPerAngleUnit(); } double factorial( double x ) { return tgamma(x+1); } double legendre0( double ) { return 1.0; } double legendre1( double x ) { return x; } double legendre2( double x ) { return (3*x*x-1)/2; } double legendre3( double x ) { return (5*x*x*x - 3*x)/2; } double legendre4( double x ) { return (35*x*x*x*x - 30*x*x +3)/8; } double legendre5( double x ) { return (63*x*x*x*x*x - 70*x*x*x + 15*x)/8; } double legendre6( double x ) { return (231*x*x*x*x*x*x - 315*x*x*x*x + 105*x*x - 5)/16; } double sign(double x) { if(x<0.) return -1.; else if(x>0.) return 1.; return 0.; } double heaviside( double x ) { if ( x < 0.0 ) return 0.0; else if ( x > 0.0 ) return 1.0; else return 0.5; } double min( const Vector & args ) { double best = HUGE_VAL; for ( int i=0; i < args.size(); ++i ) { if ( args[i] < best ) best = args[i]; } return best; } double max( const Vector & args ) { double best = -HUGE_VAL; for ( int i=0; i < args.size(); ++i ) { if ( args[i] > best ) best = args[i]; } return best; } double mod( const Vector & args ) { double squared = 0; for ( int i=0; i < args.size(); ++i ) squared += args[i]*args[i]; return std::sqrt( squared ); } double lerf(double x) { return erf(x); } double lerfc(double x) { return erfc(x); } //END predefined mathematical functions //BEGIN class ExpressionSanitizer enum StringType { ConstantString, NumberString, UnknownLetter, FunctionString, Other }; ExpressionSanitizer::ExpressionSanitizer( Parser * parser ) : m_parser( parser ) { m_str = 0l; m_decimalSymbol = QLocale().decimalPoint(); } void ExpressionSanitizer::fixExpression( QString * str ) { m_str = str; m_map.resize( m_str->length() ); for ( int i = 0; i < m_str->length(); ++i ) m_map[i] = i; // greater-equal, less-equal with proper symbols // note that this must go before the next code for implicit equations, since // it removes the spurious equals signs replace( QStringLiteral(">="), GeSymbol ); replace( QStringLiteral("<="), LeSymbol ); // hack for implicit functions: change e.g. "y = x + 2" to "y - (x+2)" so // that they can be evaluated via equality with zero. if ( str->count( '=' ) > 1 ) { int equalsPos = str->lastIndexOf( '=' ); replace( equalsPos, 1, QStringLiteral("-(") ); append( ')' ); } stripWhiteSpace(); // make sure all minus-like signs (including the actual unicode minus sign) // are represented by a dash (unicode 0x002d) QChar dashes[6] = { 0x2012, 0x2013, 0x2014, 0x2015, 0x2053, 0x2212 }; for ( unsigned i = 0; i < 6; ++i ) replace( dashes[i], '-' ); // replace the proper unicode divide sign by the forward-slash replace( QChar( 0xf7 ), '/' ); replace( QChar( 0x2215 ), '/' ); // replace the unicode middle-dot for multiplication by the star symbol replace( QChar( 0xd7 ), '*' ); replace( QChar( 0x2219 ), '*' ); // minus-plus symbol to plus-minus symbol replace( QChar( 0x2213 ), PmSymbol ); // various power symbols replace( QChar(0x00B2), QStringLiteral("^2") ); replace( QChar(0x00B3), QStringLiteral("^3") ); replace( QChar(0x2070), QStringLiteral("^0") ); replace( QChar(0x2074), QStringLiteral("^4") ); replace( QChar(0x2075), QStringLiteral("^5") ); replace( QChar(0x2076), QStringLiteral("^6") ); replace( QChar(0x2077), QStringLiteral("^7") ); replace( QChar(0x2078), QStringLiteral("^8") ); replace( QChar(0x2079), QStringLiteral("^9") ); // fractions replace( QChar(0x00BC), QStringLiteral("(1/4)") ); replace( QChar(0x00BD), QStringLiteral("(1/2)") ); replace( QChar(0x00BE), QStringLiteral("(3/4)") ); replace( QChar(0x2153), QStringLiteral("(1/3)") ); replace( QChar(0x2154), QStringLiteral("(2/3)") ); replace( QChar(0x2155), QStringLiteral("(1/5)") ); replace( QChar(0x2156), QStringLiteral("(2/5)") ); replace( QChar(0x2157), QStringLiteral("(3/5)") ); replace( QChar(0x2158), QStringLiteral("(4/5)") ); replace( QChar(0x2159), QStringLiteral("(1/6)") ); replace( QChar(0x215a), QStringLiteral("(5/6)") ); replace( QChar(0x215b), QStringLiteral("(1/8)") ); replace( QChar(0x215c), QStringLiteral("(3/8)") ); replace( QChar(0x215d), QStringLiteral("(5/8)") ); replace( QChar(0x215e), QStringLiteral("(7/8)") ); //BEGIN replace e.g. |x+2| with abs(x+2) str->replace( AbsSymbol, '|' ); int maxDepth = str->count( '(' ); QVector absAt( maxDepth+1 ); for ( int i = 0; i < maxDepth+1; ++i ) absAt[i] = false; int depth = 0; for ( int i = 0; i < str->length(); ++i ) { if ( str->at(i) == '|' ) { if ( absAt[depth] ) { // Closing it replace( i, 1, QStringLiteral(")") ); absAt[depth] = false; } else { // Opening it replace( i, 1, QStringLiteral("abs(") ); i += 3; absAt[depth] = true; } } else if ( str->at(i) == '(' ) depth++; else if ( str->at(i) == ')' ) { depth--; if ( depth < 0 ) depth = 0; } } //END replace e.g. |x+2| with abs(x+2) if (m_decimalSymbol == QLatin1String(",")) str->replace(QRegularExpression(QStringLiteral("(\\d),(\\d)")), "\\1.\\2"); //if we use comma as a decimal separator it is reasonable to separate numbers in function arguments by comma with space else str->replace(m_decimalSymbol, QLatin1String(".")); //replace the locale decimal symbol with a '.' otherwise //BEGIN build up strings QMap< LengthOrderedString, StringType > strings; - QStringList predefinedFunctions = XParser::self()->predefinedFunctions( true ); - foreach ( const QString &f, predefinedFunctions ) + const QStringList predefinedFunctions = XParser::self()->predefinedFunctions( true ); + for ( const QString &f : predefinedFunctions ) strings[f] = FunctionString; - foreach ( Function * it, m_parser->m_ufkt ) + for ( Function * it : qAsConst(m_parser->m_ufkt) ) { - foreach ( Equation * eq, it->eq ) + for ( Equation * eq : qAsConst(it->eq) ) strings[eq->name()] = FunctionString; } - QStringList constantNames = m_parser->constants()->names(); - foreach ( const QString &name, constantNames ) + const QStringList constantNames = m_parser->constants()->names(); + for ( const QString &name : constantNames ) strings[name] = ConstantString; strings[QStringLiteral("pi")] = ConstantString; //END build up strings strings.remove( QString() ); StringType prevType = Other; for(int i=1; i < str->length(); ) { int incLength = 1; StringType currentType = Other; QChar ch = str->at(i); QString remaining = str->right( str->length() - i ); QMap< LengthOrderedString, StringType >::const_iterator end = strings.constEnd(); for ( QMap< LengthOrderedString, StringType >::const_iterator it = strings.constBegin(); it != end; ++it ) { if ( !remaining.startsWith( it.key() ) ) continue; currentType = it.value(); incLength = it.key().length(); break; } if ( currentType == Other ) { if ( ch.isNumber() || (ch == '.') ) currentType = NumberString; } if ( currentType == Other ) { if ( ch.isLetter() ) currentType = UnknownLetter; // probably a variable } if ( ((currentType == FunctionString) || (ch == '(') || (currentType == UnknownLetter) || (currentType == ConstantString)) && ((prevType == NumberString) || (prevType == ConstantString) || (prevType == UnknownLetter) || (str->at(i-1) == ')')) ) { insert( i, '*' ); incLength++; } prevType = currentType; i += incLength; } } void ExpressionSanitizer::stripWhiteSpace() { int i = 0; while ( i < m_str->length() ) { if ( m_str->at(i).isSpace() ) { m_str->remove( i, 1 ); m_map.remove( i, 1 ); } else i++; } } void ExpressionSanitizer::remove( const QString & str ) { int at = 0; do { at = m_str->indexOf( str, at ); if ( at != -1 ) { m_map.remove( at, str.length() ); m_str->remove( at, str.length() ); } } while ( at != -1 ); } void ExpressionSanitizer::remove( const QChar & str ) { remove( QString(str) ); } void ExpressionSanitizer::replace( QChar before, QChar after ) { m_str->replace( before, after ); } void ExpressionSanitizer::replace( QChar before, const QString & after ) { if ( after.isEmpty() ) { remove( before ); return; } int at = 0; do { at = m_str->indexOf( before, at ); if ( at != -1 ) { int to = m_map[ at ]; for ( int i = at + 1; i < at + after.length(); ++i ) m_map.insert( i, to ); m_str->replace( at, 1, after ); at += after.length() - 1; } } while ( at != -1 ); } void ExpressionSanitizer::replace( int pos, int len, const QString & after ) { int before = m_map[pos]; m_map.remove( pos, len ); m_map.insert( pos, after.length(), before ); m_str->replace( pos, len, after ); } void ExpressionSanitizer::replace( const QString & before, const QString & after ) { int index; while ( (index = m_str->indexOf(before)) > -1 ) replace( index, before.length(), after ); } void ExpressionSanitizer::insert( int i, QChar ch ) { m_map.insert( i, m_map[i] ); m_str->insert( i, ch ); } void ExpressionSanitizer::append( QChar str ) { m_map.insert( m_map.size(), m_map[ m_map.size() - 1 ] ); m_str->append( str ); } int ExpressionSanitizer::realPos( int evalPos ) { if ( m_map.isEmpty() || (evalPos < 0) ) return -1; if ( evalPos >= m_map.size() ) { // qWarning() << "evalPos="<length(); ++i ) out += " " + (*m_str)[i]; out += '\n'; qDebug() << out; } //END class ExpressionSanitizer diff --git a/kmplot/view.cpp b/kmplot/view.cpp index 976613c..4030142 100644 --- a/kmplot/view.cpp +++ b/kmplot/view.cpp @@ -1,4271 +1,4271 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 1998, 1999, 2000, 2002 Klaus-Dieter Möller * 2006 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "view.h" #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // local includes #include "functioneditor.h" #include "functiontools.h" #include "settings.h" #include "ksliderwindow.h" #include "maindlg.h" #include "parameteranimator.h" #include "viewadaptor.h" #include "xparser.h" // other includes #include #include #ifdef HAVE_IEEEFP_H #include #endif #if defined(Q_CC_MINGW) using namespace std; #endif // does for real numbers what "%" does for integers double realModulo( double x, double mod ) { return x - floor(x/mod)*mod; } //BEGIN class View View * View::m_self = 0; View::View( bool readOnly, QMenu * functionPopup, QWidget* parent ) : QWidget( parent ), buffer( width(), height() ), m_popupMenu( functionPopup ), m_readonly( readOnly ), m_AccumulatedDelta(0), m_viewportAnimation( new QPropertyAnimation( this, "viewport" ) ) { assert( !m_self ); // this class should only be constructed once m_self = this; setAttribute( Qt::WA_StaticContents ); m_haveRoot = false; emit updateRootValue( false, 0 ); m_xmin = m_xmax = m_ymin = m_ymax = 0.0; m_printHeaderTable = false; m_printBackground = false; m_printWidth = 0.0; m_printHeight = 0.0; m_stopCalculating = false; m_isDrawing = false; m_popupMenuStatus = NoPopup; m_zoomMode = Normal; m_prevCursor = CursorArrow; m_backgroundColor = Settings::backgroundcolor(); m_textEdit = new KTextEdit; m_textEdit->setWordWrapMode( QTextOption::NoWrap ); m_textEdit->setLineWrapMode( QTextEdit::NoWrap ); m_textDocument = m_textEdit->document(); m_mousePressTimer = new QTime(); new ViewAdaptor(this); QDBusConnection::sessionBus().registerObject("/view", this); setMouseTracking( true ); m_sliderWindow = 0; m_popupMenuTitle = m_popupMenu->insertSection( MainDlg::self()->m_firstFunctionAction, "" ); connect(XParser::self(), &XParser::functionRemoved, this, &View::functionRemoved); } View::~View() { m_textEdit->deleteLater(); delete XParser::self(); delete m_mousePressTimer; } void View::initDrawLabels() { m_labelFont = Settings::labelFont(); for ( int i = 0; i < LabelGridSize; ++i ) for ( int j = 0; j < LabelGridSize; ++j ) m_usedDiagramArea[i][j] = false; // Add the axis double x = xToPixel( 0 ); double y = yToPixel( 0 ); double x0 = xToPixel( m_xmin ); double x1 = xToPixel( m_xmax ); double y0 = yToPixel( m_ymin ); double y1 = yToPixel( m_ymax ); // x-axis markDiagramAreaUsed( QRectF( x-20, y0, 40, y1-y0 ) ); // y-axis markDiagramAreaUsed( QRectF( x0, y-20, x1-x0, 40 ) ); } double View::niceTicSpacing( double length_mm, double range ) { Q_ASSERT_X( range > 0, "View::niceTicSpacing", "Range must be positive" ); if ( length_mm <= 0 ) { // Don't assert, as we can at least handle this situation - and it can // happen with extreme zooms qWarning() << "Non-positive length: length_mm="<= 2 && leading <= 4 ) return 2/scale; else return 5/scale; } double View::validatedTicSpacing( double spacing, double range, double pixels, double minPixels ) { Q_ASSERT_X( range > 0, "View::validatedTicSpacing", "Range must be positive" ); Q_ASSERT_X( minPixels > 0, "View::validatedTicSpacing", "MinPixels must be positive" ); spacing = qAbs( spacing ); if ( qFuzzyCompare( spacing, 0 ) ) return 2.0 * range; double factor; // Make sure spacing between tics is at least minPixels pixels /= range / spacing; factor = pixels / minPixels; if ( factor < 1.0 ) { int exponent; frexp( factor, &exponent ); spacing = ldexp( spacing, -exponent + 1 ); } // Make sure there are at least two tics factor = spacing / range; if ( factor > 0.5 ) { int exponent; frexp( factor, &exponent ); spacing = ldexp( spacing, -exponent - 1); } return spacing; } void View::initDrawing( QPaintDevice * device, PlotMedium medium ) { switch ( medium ) { case SVG: case Screen: { m_clipRect = QRect( 0, 0, width(), height() ); break; } case Printer: { double inchesPerMeter = 100.0/2.54; int pixels_x = int(m_printWidth * device->logicalDpiX() * inchesPerMeter); int pixels_y = int(m_printHeight * device->logicalDpiY() * inchesPerMeter); m_clipRect = QRect( 0, 0, pixels_x, pixels_y ); break; } case Pixmap: { QPixmap * pic = static_cast(device); m_clipRect = pic->rect(); break; } } if ( m_clipRect.width() <= 0 || m_clipRect.height() <= 0 ) { qWarning() << "Invalid clip rect: m_clipRect="<eval( Settings::xMin() ); m_xmax = XParser::self()->eval( Settings::xMax() ); if ( m_xmax <= m_xmin || !std::isfinite(m_xmin) || !std::isfinite(m_xmax) ) { m_xmin = -8; m_xmax = +8; } m_ymin = XParser::self()->eval( Settings::yMin() ); m_ymax = XParser::self()->eval( Settings::yMax() ); if ( m_ymax <= m_ymin || !std::isfinite(m_ymin) || !std::isfinite(m_ymax) ) { m_ymin = -8; m_ymax = +8; } //END get X/Y range //BEGIN calculate scaling matrices m_realToPixel.reset(); m_realToPixel.scale( m_clipRect.width()/(m_xmax-m_xmin), m_clipRect.height()/(m_ymin-m_ymax) ); m_realToPixel.translate( -m_xmin, -m_ymax ); m_pixelToReal = m_realToPixel.inverted(); //END calculate scaling matrices //BEGIN get Tic Separation QFontMetricsF fm( Settings::axesFont(), device ); if ( Settings::xScalingMode() == 0 ) { double length = pixelsToMillimeters( xToPixel( m_xmax ), device ); double spacing = niceTicSpacing( length, m_xmax-m_xmin ); ticSepX.updateExpression( spacing ); } else { ticSepX.updateExpression( Settings::xScaling() ); double spacing = ticSepX.value(); spacing = validatedTicSpacing( spacing, m_xmax-m_xmin, xToPixel( m_xmax ), fm.lineSpacing()); ticSepX.updateExpression( spacing ); } if ( Settings::yScalingMode() == 0 ) { double length = pixelsToMillimeters( yToPixel( m_ymin ), device ); double spacing = niceTicSpacing( length, m_ymax-m_ymin ); ticSepY.updateExpression( spacing ); } else { ticSepY.updateExpression( Settings::yScaling() ); double spacing = ticSepY.value(); spacing = validatedTicSpacing( spacing, m_ymax-m_ymin, yToPixel( m_ymin ), fm.lineSpacing()); ticSepY.updateExpression( spacing ); } ticStartX = ceil(m_xmin/ticSepX.value())*ticSepX.value(); ticStartY = ceil(m_ymin/ticSepY.value())*ticSepY.value(); //END get Tic Separation //BEGIN get colours m_backgroundColor = Settings::backgroundcolor(); if ( !m_backgroundColor.isValid() ) m_backgroundColor = Qt::white; //END get colours XParser::self()->setAngleMode( (Parser::AngleMode)Settings::anglemode() ); initDrawLabels(); } void View::draw( QPaintDevice * dev, PlotMedium medium ) { if ( m_isDrawing ) return; m_isDrawing = true; updateCursor(); initDrawing( dev, medium ); QPainter painter( dev ); switch ( medium ) { case SVG: case Screen: break; case Printer: { if ( m_printHeaderTable ) drawHeaderTable( &painter ); if ( m_printBackground ) painter.fillRect( m_clipRect, m_backgroundColor); //draw a colored background break; } case Pixmap: { QPixmap * pic = static_cast(dev); pic->fill(m_backgroundColor); break; } } painter.setClipRect( m_clipRect ); //BEGIN draw diagram background stuff painter.setRenderHint( QPainter::Antialiasing, true ); drawGrid( &painter ); if ( Settings::showAxes() ) drawAxes( &painter ); if( Settings::showLabel() ) drawLabels( &painter ); //END draw diagram background stuff //BEGIN draw the functions m_stopCalculating = false; // Antialiasing slows down rendering a lot, so turn it off if we are // sliding the view about painter.setRenderHint( QPainter::Antialiasing, m_zoomMode != Translating ); double at = -1; - foreach ( Function * function, XParser::self()->m_ufkt ) + for ( Function * function : qAsConst(XParser::self()->m_ufkt) ) { at += 1; if ( m_stopCalculating ) break; // QDBusInterface( QDBus::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::Block, "setDrawProgress", at/numPlots ); if ( function->type() == Function::Implicit ) drawImplicit( function, & painter ); else drawFunction( function, & painter ); } // QDBusInterface( QDBus::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::Block, "setDrawProgress", 1.0 ); drawFunctionInfo( & painter ); m_isDrawing=false; //END draw the functions // Reset are stuff back to the screen stuff initDrawing( & buffer, Screen ); updateCursor(); } //BEGIN coordinate mapping functions QPointF View::toPixel( const QPointF & real, ClipBehaviour clipBehaviour, const QPointF & pixelIfNaN ) { xclipflg = false; yclipflg = false; QPointF pixel = m_realToPixel.map( real ); double x = pixel.x(); double y = pixel.y(); if ( std::isnan(x) ) { xclipflg = true; x = pixelIfNaN.x(); } else if ( clipBehaviour == ClipAll ) { if ( x<0 ) { xclipflg = true; x = 0; } else if ( x > m_clipRect.right() ) { xclipflg = true; x = m_clipRect.right(); } } else { if ( std::isinf(x) && x<0 ) x = 0; else if ( std::isinf(x) && x>0 ) x = m_clipRect.right(); } if ( std::isnan(y) ) { yclipflg = true; y = pixelIfNaN.y(); } else if ( clipBehaviour == ClipAll ) { if ( y<0 ) { yclipflg = true; y = 0; } else if ( y > m_clipRect.bottom() ) { yclipflg = true; y = m_clipRect.bottom(); } } else { if ( std::isinf(y) && y<0 ) y = 0; else if ( std::isinf(y) && y>0 ) y = m_clipRect.bottom(); } // Make sure that x and y are *reasonably* bounded at least, even if they're not infinite double min_x = -1e3 * m_clipRect.width(); double max_x = +1e3 * m_clipRect.width(); double min_y = -1e3 * m_clipRect.height(); double max_y = +1e3 * m_clipRect.height(); if ( x < min_x ) x = min_x; else if ( x > max_x ) x = max_x; if ( y < min_y ) y = min_y; else if ( y > max_y ) y = max_y; return QPointF( x, y ); } double View::xToPixel( double x, ClipBehaviour clipBehaviour, double xIfNaN ) { return toPixel( QPointF( x, 0 ), clipBehaviour, QPointF( xIfNaN, 0 ) ).x(); } double View::yToPixel( double y, ClipBehaviour clipBehaviour, double yIfNaN ) { return toPixel( QPointF( 0, y ), clipBehaviour, QPointF( 0, yIfNaN ) ).y(); } QPointF View::toReal( const QPointF & pixel ) { return m_pixelToReal.map( pixel ); } double View::xToReal( double x ) { return toReal( QPointF( x, 0 ) ).x(); } double View::yToReal( double y ) { return toReal( QPointF( 0, y ) ).y(); } //END coordinate mapping functions void View::drawAxes( QPainter* painter ) { double axesLineWidth = millimetersToPixels( Settings::axesLineWidth(), painter->device() ); double ticWidth = millimetersToPixels( Settings::ticWidth(), painter->device() ); double ticLength = millimetersToPixels( Settings::ticLength(), painter->device() ); QColor axesColor = Settings::axesColor(); painter->save(); double arrowWidth = ticLength*1.4; double arrowLength = arrowWidth*2.8; painter->setPen( QPen( axesColor, axesLineWidth ) ); painter->setBrush( axesColor ); //BEGIN draw x axis double a = m_clipRect.right()-ticLength; double b = yToPixel(0.); double b_max = m_clipRect.bottom() - ticLength; if ( b < ticLength ) b = ticLength; else if ( b > b_max ) b = b_max; // horizontal line painter->Lineh(ticLength, b, a); // arrow head if ( Settings::showArrows()) { a = m_clipRect.right(); QPolygonF p(3); p[0] = QPointF( a, b ); p[1] = QPointF( a-arrowLength, b+arrowWidth ); p[2] = QPointF( a-arrowLength, b-arrowWidth ); painter->drawPolygon( p ); } //END draw x axis //BEGIN draw y axis a = xToPixel(0.); b = ticLength; double a_max = m_clipRect.right() - ticLength; if ( a < ticLength ) a = ticLength; else if ( a > a_max ) a = a_max; // vertical line painter->Linev(a, m_clipRect.bottom()-ticLength, b); // arrow head if ( Settings::showArrows() ) { b = 0; QPolygonF p(3); p[0] = QPointF( a, b ); p[1] = QPointF( a-arrowWidth, b+arrowLength ); p[2] = QPointF( a+arrowWidth, b+arrowLength ); painter->drawPolygon( p ); } //END draw y axis painter->restore(); painter->setPen( QPen( axesColor, ticWidth ) ); double da = yToPixel(0)-ticLength; double db = yToPixel(0)+ticLength; double d = ticStartX; if (da<0) { a = 0; b = 2*ticLength; } else if(db>(double)m_clipRect.bottom()) { b = m_clipRect.bottom(); a = m_clipRect.bottom()-2*ticLength; } else { a = da; b = db; } while(d ticLength ) painter->Linev(xToPixel(d), a, b); d+=ticSepX.value(); } da = xToPixel(0)-ticLength; db = xToPixel(0)+ticLength; d = ticStartY; if (da<0) { a = 0; b = 2*ticLength; } else if(db>(double)m_clipRect.right()) { b = m_clipRect.right(); a = m_clipRect.right()-2*ticLength; } else { a = da; b = db; } while(dLineh(a, d_pixel, b); d+=ticSepY.value(); } } void View::drawGrid( QPainter* painter ) { QColor gridColor = Settings::gridColor(); double gridLineWidth = millimetersToPixels( Settings::gridLineWidth(), painter->device() ); QPen pen( gridColor, gridLineWidth ); painter->setPen(pen); enum GridStyle { GridNone, GridLines, GridCrosses, GridPolar }; GridStyle gridMode = (GridStyle)Settings::gridStyle(); switch ( gridMode ) { case GridNone: break; case GridLines: { for ( double d = ticStartX; d <= m_xmax; d += ticSepX.value() ) painter->Linev(xToPixel(d), m_clipRect.bottom(), 0); for ( double d = ticStartY; d <= m_ymax; d += ticSepY.value() ) painter->Lineh(0, yToPixel(d), m_clipRect.right()); break; } case GridCrosses: { int const dx = 5; int const dy = 5; for( double x = ticStartX; xLineh(a-dx, b, a+dx); painter->Linev(a, b-dy, b+dy); } } break; } case GridPolar: { // Note: 1.42 \approx sqrt(2) double xMax = qMax( qAbs(m_xmin), qAbs(m_xmax) ) * 1.42; double yMax = qMax( qAbs(m_ymin), qAbs(m_ymax) ) * 1.42; double rMax = qMax( xMax, yMax ); // The furthest pixel away from the origin double pixelMax = qMax( xMax*m_realToPixel.m11(), yMax*m_realToPixel.m22() ); double ticSep = qMin( ticSepX.value(), ticSepY.value() ); double r = ticSep; while ( r < rMax ) { QRectF rect; rect.setTopLeft( toPixel( QPointF( -r, r ), ClipInfinite ) ); rect.setBottomRight( toPixel( QPointF( r, -r ), ClipInfinite ) ); painter->drawEllipse( rect ); r += ticSep; } for ( double theta = 0; theta < 2.0*M_PI; theta += M_PI/12.0 ) { QPointF start = toPixel( QPointF( 0, 0 ), ClipInfinite ); QPointF end = start + QPointF( pixelMax * cos(theta), pixelMax * sin(theta) ); painter->drawLine( start, end ); } break; } } } void View::drawLabels( QPainter *painter ) { const QString xLabel = Settings::labelHorizontalAxis(); const QString yLabel = Settings::labelVerticalAxis(); int const dx=10; int const dy=15; QFont const font = Settings::axesFont(); painter->setFont(font); m_textDocument->setDefaultFont( font ); double const x=xToPixel(0.); double const y=yToPixel(0.); QRectF drawRect; // Whether the x-axis is along the top of the view // and the y-axis is along the right edge of the view bool axesInTopRight = ( m_ymax-ticSepY.value()) drawRect = QRectF( xToPixel(m_xmax)-dx, y-dy, 0, 0 ); else drawRect = QRectF( xToPixel(m_xmax)-dx, y+dy, 0, 0 ); painter->drawText( drawRect, flags, xLabel ); endLabelWidth = m_clipRect.right() - painter->boundingRect( drawRect, flags, xLabel ).right(); // Draw y label if ( axesInTopRight ) drawRect = QRectF( x-dx, yToPixel(m_ymax)+(2*dy), 0, 0 ); else if (m_xmin>-ticSepX.value()) drawRect = QRectF( x+(2*dx), yToPixel(m_ymax)+dy, 0, 0 ); else drawRect = QRectF( x-dx, yToPixel(m_ymax)+dy, 0, 0 ); painter->drawText( drawRect, flags, yLabel ); // Draw the numbers on the axes drawXAxisLabels( painter, pixelsToMillimeters( endLabelWidth, painter->device() ) ); drawYAxisLabels( painter ); } /** * If \p d is a rational multiple of pi, then return a string appropriate for * displaying it in fraction form. */ QString tryPiFraction( double d, double sep ) { // Avoid strange bug where get pi at large separation if ( sep > 10 ) return QString(); bool positive = d > 0; d /= M_PI; if ( !positive ) d = -d; if ( d < 1e-2 ) return QString(); // Try denominators from 1 to 6 for ( int denom = 1; denom <= 6; ++denom ) { if ( realModulo( d * denom, 1 ) > 1e-3*sep ) continue; int num = qRound( d * denom ); QString s = positive ? "+" : QString(MinusSymbol); if ( num != 1 ) s += QString::number( num ); s += PiSymbol; if ( denom != 1 ) s += '/' + QString::number( denom ); return s; } return QString(); } void View::drawXAxisLabels( QPainter *painter, double endLabelWidth_mm ) { QColor axesColor = Settings::axesColor(); int const dy = 8; double const y = yToPixel(0.); // Used to ensure that labels aren't drawn too closely together // These numbers contain the pixel position of the left and right endpoints of the last label double last_x_start = -1e3; // (just a large negative number) double last_x_end = -1e3; // (just a large negative number) // The strange label drawing order here is so that the labels eitherside // of zero are always drawn, and then the other labels are drawn if there // is enough space bool first = true; bool forwards = true; double d = 0; while ( true ) { if ( first ) { // Draw x>0 first d = qMax( ticSepX.value(), ticStartX ); last_x_end = xToPixel(0); first = false; } else { if ( forwards ) { d += ticSepX.value(); if ( d > m_xmax ) { // Continue on other side d = qMin( -ticSepX.value(), ticStartX + floor((m_xmax-m_xmin)/ticSepX.value())*ticSepX.value() ); last_x_start = xToPixel(0); forwards = false; } } else { d -= ticSepX.value(); if ( d < m_xmin ) return; } } // Don't draw too close to the left edge if the y axis is there if ( m_xmin >= -ticSepX.value() && (d-m_xmin) <= ticSepX.value() ) continue; QString s = tryPiFraction( d, ticSepX.value() ); if ( s.isEmpty() ) s = posToString( d, ticSepX.value()*5, View::ScientificFormat, axesColor ).replace('.', QLocale().decimalPoint()); m_textDocument->setHtml( s ); double idealWidth = m_textDocument->idealWidth(); double idealHeight = m_textDocument->size().height(); double x_pos = xToPixel(d)-(idealWidth/2)-4; if ( x_pos < 0 ) continue; double y_pos = y+dy; if ( (y_pos+idealHeight) > m_clipRect.bottom() ) y_pos = y-dy-idealHeight; double x_start = x_pos; double x_end = x_start + idealWidth; // Use a minimum spacing between labels if ( (last_x_start < x_start) && pixelsToMillimeters( x_start - last_x_end, painter->device() ) < 7 ) continue; if ( (last_x_start > x_start) && pixelsToMillimeters( last_x_start - x_end, painter->device() ) < 7 ) continue; // Don't draw too close to the right edge (has x axis label) if ( pixelsToMillimeters( m_clipRect.right()-x_end, painter->device() ) < endLabelWidth_mm+3 ) continue; last_x_start = x_start; last_x_end = x_end; QPointF drawPoint( x_pos, y_pos ); painter->translate( drawPoint ); m_textDocument->documentLayout()->draw( painter, QAbstractTextDocumentLayout::PaintContext() ); painter->translate( -drawPoint ); } } void View::drawYAxisLabels( QPainter *painter ) { QColor axesColor = Settings::axesColor(); int const dx = 12; double const x=xToPixel(0.); double d = ticStartY; long long n = (long long)ceil(m_ymin/ticSepY.value()); for( ; d -ticSepY.value() && (d-m_ymin) <= ticSepY.value() ) continue; QString s = tryPiFraction( d, ticSepY.value() ); if ( s.isEmpty() ) s = posToString( d, ticSepY.value()*5, View::ScientificFormat, axesColor ).replace('.', QLocale().decimalPoint()); m_textDocument->setHtml( s ); double idealWidth = m_textDocument->idealWidth(); double idealHeight = m_textDocument->size().height(); QPointF drawPoint( 0, yToPixel(d)-(idealHeight/2) ); if ( m_xmin > -ticSepX.value() ) { drawPoint.setX( x+dx ); } else { drawPoint.setX( x-dx-idealWidth ); if ( drawPoint.x() < 0 ) { // Don't draw off the left edge of the screen drawPoint.setX( 0 ); } } // Shouldn't have the label cut off by the bottom of the view if ( drawPoint.y() + idealHeight > m_clipRect.height() ) continue; painter->translate( drawPoint ); m_textDocument->documentLayout()->draw( painter, QAbstractTextDocumentLayout::PaintContext() ); painter->translate( -drawPoint ); } } double View::h( const Plot & plot ) const { if ( (plot.plotMode == Function::Integral) || (plot.function()->type() == Function::Differential) ) return plot.function()->eq[0]->differentialStates.step().value(); double dx = (m_xmax-m_xmin)/m_clipRect.width(); double dy = (m_ymax-m_ymin)/m_clipRect.height(); switch ( plot.function()->type() ) { case Function::Cartesian: case Function::Differential: return dx; case Function::Polar: case Function::Parametric: case Function::Implicit: return qMin( dx, dy ); } qWarning() << "Unknown coord\n"; return qMin( dx, dy ); } double View::value( const Plot & plot, int eq, double x, bool updateFunction ) { Function * function = plot.function(); assert( function ); if ( updateFunction ) plot.updateFunction(); Equation * equation = function->eq[eq]; double dx = h( plot ); DifferentialState * state = plot.state(); return XParser::self()->derivative( plot.derivativeNumber(), equation, state, x, dx ); } QPointF View::realValue( const Plot & plot, double x, bool updateFunction ) { Function * function = plot.function(); assert( function ); switch ( function->type() ) { case Function::Differential: case Function::Cartesian: { double y = value( plot, 0, x, updateFunction ); return QPointF( x, y ); } case Function::Polar: { double y = value( plot, 0, x, updateFunction ); return QPointF( y * lcos(x), y * lsin(x) ); } case Function::Parametric: { double X = value( plot, 0, x, updateFunction ); double Y = value( plot, 1, x, updateFunction ); return QPointF( X, Y ); } case Function::Implicit: { // Can only calculate the value when either x or y is fixed. assert( function->m_implicitMode != Function::UnfixedXY ); double val = value( plot, 0, x, updateFunction ); if ( function->m_implicitMode == Function::FixedX ) return QPointF( function->x, val ); else return QPointF( val, function->y ); } } qWarning() << "Unknown function type!\n"; return QPointF(); } double View::getXmin( Function * function, bool overlapEdge ) { switch ( function->type() ) { case Function::Parametric: case Function::Polar: return function->dmin.value(); case Function::Implicit: qWarning() << "You probably don't want to do this!\n"; // fall through case Function::Differential: case Function::Cartesian: { double min = m_xmin; if ( overlapEdge ) min -= (m_xmax-m_xmin)*0.02; if ( function->usecustomxmin ) return qMax( min, function->dmin.value() ); else return min; } } return 0; } double View::getXmax( Function * function, bool overlapEdge ) { switch ( function->type() ) { case Function::Parametric: case Function::Polar: return function->dmax.value(); case Function::Implicit: qWarning() << "You probably don't want to do this!\n"; // fall through case Function::Differential: case Function::Cartesian: { double max = m_xmax; if ( overlapEdge ) max += (m_xmax-m_xmin)*0.02; if ( function->usecustomxmax ) return qMin( max, function->dmax.value() ); else return max; } } return 0; } // #define DEBUG_IMPLICIT #ifdef DEBUG_IMPLICIT // Used in profiling root finding int root_find_iterations; int root_find_requests; #endif /** * For comparing points where two points close together are considered equal. */ class FuzzyPoint { public: FuzzyPoint( const QPointF & point ) { x = point.x(); y = point.y(); } FuzzyPoint( double x, double y ) { FuzzyPoint::x = x; FuzzyPoint::y = y; } bool operator < ( const FuzzyPoint & other ) const { double du = qAbs(other.x - x) / dx; double dv = qAbs(other.y - y) / dy; bool x_eq = (du < 1); // Whether the x coordinates are considered equal bool y_eq = (dv < 1); // Whether the y coordinates are considered equal if ( x_eq && y_eq ) { // Points are close together. return false; } bool x_lt = !x_eq && (x < other.x); bool y_lt = !y_eq && (y < other.y); return ( x_lt || (x_eq && y_lt) ); } double x; double y; static double dx; static double dy; }; typedef QMap< FuzzyPoint, QPointF > FuzzyPointMap; double FuzzyPoint::dx = 0; double FuzzyPoint::dy = 0; double SegmentMin = 0.1; double SegmentMax = 6.0; // The viewable area is divided up into square*squares squares, and the curve // is traced around in each square. // NOTE: it is generally a good idea to make this number prime int squares = 19; void View::drawImplicit( Function * function, QPainter * painter ) { assert( function->type() == Function::Implicit ); #ifdef DEBUG_IMPLICIT QTime t; t.start(); painter->setPen( Qt::black ); for ( double i = 0; i <= squares; ++i ) { double x = m_xmin + i * (m_xmax-m_xmin)/squares; double y = m_ymin + i * (m_ymax-m_ymin)/squares; painter->drawLine( toPixel( QPointF( m_xmin, y ), ClipInfinite ), toPixel( QPointF( m_xmax, y ), ClipInfinite ) ); painter->drawLine( toPixel( QPointF( x, m_ymin ), ClipInfinite ), toPixel( QPointF( x, m_ymax ), ClipInfinite ) ); } root_find_iterations = 0; root_find_requests = 0; #endif // Need another function for investigating singular points Plot circular; QString fname( "f(x)=0" ); XParser::self()->fixFunctionName( fname, Equation::Cartesian, -1 ); circular.setFunctionID( XParser::self()->Parser::addFunction( fname, 0, Function::Cartesian ) ); assert( circular.function() ); const QList< Plot > plots = function->plots(); - foreach ( const Plot &plot, plots ) + for ( const Plot &plot : plots ) { bool setAliased = false; if ( plot.parameter.type() == Parameter::Animated ) { // Don't use antialiasing, so that rendering is sped up if ( painter->renderHints() & QPainter::Antialiasing ) { setAliased = true; painter->setRenderHint( QPainter::Antialiasing, false ); } } painter->setPen( penForPlot( plot, painter ) ); QList singular; for ( int i = 0; i <= squares; ++i ) { double y = m_ymin + i*(m_ymax-m_ymin)/double(squares); function->y = y; function->m_implicitMode = Function::FixedY; QList roots = findRoots( plot, m_xmin, m_xmax, RoughRoot ); - foreach ( double x, roots ) + for ( double x : qAsConst(roots) ) { #ifdef DEBUG_IMPLICIT painter->setPen( QPen( Qt::red, painter->pen().width() ) ); #endif drawImplicitInSquare( plot, painter, x, y, Qt::Horizontal, & singular ); } double x = m_xmin + i*(m_xmax-m_xmin)/double(squares); function->x = x; function->m_implicitMode = Function::FixedX; roots = findRoots( plot, m_ymin, m_ymax, RoughRoot ); - foreach ( double y, roots ) + for ( double y : qAsConst(roots) ) { #ifdef DEBUG_IMPLICIT painter->setPen( QPen( Qt::blue, painter->pen().width() ) ); #endif drawImplicitInSquare( plot, painter, x, y, Qt::Vertical, & singular ); } } // Sort out the implicit points FuzzyPointMap singularSorted; FuzzyPoint::dx = (m_xmax-m_xmin) * SegmentMin * 0.1 / m_clipRect.width(); FuzzyPoint::dy = (m_ymax-m_ymin) * SegmentMin * 0.1 / m_clipRect.height(); - foreach ( const QPointF &point, singular ) + for ( const QPointF &point : qAsConst(singular) ) singularSorted.insert( point, point ); singular = singularSorted.values(); - foreach ( const QPointF &point, singular ) + for ( const QPointF &point : qAsConst(singular) ) { // radius of circle around singular point double epsilon = qMin( FuzzyPoint::dx, FuzzyPoint::dy ); QString fstr; fstr = QString("%1(x)=%2(%3+%6*cos(x),%4+%6*sin(x)%5)") .arg( circular.function()->eq[0]->name() ) .arg( function->eq[0]->name() ) .arg( XParser::self()->number( point.x() ) ) .arg( XParser::self()->number( point.y() ) ) .arg( function->eq[0]->usesParameter() ? ',' + XParser::self()->number( function->k ) : QString() ) .arg( XParser::self()->number( epsilon ) ); bool setFstrOk = circular.function()->eq[0]->setFstr( fstr ); qDebug() << "------------ " << setFstrOk << endl; assert( setFstrOk ); QList roots = findRoots( circular, 0, 2*M_PI / XParser::self()->radiansPerAngleUnit(), PreciseRoot ); #ifdef DEBUG_IMPLICIT qDebug() << "Singular point at (x,y)=("<setPen( QPen( Qt::green, painter->pen().width() ) ); #endif double x = point.x() + epsilon * lcos(t); double y = point.y() + epsilon * lsin(t); drawImplicitInSquare( plot, painter, x, y, 0, & singular ); } } if ( setAliased ) painter->setRenderHint( QPainter::Antialiasing, true ); } #ifdef DEBUG_IMPLICIT if ( root_find_requests != 0 ) qDebug() << "Average iterations in root finding was " << root_find_iterations/root_find_requests; qDebug() << "Time taken was " << t.elapsed(); #endif XParser::self()->removeFunction( circular.functionID() ); } // static double View::maxSegmentLength( double curvature ) { // Use a circle angle of 4 degrees to determine the maximum segment length // Also, limit the length to be between 0.1 and 6 pixels. double arc = 4 * (M_PI / 180); if ( curvature < 0 ) curvature = -curvature; if ( curvature < 1e-20 ) return SegmentMax; // very large circle double radius = 1.0/curvature; double segment = arc * radius; if ( segment < SegmentMin ) segment = SegmentMin; else if ( segment > SegmentMax ) segment = SegmentMax; return segment; } void View::drawImplicitInSquare( const Plot & plot, QPainter * painter, double x, double y, Qt::Orientations orientation, QList * singular ) { plot.updateFunction(); Plot diff1 = plot; diff1.differentiate(); Plot diff2 = diff1; diff2.differentiate(); #ifdef DEBUG_IMPLICIT painter->save(); painter->setPen( QPen( Qt::black, painter->pen().width() ) ); QPointF tl = toPixel( QPointF( x, y ), ClipInfinite ) - QPoint( 2, 2 ); painter->drawRect( QRectF( tl, QSizeF( 4, 4 ) ) ); painter->restore(); #endif double x_side = (m_xmax-m_xmin)/squares; double y_side = (m_ymax-m_ymin)/squares; // Use a square around the root to bound the tracing // To start with, assume that tracing will go up,right. But this // might not be so, so the upper/lower boundaries may be adjusted depending // on where the tracing ends up double x_lower, x_upper, y_lower, y_upper; if ( orientation & Qt::Vertical ) { x_lower = x; x_upper = x + x_side; } else { double x_prop = (x-m_xmin)/(m_xmax-m_xmin); x_lower = std::floor( x_prop * squares ) * x_side + m_xmin; x_upper = x_lower + x_side; } if ( orientation & Qt::Horizontal ) { y_lower = y; y_upper = y + y_side; } else { double y_prop = (y-m_ymin)/(m_ymax-m_ymin); y_lower = std::floor( y_prop * squares ) * y_side + m_ymin; y_upper = y_lower + y_side; } // If during tracing, the root could not be found, then this will be set to true, // the route will be retraced using a smaller step size and it will attempt to find // a root again. If it fails for a second time, then tracing is finished. bool foundRootPreviously = true; // Used for focal points. double prevAngle = 0; int switchCount = 0; // This is so that the algorithm can "look ahead" to see what is coming up, // before drawing or committing itself to anything potentially bad QPointF prev2 = toPixel( QPointF( x, y ), ClipInfinite ); QPointF prev1 = prev2; // Allow us to doubly retrace double prev_diff_x = 0; double prev_diff_y = 0; for ( int i = 0; i < 500; ++i ) // allow a maximum of 500 traces (to prevent possibly infinite loop) { if ( i == 500 - 1 ) { qDebug() << "Implicit: got to last iteration!\n"; } // (dx, dy) is perpendicular to curve plot.function()->x = x; plot.function()->y = y; plot.function()->m_implicitMode = Function::FixedY; double dx = value( diff1, 0, x, false ); plot.function()->m_implicitMode = Function::FixedX; double dy = value( diff1, 0, y, false ); double k = pixelCurvature( plot, x, y ); double segment_step = maxSegmentLength( k ) * pow( 0.5, switchCount ); // If we couldn't find a root in the previous iteration, it was possibly // because we were using too large a step size. So reduce the step size // and try again. if ( !foundRootPreviously ) segment_step = qMin( segment_step/4, SegmentMin ); // qDebug() << "k="< 0) && (diff > (3./4.)*M_PI) && (diff < (5./4.)*M_PI); if ( switchedDirection ) { // Why do I care about suddenly changing the direction? // Because the chances are, a attracting or repelling point has been reached. // Even if not, it suggests that a smaller step size is needed. If we have // switched direction and are already at the smallest step size, then note // the dodgy point for further investigation and give up for now // qDebug() << "Switched direction: x="<drawLine( prev2, prev1 ); prev2 = prev1; prev1 = toPixel( QPointF( x, y ), ClipInfinite ); markDiagramPointUsed( prev1 ); if ( outOfBounds ) break; } // and the final line painter->drawLine( prev2, prev1 ); } void View::drawFunction( Function * function, QPainter * painter ) { if ( (function->type() == Function::Differential) && (function->eq[0]->order() == 1) && function->plotAppearance( Function::Derivative0 ).showTangentField ) { - QList plots = function->plots( Function::PlotCombinations(Function::AllCombinations) & ~Function::DifferentInitialStates ); - foreach ( const Plot &plot, plots ) + const QList plots = function->plots( Function::PlotCombinations(Function::AllCombinations) & ~Function::DifferentInitialStates ); + for ( const Plot &plot : plots ) drawTangentField( plot, painter ); } - QList plots = function->plots(); - foreach ( const Plot &plot, plots ) + const QList plots = function->plots(); + for ( const Plot &plot : plots ) drawPlot( plot, painter ); } void View::drawTangentField( const Plot & plot, QPainter * painter ) { plot.updateFunction(); Function * function = plot.function(); assert( function->type() == Function::Differential ); // Can only draw tangent fields for first order differential equations assert( function->eq[0]->order() == 1 ); painter->setPen( penForPlot( plot, painter ) ); bool useParameter = function->eq[0]->usesParameter(); Vector v( useParameter ? 3 : 2 ); if ( useParameter ) v[1] = function->k; // For converting from real to pixels double sx = m_clipRect.width() / (m_xmax - m_xmin); double sy = m_clipRect.height() / (m_ymax - m_ymin); for ( double x = ticStartX; x <= m_xmax; x += ticSepX.value() ) { v[0] = x; for ( double y = ticStartY; y <= m_ymax; y += ticSepY.value() ) { v[ useParameter ? 2 : 1 ] = y; double df = XParser::self()->fkt( function->eq[0], v ) * (sy / sx); double theta = std::atan( df ); double dx = std::cos( theta ) * (ticSepX.value() / 8.0); double dy = std::sin( theta ) * (ticSepY.value() / 8.0); QPointF mid( x, y ); QPointF diff( dx, dy ); painter->drawLine( toPixel( mid-diff ), toPixel( mid+diff ) ); } } } /** * Convenience function for drawing lines. Unfortunately, QPainter::drawPolyline * takes a long time to draw the line joins, which is only necessary when we are * using a fat pen. Therefore, draw each line individually if we are using a * thin pen to save time. */ void drawPolyline( QPainter * painter, const QPolygonF & points ) { if ( painter->pen().width() > 5 ) painter->drawPolyline( points ); else if ( points.size() >= 2 ) { QPointF prev = points.first(); for ( int i = 1; i < points.size(); ++i ) { // QPen pen( painter->pen() ); // pen.setColor( (i%2==0) ? Qt::red : Qt::blue ); // painter->setPen( pen ); QPointF next = points[i]; painter->drawLine( prev, next ); prev = next; } } } /** * Speed up drawing by only drawing one line between each straightish section of the curve * These variable are used to determine when the curve can no longer be approximate by a * straight line as the new angle has changed too much */ class CurveApproximator { public: CurveApproximator( const QPolygonF & points ) { assert( points.size() >= 2 ); reset(); QPointF diff = points[ points.size() - 2 ] - points.last(); currentAngle = atan2( diff.y(), diff.x() ); approximatingCurve = true; } CurveApproximator() { reset(); } void reset() { currentAngle = 0; maxClockwise = 0; maxAnticlockwise = 0; maxDistance = 0; approximatingCurve = false; } bool shouldDraw() const { return ((maxAnticlockwise + maxClockwise) * maxDistance) >= 0.5; } void update( const QPolygonF & points ) { // Should have at least two points in the list assert( points.size() >= 2 ); QPointF p1 = points[ points.size() - 2 ]; QPointF p2 = points.last(); QPointF diff = p1 - p2; double angle = atan2( diff.y(), diff.x() ); double lineLength = QLineF( p1, p2 ).length(); if ( lineLength > maxDistance ) maxDistance = lineLength; double clockwise = realModulo( currentAngle-angle, 2*M_PI ); double anticlockwise = realModulo( angle-currentAngle, 2*M_PI ); bool goingClockwise = (clockwise < anticlockwise); if ( goingClockwise ) { // anti-clockwise if ( clockwise > maxClockwise ) maxClockwise = clockwise; } else { // clockwise if ( anticlockwise > maxAnticlockwise ) maxAnticlockwise = anticlockwise; } } double currentAngle; double maxClockwise; double maxAnticlockwise; double maxDistance; bool approximatingCurve; }; void View::drawPlot( const Plot & plot, QPainter *painter ) { plot.updateFunction(); Function * function = plot.function(); // should use drawImplicit for implicit functions assert( function->type() != Function::Implicit ); double dmin = getXmin( function, true ); double dmax = getXmax( function, true ); if ( dmin >= dmax ) return; painter->save(); // Bug in Qt 4.2 TP - QPainter::drawPolyline draws the background as well while printing // So for testing printing, use a brush where one can see the function being drawn painter->setBrush( Qt::white ); if ( (plot.parameter.type() == Parameter::Animated) && (painter->renderHints() & QPainter::Antialiasing) ) { // Don't use antialiasing, so that rendering is speeded up painter->setRenderHint( QPainter::Antialiasing, false ); } painter->setPen( penForPlot( plot, painter ) ); // the 'middle' dx, which may be increased or decreased double max_dx = (dmax-dmin)/m_clipRect.width(); if ( (function->type() == Function::Parametric) || (function->type() == Function::Polar) ) max_dx *= 0.01; // Increase speed while translating the view bool quickDraw = ( m_zoomMode == Translating ); if ( quickDraw ) max_dx *= 4.0; double dx = max_dx; double maxLength = quickDraw ? 8.0 : (function->plotAppearance( plot.plotMode ).style == Qt::SolidLine) ? 4.0 : 1.5; double minLength = maxLength * 0.5; bool drawIntegral = m_integralDrawSettings.draw && (m_integralDrawSettings.plot == plot); double totalLength = 0.0; // total pixel length; used for drawing dotted lines bool p1Set = false; QPointF p1, p2; CurveApproximator approximator; QPolygonF drawPoints; double x = dmin; double prevX = x; // the value of x before last adding dx to it do { QPointF rv = realValue( plot, x, false ); // If we are currently plotting a differential equation, and it became infinite, // then skip x forward to a point where it is finite if ( function->type() == Function::Differential && !XParser::self()->differentialFinite ) { double new_x = XParser::self()->differentialDiverge; if ( new_x > x ) { x = new_x; prevX = x; continue; } } p2 = toPixel( rv, ClipInfinite ); if ( xclipflg || yclipflg ) { prevX = x; x += dx; p1Set = false; // p1 wouldn't be finite (if we had set it) continue; } if ( !p1Set ) { prevX = x; x += dx; p1 = p2; p1Set = true; continue; } //BEGIN adjust dx QRectF bound = QRectF( p1, QSizeF( (p2-p1).x(), (p2-p1).y() ) ).normalized(); double length = QLineF( p1, p2 ).length(); totalLength += length; double min_mod = (function->type() == Function::Cartesian || function->type() == Function::Differential) ? 1e-2 : 5e-4; bool dxAtMinimum = (dx <= max_dx*min_mod); bool dxAtMaximum = (dx >= max_dx); bool dxTooBig = false; bool dxTooSmall = false; if ( QRectF(m_clipRect).intersects( bound ) ) { dxTooBig = !dxAtMinimum && (length > maxLength); dxTooSmall = !dxAtMaximum && (length < minLength); } else dxTooSmall = !dxAtMaximum; if ( dxTooBig ) { dx *= 0.5; x = prevX + dx; totalLength -= length; continue; } if ( dxTooSmall ) dx *= 2.0; //END adjust dx if ( drawIntegral && (x >= m_integralDrawSettings.dmin) && (x <= m_integralDrawSettings.dmax) ) { double y0 = yToPixel( 0 ); /// \todo should draw the shape in one go QPointF points[4]; points[0] = QPointF( p1.x(), y0 ); points[1] = QPointF( p2.x(), y0 ); points[2] = QPointF( p2.x(), p2.y() ); points[3] = QPointF( p1.x(), p1.y() ); painter->drawPolygon( points, 4 ); } else if ( penShouldDraw( totalLength, plot ) ) { if ( drawPoints.isEmpty() ) { drawPoints << p1; } else if ( drawPoints.last() != p1 ) { drawPolyline( painter, drawPoints ); drawPoints.clear(); drawPoints << p1; approximator.reset(); } // The above code should guarantee that drawPoints isn't empty // But check it now in case I do something stupid assert( !drawPoints.isEmpty() ); if ( !approximator.approximatingCurve ) { // Cool, about to add another point. This defines the working angle of the line // approximation drawPoints << p2; approximator = CurveApproximator( drawPoints ); } else { QPointF prev = drawPoints.last(); drawPoints.last() = p2; approximator.update( drawPoints ); // Allow a maximum deviation (in pixels) if ( approximator.shouldDraw() ) { // The approximation is too bad; will have to start again now drawPoints.last() = prev; drawPoints << p2; approximator = CurveApproximator( drawPoints ); } } } markDiagramPointUsed( p2 ); p1 = p2; Q_ASSERT( dx > 0 ); prevX = x; x += dx; } while ( x <= dmax ); // qDebug() << "drawPoints.size()="<restore(); } void View::drawFunctionInfo( QPainter * painter ) { // Don't draw info if translating the view if ( m_zoomMode == Translating ) return; // The names of the plots are drawn around the edge of the view, in a clockwise // direction, starting from the top-right. Picture the positions like this: // // 7 8 9 0 // 6 1 // 5 4 3 2 // Used for determining where to draw the next label indicating the plot name int plotNameAt = 0; - foreach ( Function * function, XParser::self()->m_ufkt ) + for ( Function * function : qAsConst(XParser::self()->m_ufkt) ) { if ( m_stopCalculating ) break; - foreach ( const Plot &plot, function->plots() ) + for ( const Plot &plot : function->plots() ) { plot.updateFunction(); // Draw extrema points? if ( (function->type() == Function::Cartesian) && function->plotAppearance( plot.plotMode ).showExtrema ) { - QList stationaryPoints = findStationaryPoints( plot ); - foreach ( const QPointF &realValue, stationaryPoints ) + const QList stationaryPoints = findStationaryPoints( plot ); + for ( const QPointF &realValue : stationaryPoints ) { painter->setPen( QPen( Qt::black, millimetersToPixels( 1.5, painter->device() ) ) ); painter->drawPoint( toPixel( realValue ) ); QString x = posToString( realValue.x(), (m_xmax-m_xmin)/m_clipRect.width(), View::DecimalFormat ); QString y = posToString( realValue.y(), (m_ymax-m_ymin)/m_clipRect.width(), View::DecimalFormat ); drawLabel( painter, plot.color(), realValue, i18nc( "Extrema point", "x = %1 y = %2", x.replace('.', QLocale().decimalPoint()), y.replace('.', QLocale().decimalPoint()) ) ); } } // Show the name of the plot? if ( function->plotAppearance( plot.plotMode ).showPlotName ) { double x, y; double xmin = m_xmin + 0.1 * (m_xmax-m_xmin); double xmax = m_xmax - 0.1 * (m_xmax-m_xmin); double ymin = m_ymin + 0.1 * (m_ymax-m_ymin); double ymax = m_ymax - 0.1 * (m_ymax-m_ymin); // Find out where on the outer edge of the view to draw it if ( 0 <= plotNameAt && plotNameAt <= 2 ) { x = xmax; y = ymax - (ymax-ymin)*plotNameAt/2; } else if ( 3 <= plotNameAt && plotNameAt <= 5 ) { x = xmax - (xmax-xmin)*(plotNameAt-2)/3; y = ymin; } else if ( 6 <= plotNameAt && plotNameAt <= 7 ) { x = xmin; y = ymin + (ymax-ymin)*(plotNameAt-5)/2; } else { x = xmin + (xmax-xmin)*(plotNameAt-7)/3; y = ymax; } plotNameAt = (plotNameAt+1) % 10; QPointF realPos; if ( function->type() == Function::Implicit ) { findRoot( & x, & y, plot, RoughRoot ); realPos = QPointF( x, y ); } else { double t = getClosestPoint( QPointF( x, y ), plot ); realPos = realValue( plot, t, false ); } // If the closest point isn't in the view, then don't draw the label if ( realPos.x() < m_xmin || realPos.x() > m_xmax || realPos.y() < m_ymin || realPos.y() > m_ymax ) continue; drawLabel( painter, plot.color(), realPos, plot.name() ); } } } } void View::drawLabel( QPainter * painter, const QColor & color, const QPointF & realPos, const QString & text ) { QPalette palette; QColor outline = color; QColor background = outline.lighter( 500 ); background.setAlpha( 127 ); QPointF pixelCenter = toPixel( realPos ); QRectF rect( pixelCenter, QSizeF( 1, 1 ) ); painter->setFont( m_labelFont ); int flags = Qt::TextSingleLine | Qt::AlignLeft | Qt::AlignTop; rect = painter->boundingRect( rect, flags, text ).adjusted( -7, -3, 4, 2 ); // Try and find a nice place for inserting the rectangle int bestCost = int(1e7); QPointF bestCenter = realPos; for ( double x = pixelCenter.x() - 300; x <= pixelCenter.x() + 300; x += 20 ) { for ( double y = pixelCenter.y() - 300; y <= pixelCenter.y() + 300; y += 20 ) { QPointF center( x, y ) ; rect.moveCenter( center ); double length = (x-pixelCenter.x())*(x-pixelCenter.x()) + (y-pixelCenter.y())*(y-pixelCenter.y()); int cost = rectCost( rect ) + int(length)/100; if ( cost < bestCost ) { bestCenter = center; bestCost = cost; } } } rect.moveCenter( bestCenter ); markDiagramAreaUsed( rect ); painter->setBrush( background ); painter->setPen( outline ); painter->drawRoundedRect( rect, int(1000/rect.width()), int(1000/rect.height()) ); // If the rectangle does not lie over realPos, then draw a line to realPos from the rectangle if ( ! rect.contains( pixelCenter ) ) { QPointF lineStart = bestCenter; QLineF line( pixelCenter, bestCenter ); QPointF intersect = bestCenter; // Where does line intersect the rectangle? if ( QLineF( rect.topLeft(), rect.topRight() ).intersect( line, & intersect ) == QLineF::BoundedIntersection ) lineStart = intersect; else if ( QLineF( rect.topRight(), rect.bottomRight() ).intersect( line, & intersect ) == QLineF::BoundedIntersection ) lineStart = intersect; else if ( QLineF( rect.bottomRight(), rect.bottomLeft() ).intersect( line, & intersect ) == QLineF::BoundedIntersection ) lineStart = intersect; else if ( QLineF( rect.bottomLeft(), rect.topLeft() ).intersect( line, & intersect ) == QLineF::BoundedIntersection ) lineStart = intersect; painter->drawLine( lineStart, pixelCenter ); } painter->setPen( Qt::black ); painter->drawText( rect.adjusted( 7, 3, -4, -2 ), flags, text ); } QRect View::usedDiagramRect( const QRectF & rect ) const { double x0 = rect.left() / m_clipRect.width(); double x1 = rect.right() / m_clipRect.width(); double y0 = rect.top() / m_clipRect.height(); double y1 = rect.bottom() / m_clipRect.height(); int i0 = qMax( int( x0 * LabelGridSize ), 0 ); int i1 = qMin( int( x1 * LabelGridSize ), LabelGridSize-1 ); int j0 = qMax( int( y0 * LabelGridSize ), 0 ); int j1 = qMin( int( y1 * LabelGridSize ), LabelGridSize-1 ); return QRect( i0, j0, i1-i0+1, j1-j0+1 ) & QRect( 0, 0, LabelGridSize, LabelGridSize ); } void View::markDiagramAreaUsed( const QRectF & rect ) { if ( m_zoomMode == Translating ) return; QRect r = usedDiagramRect( rect ); for ( int i = r.left(); i <= r.right(); ++i ) for ( int j = r.top(); j <= r.bottom(); ++j ) m_usedDiagramArea[i][j] = true; } void View::markDiagramPointUsed( const QPointF & point ) { if ( m_zoomMode == Translating ) return; double x = point.x() / m_clipRect.width(); double y = point.y() / m_clipRect.height(); int i = int( x * LabelGridSize ); int j = int( y * LabelGridSize ); if ( i<0 || i>=LabelGridSize || j<0 || j>=LabelGridSize ) return; m_usedDiagramArea[i][j] = true; } int View::rectCost( QRectF rect ) const { rect = rect.normalized(); int cost = 0; // If the rectangle goes off the edge, mark it as very high cost) if ( rect.intersects( m_clipRect ) ) { QRectF intersect = (rect & m_clipRect); cost += int(rect.width() * rect.height() - intersect.width() * intersect.height()); } else { // The rectangle is completely outside! cost += int(rect.width() * rect.height()); } QRect r = usedDiagramRect( rect ); for ( int i = r.left(); i <= r.right(); ++i ) for ( int j = r.top(); j <= r.bottom(); ++j ) if ( m_usedDiagramArea[i][j] ) cost += 200; return cost; } bool View::penShouldDraw( double length, const Plot & plot ) { // Always use a solid line when translating the view if ( m_zoomMode == Translating ) return true; Function * function = plot.function(); Qt::PenStyle style = function->plotAppearance( plot.plotMode ).style; double sepBig = 8.0; // separation distance between dashes double sepMid = 7.0; // separation between a dash and a dot double sepSmall = 6.5; // separation distance between dots double dash = 9.0; // length of a dash double dot = 3.5; // length of a dot switch ( style ) { case Qt::NoPen: // *whatever*... return false; case Qt::SolidLine: return true; case Qt::DashLine: return realModulo( length, dash + sepBig ) < dash; case Qt::DotLine: return realModulo( length, dot + sepSmall ) < dot; case Qt::DashDotLine: { double at = realModulo( length, dash + sepMid + dot + sepMid ); if ( at < dash ) return true; if ( at < (dash + sepMid) ) return false; if ( at < (dash + sepMid + dot) ) return true; return false; } case Qt::DashDotDotLine: { double at = realModulo( length, dash + sepMid + dot + sepSmall + dot + sepMid ); if ( at < dash ) return true; if ( at < (dash + sepMid) ) return false; if ( at < (dash + sepMid + dot) ) return true; if ( at < (dash + sepMid + dot + sepSmall) ) return false; if ( at < (dash + sepMid + dot + sepSmall + dot) ) return true; return false; } case Qt::MPenStyle: case Qt::CustomDashLine: { assert( ! "Do not know how to handle this style!" ); return true; } } assert( ! "Unknown pen style!" ); return true; } QPen View::penForPlot( const Plot & plot, QPainter * painter ) const { QPen pen; if ( m_zoomMode == Translating ) { // plot style is always a solid line when translating the view pen.setCapStyle( Qt::FlatCap ); } else { pen.setCapStyle( Qt::RoundCap ); // (the style will be set back to FlatCap if the plot style is a solid line) } pen.setColor( plot.color() ); Function * ufkt = plot.function(); PlotAppearance appearance = ufkt->plotAppearance( plot.plotMode ); double lineWidth_mm = appearance.lineWidth; if ( appearance.style == Qt::SolidLine ) pen.setCapStyle( Qt::FlatCap ); double width = millimetersToPixels( lineWidth_mm, painter->device() ); pen.setWidthF( width ); return pen; } double View::millimetersToPixels( double width_mm, QPaintDevice * device ) const { // assert( device->logicalDpiX() == device->logicalDpiY() ); return device->logicalDpiX() * (width_mm/25.4); } double View::pixelsToMillimeters( double width_pixels, QPaintDevice * device ) const { // assert( device->logicalDpiX() == device->logicalDpiY() ); return (width_pixels * 25.4) / device->logicalDpiX(); } void View::drawHeaderTable( QPainter *painter ) { painter->setFont( Settings::headerTableFont() ); QString alx = i18nc("%1=minimum value, %2=maximum value", "%1 to %2", Settings::xMin(), Settings::xMax()); QString aly = i18nc("%1=minimum value, %2=maximum value", "%1 to %2", Settings::yMin(), Settings::yMax()); QString atx = "1E = " + ticSepX.expression(); QString aty = "1E = " + ticSepY.expression(); QString text = "" "" "" "" "
" + i18n("Parameters") + "" + i18n("Plotting Range") + "" + i18n("Axes Division") + "
" + i18n("x-Axis:") + "" + alx + "" + atx + "
" + i18n("y-Axis:") + "" + aly + "" + aty + "
"; text += "

" + i18n("Functions:") + "
    "; - foreach ( Function * function, XParser::self()->m_ufkt ) + for ( Function * function : qAsConst(XParser::self()->m_ufkt) ) text += "
  • " + function->name().replace( '\n', "
    " ) + "
  • "; text += "
"; m_textDocument->setHtml( text ); m_textDocument->documentLayout()->draw( painter, QAbstractTextDocumentLayout::PaintContext() ); QRectF br = m_textDocument->documentLayout()->frameBoundingRect( m_textDocument->rootFrame() ); painter->translate( 0, br.height() ); } QList< QPointF > View::findStationaryPoints( const Plot & plot ) { Plot plot2 = plot; plot2.differentiate(); - QList< double > roots = findRoots( plot2, getXmin( plot.function() ), getXmax( plot.function() ), RoughRoot ); + const QList< double > roots = findRoots( plot2, getXmin( plot.function() ), getXmax( plot.function() ), RoughRoot ); plot.updateFunction(); QList< QPointF > stationaryPoints; - foreach ( double x, roots ) + for ( double x : roots ) { QPointF real = realValue( plot, x, false ); if ( real.y() >= m_ymin && real.y() <= m_ymax ) stationaryPoints << real; } return stationaryPoints; } QList< double > View::findRoots( const Plot & plot, double min, double max, RootAccuracy accuracy ) { typedef QMap< double, double > DoubleMap; DoubleMap roots; int count = 10; // number of points to (initially) check for roots int prevNumRoots = 0; while ( count < 1000 ) { // Use this to detect finding the same root. double prevX = 0.0; double dx = (max-min) / double(count); for ( int i = 0; i <= count; ++i ) { double x = min + dx*i; bool found = findRoot( & x, plot, accuracy ); if ( !found || x < min || x > max ) continue; if ( !roots.isEmpty() ) { // Check if already have a close root if ( qAbs(x-prevX) <= (dx/4) ) continue; DoubleMap::iterator nextIt = roots.lowerBound(x); if ( nextIt == roots.end() ) --nextIt; double lower, upper; lower = upper = *nextIt; if ( nextIt != roots.begin() ) lower = *(--nextIt); if ( (qAbs(x-lower) <= (dx/4)) || (qAbs(x-upper) <= (dx/4)) ) continue; } roots.insert( x, x ); prevX = x; } int newNumRoots = roots.size(); if ( newNumRoots == prevNumRoots ) break; prevNumRoots = newNumRoots; count *= 4; } return roots.keys(); } void View::setupFindRoot( const Plot & plot, RootAccuracy accuracy, double * max_k, double * max_f, int * n ) { plot.updateFunction(); if ( accuracy == PreciseRoot ) { *max_k = 200; *max_f = 1e-14; } else { // Rough root *max_k = 10; *max_f = 1e-10; } *n = 1 + plot.derivativeNumber(); } bool View::findRoot( double * x, const Plot & plot, RootAccuracy accuracy ) { #ifdef DEBUG_IMPLICIT root_find_requests++; #endif double max_k, max_f; int n; setupFindRoot( plot, accuracy, & max_k, & max_f, & n ); Equation * eq = plot.function()->eq[0]; DifferentialState * state = plot.state(); double h = qMin( m_xmax-m_xmin, m_ymax-m_ymin ) * 1e-4; double f = value( plot, 0, *x, false ); int k; for ( k=0; k < max_k; ++k ) { double df = XParser::self()->derivative( n, eq, state, *x, h ); if ( qAbs(df) < 1e-20 ) df = 1e-20 * ((df < 0) ? -1 : 1); double dx = f / df; *x -= dx; f = value( plot, 0, *x, false ); if ( (qAbs(f) <= max_f) && (qAbs(dx) <= (h*1e-5)) ) break; } #ifdef DEBUG_IMPLICIT root_find_iterations += k; #endif // We continue calculating until |f| < max_f; this may result in k reaching // max_k. However, if |f| is reasonably small (even if reaching max_k), // we consider it a root. return ( qAbs(f) < 1e-6 ); } bool View::findRoot( double * x, double * y, const Plot & plot, RootAccuracy accuracy ) { double max_k, max_f; int n; setupFindRoot( plot, accuracy, & max_k, & max_f, & n ); Function * function = plot.function(); Equation * eq = function->eq[0]; DifferentialState * state = plot.state(); double hx = (m_xmax-m_xmin) * 1e-5; double hy = (m_ymax-m_ymin) * 1e-5; function->y = *y; function->m_implicitMode = Function::FixedY; double f = value( plot, 0, *x, false ); for ( int k=0; k < max_k; ++k ) { function->x = *x; function->y = *y; function->m_implicitMode = Function::FixedY; double dfx = XParser::self()->derivative( n, eq, state, *x, hx ); function->m_implicitMode = Function::FixedX; double dfy = XParser::self()->derivative( n, eq, state, *y, hy ); double dff = dfx*dfx + dfy*dfy; if ( dff < 1e-20 ) dff = 1e-20; double dx = f * dfx / dff; *x -= dx; double dy = f * dfy / dff; *y -= dy; function->y = *y; function->m_implicitMode = Function::FixedY; f = value( plot, 0, *x, false ); if ( (qAbs(f) <= max_f) && (qAbs(dx) <= (hx*1e-5)) && (qAbs(dy) <= (hy*1e-5)) ) break; } // We continue calculating until |f| < max_f; this may result in k reaching // max_k. However, if |f| is reasonably small (even if reaching max_k), // we consider it a root. return ( qAbs(f) < 1e-6 ); } void View::paintEvent(QPaintEvent *) { // Note: it is important to have this function call before we begin painting // as updateCrosshairPosition may set the statusbar text bool inBounds = updateCrosshairPosition(); QPainter p; p.begin(this); p.drawPixmap( QPoint( 0, 0 ), buffer ); // the current cursor position in widget coordinates QPoint mousePos = mapFromGlobal( QCursor::pos() ); if ( (m_zoomMode == ZoomInDrawing) || (m_zoomMode == ZoomOutDrawing) ) { QPalette palette; QColor highlightColor = palette.color( QPalette::Highlight ); QColor backgroundColor = highlightColor; backgroundColor.setAlpha( 63 ); p.setPen( highlightColor ); p.setBrush( backgroundColor ); p.setBackgroundMode (Qt::OpaqueMode); p.setBackground (Qt::blue); QRect rect( m_zoomRectangleStart, mousePos ); p.drawRect( rect ); } else if ( m_zoomMode == AnimatingZoom ) { QPointF tl( toPixel( m_animateZoomRect.topLeft() ) ); QPointF br( toPixel( m_animateZoomRect.bottomRight() ) ); p.drawRect( QRectF( tl, QSizeF( br.x()-tl.x(), br.y()-tl.y() ) ) ); } else if ( shouldShowCrosshairs() ) { Function * function = m_currentPlot.function(); QPen pen; if ( function ) { QColor functionColor = m_currentPlot.color(); pen.setColor( functionColor ); p.setPen( pen ); p.setRenderHint( QPainter::Antialiasing, true ); double x = m_crosshairPosition.x(); double y = m_crosshairPosition.y(); //BEGIN calculate curvature, normal double k = 0; double normalAngle = 0; switch ( function->type() ) { case Function::Parametric: case Function::Polar: normalAngle = pixelNormal( m_currentPlot, m_trace_x ); k = pixelCurvature( m_currentPlot, m_trace_x ); break; case Function::Differential: case Function::Cartesian: case Function::Implicit: normalAngle = pixelNormal( m_currentPlot, x, y ); k = pixelCurvature( m_currentPlot, x, y ); break; } if ( k < 0 ) { k = -k; normalAngle += M_PI; } //END calculate curvature, normal if ( k > 1e-5 && Settings::detailedTracing() && inBounds ) { p.save(); // Transform the painter so that the center of the osculating circle is the origin, // with the normal line coming in from the left. QPointF center = m_crosshairPixelCoords + (1/k) * QPointF( cos( normalAngle ), sin( normalAngle ) ); p.translate( center ); p.rotate( normalAngle * 180 / M_PI ); // draw osculating circle pen.setColor( functionColor ); p.setPen( pen ); p.drawEllipse( QRectF( -QPointF( 1/k, 1/k ), QSizeF( 2/k, 2/k ) ) ); // draw normal pen.setColor( functionColor ); p.setPen( pen ); p.setBrush( pen.color() ); p.drawLine( QLineF( -1/k, 0, 0, 0 ) ); // draw normal arrow QPolygonF arrowHead(3); arrowHead[0] = QPointF( 0, 0 ); arrowHead[1] = QPointF( -3, -2 ); arrowHead[2] = QPointF( -3, +2 ); p.drawPolygon( arrowHead ); // draw tangent double tangent_scale = 1.2; // make the tangent look better p.drawLine( QLineF( -1/k, -qMax( 1/k, qreal(15.) ) * tangent_scale, -1/k, qMax( 1/k, qreal(15.) ) * tangent_scale ) ); // draw perpendicular symbol QPolygonF perp(3); perp[0] = QPointF( -1/k, 10 ); perp[1] = QPointF( -1/k + 10, 10 ); perp[2] = QPointF( -1/k + 10, 0 ); p.drawPolyline( perp ); // draw intersection blob p.drawRect( QRectF( -1/k-1, -1, 2, 2 ) ); p.restore(); // Already show osculating circle, etc, so don't draw crosshairs quite so prominently functionColor.setAlpha( 63 ); pen.setColor( functionColor ); } } else { // Use an inverted background color for contrast QColor inverted = QColor( 255-m_backgroundColor.red(), 255-m_backgroundColor.green(), 255-m_backgroundColor.blue() ); pen.setColor( inverted ); } p.setPen( pen ); double x = m_crosshairPixelCoords.x(); double y = m_crosshairPixelCoords.y(); p.drawLine( QPointF( 0, y ), QPointF( m_clipRect.right(), y ) ); p.drawLine( QPointF( x, 0 ), QPointF( x, m_clipRect.height() ) ); } p.end(); } double View::pixelNormal( const Plot & plot, double x, double y ) { Function * f = plot.function(); assert( f ); plot.updateFunction(); // For converting from real to pixels double sx = m_clipRect.width() / (m_xmax - m_xmin); double sy = m_clipRect.height() / (m_ymax - m_ymin); double dx = 0; double dy = 0; double h = this->h( plot ); int d0 = plot.derivativeNumber(); int d1 = d0+1; switch ( f->type() ) { case Function::Differential: case Function::Cartesian: { double df = XParser::self()->derivative( d1, f->eq[0], plot.state(), x, h ); return -atan( df * (sy/sx) ) - (M_PI/2); } case Function::Implicit: { dx = XParser::self()->partialDerivative( d1, d0, f->eq[0], 0, x, y, h, h ) / sx; dy = XParser::self()->partialDerivative( d0, d1, f->eq[0], 0, x, y, h, h ) / sy; double theta = -atan( dy / dx ); if ( dx < 0 ) theta += M_PI; theta += M_PI; return theta; } case Function::Polar: { double r = XParser::self()->derivative( d0, f->eq[0], 0, x, h ); double dr = XParser::self()->derivative( d1, f->eq[0], 0, x, h ); dx = (dr * lcos(x) - r * lsin(x) * XParser::self()->radiansPerAngleUnit()) * sx; dy = (dr * lsin(x) + r * lcos(x) * XParser::self()->radiansPerAngleUnit()) * sy; break; } case Function::Parametric: { dx = XParser::self()->derivative( d1, f->eq[0], 0, x, h ) * sx; dy = XParser::self()->derivative( d1, f->eq[1], 0, x, h ) * sy; break; } } double theta = - atan( dy / dx ) - (M_PI/2); if ( dx < 0 ) theta += M_PI; return theta; } double View::pixelCurvature( const Plot & plot, double x, double y ) { Function * f = plot.function(); // For converting from real to pixels double sx = m_clipRect.width() / (m_xmax - m_xmin); double sy = m_clipRect.height() / (m_ymax - m_ymin); double fdx = 0; double fdy = 0; double fddx = 0; double fddy = 0; double fdxy = 0; double h = this->h( plot ); int d0 = plot.derivativeNumber(); int d1 = d0+1; int d2 = d0+2; switch ( f->type() ) { case Function::Differential: case Function::Cartesian: { DifferentialState * state = plot.state(); fdx = sx; fddx = 0; fdy = XParser::self()->derivative( d1, f->eq[0], state, x, h ) * sy; fddy = XParser::self()->derivative( d2, f->eq[0], state, x, h) * sy; // qDebug() << "fdy="<type() != Function::Cartesian ) return true; bool lowerOk = ((!plot->usecustomxmin) || (plot->usecustomxmin && m_crosshairPosition.x()>plot->dmin.value())); bool upperOk = ((!plot->usecustomxmax) || (plot->usecustomxmax && m_crosshairPosition.x()dmax.value())); return lowerOk && upperOk; } void View::mousePressEvent(QMouseEvent *e) { m_AccumulatedDelta = 0; m_mousePressTimer->start(); // In general, we want to update the view update(); if ( m_popupMenuStatus != NoPopup ) return; if (m_isDrawing) { m_stopCalculating = true; //stop drawing return; } if ( m_zoomMode != Normal ) { // If the user clicked with the right mouse button will zooming in or out, then cancel it if ( (m_zoomMode == ZoomInDrawing) || (m_zoomMode == ZoomOutDrawing) ) { m_zoomMode = Normal; } updateCursor(); return; } m_haveRoot = false; bool hadFunction = (m_currentPlot.functionID() != -1 ); updateCrosshairPosition(); if( !m_readonly && e->button()==Qt::RightButton) //clicking with the right mouse button { getPlotUnderMouse(); if ( m_currentPlot.function() ) { if ( hadFunction ) m_popupMenuStatus = PopupDuringTrace; else m_popupMenuStatus = Popup; fillPopupMenu(); m_popupMenu->exec( QCursor::pos() ); } return; } if(e->button()!=Qt::LeftButton) return; if ( m_currentPlot.functionID() >= 0 ) //disable trace mode if trace mode is enable { m_currentPlot.setFunctionID( -1 ); setStatusBar( QString(), RootSection ); setStatusBar( QString(), FunctionSection ); mouseMoveEvent(e); return; } QPointF closestPoint = getPlotUnderMouse(); Function * function = m_currentPlot.function(); if ( function ) { QPointF ptd( toPixel( closestPoint ) ); QPoint globalPos = mapToGlobal( ptd.toPoint() ); QCursor::setPos( globalPos ); setStatusBar( m_currentPlot.name().replace( '\n', " ; " ), FunctionSection ); return; } // user didn't click on a plot; so we prepare to enter translation mode m_currentPlot.setFunctionID( -1 ); m_zoomMode = AboutToTranslate; m_prevDragMousePos = e->pos(); updateCursor(); } void View::fillPopupMenu( ) { Function * function = m_currentPlot.function(); if ( !function ) return; m_popupMenuTitle->setText( m_currentPlot.name().replace( '\n', "; " ) ); QAction *calcArea = MainDlg::self()->actionCollection()->action("grapharea"); QAction *maxValue = MainDlg::self()->actionCollection()->action("maximumvalue"); QAction *minValue = MainDlg::self()->actionCollection()->action("minimumvalue"); m_popupMenu->removeAction(calcArea); m_popupMenu->removeAction(maxValue); m_popupMenu->removeAction(minValue); if ( function->type() == Function::Cartesian || function->type() == Function::Differential ) { m_popupMenu->addAction(calcArea); m_popupMenu->addAction(maxValue); m_popupMenu->addAction(minValue); } } QPointF View::getPlotUnderMouse() { m_currentPlot.setFunctionID( -1 ); m_trace_x = 0.0; Plot bestPlot; double best_distance = 1e30; // a nice large number QPointF best_cspos; - foreach ( Function * function, XParser::self()->m_ufkt ) + for ( Function * function : qAsConst(XParser::self()->m_ufkt) ) { const QList< Plot > plots = function->plots(); - foreach ( const Plot &plot, plots ) + for ( const Plot &plot : plots ) { plot.updateFunction(); double best_x = 0.0, distance; QPointF cspos; if ( function->type() == Function::Implicit ) { double x = m_crosshairPosition.x(); double y = m_crosshairPosition.y(); findRoot( & x, & y, plot, PreciseRoot ); QPointF d = toPixel( QPointF( x, y ), ClipInfinite ) - toPixel( QPointF( m_crosshairPosition.x(), m_crosshairPosition.y() ), ClipInfinite ); distance = std::sqrt( d.x()*d.x() + d.y()*d.y() ); cspos = QPointF( x, y ); } else { best_x = getClosestPoint( m_crosshairPosition, plot ); distance = pixelDistance( m_crosshairPosition, plot, best_x, false ); cspos = realValue( plot, best_x, false ); } if ( distance < best_distance ) { best_distance = distance; bestPlot = plot; m_trace_x = best_x; best_cspos = cspos; } } } if ( best_distance < 10.0 ) { m_currentPlot = bestPlot; m_crosshairPosition = best_cspos; return m_crosshairPosition; } else return QPointF(); } double View::getClosestPoint( const QPointF & pos, const Plot & plot ) { plot.updateFunction(); double best_x = 0.0; Function * function = plot.function(); assert( function->type() != Function::Implicit ); // should use findRoot (3D version) for this switch ( function->type() ) { case Function::Implicit: break; case Function::Differential: case Function::Cartesian: { double best_pixel_x = m_clipRect.width() / 2; QPointF pixelPos = toPixel( pos, ClipInfinite ); double dmin = getXmin( function ); double dmax = getXmax( function ); double stepSize = (m_xmax-m_xmin)/m_clipRect.width(); // Algorithm in use here: Work out the shortest distance between the // line joining (x0,y0) to (x1,y1) and the given point (real_x,real_y) double x = dmin; double y0 = value( plot, 0, x, false ); double best_distance = 1e20; // a large distance while ( x <= dmax && (xToPixel(x) < best_pixel_x+best_distance) ) { x += stepSize; double y1 = value( plot, 0, x, false ); double _x0 = xToPixel( x-stepSize, ClipInfinite ); double _x1 = xToPixel( x, ClipInfinite ); double _y0 = yToPixel( y0, ClipInfinite ); double _y1 = yToPixel( y1, ClipInfinite ); double k = (_y1-_y0)/(_x1-_x0); double closest_x, closest_y; if ( k == 0 ) { closest_x = pixelPos.x(); closest_y = _y0; } else { closest_x = (pixelPos.y() + pixelPos.x()/k + _x0*k - _y0) / (k + 1.0/k); closest_y = (pixelPos.x() + pixelPos.y()*k + _y0/k - _x0) / (k + 1.0/k); } bool valid = (x-1.5*stepSize <= xToReal(closest_x)) && (xToReal(closest_x) <= x+0.5*stepSize); double dfx = closest_x - pixelPos.x(); double dfy = closest_y - pixelPos.y(); double distance = sqrt( dfx*dfx + dfy*dfy ); bool insideView = 0 <= closest_y && closest_y <= m_clipRect.height(); if ( distance < best_distance && insideView && valid ) { best_distance = distance; best_pixel_x = closest_x; } y0 = y1; } best_x = xToReal( best_pixel_x ); break; } case Function::Polar: case Function::Parametric: { double minX = getXmin( function ); double maxX = getXmax( function ); double stepSize = 0.001; while ( stepSize > 0.0000009 ) { double best_distance = 1e20; // a large distance double x = minX; while ( x <= maxX ) { double distance = pixelDistance( pos, plot, x, false ); bool insideView = QRectF(m_clipRect).contains( toPixel( realValue( plot, x, false ), ClipInfinite ) ); if ( distance < best_distance && insideView ) { best_distance = distance; best_x = x; } x += stepSize; } minX = best_x - stepSize; maxX = best_x + stepSize; stepSize *= 0.1; } break; } } return best_x; } double View::pixelDistance( const QPointF & pos, const Plot & plot, double x, bool updateFunction ) { QPointF f = realValue( plot, x, updateFunction ); QPointF df = toPixel( pos, ClipInfinite ) - toPixel( f, ClipInfinite ); return std::sqrt( df.x()*df.x() + df.y()*df.y() ); } QString View::posToString( double x, double delta, PositionFormatting format, const QColor &color ) const { delta = qAbs(delta); if ( delta == 0 ) delta = 1; QString numberText; int decimalPlaces = 1-int(log(delta)/log(10.0)); // Avoid exponential format for smallish numbers if ( 0.01 < qAbs(x) && qAbs(x) < 10000 ) format = DecimalFormat; switch ( format ) { case ScientificFormat: { int accuracy = 1 + decimalPlaces + int(log(qAbs(x))/log(10.0)); if ( accuracy < 2 ) { // Ensure a minimum of two significant digits accuracy = 2; } QString number = QString::number( x, 'g', accuracy ); if ( number.contains( 'e' ) ) { number.remove( "+0" ); number.remove( '+' ); number.replace( "-0", MinusSymbol ); number.replace( 'e', QChar(215) + QString("10") ); number.append( "" ); } if ( x > 0.0 ) number.prepend('+'); numberText = QString("").arg( color.name() ) + number + ""; break; } case DecimalFormat: { if ( decimalPlaces >= 0 ) numberText = QString::number( x, 'f', decimalPlaces ); else numberText = QString::number( x*(pow(10.0,decimalPlaces)), 'f', 0 ) + QString( -decimalPlaces, '0' ); break; } } numberText.replace( '-', MinusSymbol ); return numberText; } void View::mouseMoveEvent(QMouseEvent *e) { if ( m_previousMouseMovePos != e->globalPos() ) { m_AccumulatedDelta = 0; } m_previousMouseMovePos = e->globalPos(); m_AccumulatedDelta = 0; if ( m_isDrawing || !e) return; bool inBounds = updateCrosshairPosition(); if ( !m_haveRoot ) setStatusBar( QString(), RootSection ); QString sx, sy; if ( inBounds ) { sx = i18n( "x = %1", posToString( m_crosshairPosition.x(), (m_xmax-m_xmin)/m_clipRect.width(), View::DecimalFormat ).replace('.', QLocale().decimalPoint()) ); sy = i18n( "y = %1", posToString( m_crosshairPosition.y(), (m_ymax-m_ymin)/m_clipRect.width(), View::DecimalFormat ).replace('.', QLocale().decimalPoint()) ); } else sx = sy = ""; setStatusBar( sx, XSection ); setStatusBar( sy, YSection ); if ( e->buttons() & Qt::LeftButton ) { if ( m_zoomMode == ZoomIn ) { m_zoomMode = ZoomInDrawing; m_zoomRectangleStart = e->pos(); } else if ( m_zoomMode == ZoomOut ) { m_zoomMode = ZoomOutDrawing; m_zoomRectangleStart = e->pos(); } else if ( ((m_zoomMode == AboutToTranslate) || (m_zoomMode == Translating)) && (e->pos() != m_prevDragMousePos) ) { m_zoomMode = Translating; QPoint d = m_prevDragMousePos - e->pos(); m_prevDragMousePos = e->pos(); translateView( d.x(), d.y() ); } } if ( (m_zoomMode == Normal) && (m_popupMenuStatus != NoPopup) && !m_popupMenu->isVisible() ) { if ( m_popupMenuStatus==Popup) m_currentPlot.setFunctionID( -1 ); m_popupMenuStatus = NoPopup; } update(); updateCursor(); } void View::leaveEvent(QEvent *) { setStatusBar( "", XSection ); setStatusBar( "", YSection ); updateCrosshairPosition(); update(); } void View::wheelEvent(QWheelEvent *e) { m_AccumulatedDelta += e->delta(); if (e->modifiers() & Qt::ControlModifier) { if (m_AccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep) { zoomIn( e->pos(), double(Settings::zoomInStep())/100.0 ); m_AccumulatedDelta = 0; } else if (m_AccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep) { zoomIn( e->pos(), (double(Settings::zoomOutStep())/100.0) + 1.0 ); m_AccumulatedDelta = 0; } e->accept(); return; } else { m_AccumulatedDelta = 0; } QWidget::wheelEvent(e); } bool View::updateCrosshairPosition() { QPointF mousePos = mapFromGlobal( QCursor::pos() ); bool out_of_bounds = false; // for the ypos m_crosshairPosition = toReal( mousePos ); m_currentPlot.updateFunction(); Function * it = m_currentPlot.function(); if ( it && crosshairPositionValid( it ) && (m_popupMenuStatus != Popup) ) { // The user currently has a plot selected, with the mouse in a valid position if ( (it->type() == Function::Parametric) || (it->type() == Function::Polar) ) { // Should we increase or decrease t to get closer to the mouse? double dx[2] = { -0.00001, +0.00001 }; double d[] = { 0.0, 0.0 }; for ( int i = 0; i < 2; ++ i ) d[i] = pixelDistance( m_crosshairPosition, m_currentPlot, m_trace_x + dx[i], false ); double prev_best = pixelDistance( m_crosshairPosition, m_currentPlot, m_trace_x, false ); double current_dx = dx[(d[0] < d[1]) ? 0 : 1]*1e3; while ( true ) { double new_distance = pixelDistance( m_crosshairPosition, m_currentPlot, m_trace_x + current_dx, false ); if ( new_distance < prev_best ) { prev_best = new_distance; m_trace_x += current_dx; } else { if ( qAbs(current_dx) > 9e-10 ) current_dx *= 0.1; else break; } } double min = getXmin( it ); double max = getXmax( it ); if ( m_trace_x > max ) m_trace_x = max; else if ( m_trace_x < min ) m_trace_x = min; m_crosshairPosition = realValue( m_currentPlot, m_trace_x, false ); } else if ( it->type() == Function::Implicit ) { double x = m_crosshairPosition.x(); double y = m_crosshairPosition.y(); findRoot( & x, & y, m_currentPlot, PreciseRoot ); m_crosshairPosition = QPointF( x, y ); } else { // cartesian or differential plot m_crosshairPosition.setY( value( m_currentPlot, 0, m_crosshairPosition.x(), false ) ); mousePos.setY( yToPixel( m_crosshairPosition.y() )); if ( m_crosshairPosition.y()m_ymax) //the ypoint is not visible { out_of_bounds = true; } else if ( (fabs(yToReal(mousePos.y())) < (m_ymax-m_ymin)/80) && (it->type() == Function::Cartesian || it->type() == Function::Differential) ) { double x0 = m_crosshairPosition.x(); if ( !m_haveRoot && findRoot( &x0, m_currentPlot, PreciseRoot ) ) { QString str=" "; str += i18nc("%1 is a subscript zero symbol", "root: x%1 = ", SubscriptZeroSymbol); setStatusBar( str+QLocale().toString( x0, 'f', 5 ), RootSection ); m_haveRoot=true; emit updateRootValue( true, x0 ); } } else { m_haveRoot=false; emit updateRootValue( false, 0 ); } } // For Cartesian plots, only adjust the cursor position if it is not at the ends of the view if ( ((it->type() != Function::Cartesian) && (it->type() != Function::Differential)) || m_clipRect.contains( mousePos.toPoint() ) ) { mousePos = toPixel( m_crosshairPosition, ClipAll, mousePos ); QPoint globalPos = mapToGlobal( mousePos.toPoint() ); QCursor::setPos( globalPos ); } } m_crosshairPixelCoords = mousePos; return !out_of_bounds && m_clipRect.contains( mousePos.toPoint() ); } void View::mouseReleaseEvent ( QMouseEvent * e ) { bool doDrawPlot = false; // avoid zooming in if the zoom rectangle is very small and the mouse was // just pressed, which suggests that the user dragged the mouse accidentally QRect zoomRect = QRect( m_zoomRectangleStart, e->pos() ).normalized(); int area = zoomRect.width() * zoomRect.height(); if ( (area <= 500) && (m_mousePressTimer->elapsed() < QApplication::startDragTime()) ) { if ( m_zoomMode == ZoomInDrawing ) m_zoomMode = ZoomIn; else if ( m_zoomMode == ZoomOutDrawing ) m_zoomMode = ZoomOut; } switch ( m_zoomMode ) { case Normal: case AnimatingZoom: case AboutToTranslate: break; case Translating: doDrawPlot = true; Settings::self()->save(); MainDlg::self()->requestSaveCurrentState(); break; case ZoomIn: zoomIn( e->pos(), double(Settings::zoomInStep())/100.0 ); break; case ZoomOut: zoomIn( e->pos(), (double(Settings::zoomOutStep())/100.0) + 1.0 ); break; case ZoomInDrawing: zoomIn( zoomRect ); break; case ZoomOutDrawing: zoomOut( zoomRect ); break; } m_zoomMode = Normal; if ( doDrawPlot ) drawPlot(); else update(); updateCursor(); } void View::zoomIn( const QPoint & mousePos, double zoomFactor ) { QPointF real = toReal( mousePos ); double diffx = (m_xmax-m_xmin)*zoomFactor; double diffy = (m_ymax-m_ymin)*zoomFactor; animateZoom( QRectF( real.x()-diffx, real.y()-diffy, 2.0*diffx, 2.0*diffy ) ); } void View::zoomIn( const QRectF & zoomRect ) { QPointF p = zoomRect.topLeft(); double real1x = xToReal(p.x() ); double real1y = yToReal(p.y() ); p = zoomRect.bottomRight(); double real2x = xToReal(p.x() ); double real2y = yToReal(p.y() ); if ( real1x > real2x ) qSwap( real1x, real2x ); if ( real1y > real2y ) qSwap( real1y, real2y ); animateZoom( QRectF( QPointF( real1x, real1y ), QSizeF( real2x-real1x, real2y-real1y ) ) ); } void View::zoomOut( const QRectF & zoomRect ) { QPointF p = zoomRect.topLeft(); double _real1x = xToReal(p.x() ); double _real1y = yToReal(p.y() ); p = zoomRect.bottomRight(); double _real2x = xToReal(p.x() ); double _real2y = yToReal(p.y() ); double kx = (_real1x-_real2x)/(m_xmin-m_xmax); double lx = _real1x - (kx * m_xmin); double ky = (_real1y-_real2y)/(m_ymax-m_ymin); double ly = _real1y - (ky * m_ymax); double real1x = (m_xmin-lx)/kx; double real2x = (m_xmax-lx)/kx; double real1y = (m_ymax-ly)/ky; double real2y = (m_ymin-ly)/ky; animateZoom( QRectF( QPointF( real1x, real1y ), QSizeF( real2x-real1x, real2y-real1y ) ) ); } void View::animateZoom( const QRectF & _newCoords ) { QRectF oldCoords( m_xmin, m_ymin, m_xmax-m_xmin, m_ymax-m_ymin ); QRectF newCoords( _newCoords.normalized() ); if ( newCoords.left() == m_xmin && newCoords.right() == m_xmax && newCoords.top() == m_ymin && newCoords.bottom() == m_ymax ) return; m_zoomMode = AnimatingZoom; if ( style()->styleHint(QStyle::SH_Widget_Animate) && m_viewportAnimation->state() == QAbstractAnimation::Stopped ) { m_viewportAnimation->setDuration( 150 ); m_viewportAnimation->setEasingCurve( QEasingCurve::OutCubic ); m_viewportAnimation->setStartValue( oldCoords ); m_viewportAnimation->setEndValue( newCoords ); m_viewportAnimation->start(); connect(m_viewportAnimation, &QPropertyAnimation::finished, [this, newCoords] { finishAnimation( newCoords ); }); } else { finishAnimation( newCoords ); } Settings::self()->save(); } void View::finishAnimation( const QRectF & rect ) { m_xmin = rect.left(); m_xmax = rect.right(); m_ymin = rect.top(); m_ymax = rect.bottom(); Settings::setXMin( Parser::number( m_xmin ) ); Settings::setXMax( Parser::number( m_xmax ) ); Settings::setYMin( Parser::number( m_ymin ) ); Settings::setYMax( Parser::number( m_ymax ) ); MainDlg::self()->coordsDialog()->updateXYRange(); MainDlg::self()->requestSaveCurrentState(); drawPlot(); //update all graphs m_zoomMode = Normal; } const QRectF View::getViewport() { return m_animateZoomRect; } void View::setViewport( const QRectF & rect ) { m_animateZoomRect = rect; repaint(); } void View::translateView( int dx, int dy ) { double rdx = xToReal( dx ) - xToReal( 0.0 ); double rdy = yToReal( dy ) - yToReal( 0.0 ); m_xmin += rdx; m_xmax += rdx; m_ymin += rdy; m_ymax += rdy; Settings::setXMin( Parser::number( m_xmin ) ); Settings::setXMax( Parser::number( m_xmax ) ); Settings::setYMin( Parser::number( m_ymin ) ); Settings::setYMax( Parser::number( m_ymax ) ); MainDlg::self()->coordsDialog()->updateXYRange(); drawPlot(); //update all graphs } void View::stopDrawing() { if (m_isDrawing) m_stopCalculating = true; } QPointF View::findMinMaxValue( const Plot & plot, ExtremaType type, double dmin, double dmax ) { Function * ufkt = plot.function(); assert( (ufkt->type() == Function::Cartesian) || (ufkt->type() == Function::Differential) ); Q_UNUSED(ufkt); plot.updateFunction(); Plot differentiated = plot; differentiated.differentiate(); QList roots = findRoots( differentiated, dmin, dmax, RoughRoot ); // The minimum / maximum might occur at the end points roots << dmin << dmax; double best = (type == Maximum) ? -HUGE_VAL : +HUGE_VAL; QPointF bestPoint; - foreach ( double root, roots ) + for ( double root : qAsConst(roots) ) { QPointF rv = realValue( plot, root, false ); if ( (type == Maximum && rv.y() > best) || (type == Minimum && rv.y() < best) ) { best = rv.y(); bestPoint = QPointF(rv.x(), rv.y()); } } return bestPoint; } void View::keyPressEvent( QKeyEvent * e ) { // if a zoom operation is in progress, assume that the key press is to cancel it if ( m_zoomMode != Normal ) { m_zoomMode = Normal; update(); updateCursor(); return; } if (m_isDrawing) { m_stopCalculating=true; return; } if ( m_currentPlot.functionID() == -1 ) return; QMouseEvent * event = 0; if (e->key() == Qt::Key_Left ) event = new QMouseEvent( QEvent::MouseMove, m_crosshairPixelCoords.toPoint() - QPoint(1,1), Qt::LeftButton, Qt::LeftButton, 0 ); else if (e->key() == Qt::Key_Right ) event = new QMouseEvent( QEvent::MouseMove, m_crosshairPixelCoords.toPoint() + QPoint(1,1), Qt::LeftButton, Qt::LeftButton, 0 ); else if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down) //switch graph in trace mode { /// \todo reimplement moving between plots #if 0 QMap::iterator it = XParser::self()->m_ufkt.find( m_currentPlot.functionID ); int const ke=(*it)->parameters.count(); if (ke>0) { m_currentFunctionParameter++; if (m_currentFunctionParameter >= ke) m_currentFunctionParameter=0; } if (m_currentFunctionParameter==0) { int const old_m_currentPlot.functionID=m_currentPlot.functionID; Function::PMode const old_m_currentPlot.plotMode = m_currentPlot.plotMode; bool start = true; bool found = false; while ( 1 ) { if ( old_m_currentPlot.functionID==m_currentPlot.functionID && !start) { m_currentPlot.plotMode=old_m_currentPlot.plotMode; break; } qDebug() << "m_currentPlot.functionID: " << m_currentPlot.functionID; switch ( (*it)->type() ) { case Function::Parametric: case Function::Polar: break; default: { //going through the function, the first and the second derivative for ( m_currentPlot.plotMode = (Function::PMode)0; m_currentPlot.plotMode < 3; m_currentPlot.plotMode = (Function::PMode)(m_currentPlot.plotMode+1) ) // for (m_currentPlot.plotMode=0;m_currentPlot.plotMode<3;m_currentPlot.plotMode++) { if (start) { if ( m_currentPlot.plotMode==Function::Derivative2) m_currentPlot.plotMode=Function::Derivative0; else m_currentPlot.plotMode = (Function::PMode)(old_m_currentPlot.plotMode+1); start=false; } qDebug() << " m_currentPlot.plotMode: " << (int)m_currentPlot.plotMode; if ( (*it)->plotAppearance( m_currentPlot.plotMode ).visible ) found = true; if (found) break; } break; } } if (found) break; if ( ++it == XParser::self()->m_ufkt.end()) it = XParser::self()->m_ufkt.begin(); m_currentPlot.functionID = (*it)->id(); } } qDebug() << "************************"; qDebug() << "m_currentPlot.functionID: " << (int)m_currentPlot.functionID; qDebug() << "m_currentPlot.plotMode: " << (int)m_currentPlot.plotMode; qDebug() << "m_currentFunctionParameter: " << m_currentFunctionParameter; setStatusBar( (*it)->prettyName( m_currentPlot.plotMode ), FunctionSection ); event = new QMouseEvent( QEvent::MouseMove, m_crosshairPixelCoords.toPoint(), Qt::LeftButton, Qt::LeftButton, 0 ); #endif } else if ( e->key() == Qt::Key_Space ) { event = new QMouseEvent( QEvent::MouseButtonPress, QCursor::pos(), Qt::RightButton, Qt::RightButton, 0 ); mousePressEvent(event); delete event; return; } else { event = new QMouseEvent( QEvent::MouseButtonPress, m_crosshairPixelCoords.toPoint(), Qt::LeftButton, Qt::LeftButton, 0 ); mousePressEvent(event); delete event; return; } mouseMoveEvent(event); delete event; } double View::areaUnderGraph( IntegralDrawSettings s ) { int sign = 1; if ( s.dmax < s.dmin ) { qSwap( s.dmin, s.dmax ); sign = -1; } else if ( s.dmax == s.dmin ) return 0; Function * ufkt = s.plot.function(); assert( ufkt ); double dx = (s.dmax-s.dmin)/m_clipRect.width(); if ( s.plot.plotMode == Function::Integral ) { double max_dx = ufkt->eq[0]->differentialStates.step().value(); if ( dx > max_dx ) dx = max_dx; } // Make sure that we calculate the exact area (instead of missing out a // vertical slither at the end) by making sure dx tiles the x-range // a whole number of times int intervals = qRound( (s.dmax-s.dmin)/dx ); dx = (s.dmax-s.dmin) / intervals; double calculated_area=0; double x = s.dmin; s.plot.updateFunction(); for ( int i = 0; i <= intervals; ++i ) { double y = value( s.plot, 0, x, false ); // Trapezoid rule for integrals: only add on half for the first and last value if ( (i == 0) || (i == intervals) ) calculated_area += 0.5*dx*y; else calculated_area += dx*y; x=x+dx; } m_integralDrawSettings = s; m_integralDrawSettings.draw = true; drawPlot(); m_integralDrawSettings.draw = false; return calculated_area * sign; } bool View::isCalculationStopped() { if ( m_stopCalculating) { m_stopCalculating = false; return true; } else return false; } void View::updateSliders() { bool needSliderWindow = false; - foreach ( Function * it, XParser::self()->m_ufkt ) + for ( Function * it : qAsConst(XParser::self()->m_ufkt) ) { if ( it->m_parameters.useSlider && !it->allPlotsAreHidden() ) { needSliderWindow = true; break; } } if ( !needSliderWindow ) { if ( m_sliderWindow ) m_sliderWindow->hide(); m_menuSliderAction->setChecked( false ); return; } if ( !m_sliderWindow ) { m_sliderWindow = new KSliderWindow( this ); connect( m_sliderWindow, &KSliderWindow::valueChanged, this, QOverload<>::of(&View::drawPlot) ); connect( m_sliderWindow, &KSliderWindow::windowClosed, this, &View::sliderWindowClosed ); connect( m_sliderWindow, &KSliderWindow::finished, this, &View::sliderWindowClosed ); } if ( m_menuSliderAction->isChecked() ) m_sliderWindow->show(); } void View::sliderWindowClosed() { m_menuSliderAction->setChecked( false ); //set the slider-item in the menu } void View::functionRemoved( int id ) { if ( id == m_currentPlot.functionID() ) { m_currentPlot.setFunctionID( -1 ); setStatusBar( QString(), RootSection ); setStatusBar( QString(), FunctionSection ); } } void View::hideCurrentFunction() { if ( m_currentPlot.functionID() == -1 ) return; Function * ufkt = m_currentPlot.function(); ufkt->plotAppearance( m_currentPlot.plotMode ).visible = false; MainDlg::self()->functionEditor()->functionsChanged(); drawPlot(); MainDlg::self()->requestSaveCurrentState(); updateSliders(); if ( m_currentPlot.functionID() == -1 ) return; if ( ufkt->allPlotsAreHidden() ) { m_currentPlot.setFunctionID( -1 ); QMouseEvent *event = new QMouseEvent( QMouseEvent::KeyPress, QCursor::pos(), Qt::LeftButton, Qt::LeftButton, 0 ); mousePressEvent(event); //leave trace mode delete event; return; } else { QKeyEvent *event = new QKeyEvent( QKeyEvent::KeyPress, Qt::Key_Up, 0 ); keyPressEvent(event); //change selected graph delete event; return; } } void View::removeCurrentPlot() { if ( m_currentPlot.functionID() == -1 ) return; Function * ufkt = m_currentPlot.function(); Function::Type function_type = ufkt->type(); if (!XParser::self()->removeFunction( ufkt )) return; if ( m_currentPlot.functionID() != -1 ) // if trace mode is enabled { m_currentPlot.setFunctionID( -1 ); QMouseEvent *event = new QMouseEvent( QMouseEvent::KeyPress, QCursor::pos(), Qt::LeftButton, Qt::LeftButton, 0 ); mousePressEvent(event); //leave trace mode delete event; } drawPlot(); if ( function_type == Function::Cartesian ) updateSliders(); MainDlg::self()->requestSaveCurrentState(); } void View::animateFunction() { Function * f = m_currentPlot.function(); if ( !f ) return; ParameterAnimator * anim = new ParameterAnimator( this, f ); anim->show(); } void View::editCurrentPlot() { MainDlg::self()->functionEditor()->setCurrentFunction( m_currentPlot.functionID() ); } void View::zoomIn() { m_zoomMode = ZoomIn; updateCursor(); } void View::zoomOut() { m_zoomMode = ZoomOut; updateCursor(); } void View::zoomToTrigonometric() { double rpau = XParser::self()->radiansPerAngleUnit(); animateZoom( QRectF( -2*M_PI/rpau, -4.0, 4*M_PI/rpau, 8.0 ) ); } void View::updateCursor() { Cursor newCursor = m_prevCursor; if ( m_isDrawing && (m_zoomMode != Translating) ) newCursor = CursorWait; else switch (m_zoomMode) { case AnimatingZoom: newCursor = CursorArrow; break; case Normal: if ( shouldShowCrosshairs() ) { // Don't show any cursor if we're tracing a function or the crosshairs should be shown newCursor = CursorBlank; } else newCursor = CursorArrow; break; case ZoomIn: case ZoomInDrawing: newCursor = CursorMagnify; break; case ZoomOut: case ZoomOutDrawing: newCursor = CursorLessen; break; case AboutToTranslate: case Translating: newCursor = CursorMove; break; } if ( newCursor == m_prevCursor ) return; m_prevCursor = newCursor; switch ( newCursor ) { case CursorWait: setCursor( Qt::WaitCursor ); break; case CursorBlank: setCursor( Qt::BlankCursor ); break; case CursorArrow: setCursor( Qt::ArrowCursor ); break; case CursorCross: setCursor( Qt::CrossCursor ); break; case CursorMagnify: setCursor( QCursor( QIcon::fromTheme( "zoom-in").pixmap(48), 22, 15 ) ); break; case CursorLessen: setCursor( QCursor( QIcon::fromTheme( "zoom-out").pixmap(48), 22, 15 ) ); break; case CursorMove: setCursor( Qt::SizeAllCursor ); } } bool View::shouldShowCrosshairs() const { switch ( m_zoomMode ) { case Normal: case ZoomIn: case ZoomOut: break; case AnimatingZoom: case ZoomInDrawing: case ZoomOutDrawing: case AboutToTranslate: case Translating: return false; } if ( m_popupMenuStatus != NoPopup ) return false; Function * it = m_currentPlot.function(); return ( underMouse() && (!it || crosshairPositionValid( it )) ); } bool View::event( QEvent * e ) { if ( e->type() == QEvent::WindowDeactivate && m_isDrawing) { m_stopCalculating = true; return true; } return QWidget::event(e); //send the information further } void View::setStatusBar( const QString & t, StatusBarSection section ) { QString text; if ( section == FunctionSection ) text = ' ' + t + ' '; else text = t; if ( m_readonly) //if KmPlot is shown as a KPart with e.g Konqueror, it is only possible to change the status bar in one way: to call setStatusBarText { m_statusBarText[ section - 1 ] = text; QString text; for ( int i = 0; i < 4; ++i ) { if ( m_statusBarText[i].isEmpty() ) continue; if ( !text.isEmpty() ) text.append( " | " ); text.append( m_statusBarText[i] ); } emit setStatusBarText(text); } else { QDBusReply reply = QDBusInterface( QDBusConnection::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::NoBlock, "setStatusBarText", text, (int)section ); } } void View::setPrintHeaderTable( bool status ) { m_printHeaderTable = status; } void View::setPrintBackground( bool status ) { m_printBackground = status; } void View::setPrintWidth( double width ) { m_printWidth = width; } void View::setPrintHeight( double height ) { m_printHeight = height; } QPointF View::getCrosshairPosition() const { return m_crosshairPosition; } //END class View //BEGIN class IntegralDrawSettings IntegralDrawSettings::IntegralDrawSettings() { dmin = dmax = 0.0; draw = false; } //END class IntegralDrawSettings diff --git a/kmplot/xparser.cpp b/kmplot/xparser.cpp index a5de627..6a58411 100644 --- a/kmplot/xparser.cpp +++ b/kmplot/xparser.cpp @@ -1,825 +1,825 @@ /* * KmPlot - a math. function plotter for the KDE-Desktop * * Copyright (C) 1998, 1999, 2000, 2002 Klaus-Dieter Möller * 2006, 2007 David Saxton * * This file is part of the KDE Project. * KmPlot is part of the KDE-EDU Project. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "xparser.h" #include // local includes #include "parseradaptor.h" #include "maindlg.h" // KDE includes #include #include #include #include #include #include #ifdef HAVE_IEEEFP_H #include #endif XParser * XParser::m_self = 0; XParser * XParser::self() { if ( !m_self ) m_self = new XParser(); return m_self; } XParser::XParser() { differentialFinite = true; differentialDiverge = 0; new ParserAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/parser"), this); } XParser::~XParser() { } bool XParser::getext( Function *item, const QString &fstr ) { bool errflg = false; int p1, p2, p3, pe; QString tstr; pe = fstr.length(); if ( fstr.indexOf( 'N' ) != -1 ) item->plotAppearance( Function::Derivative0 ).visible = false; else { if ( fstr.indexOf( QLatin1String("A1") ) != -1 ) item->plotAppearance( Function::Derivative1 ).visible = true; if ( fstr.indexOf( QLatin1String("A2") ) != -1 ) item->plotAppearance( Function::Derivative2 ).visible = true; } switch ( fstr[0].unicode() ) { case 'x': case 'y': case 'r': item->plotAppearance( Function::Derivative1 ).visible = item->plotAppearance( Function::Derivative2 ).visible = false; } p1 = fstr.indexOf( QLatin1String("D[") ); if ( p1 != -1 ) { p1 += 2; const QString str = fstr.mid( p1, pe - p1); p2 = str.indexOf(','); p3 = str.indexOf(']'); if ( p2 > 0 && p2 < p3 ) { tstr = str.left( p2 ); errflg |= !item->dmin.updateExpression( tstr ); tstr = str.mid( p2 + 1, p3 - p2 - 1 ); errflg |= !item->dmax.updateExpression( tstr ); if ( item->dmin.value() > item->dmax.value() ) errflg = true; } else errflg = true; } p1 = fstr.indexOf( QLatin1String("P[") ); if ( p1 != -1 ) { int i = 0; p1 += 2; QString str = fstr.mid( p1, 1000); p3 = str.indexOf( ']' ); do { p2 = str.indexOf( ',' ); if ( p2 == -1 || p2 > p3 ) p2 = p3; tstr = str.left( p2++ ); str = str.mid( p2, 1000 ); Value value; if ( !value.updateExpression( tstr ) ) { errflg = true; break; } item->m_parameters.list.append( value ); p3 -= p2; } while ( p3 > 0 && i < 10 ); } if ( errflg ) { KMessageBox::error( 0, i18n( "Error in extension." ) ); return false; } else return true; } double XParser::derivative( int n, Equation * eq, DifferentialState * state, double x, double h ) { if ( n < -1 ) { qCritical() << "Can't handle derivative < -1\n"; return 0.0; } switch ( n ) { case -1: return differential( eq, & eq->differentialStates[0], x, h ); case 0: if ( state ) return differential( eq, state, x, h ); else return fkt( eq, x ); case 1: if ( state ) return ( differential(eq, state, x + (h/2), h ) - differential( eq, state, x - (h/2), h ) ) / h; else return ( fkt(eq, x + (h/2) ) - fkt( eq, x - (h/2) ) ) / h; default: return ( derivative( n-1, eq, state, x+(h/2), (h/4) ) - derivative( n-1, eq, state, x-(h/2), (h/4) ) ) / h; } } double XParser::partialDerivative( int n1, int n2, Equation * eq, DifferentialState * state, double x, double y, double h1, double h2 ) { if ( n1 < 0 || n2 < 0 ) { qCritical() << "Can't handle derivative < 0\n"; return 0.0; } if ( n1 > 0 ) return ( partialDerivative( n1-1, n2, eq, state, x+(h1/2), y, (h1/4), h2 ) - partialDerivative( n1-1, n2, eq, state, x-(h1/2), y, (h1/4), h2 ) ) / h1; Function * f = eq->parent(); f->m_implicitMode = Function::FixedX; f->x = x; return derivative( n2, eq, state, y, h2 ); } QString XParser::findFunctionName( const QString & preferredName, int id, const QStringList& neededPatterns ) { // The position of the character attempting to replace int pos = preferredName.length()-1; QString name = preferredName; for ( ; ; ++pos) { for ( QChar lastChar = 'f'; lastChar<'x'; ++lastChar.unicode() ) { bool ok = true; name[pos] = lastChar; - foreach ( Function * it, m_ufkt ) + for ( Function * it : qAsConst(m_ufkt) ) { if ( int(it->id()) == id ) continue; - foreach ( Equation * eq, it->eq ) + for ( Equation * eq : qAsConst(it->eq) ) { - foreach ( const QString& pattern, neededPatterns) { + for ( const QString& pattern : neededPatterns) { if ( eq->name() == pattern.arg(name) ) ok = false; } } if (!ok) break; } if ( !ok ) continue; // Found a free name :) return name; } name[pos]='f'; name.append('f'); } } void XParser::fixFunctionName( QString &str, Equation::Type const type, int const id) { int p1 = str.indexOf('('); int p2 = str.indexOf(')'); int p3 = str.indexOf('='); if ( p1 < 0 ) return; for ( int i = p2+1; i < p3; ++i ) { if ( !str.at(i).isSpace() ) return; } QString const fname = str.left(p1); - foreach ( Function * it, m_ufkt ) + for ( Function * it : qAsConst(m_ufkt) ) { if ( int(it->id()) == id ) continue; - foreach ( Equation * eq, it->eq ) + for ( Equation * eq : qAsConst(it->eq) ) { if ( eq->name() != fname ) continue; str = str.mid(p1,str.length()-1); QString function_name; if ( type == Equation::ParametricX ) function_name = 'x'; else if ( type == Equation::ParametricY ) function_name = 'y'; else function_name = 'f'; function_name = findFunctionName( function_name, id ); str.prepend( function_name ); return; } } } Vector XParser::rk4_f( int order, Equation * eq, double x, const Vector & y ) { bool useParameter = eq->usesParameter(); m_result.resize( order ); m_arg.resize( order+1 + (useParameter ? 1 : 0) ); m_arg[0] = x; if ( useParameter ) m_arg[1] = eq->parent()->k; memcpy( m_arg.data() + 1 + (useParameter ? 1 : 0), y.data(), order*sizeof(double) ); memcpy( m_result.data(), y.data() + 1, (order-1)*sizeof(double) ); m_result[order-1] = XParser::fkt( eq, m_arg ); return m_result; } double XParser::differential( Equation * eq, DifferentialState * state, double x_target, double max_dx ) { differentialFinite = true; if ( eq->order() < 1 ) { qWarning() << "Zero order!\n"; return 0; } max_dx = qAbs(max_dx); assert( max_dx > 0 ); // in case anyone tries to pass us a zero h // the difference between h and dx is that h is only used as a hint for the // stepwidth; dx is made similar to h in size, yet tiles the gap between x // and the previous x perfectly // see if the initial integral point in the function is closer to our // required x value than the last one (or the last point is invalid) if ( qAbs( state->x0.value() - x_target ) < qAbs( state->x - x_target ) ) state->resetToInitial(); int order = eq->order(); m_k1.resize( order ); m_k2.resize( order ); m_k3.resize( order ); m_k4.resize( order ); m_y_temp.resize( order ); double x = state->x; m_y = state->y; if ( x_target == x ) return m_y[0]; int intervals = int( qAbs(x_target-x)/max_dx + 1 ); double dx = (x_target-x) / double(intervals); for ( int i = 0; i < intervals; ++i ) { // Update differentialDiverge before y possible becomes infinite differentialDiverge = x; x = state->x + i*dx; m_k1 = rk4_f( order, eq, x, m_y ); m_y_temp.combine( m_y, dx/2, m_k1 ); m_k2 = rk4_f( order, eq, x + dx/2, m_y_temp); m_y_temp.combine( m_y, dx/2, m_k2 ); m_k3 = rk4_f( order, eq, x + dx/2, m_y_temp ); m_y_temp.combine( m_y, dx, m_k3 ); m_k4 = rk4_f( order, eq, x + dx, m_y_temp ); m_y.addRK4( dx, m_k1, m_k2, m_k3, m_k4 ); if ( !std::isfinite(m_y[0]) ) { differentialFinite = false; state->resetToInitial(); return 0; } } state->x = x + dx; state->y = m_y; return m_y[0]; } QColor XParser::defaultColor(int function) { switch ( function % 10 ) { case 0: return Settings::color0(); case 1: return Settings::color1(); case 2: return Settings::color2(); case 3: return Settings::color3(); case 4: return Settings::color4(); case 5: return Settings::color5(); case 6: return Settings::color6(); case 7: return Settings::color7(); case 8: return Settings::color8(); case 9: return Settings::color9(); } assert( !"Should not happen - XParser::defaultColor" ); return QColor(); } QStringList XParser::listFunctionNames() { return userFunctions(); } bool XParser::functionFVisible(uint id) { return m_ufkt.contains(id) ? m_ufkt[id]->plotAppearance( Function::Derivative0 ).visible : false; } bool XParser::functionF1Visible(uint id) { return m_ufkt.contains(id) ? m_ufkt[id]->plotAppearance( Function::Derivative1 ).visible : false; } bool XParser::functionF2Visible(uint id) { return m_ufkt.contains(id) ? m_ufkt[id]->plotAppearance( Function::Derivative2 ).visible : false; } bool XParser::functionIntVisible(uint id) { return m_ufkt.contains(id) ? m_ufkt[id]->plotAppearance( Function::Integral ).visible : false; } bool XParser::setFunctionFVisible(uint id, bool visible) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Derivative0 ).visible = visible; MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::setFunctionF1Visible(uint id, bool visible) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Derivative1 ).visible = visible; MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::setFunctionF2Visible(uint id, bool visible) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Derivative2 ).visible = visible; MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::setFunctionIntVisible(uint id, bool visible) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Integral ).visible = visible; MainDlg::self()->requestSaveCurrentState(); return true; } QString XParser::functionStr(uint id, uint eq) { if ( !m_ufkt.contains( id ) || (eq>=2) ) return QLatin1String(""); return m_ufkt[id]->eq[eq]->fstr(); } QColor XParser::functionFColor(uint id) { if ( !m_ufkt.contains( id ) ) return QColor(); return QColor(m_ufkt[id]->plotAppearance( Function::Derivative0 ).color); } QColor XParser::functionF1Color(uint id) { if ( !m_ufkt.contains( id ) ) return QColor(); return QColor(m_ufkt[id]->plotAppearance( Function::Derivative1 ).color); } QColor XParser::functionF2Color(uint id) { if ( !m_ufkt.contains( id ) ) return QColor(); return QColor(m_ufkt[id]->plotAppearance( Function::Derivative2 ).color); } QColor XParser::functionIntColor(uint id) { if ( !m_ufkt.contains( id ) ) return QColor(); return QColor(m_ufkt[id]->plotAppearance( Function::Integral ).color); } bool XParser::setFunctionFColor(uint id, const QColor &color) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Derivative0 ).color = color; MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::setFunctionF1Color(uint id, const QColor &color) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Derivative1 ).color = color; MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::setFunctionF2Color(uint id, const QColor &color) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Derivative2 ).color = color; MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::setFunctionIntColor(uint id, const QColor &color) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Integral ).color = color; MainDlg::self()->requestSaveCurrentState(); return true; } double XParser::functionFLineWidth(uint id) { if ( !m_ufkt.contains( id ) ) return 0; return m_ufkt[id]->plotAppearance( Function::Derivative0 ).lineWidth; } double XParser::functionF1LineWidth(uint id) { if ( !m_ufkt.contains( id ) ) return 0; return m_ufkt[id]->plotAppearance( Function::Derivative1 ).lineWidth; } double XParser::functionF2LineWidth(uint id) { if ( !m_ufkt.contains( id ) ) return 0; return m_ufkt[id]->plotAppearance( Function::Derivative2 ).lineWidth; } double XParser::functionIntLineWidth(uint id) { if ( !m_ufkt.contains( id ) ) return 0; return m_ufkt[id]->plotAppearance( Function::Integral ).lineWidth; } bool XParser::setFunctionFLineWidth(uint id, double linewidth) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Derivative0 ).lineWidth = linewidth; MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::setFunctionF1LineWidth(uint id, double linewidth) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Derivative1 ).lineWidth = linewidth; MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::setFunctionF2LineWidth(uint id, double linewidth) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Derivative2 ).lineWidth = linewidth; MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::setFunctionIntLineWidth(uint id, double linewidth) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->plotAppearance( Function::Integral ).lineWidth = linewidth; MainDlg::self()->requestSaveCurrentState(); return true; } QString XParser::functionMinValue(uint id) { if ( !m_ufkt.contains( id ) ) return 0; return m_ufkt[id]->dmin.expression(); } bool XParser::setFunctionMinValue(uint id, const QString &min) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->dmin.expression() = min; MainDlg::self()->requestSaveCurrentState(); return true; } QString XParser::functionMaxValue(uint id) { if ( !m_ufkt.contains( id ) ) return 0; return m_ufkt[id]->dmax.expression(); } bool XParser::setFunctionMaxValue(uint id, const QString &max) { if ( !m_ufkt.contains( id ) ) return false; m_ufkt[id]->dmax.expression() = max; MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::setFunctionStartValue(uint id, const QString &x, const QString &y) { if ( !m_ufkt.contains( id ) ) return false; DifferentialState * state = & m_ufkt[id]->eq[0]->differentialStates[0]; state->x0.updateExpression( x ); state->y0[0].updateExpression( y ); MainDlg::self()->requestSaveCurrentState(); return true; } QString XParser::functionStartXValue(uint id) { if ( !m_ufkt.contains( id ) ) return 0; DifferentialState * state = & m_ufkt[id]->eq[0]->differentialStates[0]; return state->x0.expression(); } QString XParser::functionStartYValue(uint id) { if ( !m_ufkt.contains( id ) ) return 0; DifferentialState * state = & m_ufkt[id]->eq[0]->differentialStates[0]; return state->y0[0].expression(); } QStringList XParser::functionParameterList(uint id) { if ( !m_ufkt.contains( id ) ) return QStringList(); Function *item = m_ufkt[id]; QStringList str_parameter; - foreach ( const Value &it, item->m_parameters.list ) + for ( const Value &it : qAsConst(item->m_parameters.list) ) str_parameter << it.expression(); return str_parameter; } bool XParser::functionAddParameter(uint id, const QString &new_parameter) { if ( !m_ufkt.contains( id ) ) return false; Function *tmp_ufkt = m_ufkt[id]; //check if the parameter already exists - foreach ( const Value &it, tmp_ufkt->m_parameters.list ) + for ( const Value &it : qAsConst(tmp_ufkt->m_parameters.list) ) { if ( it.expression() == new_parameter ) return false; } Value value; if ( !value.updateExpression( new_parameter ) ) return false; tmp_ufkt->m_parameters.list.append( value ); MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::functionRemoveParameter(uint id, const QString &remove_parameter) { if ( !m_ufkt.contains( id ) ) return false; Function *tmp_ufkt = m_ufkt[id]; bool found = false; QList::iterator it; for ( it = tmp_ufkt->m_parameters.list.begin(); it != tmp_ufkt->m_parameters.list.end(); ++it) { if ( (*it).expression() == remove_parameter) //check if the parameter already exists { found = true; break; } } if (!found) return false; tmp_ufkt->m_parameters.list.erase(it); MainDlg::self()->requestSaveCurrentState(); return true; } int XParser::addFunction(const QString &f_str0, const QString &_f_str1) { QString added_function(f_str0); QString f_str1(_f_str1); int const pos = added_function.indexOf(';'); if (pos!=-1) added_function = added_function.left(pos); fixFunctionName(added_function); if ( !f_str1.isEmpty() ) fixFunctionName( f_str1 ); Function::Type type; if ( !f_str1.isEmpty() ) type = Function::Parametric; else if ( f_str0.count( '=' ) > 1 ) type = Function::Implicit; else type = (added_function[0] == 'r') ? Function::Polar : Function::Cartesian; int const id = Parser::addFunction( added_function, f_str1, type ); if (id==-1) return -1; Function *tmp_ufkt = m_ufkt[id]; if ( pos!=-1 && !getext( tmp_ufkt, f_str0 ) ) { Parser::removeFunction( tmp_ufkt ); return -1; } MainDlg::self()->requestSaveCurrentState(); return id; } bool XParser::addFunction(const QString &fstr_const0, const QString &fstr_const1, bool f_mode, bool f1_mode, bool f2_mode, bool integral_mode, double linewidth, double f1_linewidth, double f2_linewidth, double integral_linewidth, const QString &str_dmin, const QString &str_dmax, const QString &str_startx, const QString &str_starty, double integral_precision, const QColor &color, const QColor &f1_color, const QColor &f2_color, const QColor &integral_color, const QStringList & str_parameter, int use_slider) { QString fstr[2] = { fstr_const0, fstr_const1 }; Function::Type type = Function::Cartesian; for ( unsigned i = 0; i < 2; ++i ) { if ( fstr[i].isEmpty() ) continue; switch ( fstr[i][0].unicode() ) { case 'r': { fixFunctionName(fstr[i], Equation::Polar); type = Function::Polar; break; } case 'x': fixFunctionName(fstr[i], Equation::ParametricX); type = Function::Parametric; break; case 'y': fixFunctionName(fstr[i], Equation::ParametricY); type = Function::Parametric; break; default: fixFunctionName(fstr[i], Equation::Cartesian ); type = Function::Cartesian; break; } } int const id = Parser::addFunction( fstr[0], fstr[1], type ); if ( id==-1 ) return false; Function *added_function = m_ufkt[id]; PlotAppearance appearance; // f0 appearance.visible = f_mode; appearance.color = color; appearance.lineWidth = linewidth; added_function->plotAppearance( Function::Derivative0 ) = appearance; // f1 appearance.visible = f1_mode; appearance.color = f1_color; appearance.lineWidth = f1_linewidth; added_function->plotAppearance( Function::Derivative1 ) = appearance; // f2 appearance.visible = f2_mode; appearance.color = f2_color; appearance.lineWidth = f2_linewidth; added_function->plotAppearance( Function::Derivative2 ) = appearance; // integral appearance.visible = integral_mode; appearance.color = integral_color; appearance.lineWidth = integral_linewidth; added_function->plotAppearance( Function::Integral ) = appearance; added_function->dmin.updateExpression( str_dmin ); added_function->usecustomxmin = !str_dmin.isEmpty(); added_function->dmax.updateExpression( str_dmax ); added_function->usecustomxmax = !str_dmax.isEmpty(); DifferentialState * state = & added_function->eq[0]->differentialStates[0]; state->x0.updateExpression( str_startx ); state->y0[0].updateExpression( str_starty ); added_function->eq[0]->differentialStates.setStep( Value( integral_precision ) ); added_function->m_parameters.sliderID = use_slider; for( QStringList::ConstIterator it = str_parameter.begin(); it != str_parameter.end(); ++it ) { added_function->m_parameters.list.append( *it ); } MainDlg::self()->requestSaveCurrentState(); return true; } bool XParser::setFunctionExpression(uint id, uint eq, const QString &f_str) { Function * tmp_ufkt = functionWithID( id ); if ( !tmp_ufkt ) return false; QString const old_fstr = tmp_ufkt->eq[eq]->fstr(); QString const fstr_begin = tmp_ufkt->eq[eq]->fstr().left(tmp_ufkt->eq[eq]->fstr().indexOf('=')+1); return tmp_ufkt->eq[eq]->setFstr( fstr_begin+f_str ); }