functioneditor.cpp 25.3 KB
Newer Older
1
/*
2
3
4
5
6
7
8
9
10
11
12
    KmPlot - a math. function plotter for the KDE-Desktop

    SPDX-FileCopyrightText: 1998-2002 Klaus-Dieter Möller <kd.moeller@t-online.de>
    SPDX-FileCopyrightText: 2004 Fredrik Edemar <f_edemar@linux.se>
    SPDX-FileCopyrightText: 2006 David Saxton <david@bluehaze.org>

    This file is part of the KDE Project.
    KmPlot is part of the KDE-EDU Project.

    SPDX-License-Identifier: GPL-2.0-or-later

13
14
15
*/

#include "functioneditor.h"
Peter Hedlund's avatar
Peter Hedlund committed
16
#include "equationedit.h"
17
#include "kgradientdialog.h"
18
#include "kmplotio.h"
19
#include "maindlg.h"
20
21
#include "parameterswidget.h"
#include "ui_functioneditorwidget.h"
22
#include "view.h"
23
24
#include "xparser.h"

25
26
#include <KColorButton>
#include <KMessageBox>
27

Yuri Chornoivan's avatar
Yuri Chornoivan committed
28
#include <QDebug>
29
#include <QRadioButton>
30
#include <QTimer>
31
32
33
34
35
36
37
38
39
40
41
42
43
44

#include <assert.h>

class FunctionEditorWidget : public QWidget, public Ui::FunctionEditorWidget
{
	public:
		FunctionEditorWidget(QWidget *parent = 0)
	: QWidget(parent)
		{ setupUi(this); }
};



//BEGIN class FunctionEditor
Laurent Montel's avatar
Laurent Montel committed
45
FunctionEditor::FunctionEditor( QMenu * createNewPlotsMenu, QWidget * parent )
46
	: QDockWidget( i18n("Functions"), parent )
47
{
48
	m_functionID = -1;
49
50
	
	// need a name for saving and restoring the position of this dock widget
Laurent Montel's avatar
Laurent Montel committed
51
	setObjectName( QStringLiteral("FunctionEditor") );
52
53
	
	setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
54
	setFeatures( QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable );
55
	
56
57
58
59
60
	for ( int i = 0; i < 5; ++i )
	{
		m_saveTimer[i] = new QTimer( this );
		m_saveTimer[i]->setSingleShot( true );
	}
61
	
62
	m_syncFunctionListTimer = new QTimer( this );
63
64
	m_syncFunctionListTimer->setSingleShot( true );
	
65
66
67
68
69
	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 );
Laurent Montel's avatar
Laurent Montel committed
70
	connect(m_syncFunctionListTimer, &QTimer::timeout, this, &FunctionEditor::syncFunctionList);
71
72
	
	m_editor = new FunctionEditorWidget;
73
	m_functionList = m_editor->functionList;
74
	
Laurent Montel's avatar
Laurent Montel committed
75
76
	m_editor->createNewPlot->setIcon( QIcon::fromTheme(QStringLiteral("document-new")) );
	m_editor->deleteButton->setIcon( QIcon::fromTheme(QStringLiteral("edit-delete")) );
77
	
78
	//BEGIN initialize equation edits
79
	m_editor->cartesianEquation->setInputType( EquationEdit::Function );
80
	m_editor->cartesianEquation->setEquationType( Equation::Cartesian );
81
	m_editor->cartesianParameters->associateEquationEdit( m_editor->cartesianEquation );
82
	
83
	m_editor->polarEquation->setInputType( EquationEdit::Function );
84
	m_editor->polarEquation->setEquationType( Equation::Polar );
85
	m_editor->polarParameters->associateEquationEdit( m_editor->polarEquation );
86
	
87
	m_editor->parametricX->setInputType( EquationEdit::Function );
88
	m_editor->parametricX->setEquationType( Equation::ParametricX );
89
	m_editor->parametricParameters->associateEquationEdit( m_editor->parametricX );
90
	
