Files
templates/tools/dsview/native/scope_trace.h
T

56 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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.