From: Soeren Apel Date: Sun, 27 Sep 2020 21:18:53 +0000 (+0200) Subject: MathSignal: Implement custom math signal dialog X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=2823de2c821f01b9b6d8df7fc972e290518075ae;p=pulseview.git MathSignal: Implement custom math signal dialog --- diff --git a/pv/data/mathsignal.cpp b/pv/data/mathsignal.cpp index 2217bffd..f7de2ff2 100644 --- a/pv/data/mathsignal.cpp +++ b/pv/data/mathsignal.cpp @@ -67,12 +67,18 @@ struct fnc_sample : public exprtk::igeneric_function const scalar_t exprtk_sample_num = scalar_t(parameters[1]); const std::string str_sig_name = to_str(exprtk_sig_name); - const double sample_num = std::max(exprtk_sample_num(), (double)0); + const double sample_num = exprtk_sample_num(); + + if (sample_num < 0) + return 0; if (!sig_data) sig_data = owner_.signal_from_name(str_sig_name); - assert(sig_data); + if (!sig_data) + // There doesn't actually exist a signal with that name + return 0; + owner_.update_signal_sample(sig_data, current_segment, sample_num); return T(sig_data->sample_value); @@ -288,6 +294,8 @@ void MathSignal::reset_generation() // TODO Emulate noquote() qDebug().nospace() << name() << ": Error cleared"; } + + generation_chunk_size_ = ChunkLength; } void MathSignal::begin_generation() @@ -307,6 +315,7 @@ void MathSignal::begin_generation() exprtk_unknown_symbol_table_ = new exprtk::symbol_table(); exprtk_symbol_table_ = new exprtk::symbol_table(); + exprtk_symbol_table_->add_constant("T", 1 / session_.get_samplerate()); exprtk_symbol_table_->add_function("sample", *fnc_sample_); exprtk_symbol_table_->add_variable("t", exprtk_current_time_); exprtk_symbol_table_->add_variable("s", exprtk_current_sample_); @@ -347,7 +356,7 @@ void MathSignal::begin_generation() signal_data* sig_data = signal_from_name(unknown); const shared_ptr signal = (sig_data) ? (sig_data->sb) : nullptr; if (!signal || (!signal->analog_data())) { - set_error(MATH_ERR_INVALID_SIGNAL, QString(tr("%1 isn't a valid analog signal")) \ + set_error(MATH_ERR_INVALID_SIGNAL, QString(tr("\"%1\" isn't a valid analog signal")) \ .arg(QString::fromStdString(unknown))); } else sig_data->ref = &(exprtk_unknown_symbol_table_->variable_ref(unknown)); @@ -370,9 +379,11 @@ void MathSignal::begin_generation() } } -void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample, +uint64_t MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_sample, const int64_t sample_count) { + uint64_t count = 0; + shared_ptr analog = dynamic_pointer_cast(data_); shared_ptr segment = analog->analog_segments().at(segment_id); @@ -396,11 +407,20 @@ void MathSignal::generate_samples(uint32_t segment_id, const uint64_t start_samp double value = exprtk_expression_->value(); sample_data[i] = value; exprtk_current_sample_ += 1; + count++; + + // If during the evaluation of the expression it was found that this + // math signal itself is being accessed, the chunk size was reduced + // to 1, which means we must stop after this sample we just generated + if (generation_chunk_size_ == 1) + break; } - segment->append_interleaved_samples(sample_data, sample_count, 1); + segment->append_interleaved_samples(sample_data, count, 1); delete[] sample_data; + + return count; } void MathSignal::generation_proc() @@ -440,15 +460,13 @@ void MathSignal::generation_proc() // Process the samples if necessary... if (samples_to_process > 0) { - const uint64_t chunk_sample_count = ChunkLength; - uint64_t processed_samples = 0; do { const uint64_t start_sample = output_sample_count + processed_samples; - const uint64_t sample_count = - min(samples_to_process - processed_samples, chunk_sample_count); + uint64_t sample_count = + min(samples_to_process - processed_samples, generation_chunk_size_); - generate_samples(segment_id, start_sample, sample_count); + sample_count = generate_samples(segment_id, start_sample, sample_count); processed_samples += sample_count; // Notify consumers of this signal's data @@ -477,6 +495,12 @@ void MathSignal::generation_proc() signal_data* MathSignal::signal_from_name(const std::string& name) { + // If the expression contains the math signal itself, we must add every sample to + // the output segment immediately so that it can be accessed + const QString sig_name = QString::fromStdString(name); + if (sig_name == this->name()) + generation_chunk_size_ = 1; + // Look up signal in the map and if it doesn't exist yet, add it for future use auto element = input_signals_.find(name); @@ -485,7 +509,6 @@ signal_data* MathSignal::signal_from_name(const std::string& name) return &(element->second); } else { const vector< shared_ptr > signalbases = session_.signalbases(); - const QString sig_name = QString::fromStdString(name); for (const shared_ptr& sb : signalbases) if (sb->name() == sig_name) { @@ -504,6 +527,11 @@ signal_data* MathSignal::signal_from_name(const std::string& name) } } + // If we reach this point, no valid signal was found with the supplied name + if (error_type_ == MATH_ERR_NONE) + set_error(MATH_ERR_INVALID_SIGNAL, QString(tr("\"%1\" isn't a valid analog signal")) \ + .arg(QString::fromStdString(name))); + return nullptr; } @@ -524,7 +552,11 @@ void MathSignal::update_signal_sample(signal_data* sig_data, uint32_t segment_id const shared_ptr segment = analog->analog_segments().at(segment_id); sig_data->sample_num = sample_num; - sig_data->sample_value = segment->get_sample(sample_num); + + if (sample_num < segment->get_sample_count()) + sig_data->sample_value = segment->get_sample(sample_num); + else + sig_data->sample_value = 0; // We only have a reference if this signal is used as a scalar; // if it's used by a function, it's null diff --git a/pv/data/mathsignal.hpp b/pv/data/mathsignal.hpp index 76427c56..e40dbfb3 100644 --- a/pv/data/mathsignal.hpp +++ b/pv/data/mathsignal.hpp @@ -20,6 +20,10 @@ #ifndef PULSEVIEW_PV_DATA_MATHSIGNAL_HPP #define PULSEVIEW_PV_DATA_MATHSIGNAL_HPP +#define exprtk_disable_rtl_io_file /* Potential security issue, doubt anyone would use those anyway */ +#define exprtk_disable_rtl_vecops /* Vector ops are rather useless for math channels */ +#define exprtk_disable_caseinsensitivity /* So that we can have both 't' and 'T' */ + #include #include @@ -91,7 +95,7 @@ private: void reset_generation(); virtual void begin_generation(); - void generate_samples(uint32_t segment_id, const uint64_t start_sample, + uint64_t generate_samples(uint32_t segment_id, const uint64_t start_sample, const int64_t sample_count); void generation_proc(); @@ -122,6 +126,7 @@ private: uint64_t custom_sample_rate_; uint64_t custom_sample_count_; bool use_custom_sample_rate_, use_custom_sample_count_; + uint64_t generation_chunk_size_; map input_signals_; QString expression_; diff --git a/pv/views/trace/mathsignal.cpp b/pv/views/trace/mathsignal.cpp index 13dd0665..2d242b99 100644 --- a/pv/views/trace/mathsignal.cpp +++ b/pv/views/trace/mathsignal.cpp @@ -24,8 +24,12 @@ #include #include #include +#include #include +#include #include +#include +#include #include "mathsignal.hpp" @@ -39,32 +43,236 @@ namespace trace { #define MATHSIGNAL_INPUT_TIMEOUT (2000) +const vector< pair > MathEditDialog::Examples = { + {"Product of signals named A1 and A2:", + "A1 * A2"}, + {"Create a sine wave with f=1MHz:", + "sin(1e6 * 2 * pi * t)"}, + {"Average the past 4 samples of A2:", + "var v := 0;\n" \ + "var n := 4;\n\n" \ + "for (var i := 0; i < n; i += 1) {\n" \ + "\tv += sample('A2', s - i) / n;\n" \ + "}"}, + {"Create a frequency sweep from 1Hz to 1MHz over 10 seconds:", + "var f_min := 1;\n" \ + "var f_max := 1e6;\n" \ + "var duration := 10;\n" \ + "var gradient := (f_max - f_min) / duration;\n" \ + "// phase is the antiderivative of (gradient * t + f_min) dt\n" \ + "var phase := gradient * (t ^ 2 / 2) + f_min * t;\n" \ + "sin(2 * pi * phase)"}, + {"Single-pole low-pass IIR filter, see " \ + "this " \ + "and this article:", + "// -3db point is set to 10% of the sampling frequency\n" \ + "var f_c := 0.1;\n" \ + "var omega_c := 2 * pi * f_c;\n" \ + "var b := 2 - cos(omega_c) - sqrt(((2 - cos(omega_c)) ^ 2) - 1);\n" \ + "var a := 1 - b;\n\n" \ + "// formula is y[n] = ax[n] + (1 - a) * y[n - 1]\n" \ + "// x[n] becomes the input signal, here A4\n" \ + "// y[n - 1] becomes sample('Math1', s - 1)\n" \ + "a * A4 + (1 - a) * sample('Math1', s - 1)"} +}; + MathEditDialog::MathEditDialog(pv::Session &session, shared_ptr math_signal, QWidget *parent) : QDialog(parent), session_(session), math_signal_(math_signal), - expression_(math_signal->get_expression()), - old_expression_(math_signal->get_expression()) + old_expression_(math_signal->get_expression()), + expr_edit_(new QPlainTextEdit()) { setWindowTitle(tr("Math Expression Editor")); + // Create the pages + QWidget *basics_page = new QWidget(); + QVBoxLayout *basics_layout = new QVBoxLayout(basics_page); + basics_layout->addWidget(new QLabel("" + tr("Inputs:") + "")); + basics_layout->addWidget(new QLabel("signal_name\tCurrent value of signal signal_name (e.g. A0 or CH1)")); + basics_layout->addWidget(new QLabel("T\t\tSampling interval (= 1 / sample rate)")); + basics_layout->addWidget(new QLabel("t\t\tCurrent time in seconds")); + basics_layout->addWidget(new QLabel("s\t\tCurrent sample number")); + basics_layout->addWidget(new QLabel("sample('s', n)\tValue of sample #n of the signal named s")); + basics_layout->addWidget(new QLabel("" + tr("Variables:") + "")); + basics_layout->addWidget(new QLabel("var x;\t\tCreate variable\nvar x := 5;\tCreate variable with initial value")); + basics_layout->addWidget(new QLabel("" + tr("Basic operators:") + "")); + basics_layout->addWidget(new QLabel("x + y\t\tAddition of x and y")); + basics_layout->addWidget(new QLabel("x - y\t\tSubtraction of x and y")); + basics_layout->addWidget(new QLabel("x * y\t\tMultiplication of x and y")); + basics_layout->addWidget(new QLabel("x / y\t\tDivision of x and y")); + basics_layout->addWidget(new QLabel("x % y\t\tRemainder of division x / y")); + basics_layout->addWidget(new QLabel("x ^ y\t\tx to the power of y")); + basics_layout->addWidget(new QLabel("" + tr("Assignments:") + "")); + basics_layout->addWidget(new QLabel("x := y\t\tAssign the value of y to x")); + basics_layout->addWidget(new QLabel("x += y\t\tIncrement x by y")); + basics_layout->addWidget(new QLabel("x -= y\t\tDecrement x by y")); + basics_layout->addWidget(new QLabel("x *= y\t\tMultiply x by y")); + basics_layout->addWidget(new QLabel("x /= y\t\tDivide x by y")); + basics_layout->addWidget(new QLabel("x %= y\t\tSet x to the value of x % y")); + + QWidget *func1_page = new QWidget(); + QVBoxLayout *func1_layout = new QVBoxLayout(func1_page); + func1_layout->addWidget(new QLabel("" + tr("General purpose functions:") + "")); + func1_layout->addWidget(new QLabel(tr("abs(x)\t\tAbsolute value of x"))); + func1_layout->addWidget(new QLabel(tr("avg(x, y, ...)\tAverage of all input values"))); + func1_layout->addWidget(new QLabel(tr("ceil(x)\t\tSmallest integer that is greater than or equal to x"))); + func1_layout->addWidget(new QLabel(tr("clamp(lb, x, ub)\tClamp x in range between lb and ub, where lb < ub"))); + func1_layout->addWidget(new QLabel(tr("equal(x, y)\tEquality test between x and y using normalised epsilon"))); + func1_layout->addWidget(new QLabel(tr("erf(x)\t\tError function of x"))); + func1_layout->addWidget(new QLabel(tr("erfc(x)\t\tComplimentary error function of x"))); + func1_layout->addWidget(new QLabel(tr("exp(x)\t\te to the power of x"))); + func1_layout->addWidget(new QLabel(tr("expm1(x)\te to the power of x minus 1, where x is very small."))); + func1_layout->addWidget(new QLabel(tr("floor(x)\t\tLargest integer that is less than or equal to x"))); + func1_layout->addWidget(new QLabel(tr("frac(x)\t\tFractional portion of x"))); + func1_layout->addWidget(new QLabel(tr("hypot(x)\t\tHypotenuse of x and y (i.e. sqrt(x*x + y*y))"))); + func1_layout->addWidget(new QLabel(tr("iclamp(lb, x, ub)\tInverse-clamp x outside of the range lb and ub, where lb < ub.\n\t\tIf x is within the range it will snap to the closest bound"))); + func1_layout->addWidget(new QLabel(tr("inrange(lb, x, ub)\tIn-range returns true when x is within the range lb and ub, where lb < ub."))); + func1_layout->addWidget(new QLabel(tr("log(x)\t\tNatural logarithm of x"))); + func1_layout->addWidget(new QLabel(tr("log10(x)\t\tBase 10 logarithm of x"))); + + QWidget *func2_page = new QWidget(); + QVBoxLayout *func2_layout = new QVBoxLayout(func2_page); + func2_layout->addWidget(new QLabel(tr("log1p(x)\t\tNatural logarithm of 1 + x, where x is very small"))); + func2_layout->addWidget(new QLabel(tr("log2(x)\t\tBase 2 logarithm of x"))); + func2_layout->addWidget(new QLabel(tr("logn(x)\t\tBase N logarithm of x, where n is a positive integer"))); + func2_layout->addWidget(new QLabel(tr("max(x, y, ...)\tLargest value of all the inputs"))); + func2_layout->addWidget(new QLabel(tr("min(x, y, ...)\tSmallest value of all the inputs"))); + func2_layout->addWidget(new QLabel(tr("mul(x, y, ...)\tProduct of all the inputs"))); + func2_layout->addWidget(new QLabel(tr("ncdf(x)\t\tNormal cumulative distribution function"))); + func2_layout->addWidget(new QLabel(tr("nequal(x, y)\tNot-equal test between x and y using normalised epsilon"))); + func2_layout->addWidget(new QLabel(tr("pow(x, y)\tx to the power of y"))); + func2_layout->addWidget(new QLabel(tr("root(x, n)\tNth-Root of x, where n is a positive integer"))); + func2_layout->addWidget(new QLabel(tr("round(x)\t\tRound x to the nearest integer"))); + func2_layout->addWidget(new QLabel(tr("roundn(x, n)\tRound x to n decimal places, where n > 0 and is an integer"))); + func2_layout->addWidget(new QLabel(tr("sgn(x)\t\tSign of x; -1 if x < 0, +1 if x > 0, else zero"))); + func2_layout->addWidget(new QLabel(tr("sqrt(x)\t\tSquare root of x, where x >= 0"))); + func2_layout->addWidget(new QLabel(tr("sum(x, y, ..,)\tSum of all the inputs"))); + func2_layout->addWidget(new QLabel(tr("swap(x, y)\tSwap the values of the variables x and y and return the current value of y"))); + func2_layout->addWidget(new QLabel(tr("trunc(x)\t\tInteger portion of x"))); + + QWidget *trig_page = new QWidget(); + QVBoxLayout *trig_layout = new QVBoxLayout(trig_page); + trig_layout->addWidget(new QLabel("" + tr("Trigonometry functions:") + "")); + trig_layout->addWidget(new QLabel(tr("acos(x)\t\tArc cosine of x expressed in radians. Interval [-1,+1]"))); + trig_layout->addWidget(new QLabel(tr("acosh(x)\t\tInverse hyperbolic cosine of x expressed in radians"))); + trig_layout->addWidget(new QLabel(tr("asin(x)\t\tArc sine of x expressed in radians. Interval [-1,+1]"))); + trig_layout->addWidget(new QLabel(tr("asinh(x)\t\tInverse hyperbolic sine of x expressed in radians"))); + trig_layout->addWidget(new QLabel(tr("atan(x)\t\tArc tangent of x expressed in radians. Interval [-1,+1]"))); + trig_layout->addWidget(new QLabel(tr("atan2(x, y)\tArc tangent of (x / y) expressed in radians. [-pi,+pi] "))); + trig_layout->addWidget(new QLabel(tr("atanh(x)\t\tInverse hyperbolic tangent of x expressed in radians"))); + trig_layout->addWidget(new QLabel(tr("cos(x)\t\tCosine of x"))); + trig_layout->addWidget(new QLabel(tr("cosh(x)\t\tHyperbolic cosine of x"))); + trig_layout->addWidget(new QLabel(tr("cot(x)\t\tCotangent of x"))); + trig_layout->addWidget(new QLabel(tr("csc(x)\t\tCosectant of x"))); + trig_layout->addWidget(new QLabel(tr("sec(x)\t\tSecant of x"))); + trig_layout->addWidget(new QLabel(tr("sin(x)\t\tSine of x"))); + trig_layout->addWidget(new QLabel(tr("sinc(x)\t\tSine cardinal of x"))); + trig_layout->addWidget(new QLabel(tr("sinh(x)\t\tHyperbolic sine of x"))); + trig_layout->addWidget(new QLabel(tr("tan(x)\t\tTangent of x"))); + trig_layout->addWidget(new QLabel(tr("tanh(x)\t\tHyperbolic tangent of x"))); + trig_layout->addWidget(new QLabel(tr("deg2rad(x)\tConvert x from degrees to radians"))); + trig_layout->addWidget(new QLabel(tr("deg2grad(x)\tConvert x from degrees to gradians"))); + trig_layout->addWidget(new QLabel(tr("rad2deg(x)\tConvert x from radians to degrees"))); + trig_layout->addWidget(new QLabel(tr("grad2deg(x)\tConvert x from gradians to degrees"))); + + QWidget *logic_page = new QWidget(); + QVBoxLayout *logic_layout = new QVBoxLayout(logic_page); + logic_layout->addWidget(new QLabel("" + tr("Logic operators:") + "")); + logic_layout->addWidget(new QLabel("true\t\tTrue state or any value other than zero (typically 1)")); + logic_layout->addWidget(new QLabel("false\t\tFalse state, value of exactly zero")); + logic_layout->addWidget(new QLabel("x and y\t\tLogical AND, true only if x and y are both true")); + logic_layout->addWidget(new QLabel("mand(x, y, z, ...)\tMulti-input logical AND, true only if all inputs are true")); + logic_layout->addWidget(new QLabel("x or y\t\tLogical OR, true if either x or y is true")); + logic_layout->addWidget(new QLabel("mor(x, y, z, ...)\tMulti-input logical OR, true if at least one of the inputs is true")); + logic_layout->addWidget(new QLabel("x nand y\t\tLogical NAND, true only if either x or y is false")); + logic_layout->addWidget(new QLabel("x nor y\t\tLogical NOR, true only if the result of x or y is false")); + logic_layout->addWidget(new QLabel("not(x)\t\tLogical NOT, negate the logical sense of the input")); + logic_layout->addWidget(new QLabel("x xor y\t\tLogical NOR, true only if the logical states of x and y differ")); + logic_layout->addWidget(new QLabel("x xnor y\t\tLogical NOR, true only if x and y have the same state")); + logic_layout->addWidget(new QLabel("x & y\t\tSame as AND but with left to right expression short circuiting optimisation")); + logic_layout->addWidget(new QLabel("x | y\t\tSame as OR but with left to right expression short circuiting optimisation")); + + QWidget *control1_page = new QWidget(); + QVBoxLayout *control1_layout = new QVBoxLayout(control1_page); + control1_layout->addWidget(new QLabel("" + tr("Comparisons:") + "")); + control1_layout->addWidget(new QLabel(tr("x = y or x == y\tTrue only if x is strictly equal to y"))); + control1_layout->addWidget(new QLabel(tr("x <> y or x != y\tTrue only if x does not equal y"))); + control1_layout->addWidget(new QLabel(tr("x < y\t\tTrue only if x is less than y"))); + control1_layout->addWidget(new QLabel(tr("x <= y\t\tTrue only if x is less than or equal to y"))); + control1_layout->addWidget(new QLabel(tr("x > y\t\tTrue only if x is greater than y"))); + control1_layout->addWidget(new QLabel(tr("x >= y\t\tTrue only if x is greater than or equal to y"))); + control1_layout->addWidget(new QLabel("" + tr("Flow control:") + "")); + control1_layout->addWidget(new QLabel(tr("{ ... }\t\tBeginning and end of instruction block"))); + control1_layout->addWidget(new QLabel(tr("if (x, y, z)\tIf x is true then return y else return z\nif (x) y;\t\t\tvariant without implied else\nif (x) { y };\t\tvariant with an instruction block\nif (x) y; else z;\t\tvariant with explicit else\nif (x) { y } else { z };\tvariant with instruction blocks"))); + control1_layout->addWidget(new QLabel(tr("x ? y : z\tTernary operator, equivalent to 'if (x, y, z)'"))); + control1_layout->addWidget(new QLabel(tr("switch {\t\tThe first true case condition that is encountered will\n case x > 1: a;\tdetermine the result of the switch. If none of the case\n case x < 1: b;\tconditions hold true, the default action is used\n default: c;\tto determine the return value\n}"))); + + QWidget *control2_page = new QWidget(); + QVBoxLayout *control2_layout = new QVBoxLayout(control2_page); + control2_layout->addWidget(new QLabel(tr("while (conditon) {\tEvaluates expression repeatedly as long as condition is true,\n expression;\treturning the last value of expression\n}"))); + control2_layout->addWidget(new QLabel(tr("repeat\t\tEvalues expression repeatedly as long as condition is false,\n expression;\treturning the last value of expression\nuntil (condition)\n"))); + control2_layout->addWidget(new QLabel(tr("for (var x := 0; condition; x += 1) {\tRepeatedly evaluates expression while the condition is true,\n expression\t\t\twhile evaluating the 'increment' expression on each loop\n}"))); + control2_layout->addWidget(new QLabel(tr("break\t\tTerminates the execution of the nearest enclosed loop, returning NaN"))); + control2_layout->addWidget(new QLabel(tr("break[x]\t\tTerminates the execution of the nearest enclosed loop, returning x"))); + control2_layout->addWidget(new QLabel(tr("continue\t\tInterrupts loop execution and resumes with the next loop iteration"))); + control2_layout->addWidget(new QLabel(tr("return[x]\t\tReturns immediately from within the current expression, returning x"))); + control2_layout->addWidget(new QLabel(tr("~(expr; expr; ...)\tEvaluates each sub-expression and returns the value of the last one\n~{expr; expr; ...}"))); + + QWidget *example_page = new QWidget(); + QVBoxLayout *example_layout = new QVBoxLayout(example_page); + for (const pair &entry : Examples) { + const string &desc = entry.first; + const string &example = entry.second; + + QLabel *desc_label = new QLabel("" + tr(desc.c_str()) + ""); + desc_label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); + desc_label->setOpenExternalLinks(true); + example_layout->addWidget(desc_label); + QPushButton *btn = new QPushButton(tr("Copy to expression")); + connect(btn, &QPushButton::clicked, this, [&]() { set_expr(QString::fromStdString(example)); }); + QGridLayout *gridlayout = new QGridLayout(); + gridlayout->addWidget(new QLabel(QString::fromStdString(example)), 0, 0, Qt::AlignLeft); + gridlayout->addWidget(btn, 0, 1, Qt::AlignRight); + example_layout->addLayout(gridlayout); + } + // Create the rest of the dialog QDialogButtonBox *button_box = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - QVBoxLayout* root_layout = new QVBoxLayout(this); -// root_layout->addLayout(tab_layout); + QTabWidget *tabs = new QTabWidget(); + tabs->addTab(basics_page, tr("Basics")); + tabs->addTab(func1_page, tr("Functions 1")); + tabs->addTab(func2_page, tr("Functions 2")); + tabs->addTab(trig_page, tr("Trigonometry")); + tabs->addTab(logic_page, tr("Logic")); + tabs->addTab(control1_page, tr("Flow Control 1")); + tabs->addTab(control2_page, tr("Flow Control 2")); + tabs->addTab(example_page, tr("Examples")); + + QVBoxLayout *root_layout = new QVBoxLayout(this); + root_layout->addWidget(tabs); + root_layout->addWidget(expr_edit_); root_layout->addWidget(button_box); + // Set tab width to 4 characters + expr_edit_->setTabStopWidth(util::text_width(QFontMetrics(font()), "XXXX")); + connect(button_box, SIGNAL(accepted()), this, SLOT(accept())); connect(button_box, SIGNAL(rejected()), this, SLOT(reject())); } +void MathEditDialog::set_expr(const QString &expr) +{ + expr_edit_->document()->setPlainText(expr); +} + void MathEditDialog::accept() { - math_signal_->set_expression(expression_); + math_signal_->set_expression(expr_edit_->document()->toPlainText()); QDialog::accept(); } @@ -139,6 +347,7 @@ void MathSignal::on_sample_count_changed(const QString &text) void MathSignal::on_edit_clicked() { MathEditDialog dlg(session_, math_signal_); + dlg.set_expr(expression_edit_->text()); dlg.exec(); } diff --git a/pv/views/trace/mathsignal.hpp b/pv/views/trace/mathsignal.hpp index 4b8fcb18..6af708e5 100644 --- a/pv/views/trace/mathsignal.hpp +++ b/pv/views/trace/mathsignal.hpp @@ -20,15 +20,22 @@ #ifndef PULSEVIEW_PV_VIEWS_TRACE_MATHSIGNAL_HPP #define PULSEVIEW_PV_VIEWS_TRACE_MATHSIGNAL_HPP +#include +#include + #include #include +#include #include #include #include #include +using std::pair; using std::shared_ptr; +using std::string; +using std::vector; namespace pv { namespace views { @@ -38,10 +45,15 @@ class MathEditDialog : public QDialog { Q_OBJECT +private: + static const vector< pair > Examples; + public: MathEditDialog(pv::Session &session, shared_ptr math_signal, QWidget *parent = nullptr); + void set_expr(const QString &expr); + private Q_SLOTS: void accept(); void reject(); @@ -49,7 +61,9 @@ private Q_SLOTS: private: pv::Session &session_; shared_ptr math_signal_; - QString expression_, old_expression_; + QString old_expression_; + + QPlainTextEdit *expr_edit_; };