91
	m_editor->parametricY->setInputType( EquationEdit::Function );
92
	m_editor->parametricY->setEquationType( Equation::ParametricY );
93
	m_editor->parametricParameters->associateEquationEdit( m_editor->parametricY );
94
	
95
	m_editor->implicitEquation->setInputType( EquationEdit::Function );
96
97
	m_editor->implicitEquation->setEquationType( Equation::Implicit );
	
98
	m_editor->differentialEquation->setInputType( EquationEdit::Function );
99
	m_editor->differentialEquation->setEquationType( Equation::Differential );
100
	m_editor->differentialParameters->associateEquationEdit( m_editor->differentialEquation );
101
	//END initialize equation edits
102
	
103
	for ( unsigned i = 0; i < 5; ++i )
Laurent Montel's avatar
Laurent Montel committed
104
		m_editor->stackedWidget->widget(i)->layout()->setContentsMargins( 0, 0, 0, 0 );
105
	
Laurent Montel's avatar
Laurent Montel committed
106
107
	connect(m_editor->deleteButton, &QPushButton::clicked, this, &FunctionEditor::deleteCurrent);
	connect(m_functionList, &FunctionListWidget::currentItemChanged, this, &FunctionEditor::functionSelected);
108
	connect(m_functionList, &FunctionListWidget::itemClicked, this, &FunctionEditor::saveItem);
109
110
	
	//BEGIN connect up all editing widgets
111
112
113
#define CONNECT_WIDGETS( name, signal ) \
	{ \
		QList<name *> widgets = m_editor->findChildren<name *>(); \
114
		for ( name * w : qAsConst(widgets) ) \
115
116
117
118
119
120
121
122
123
124
125
			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 &) );
126
	
Laurent Montel's avatar
Laurent Montel committed
127
	connect(m_editor->initialConditions, &InitialConditionsEditor::dataChanged, this, &FunctionEditor::save);
128
129
	//END connect up all editing widgets
	
Laurent Montel's avatar
Laurent Montel committed
130
131
	connect(XParser::self(), &XParser::functionAdded, this, &FunctionEditor::functionsChanged);
	connect(XParser::self(), &XParser::functionRemoved, this, &FunctionEditor::functionsChanged);
132
	
133
	m_editor->createNewPlot->setMenu( createNewPlotsMenu );
134
	
135
136
137
138
139
140
141
142
143
144
145
146
	resetFunctionEditing();
	setWidget( m_editor );
}


FunctionEditor::~ FunctionEditor()
{
}


void FunctionEditor::deleteCurrent()
{
147
148
	m_editor->initialConditions->init( 0 );
	
149
	FunctionListItem * functionItem = static_cast<FunctionListItem*>(m_functionList->currentItem());
150
	if ( !functionItem )
151
	{
152
		qDebug() << "Nothing currently selected!\n";
153
		return;
154
	}
155
	
156
	if ( !XParser::self()->removeFunction( functionItem->function() ) )
157
	{
158
		qDebug() << "Couldn't delete function.\n";
159
160
161
162
		// couldn't delete it, as e.g. another function depends on it
		return;
	}
	
163
	MainDlg::self()->requestSaveCurrentState();
164
	View::self()->drawPlot();
165
166
167
168
169
170
171
172
173
174
175
}


void FunctionEditor::functionsChanged()
{
	m_syncFunctionListTimer->start( 0 );
}


