56 lines
10 KiB
C++
56 lines
10 KiB
C++
#pragma once // Заголовочная реализация добавляет дорожку без отдельного списка исходников CMake и генерации moc.
|
||
#include "trace.h" // Базовая дорожка обеспечивает выбор, название, цвет и размещение на общей временной шкале.
|
||
#include "view.h" // Масштаб и смещение берутся из того же View, который рисует цифровые каналы.
|
||
#include "scope_demo.h" // Переносимая модель повторяет демонстрационные сигналы осциллографа Zynq.
|
||
#include <QPainterPath> // Непрерывная кривая передаётся Qt одним пакетом вместо вызова для каждого отсчёта.
|
||
namespace pv { namespace view { // Дорожка принадлежит тому же пространству имён, что остальные графики DSView.
|
||
class ScopeTrace : public Trace { // Виртуальный осциллограф дополняет цифровую сессию без подмены устройства и его каналов.
|
||
unsigned _channel; // Индекс 0…3 выбирает частоту, амплитуду, форму и цвет исходного эмулятора Zynq.
|
||
bool _active = false; // Канал появляется только после явного выбора в меню осциллографа.
|
||
public: // Управление доступно только владельцу View и общему рендереру дорожек.
|
||
enum { Type = 100 }; // Локальный тип отделён от SR_CHANNEL_* библиотеки, чтобы аппаратные приведения типов не затрагивали эмулятор.
|
||
explicit ScopeTrace(unsigned channel) : Trace(QString::fromUtf8("A%1 · Zynq DEMO").arg(channel + 1), channel, Type), _channel(channel) { // Номер A1…A4 явно отличает эмуляцию от входов D0…D15.
|
||
_colour = QColor::fromRgb(set_scope::rgb[channel]); // Цвет трассы совпадает с исходным каналом Zynq.
|
||
_v_offset = 0; // До первой компоновки координата определена и не участвует в переполнении сортировки.
|
||
} // Родитель QObject назначается владельцем после создания дорожки.
|
||
bool enabled() override { return _active; } // Общий рендерер обрабатывает только выбранные каналы.
|
||
void set_active(bool active) { _active = active; } // Повторный выбор скрывает ту же дорожку, сохраняя её имя и цвет.
|
||
int get_zero_vpos() override { return get_y(); } // Маркер нуля следует за дорожкой при вертикальной прокрутке цифровой записи.
|
||
int rows_size() override { return 6; } // Шесть обычных строк дают начальную высоту 120 пикселей при масштабе 1X DSView.
|
||
QRect get_view_rect() override { return QRect(0, get_y() - _totalHeight / 2, _view->get_view_width(), _totalHeight); } // Область графика учитывает прокрутку и реальную высоту дорожки.
|
||
void paint_back(QPainter &, int, int, QColor, QColor) override {} // Сетка рисуется вместе с кривой и не меняет общий фон цифровых дорожек.
|
||
void paint_mid(QPainter &, int, int, QColor, QColor) override {} // Эмулятор рисуется передним слоем также до первого аппаратного захвата.
|
||
void paint_fore(QPainter &p, int left, int right, QColor fore, QColor) override { // Общий слой Viewport обеспечивает показ эмуляции при пустой или остановленной записи.
|
||
if (!_active || !_view || right <= left || !std::isfinite(_view->scale()) || _view->scale() <= 0) return; // Неполная геометрия при запуске и изменении размера безопасно пропускается.
|
||
const QRect area = get_view_rect(); // Вертикальные границы принадлежат исключительно этой дорожке.
|
||
const double top = area.top() + 22, bottom = area.bottom() - 12, zero = (top + bottom) / 2, gain = (bottom - top) / 2; // Верхние 22 и нижние 12 пикселей оставляют место подписи; полный диапазон равен −1…+1 В.
|
||
if (gain <= 0) return; // Сжатая до нуля дорожка не создаёт отрицательный масштаб напряжения.
|
||
p.save(); // Перо, сглаживание и область отсечения других каналов сохраняются.
|
||
p.setClipRect(area.intersected(QRect(left, area.top(), right - left, area.height()))); // Кривая не перекрывает соседние сигналы и заголовки.
|
||
p.setPen(QPen(fore, 1, Qt::DotLine)); // Пунктирная сетка имеет постоянную толщину одного экранного пикселя.
|
||
for (int division = -4; division <= 4; ++division) p.drawLine(QPointF(left, zero - gain * division / 4), QPointF(right, zero - gain * division / 4)); // Восемь делений охватывают 2 В, то есть 0,25 В/дел.
|
||
p.drawText(QPointF(left + 4, top - 5), QString::fromUtf8("Zynq DEMO · %1 · %2 Гц · ±%3 В · 0,25 В/дел").arg((_channel & 1) ? QString::fromUtf8("меандр") : QString::fromUtf8("синус")).arg(set_scope::hz[_channel]).arg(set_scope::peak[_channel])); // Единицы и источник остаются видимыми рядом с осциллограммой.
|
||
p.drawText(QPointF(left + 4, zero - 3), QString::fromUtf8("0 В")); // Нулевой уровень исключает неоднозначность вертикального положения сигнала.
|
||
p.setPen(QPen(_colour, 1.5)); // Толщина в экранных пикселях и цвет сохраняются при масштабировании времени.
|
||
p.setBrush(Qt::NoBrush); // Наследованная кисть маркеров не должна заливать область между началом и концом осциллограммы.
|
||
p.setRenderHint(QPainter::Antialiasing, true); // Сглаживание применяется к геометрии, ограниченной шириной экрана.
|
||
QPainterPath path; // Путь содержит не более нескольких точек на экранный пиксель независимо от длины записи.
|
||
const double scale = _view->scale(), offset = _view->offset(); // Секунды на пиксель и смещение в пикселях совпадают с цифровыми сигналами DSView.
|
||
const bool envelope = scale * set_scope::hz[_channel] >= 0.25; // При четверти периода на пиксель и плотнее рисуем точную огибающую, предотвращая алиасинг.
|
||
for (int x = left; x <= right; ++x) { // Линейная сложность по ширине окна не зависит от частоты эмулятора или длительности захвата.
|
||
const double first = (x + offset) * scale, last = first + scale; // Абсолютное время сохраняет фазу при прокрутке и увеличении записи.
|
||
if (envelope) { // Каждый пиксель представляет диапазон напряжений за соответствующий интервал времени.
|
||
const auto range = set_scope::extrema(_channel, first, last); // Аналитический расчёт сохраняет оба пика даже при множестве периодов внутри пикселя.
|
||
path.moveTo(x, zero - range.low * gain); // Нижний экстремум переводится из вольт в вертикальные экранные координаты.
|
||
path.lineTo(x, zero - range.high * gain); // Верхний экстремум завершает вертикальный отрезок огибающей.
|
||
} else { // При увеличении масштаба показываем форму исходного синуса или меандра.
|
||
const double y = zero - set_scope::value(_channel, first) * gain; // Напряжение рассчитывается в тот же момент времени, что и цифровая дорожка.
|
||
if (x == left) path.moveTo(x, y); else path.lineTo(x, y); // Последовательные точки образуют непрерывную осциллограмму без соединения разных дорожек.
|
||
} // Значение фазы зависит только от времени записи, поэтому перерисовка не запускает генератор заново.
|
||
} // Объём временной геометрии остаётся ограниченным даже у часового захвата.
|
||
p.drawPath(path); // Одним вызовом выводим готовую кривую или огибающую.
|
||
p.restore(); // Последующие каналы получают исходные настройки QPainter.
|
||
} // Эмуляция не записывается в аппаратные данные и не меняет результаты декодеров.
|
||
}; // Жизненным циклом дорожки управляет родительский View через QObject.
|
||
} } // Завершаем пространства имён компонентов отображения DSView.
|