void FunctionEditor::syncFunctionList()
{
176
177
178
	int oldFunctionCount = m_functionList->count();
	
	QListWidgetItem * currentItem = m_functionList->currentItem();
Arto Hytönen's avatar
Arto Hytönen committed
179
	QString currentText = currentItem ? currentItem->text() : QString();
180
	
181
182
183
	// build up a list of IDs that we have
	QMap< int, FunctionListItem * > currentIDs;
	QList< FunctionListItem * > currentFunctionItems;
184
	for ( int row = 0; row < m_functionList->count(); ++row )
185
	{
186
		FunctionListItem * item = static_cast<FunctionListItem*>(m_functionList->item( row ));
187
		currentFunctionItems << item;
188
		currentIDs[ item->function() ] = item;
189
190
191
		
		// also update what is displayed
		item->update();
192
193
194
195
196
	}
	
	FunctionListItem * toSelect = 0l;
	int newFunctionCount = 0;
	
197
	for ( QMap<int, Function*>::iterator it = XParser::self()->m_ufkt.begin(); it != XParser::self()->m_ufkt.end(); ++it)
198
	{
199
		Function * function = *it;
200
		
201
		if ( currentIDs.contains( function->id() ) )
202
203
		{
			// already have the function
204
205
			currentFunctionItems.removeAll( currentIDs[ function->id() ] );
			currentIDs.remove( function->id() );
206
207
208
			continue;
		}
		
209
		toSelect = new FunctionListItem( m_functionList, function->id() );
210
211
212
		newFunctionCount++;
	}
	
213
214
215
216
217
218
219
	if ( newFunctionCount != 1 )
	{
		// only select a new functionlistitem if there was precisely one added
		toSelect = 0l;
	}
	
	
220
	// Now, any IDs left in currentIDs are of functions that have been deleted
221
	for ( FunctionListItem * item : qAsConst(currentFunctionItems) )
222
	{
223
224
		if ( m_functionID == item->function() )
			m_functionID = -1;
225
		
226
		delete m_functionList->takeItem( m_functionList->row( item ) );
227
228
	}
	
229
	m_functionList->sortItems();
230
	
231
	// Try and see if there is an item with the same text as was initially selected, if we have
232
	// the same number of functions
233
234
235
236
237
238
239
240
	if ( (oldFunctionCount == m_functionList->count()) && !currentText.isEmpty() )
	{
		QList<QListWidgetItem *> matchedItems = m_functionList->findItems( currentText, Qt::MatchExactly );
		if ( matchedItems.count() == 1 )
			toSelect = static_cast<FunctionListItem*>(matchedItems.first());
	}
	
	if ( toSelect )
241
		m_functionList->setCurrentItem( toSelect );
242
	
243
	if ( m_functionList->count() == 0 )
244
245
246
247
		resetFunctionEditing();
}


248
249
250
251
252
void FunctionEditor::setCurrentFunction( int functionID )
{
	for ( int row = 0; row < m_functionList->count(); ++row )
	{
		FunctionListItem * item = static_cast<FunctionListItem*>(m_functionList->item( row ));
253
		if ( item->function() != functionID )
254
255
256
257
258
259
260
261
			continue;
		
		m_functionList->setCurrentRow( row );
		return;
	}
}


262
263
264
265
266
267
void FunctionEditor::functionSelected( QListWidgetItem * item )
{
	m_editor->deleteButton->setEnabled( item != 0 );
	if ( !item )
		return;
	
268
	// If there are any pending save events, then cancel them
269
	for ( int i = 0; i < 5; ++i )
270
		m_saveTimer[i]->stop();
271
	
272
273
	FunctionListItem * functionItem = static_cast<FunctionListItem*>(item);
	
274
275
276
277
278
279
	m_functionID = functionItem->function();
	Function * f = XParser::self()->functionWithID( m_functionID );
	if ( !f )
		return;
	
	switch ( f->type() )
280
	{
281
282
283
284
285
286
287
288
289
290
291
		case Function::Cartesian:
			initFromCartesian();
			break;
			
		case Function::Polar:
			initFromPolar();
			break;
			
		case Function::Parametric:
			initFromParametric();
			break;
292
293
294
295
			
		case Function::Implicit:
			initFromImplicit();
			break;
296
297
298
			
		case Function::Differential:
			initFromDifferential();
299
	}
300
301
	
	functionItem->update();
302
303
304
305
306
}


void FunctionEditor::initFromCartesian()
{
307
	Function * f = XParser::self()->functionWithID(m_functionID);
308
309
310
	
	if ( !f )
	{
311
		qWarning() << "No f! (id="<<m_functionID<<")\n";
312
313
314
		return;
	}
	
315
	m_editor->cartesianEquation->setText( f->eq[0]->fstr() );
316
317
318
319
	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 );
320
	
321
322
	m_editor->showDerivative1->setChecked( f->plotAppearance( Function::Derivative1 ).visible );
	m_editor->showDerivative2->setChecked( f->plotAppearance( Function::Derivative2 ).visible );
323
	
David Saxton's avatar
David Saxton committed
324
	m_editor->cartesianCustomMin->setChecked( f->usecustomxmin );
325
	m_editor->cartesianMin->setText( f->dmin.expression() );
David Saxton's avatar
David Saxton committed
326
327
	
	m_editor->cartesianCustomMax->setChecked( f->usecustomxmax );
328
	m_editor->cartesianMax->setText( f->dmax.expression() );
329
	
330
	m_editor->cartesianParameters->init( f->m_parameters );
331
	
332
	m_editor->showIntegral->setChecked( f->plotAppearance( Function::Integral ).visible );
333
	m_editor->integralStep->setText( f->eq[0]->differentialStates.step().expression() );
334
335
336
337
	
	DifferentialState state = f->eq[0]->differentialStates[0];
	m_editor->txtInitX->setText( state.x0.expression() );
	m_editor->txtInitY->setText( state.y0[0].expression() );
338
339
	
	m_editor->stackedWidget->setCurrentIndex( 0 );
340
	m_editor->tabWidget->setCurrentIndex( 0 );
341
342
343
344
345
346
	m_editor->cartesianEquation->setFocus();
}


void FunctionEditor::initFromPolar()
{
347
	Function * f = XParser::self()->functionWithID(m_functionID);
348
349
350
351
	
	if ( !f )
		return;
	
352
	QString function = f->eq[0]->fstr();
353
	m_editor->polarEquation->setText( function );
354
355
	m_editor->polarMin->setText( f->dmin.expression() );
	m_editor->polarMax->setText( f->dmax.expression() );
356
	m_editor->polar_f0->init( f->plotAppearance( Function::Derivative0 ), Function::Polar );
357
	
358
359
	m_editor->polarParameters->init( f->m_parameters );
	
360
361
362
363
364
365
366
	m_editor->stackedWidget->setCurrentIndex( 2 );
	m_editor->polarEquation->setFocus();
}


void FunctionEditor::initFromParametric()
{
367
	Function * f = XParser::self()->functionWithID(m_functionID);
368
	
369
	if ( !f )
370
371
		return;
	
372
373
	m_editor->parametricX->setText( f->eq[0]->fstr() );
	m_editor->parametricY->setText( f->eq[1]->fstr() );
374

375
376
	m_editor->parametricMin->setText( f->dmin.expression() );
	m_editor->parametricMax->setText( f->dmax.expression() );
377
	
378
379
	m_editor->parametricParameters->init( f->m_parameters );
	
380
	m_editor->parametric_f0->init( f->plotAppearance( Function::Derivative0 ), Function::Parametric );
381
382
	
	m_editor->stackedWidget->setCurrentIndex( 1 );
383
	m_editor->parametricX->setFocus();
384
385
386
}


387
388
389
390
391
392
393
394
395
396
397
398
399
400
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 );
401
	m_editor->implicit_f0->init( f->plotAppearance( Function::Derivative0 ), Function::Implicit );
402
403
404
405
406
407
408
409
	
	m_editor->implicitParameters->init( f->m_parameters );
	
	m_editor->stackedWidget->setCurrentIndex( 3 );
	m_editor->implicitEquation->setFocus();
}


410
411
412
413
414
415
416
void FunctionEditor::initFromDifferential()
{
	Function * f = XParser::self()->functionWithID(m_functionID);
	
	if ( !f )
		return;
	
417
	m_editor->differentialEquation->setText( f->eq[0]->fstr());
418
	m_editor->differentialStep->setText( f->eq[0]->differentialStates.step().expression() );
419
	
420
	m_editor->differential_f0->init( f->plotAppearance( Function::Derivative0 ), Function::Differential );
421
	m_editor->differentialParameters->init( f->m_parameters );
422
	m_editor->initialConditions->init( f );
423
	
424
	m_editor->differentialTabWidget->setCurrentIndex( 0 );
425
426
427
428
429
	m_editor->stackedWidget->setCurrentIndex( 4 );
	m_editor->differentialEquation->setFocus();
}


Peter Hedlund's avatar
Peter Hedlund committed
430
void FunctionEditor::splitImplicitEquation( const QString &equation, QString * name, QString * expression )
431
432
433
{
	int equalsPos = equation.indexOf( '=' );
	assert( equalsPos >= 0 );
434
435
	*name = equation.left( equalsPos ).trimmed();
	*expression = equation.right( equation.length() - equalsPos - 1 ).trimmed();
436
437
438
}


439
440
void FunctionEditor::resetFunctionEditing()
{
441
	m_functionID = -1;
442
	
443
444
	// page 5 is an empty page
	m_editor->stackedWidget->setCurrentIndex( 5 );
445
446
	
	// assume that if there are functions in the list, then one will be selected
447
	m_editor->deleteButton->setEnabled( m_functionList->count() != 0 );
448
449
450
451
452
}


void FunctionEditor::createCartesian()
{
453
454
	QString name;
	if ( Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function )
Laurent Montel's avatar
Laurent Montel committed
455
		name = XParser::self()->findFunctionName( QStringLiteral("f"), -1 ) + "(x)";
456
	else
457
		name = 'y';
458
	
459
	createFunction( name + " = 0", QString(), Function::Cartesian );
460
461
462
463
464
}


void FunctionEditor::createParametric()
{
Laurent Montel's avatar
Laurent Montel committed
465
	QString name = XParser::self()->findFunctionName( QStringLiteral("f"), -1, QStringList() << QStringLiteral("%1") << QStringLiteral("%1_x") << QStringLiteral("%1_y") );
466
467
468
469
470
	
	QString name_x, name_y;
	
	if ( Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function )
	{
Laurent Montel's avatar
Laurent Montel committed
471
472
		name_x = QStringLiteral("%1_x(t)").arg( name );
		name_y = QStringLiteral("%1_y(t)").arg( name );
473
474
475
	}
	else
	{
476
477
		name_x = 'x';
		name_y = 'y';
478
479
	}
	
480
	createFunction( name_x + " = 0", name_y + " = 0", Function::Parametric );
481
482
483
484
485
}


void FunctionEditor::createPolar()
{
486
487
	QString name;
	if ( Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function )
Laurent Montel's avatar
Laurent Montel committed
488
		name = XParser::self()->findFunctionName( QStringLiteral("f"), -1 ) + "(x)";
489
	else
490
		name = 'r';
491
	
492
	createFunction( name + " = 0", QString(), Function::Polar );
493
494
495
}


496
497
void FunctionEditor::createImplicit()
{
Laurent Montel's avatar
Laurent Montel committed
498
	QString name = XParser::self()->findFunctionName( QStringLiteral("f"), -1 );
499
	if ( Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function )
Laurent Montel's avatar
Laurent Montel committed
500
		name += QLatin1String("(x,y)");
501
	
502
	createFunction( name + " = y² = x³ − x + 1", QString(), Function::Implicit );
503
504
505
}


506
507
void FunctionEditor::createDifferential()
{
508
509
	QString name;
	if ( Settings::defaultEquationForm() == Settings::EnumDefaultEquationForm::Function )
Laurent Montel's avatar
Laurent Montel committed
510
		name = QStringLiteral( "%1''(x) = -%1" ).arg( XParser::self()->findFunctionName( QStringLiteral("f"), -1 ) );
511
	else
Laurent Montel's avatar
Laurent Montel committed
512
		name = QLatin1String("y'' = -y");
513
	
514
515
516
517
518
519
520
	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 );
521
522
523
524
525
	assert( m_functionID != -1 );
	MainDlg::self()->requestSaveCurrentState();
}


526
527
void FunctionEditor::save()
{
528
	Function * f = XParser::self()->functionWithID( m_functionID );
529
530
531
	if ( !f )
		return;
	
532
	m_saveTimer[ f->type() ]->start( 0 );
533
534
}

535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
// 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();
}
552
553
554

void FunctionEditor::saveCartesian()
{
555
	FunctionListItem * functionListItem = static_cast<FunctionListItem*>(m_functionList->currentItem());
556
557
	if ( !functionListItem )
		return;
558
	
559
	QString f_str( m_editor->cartesianEquation->text() );
560
	XParser::self()->fixFunctionName(f_str, Equation::Cartesian, m_functionID );
561
	
562
	Function tempFunction( Function::Cartesian );
563
	tempFunction.setId( m_functionID );
564
565
	
	tempFunction.usecustomxmin = m_editor->cartesianCustomMin->isChecked();
566
	if ( !tempFunction.dmin.updateExpression( m_editor->cartesianMin->text() ) )
567
		return;
568
569
	
	tempFunction.usecustomxmax = m_editor->cartesianCustomMax->isChecked();
570
	if ( !tempFunction.dmax.updateExpression( m_editor->cartesianMax->text() ) )
571
		return;
572
	
573
	tempFunction.plotAppearance( Function::Derivative0 ) = m_editor->cartesian_f0->plot( (functionListItem->checkState() == Qt::Checked) );
574
575
576
	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() );
577
	
578
	DifferentialState * state = & tempFunction.eq[0]->differentialStates[0];
579
	state->setOrder( 1 );
580
581
	state->x0.updateExpression( m_editor->txtInitX->text() );
	state->y0[0].updateExpression( m_editor->txtInitY->text() );
582

583
	if ( !tempFunction.eq[0]->differentialStates.setStep( m_editor->integralStep->text() ) )
584
		return;
585
	tempFunction.m_parameters = m_editor->cartesianParameters->parameterSettings();
586
	
587
	if ( !tempFunction.eq[0]->setFstr( f_str ) )
588
589
		return;
	
590
	saveFunction( & tempFunction );
591
592
593
594
595
}


void FunctionEditor::savePolar()
{
596
	FunctionListItem * functionListItem = static_cast<FunctionListItem*>(m_functionList->currentItem());
597
598
	if ( !functionListItem )
		return;
599
	
600
601
	QString f_str = m_editor->polarEquation->text();

602
	XParser::self()->fixFunctionName( f_str, Equation::Polar, m_functionID );
603
	Function tempFunction( Function::Polar );  // all settings are saved here until we know that no errors have appeared
604
	tempFunction.setId( m_functionID );
605
	
606
	if ( !tempFunction.dmin.updateExpression( m_editor->polarMin->text() ) )
607
		return;
608
	if ( !tempFunction.dmax.updateExpression( m_editor->polarMax->text() ) )
609
610
		return;
	
611
	tempFunction.m_parameters = m_editor->polarParameters->parameterSettings();
612
	tempFunction.plotAppearance( Function::Derivative0 ) = m_editor->polar_f0->plot( (functionListItem->checkState() == Qt::Checked) );
613
	
614
	if ( !tempFunction.eq[0]->setFstr( f_str ) )
615
616
		return;
	
617
	saveFunction( & tempFunction );
618
619
620
621
622
}


void FunctionEditor::saveParametric()
{
623
	FunctionListItem * functionListItem = static_cast<FunctionListItem*>(m_functionList->currentItem());
624
	if ( !functionListItem )
625
		return;
626
	
627
	Function tempFunction( Function::Parametric );
628
	tempFunction.setId( m_functionID );
629
	
630
	QString f_str = m_editor->parametricX->text();
631
	XParser::self()->fixFunctionName( f_str, Equation::ParametricX, m_functionID );
632
	if ( !tempFunction.eq[0]->setFstr( f_str ) )
633
		return;
634
	
635
	f_str = m_editor->parametricY->text();
636
	XParser::self()->fixFunctionName( f_str, Equation::ParametricY, m_functionID );
637
638
	if ( !tempFunction.eq[1]->setFstr( f_str ) )
		return;
639
	
640
	if ( !tempFunction.dmin.updateExpression( m_editor->parametricMin->text() ) )
641
642
		return;
	
643
	if ( !tempFunction.dmax.updateExpression( m_editor->parametricMax->text() ) )
644
		return;
645
	
646
	tempFunction.m_parameters = m_editor->parametricParameters->parameterSettings();
647
	tempFunction.plotAppearance( Function::Derivative0 ) = m_editor->parametric_f0->plot( (functionListItem->checkState() == Qt::Checked) );
648
	
649
	saveFunction( & tempFunction );
650
651
652
}


653
654
655
void FunctionEditor::saveImplicit()
{
	FunctionListItem * functionListItem = static_cast<FunctionListItem*>(m_functionList->currentItem());
656
657
	if ( !functionListItem )
		return;
658
659
660
661
662
	
	// find a name not already used 
	if ( m_editor->implicitName->text().isEmpty() )
	{
		QString fname;
663
		XParser::self()->fixFunctionName(fname, Equation::Implicit, m_functionID );
664
665
666
667
		int const pos = fname.indexOf('(');
		m_editor->implicitName->setText(fname.mid(1,pos-1));
	}
	
668
	QString prefix = m_editor->implicitName->text() + " = ";
669
670
671
672
	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
673
	tempFunction.setId( m_functionID );
674
675
	
	tempFunction.m_parameters = m_editor->implicitParameters->parameterSettings();
676
	tempFunction.plotAppearance( Function::Derivative0 ) = m_editor->implicit_f0->plot( (functionListItem->checkState() == Qt::Checked) );
677
678
679
680
	
	if ( !tempFunction.eq[0]->setFstr( f_str ) )
		return;
	
681
	saveFunction( & tempFunction );
682
683
684
}


685
686
687
void FunctionEditor::saveDifferential()
{
	FunctionListItem * functionListItem = static_cast<FunctionListItem*>(m_functionList->currentItem());
688
689
	if ( !functionListItem )
		return;
690
691
	
	Function tempFunction( Function::Differential );  // all settings are saved here until we know that no errors have appeared
692
	tempFunction.setId( m_functionID );
693
	
694
695
696
	QString f_str = m_editor->differentialEquation->text();
	if ( !tempFunction.eq[0]->setFstr( f_str ) )
		return;
697
698
	
	tempFunction.m_parameters = m_editor->differentialParameters->parameterSettings();
699
	tempFunction.plotAppearance( Function::Derivative0 ) = m_editor->differential_f0->plot( (functionListItem->checkState() == Qt::Checked) );
700
	
701
702
	m_editor->initialConditions->setOrder( tempFunction.eq[0]->order() );
	tempFunction.eq[0]->differentialStates = *m_editor->initialConditions->differentialStates();
703
704
705
706
707
708
709
710
711
712
713
714
715
	if ( !tempFunction.eq[0]->differentialStates.setStep( m_editor->differentialStep->text() ) )
		return;
	
	saveFunction( & tempFunction );
}


void FunctionEditor::saveFunction( Function * tempFunction )
{
	FunctionListItem * functionListItem = static_cast<FunctionListItem*>(m_functionList->currentItem());
	Function * f = XParser::self()->functionWithID( m_functionID );
	if ( !f || !functionListItem )
		return;
716
	
717
	for ( Equation * eq : qAsConst(f->eq) )
718
		eq->differentialStates.resetToInitial();
719
	
720
	//save all settings in the function now when we know no errors have appeared
721
	bool changed = f->copyFrom( *tempFunction );
722
723
	if ( !changed )
		return;
724
	
725
	qDebug() << "Changed\n";
726
	
727
728
729
730
	if ( f->eq[0]->looksLikeFunction() )
		Settings::setDefaultEquationForm( Settings::EnumDefaultEquationForm::Function );
	else
		Settings::setDefaultEquationForm( Settings::EnumDefaultEquationForm::Implicit );
Laurent Montel's avatar
Laurent Montel committed
731
	Settings::self()->save();
732
	
733
	MainDlg::self()->requestSaveCurrentState();
734
	functionListItem->update();
735
	View::self()->drawPlot();
736
	
737
}
738
739
740
741
//END class FunctionEditor



742
//BEGIN class FunctionListWidget
743
FunctionListWidget::FunctionListWidget( QWidget * parent )
744
745
746
747
748
749
750
751
	: QListWidget( parent )
{
	setAcceptDrops(true);
    setDragEnabled(true);
	show();
}


752
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
753
QMimeData * FunctionListWidget::mimeData( const QList<QListWidgetItem *> items ) const
754
755
756
#else
QMimeData * FunctionListWidget::mimeData(const QList<QListWidgetItem *> &items) const
#endif
757
{
Laurent Montel's avatar
Laurent Montel committed
758
759
	QDomDocument doc( QStringLiteral("kmpdoc") );
	QDomElement root = doc.createElement( QStringLiteral("kmpdoc") );
760
761
	doc.appendChild( root );
	
762
763
	KmPlotIO io;
	
764
	for ( QListWidgetItem * item : qAsConst(items) )
765
	{
766
		int f = static_cast<FunctionListItem*>(item)->function();
767
		
768
		if ( Function * function = XParser::self()->functionWithID( f ) )
769
			io.addFunction( doc, root, function );
770
771
772
	}
	
	QMimeData * md = new QMimeData;
Laurent Montel's avatar
Laurent Montel committed
773
	md->setData( QStringLiteral("text/kmplot"), doc.toByteArray() );
774
775
776
777
778
779
780
781
	
	return md;
}


QStringList FunctionListWidget::mimeTypes() const
{
	QStringList mt;
Laurent Montel's avatar
Laurent Montel committed
782
	mt << QStringLiteral("text/kmplot");
783
784
785
786
787
788
789
	return mt;
}


void FunctionListWidget::dragEnterEvent( QDragEnterEvent * event )
{
	const QMimeData * md = event->mimeData();
Laurent Montel's avatar
Laurent Montel committed
790
	if ( md->hasFormat( QStringLiteral("text/kmplot") ) )
David Saxton's avatar
David Saxton committed
791
		event->acceptProposedAction();
792
793
794
795
796
797
798
}


void FunctionListWidget::dropEvent( QDropEvent * event )
{
	const QMimeData * md = event->mimeData();
	
Laurent Montel's avatar
Laurent Montel committed
799
800
	QDomDocument doc( QStringLiteral("kmpdoc") );
	doc.setContent( md->data( QStringLiteral("text/kmplot") ) );
801
802
	QDomElement element = doc.documentElement();
	
803
804
	KmPlotIO io;
	
805
806
	for ( QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() )
	{
Laurent Montel's avatar
Laurent Montel committed
807
		if ( n.nodeName() == QLatin1String("function") )
808
			io.parseFunction( n.toElement(), true );
809
		else
810
			qWarning() << "Unexpected node with name " << n.nodeName() ;
811
812
813
814
815
816
	}
}
//END class FunctionListWidget



817
//BEGIN class FunctionListItem
818
FunctionListItem::FunctionListItem( QListWidget * parent, int function )
819
820
	: QListWidgetItem( parent )
{
821
822
	m_function = function;
	assert( m_function != -1 );
823
824
825
826
827
828
	update();
}


void FunctionListItem::update()
{
829
	Function * f = XParser::self()->functionWithID( m_function );
830
	
831
	if ( !f )
832
833
834
835
836
	{
		// The function was probably deleted
		return;
	}
	
837
	setText( f->name() );
838
	setCheckState( f->plotAppearance( Function::Derivative0 ).visible ? Qt::Checked : Qt::Unchecked );
839
	setForeground( f->plotAppearance( Function::Derivative0 ).color );
840
841
}
//END class FunctionListItem