Files
templates/tools/dsview/zynq-ethernet/ethernet.patch
T

2106 lines
125 KiB
Diff

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 575eb85..6102914 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -94,6 +94,7 @@ include_directories(
#= glib-2.0
#-------------------------------------------------------------------------------
pkg_search_module(GLIB glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
if(NOT GLIB_FOUND)
message(FATAL_ERROR "Please install glib!")
@@ -477,6 +478,8 @@ set(libsigrok4DSL_SOURCES
libsigrok4DSL/output/gnuplot.c
libsigrok4DSL/output/srzip.c
libsigrok4DSL/output/vcd.c
+ libsigrok4DSL/hardware/zynq/zynq.c
+ libsigrok4DSL/hardware/zynq/zynq_record.c
libsigrok4DSL/hardware/DSL/dslogic.c
libsigrok4DSL/hardware/common/usb.c
libsigrok4DSL/hardware/common/ezusb.c
@@ -619,6 +622,7 @@ add_compile_options(-O3)
set(DSVIEW_LINK_LIBS
-lz
-lglib-2.0
+ PkgConfig::GIO
${CMAKE_THREAD_LIBS_INIT}
${QT_LIBRARIES}
${LIBUSB_1_LIBRARIES}
diff --git a/DSView/main.cpp b/DSView/main.cpp
index 58d2d15..869a6c1 100644
--- a/DSView/main.cpp
+++ b/DSView/main.cpp
@@ -38,6 +38,7 @@
#include <QDateTime>
#include <string>
#include <ds_types.h>
+#include <libsigrok.h>
#ifdef _WIN32
#include <windows.h>
@@ -224,7 +225,8 @@ bool bHighScale = true;
AppControl *control = AppControl::Instance();
AppConfig &app = AppConfig::Instance();
app.LoadAll(); //load app config
- if (embedParent) {
+ const bool localZynq = qEnvironmentVariableIsSet("DSVIEW_LOCAL_ZYNQ");
+ if (embedParent || localZynq) {
app.frameOptions.style = "dark";
app.appOptions.fontSize = 10;
QString captureDir = QString::fromUtf8(qgetenv("DSVIEW_CAPTURE_DIR"));
@@ -264,6 +266,15 @@ bool bHighScale = true;
dsv_err("init error!");
return 1;
}
+ if (localZynq) {
+ ds_device_handle analyzer = 0;
+ if (ds_add_zynq_device("127.0.0.1", &analyzer) != SR_OK) {
+ dsv_err("Local Zynq capture service did not answer.");
+ control->UnInit();
+ dsv_log_uninit();
+ return 1;
+ }
+ }
if (open_file != NULL){
control->_open_file_name = open_file;
@@ -287,8 +298,12 @@ bool bHighScale = true;
} else
#endif
{
- w.ShowFormInit();
- w.ShowHelpDocAsync();
+ if (localZynq)
+ w.showMaximized();
+ else {
+ w.ShowFormInit();
+ w.ShowHelpDocAsync();
+ }
}
ret = a.exec(); //Run the application
diff --git a/DSView/pv/data/decode/decoder.cpp b/DSView/pv/data/decode/decoder.cpp
index 2a1bdeb..5b1f9ff 100644
--- a/DSView/pv/data/decode/decoder.cpp
+++ b/DSView/pv/data/decode/decoder.cpp
@@ -43,6 +43,7 @@ Decoder::Decoder(const srd_decoder *const dec):
Decoder::~Decoder()
{
+ for (const auto &option : _options) if (option.second) g_variant_unref(option.second); // Применённый снимок владеет своими ссылками независимо от редактируемых значений диалога.
for (auto i = _options_back.begin(); i != _options_back.end(); i++){
if ((*i).second)
g_variant_unref((*i).second);
@@ -80,12 +81,26 @@ void Decoder::set_decode_region(uint64_t start, uint64_t end)
bool Decoder::commit()
{
if (_setted) {
+ bool changed = !_committed || _probes != _probes_back || _decode_start != _decode_start_back || _decode_end != _decode_end_back; // Повторный анализ нужен при первом применении, новых входах или изменении границ отсчётов.
+ for (const auto &option : _options_back) { // Диалог повторно передаёт и неизменённые параметры, поэтому сравниваем значения, а не факт вызова setter.
+ if (std::string(_decoder->id) == "pwm_waveform" && option.first == "waveform_layout") continue; // Наложение фаз меняет только отрисовку уже рассчитанных кривых.
+ const auto previous = _options.find(option.first); // Ищем значение, с которым построены сохранённые аннотации.
+ GVariant *value = previous == _options.end() ? nullptr : previous->second; // Отсутствующий ключ означает штатное значение декодера, а не автоматическое изменение.
+ if (!value) for (const GSList *item = _decoder->options; item; item = item->next) { // Сравниваем явно сохранённый параметр с его исходным значением по умолчанию.
+ const auto definition = static_cast<const srd_decoder_option*>(item->data); // Описание sigrok содержит типизированное значение по умолчанию для каждого параметра.
+ if (option.first == definition->id) { value = definition->def; break; } // Выбираем только совпадающий идентификатор параметра текущего декодера.
+ } // Неизвестный параметр с новым значением безопасно считается изменением анализа.
+ if (!value || !option.second || !g_variant_equal(value, option.second)) changed = true; // GVariant сравнивается по типу и содержимому, а не по адресу нового объекта из диалога.
+ } // Видимость строк хранится отдельно и никогда не влияет на вычислительные параметры.
+ for (const auto &option : _options) if (option.second) g_variant_unref(option.second); // Освобождаем ссылки предыдущего снимка только после сравнения, чтобы значения оставались действительными.
_probes = _probes_back;
_options = _options_back;
+ for (const auto &option : _options) if (option.second) g_variant_ref(option.second); // Отдельная ссылка сохраняет применённое значение при последующем редактировании или отмене диалога.
_decode_start = _decode_start_back;
_decode_end = _decode_end_back;
_setted = false;
- return true;
+ _committed = true; // После первого применения можно повторно использовать готовые результаты при равных настройках.
+ return changed; // Принятие диалога без изменения анализа вызывает только обновление представления.
}
else {
return false;
diff --git a/DSView/pv/data/decode/decoder.h b/DSView/pv/data/decode/decoder.h
index 9f8e084..17c4044 100644
--- a/DSView/pv/data/decode/decoder.h
+++ b/DSView/pv/data/decode/decoder.h
@@ -123,6 +123,7 @@ private:
uint64_t _decode_end_back;
bool _setted;
+ bool _committed = false; // Первый набор настроек требует анализа даже при пустых параметрах; последующие сравниваются с применённым снимком.
bool _shown;
};
diff --git a/DSView/pv/data/logicsnapshot.cpp b/DSView/pv/data/logicsnapshot.cpp
index b2226cd..59d5a0c 100644
--- a/DSView/pv/data/logicsnapshot.cpp
+++ b/DSView/pv/data/logicsnapshot.cpp
@@ -64,11 +64,18 @@ LogicSnapshot::LogicSnapshot() :
LogicSnapshot::~LogicSnapshot()
{
+ if (_disk)
+ ds_zynq_disk_close(_disk);
}
void LogicSnapshot::free_data()
{
Snapshot::free_data();
+ if (_disk) {
+ ds_zynq_disk_close(_disk);
+ _disk = nullptr;
+ _disk_cache.clear();
+ }
for(auto& iter : _ch_data) {
for(auto& iter_rn : iter) {
@@ -117,6 +124,92 @@ void LogicSnapshot::clear()
_free_count = 0;
}
+bool LogicSnapshot::load_zynq_recording(const QString &file, bool dense_channels)
+{
+ struct ds_zynq_record_info info;
+ auto path = file.toUtf8();
+ auto *disk = ds_zynq_disk_open(path.constData(), &info);
+ if (!disk)
+ return false;
+ std::lock_guard<std::mutex> lock(_mutex);
+ free_data();
+ init_all();
+ _disk = disk;
+ _is_loop = false;
+ _ch_index.clear();
+ _samplerate = double(info.clock) / info.divider;
+ _total_sample_count = _sample_count = _ring_sample_count = info.samples;
+ const auto *tiles = ds_zynq_disk_tiles(disk);
+ const uint64_t tile_count = (info.samples + DS_ZYNQ_TILE_SAMPLES - 1) / DS_ZYNQ_TILE_SAMPLES;
+ const uint64_t leaves = (info.samples + LeafBlockSamples - 1) / LeafBlockSamples;
+ for (unsigned channel = 0; channel < 8; channel++) {
+ const unsigned mask = 1u << channel;
+ if (!(info.mask & mask))
+ continue;
+ _disk_channels[_ch_index.size()] = channel;
+ _ch_index.push_back(dense_channels ? _ch_index.size() : channel);
+ _ch_data.emplace_back((leaves + RootScale - 1) / RootScale);
+ auto &roots = _ch_data.back();
+ for (uint64_t leaf = 0; leaf < leaves; leaf++) {
+ const uint64_t first_tile = leaf * (LeafBlockSamples / DS_ZYNQ_TILE_SAMPLES);
+ const uint64_t stop_tile = min(tile_count, first_tile + LeafBlockSamples / DS_ZYNQ_TILE_SAMPLES);
+ const bool first = tiles[first_tile].first & mask;
+ bool last = first;
+ bool changes = false;
+ for (uint64_t tile = first_tile; tile < stop_tile; tile++) {
+ changes |= (tiles[tile].changes & mask) || (bool(tiles[tile].first & mask) != last);
+ last = tiles[tile].last & mask;
+ }
+ auto &root = roots[leaf / RootScale];
+ const uint64_t bit = 1ULL << (leaf % RootScale);
+ if (first) root.first |= bit;
+ if (last) root.last |= bit;
+ if (changes) root.tog |= bit;
+ }
+ }
+ _channel_num = _ch_index.size();
+ return true;
+}
+
+std::shared_ptr<uint8_t> LogicSnapshot::disk_leaf(unsigned order, uint64_t root, unsigned pos)
+{
+ if (!(_ch_data[order][root].tog & (1ULL << pos)))
+ return {};
+ const uint64_t leaf = root * RootScale + pos;
+ const uint64_t key = leaf * CHANNEL_MAX_COUNT + order;
+ for (auto i = _disk_cache.begin(); i != _disk_cache.end(); ++i) {
+ if (i->first == key) {
+ _disk_cache.splice(_disk_cache.begin(), _disk_cache, i);
+ return _disk_cache.front().second;
+ }
+ }
+ std::shared_ptr<uint8_t> buffer(static_cast<uint8_t *>(calloc(1, LeafBlockSpace)), free);
+ if (!buffer || ds_zynq_disk_leaf(_disk, leaf, order, buffer.get(), LeafBlockSamples / 8) != SR_OK) {
+ dsv_err("Could not read an RJ2 recording block.");
+ return {};
+ }
+ // Reuse DSView's edge index. The root summary comes from the recording.
+ auto &node = _ch_data[order][root];
+ const auto first = node.first, last = node.last, tog = node.tog;
+ node.lbp[pos] = buffer.get();
+ _last_calc_count[order] = 0;
+ calc_mipmap(order, root, pos, LeafBlockSamples, false);
+ node.lbp[pos] = nullptr;
+ node.first = first;
+ node.last = last;
+ node.tog = tog;
+ _disk_cache.emplace_front(key, buffer);
+ if (_disk_cache.size() > 24)
+ _disk_cache.pop_back();
+ return buffer;
+}
+
+uint8_t *LogicSnapshot::leaf_buffer(unsigned order, uint64_t root, unsigned pos)
+{
+ return _disk ? disk_leaf(order, root, pos).get() :
+ static_cast<uint8_t *>(_ch_data[order][root].lbp[pos]);
+}
+
void LogicSnapshot::first_payload(const sr_datafeed_logic &logic, uint64_t total_sample_count, GSList *channels, bool able_free)
{
bool channel_changed = false;
@@ -139,7 +232,7 @@ void LogicSnapshot::first_payload(const sr_datafeed_logic &logic, uint64_t total
}
}
- if (total_sample_count != _total_sample_count
+ if (_disk || total_sample_count != _total_sample_count
|| channel_num != _channel_num
|| channel_changed
|| _is_loop) {
@@ -464,7 +557,9 @@ void LogicSnapshot::capture_ended()
{
std::lock_guard<std::mutex> lock(_mutex);
- Snapshot::capture_ended();
+ Snapshot::capture_ended();
+ if (_disk)
+ return;
_sample_count = _ring_sample_count;
_ring_sample_count += _loop_offset;
@@ -495,7 +590,7 @@ void LogicSnapshot::capture_ended()
}
}
-void LogicSnapshot::calc_mipmap(unsigned int order, uint8_t index0, uint8_t index1, uint64_t samples, bool isEnd)
+void LogicSnapshot::calc_mipmap(unsigned int order, uint64_t index0, uint8_t index1, uint64_t samples, bool isEnd)
{
void *lbp = _ch_data[order][index0].lbp[index1];
void *level1_ptr = (uint8_t*)lbp + LeafBlockSamples / 8;
@@ -610,6 +705,8 @@ const uint8_t *LogicSnapshot::get_samples(uint64_t start_sample, uint64_t &end_s
uint64_t logic_sample_index = start_sample + _loop_offset;
int order = get_ch_order(sig_index);
+ if (order == -1)
+ return NULL;
// uint64_t index0 = logic_sample_index >> (LeafBlockPower + RootScalePower);
// uint64_t index1 = (logic_sample_index & RootMask) >> LeafBlockPower;
// uint64_t offset = (start_sample & LeafMask) / 8;
@@ -618,7 +715,15 @@ const uint8_t *LogicSnapshot::get_samples(uint64_t start_sample, uint64_t &end_s
uint64_t index1 = (logic_sample_index / LeafBlockSamples) % RootScale;
uint64_t offset = (start_sample % LeafBlockSamples) / 8;
- uint8_t *block_buffer = (uint8_t*)_ch_data[order][index0].lbp[index1];
+ uint8_t *block_buffer;
+ if (_disk) {
+ // A decoder uses several channel pointers outside the snapshot lock.
+ static thread_local std::shared_ptr<uint8_t> pinned[CHANNEL_MAX_COUNT];
+ pinned[order] = disk_leaf(order, index0, index1);
+ block_buffer = pinned[order].get();
+ } else {
+ block_buffer = leaf_buffer(order, index0, index1);
+ }
int block_num = get_block_num_unlock();
@@ -658,7 +763,7 @@ const uint8_t *LogicSnapshot::get_samples(uint64_t start_sample, uint64_t &end_s
}
else{
if (lbp != NULL){
- *lbp = _ch_data[order][index0].lbp[index1];
+ *lbp = block_buffer;
}
_cur_ref_block_indexs[order].root_index = index0;
@@ -701,8 +806,9 @@ bool LogicSnapshot::get_sample_self(uint64_t index, int sig_index)
return (_ch_data[order][index0].first & root_pos_mask) != 0;
}
else {
- uint64_t *lbp = (uint64_t*)_ch_data[order][index0].lbp[index1];
- return *(lbp + ((index & LeafMask) >> ScalePower)) & index_mask;
+ const auto *lbp = reinterpret_cast<const uint64_t *>(leaf_buffer(order, index0, index1));
+ return lbp ? lbp[(index & LeafMask) >> ScalePower] & index_mask :
+ (_ch_data[order][index0].first & root_pos_mask) != 0;
}
}
@@ -728,6 +834,9 @@ bool LogicSnapshot::get_display_edges(std::vector<std::pair<bool, bool> > &edges
assert(start <= end);
assert(min_length > 0);
+ if (_disk && min_length >= DS_ZYNQ_TILE_SAMPLES)
+ return disk_display_edges(edges, togs, start, end, width, max_togs, pixels_offset, min_length, sig_index);
+
uint64_t index = start;
bool last_sample;
bool start_sample;
@@ -771,6 +880,37 @@ bool LogicSnapshot::get_display_edges(std::vector<std::pair<bool, bool> > &edges
return start_sample;
}
+bool LogicSnapshot::disk_display_edges(std::vector<std::pair<bool, bool>> &edges,
+ std::vector<std::pair<uint16_t, bool>> &togs, uint64_t start, uint64_t end,
+ uint16_t width, uint16_t max_togs, double pixels_offset, double min_length, uint16_t sig_index)
+{
+ const auto *tiles = ds_zynq_disk_tiles(_disk);
+ const int order = get_ch_order(sig_index);
+ if (order < 0)
+ return false;
+ const unsigned mask = 1u << _disk_channels[order];
+ uint64_t tile = start / DS_ZYNQ_TILE_SAMPLES;
+ const bool first = tiles[tile].first & mask;
+ bool level = first;
+ togs.emplace_back(0, first);
+ for (unsigned pixel = 0; pixel < width; pixel++) {
+ const double pixel_end = (pixels_offset + pixel + 1) * min_length;
+ const uint64_t last_tile = uint64_t(max(double(start), min(double(end), pixel_end))) / DS_ZYNQ_TILE_SAMPLES;
+ bool changes = false;
+ while (tile <= last_tile) {
+ changes |= (tiles[tile].changes & mask) || bool(tiles[tile].first & mask) != level;
+ level = tiles[tile].last & mask;
+ tile++;
+ }
+ edges.emplace_back(changes, level);
+ if (changes && togs.size() < max_togs)
+ togs.emplace_back(pixel, level);
+ }
+ if (width && togs.size() < max_togs)
+ togs.emplace_back(width - 1, level);
+ return first;
+}
+
bool LogicSnapshot::get_nxt_edge(uint64_t &index, bool last_sample, uint64_t end,
double min_length, int sig_index)
{
@@ -829,13 +969,13 @@ bool LogicSnapshot::get_nxt_edge_self(uint64_t &index, bool last_sample, uint64_
}
if (!edge_hit) {
- uint64_t *lbp = (uint64_t*)_ch_data[order][i].lbp[inner_tog_pos];
uint64_t blk_start = (i << (LeafBlockPower + RootScalePower)) + (inner_tog_pos << LeafBlockPower);
index = max(blk_start, index);
if (min_level < ScaleLevel) {
uint64_t block_end = min(index | LeafMask, end);
- edge_hit = block_nxt_edge(lbp, index, block_end, last_sample, min_level);
+ auto *lbp = reinterpret_cast<uint64_t *>(leaf_buffer(order, i, inner_tog_pos));
+ edge_hit = lbp && block_nxt_edge(lbp, index, block_end, last_sample, min_level);
}
else {
edge_hit = true;
@@ -922,12 +1062,12 @@ bool LogicSnapshot::get_pre_edge_self(uint64_t &index, bool last_sample,
}
if (!edge_hit) {
- uint64_t *lbp = (uint64_t*)_ch_data[order][i].lbp[inner_tog_pos];
uint64_t blk_end = ((i << (LeafBlockPower + RootScalePower)) +
(inner_tog_pos << LeafBlockPower)) | LeafMask;
index = min(blk_end, index);
if (min_level < ScaleLevel) {
- edge_hit = block_pre_edge(lbp, index, last_sample, min_level, sig_index);
+ auto *lbp = reinterpret_cast<uint64_t *>(leaf_buffer(order, i, inner_tog_pos));
+ edge_hit = lbp && block_pre_edge(lbp, index, last_sample, min_level, sig_index);
} else {
edge_hit = true;
}
@@ -1431,6 +1571,11 @@ uint64_t LogicSnapshot::get_block_size_unlock(int block_index)
assert(block_index < block_num);
auto align_sample_count = _ring_sample_count;
+ if (_disk) {
+ const uint64_t count = min(LeafBlockSamples,
+ align_sample_count - uint64_t(block_index) * LeafBlockSamples);
+ return (count + Scale - 1) / Scale * ScaleSize;
+ }
if (_loop_offset > 0)
{
@@ -1489,7 +1634,14 @@ uint8_t *LogicSnapshot::get_block_buf_unlock(int block_index, int sig_index, boo
uint64_t index = block_index / RootScale;
uint8_t pos = block_index % RootScale;
- uint8_t *lbp = (uint8_t*)_ch_data[order][index].lbp[pos];
+ uint8_t *lbp;
+ if (_disk) {
+ static thread_local std::shared_ptr<uint8_t> pinned;
+ pinned = disk_leaf(order, index, pos);
+ lbp = pinned.get();
+ } else {
+ lbp = leaf_buffer(order, index, pos);
+ }
if (lbp == NULL){
sample = (_ch_data[order][index].first & 1ULL << pos) != 0;
diff --git a/DSView/pv/data/logicsnapshot.h b/DSView/pv/data/logicsnapshot.h
index acd36dd..7893a3e 100644
--- a/DSView/pv/data/logicsnapshot.h
+++ b/DSView/pv/data/logicsnapshot.h
@@ -30,6 +30,8 @@
#include <utility>
#include <vector>
#include <map>
+#include <list>
+#include <memory>
#define CHANNEL_MAX_COUNT 64
@@ -99,6 +101,8 @@ public:
void clear();
void init();
+ bool load_zynq_recording(const QString &file, bool dense_channels = false);
+ bool is_disk_recording() const { return _disk != nullptr; }
void first_payload(const sr_datafeed_logic &logic, uint64_t total_sample_count, GSList *channels, bool able_free);
@@ -176,7 +180,13 @@ private:
int get_ch_order(int sig_index);
- void calc_mipmap(unsigned int order, uint8_t index0, uint8_t index1, uint64_t samples, bool isEnd);
+ void calc_mipmap(unsigned int order, uint64_t index0, uint8_t index1, uint64_t samples, bool isEnd);
+ std::shared_ptr<uint8_t> disk_leaf(unsigned order, uint64_t root, unsigned pos);
+ uint8_t *leaf_buffer(unsigned order, uint64_t root, unsigned pos);
+ bool disk_display_edges(std::vector<std::pair<bool, bool>> &edges,
+ std::vector<std::pair<uint16_t, bool>> &togs, uint64_t start, uint64_t end,
+ uint16_t width, uint16_t max_togs, double pixels_offset, double min_length,
+ uint16_t sig_index);
void append_cross_payload(const sr_datafeed_logic &logic);
@@ -249,6 +259,9 @@ private:
private:
std::vector<std::vector<struct RootNode>> _ch_data;
+ struct ds_zynq_disk *_disk = nullptr;
+ unsigned _disk_channels[8] = {};
+ std::list<std::pair<uint64_t, std::shared_ptr<uint8_t>>> _disk_cache;
uint8_t _byte_fraction;
uint16_t _ch_fraction;
uint8_t *_dest_ptr;
diff --git a/DSView/pv/deviceagent.h b/DSView/pv/deviceagent.h
index ae9132d..12a88a8 100644
--- a/DSView/pv/deviceagent.h
+++ b/DSView/pv/deviceagent.h
@@ -72,7 +72,11 @@ public:
}
inline bool is_hardware(){
- return _dev_type == DEV_TYPE_USB;
+ return _dev_type == DEV_TYPE_USB || is_network();
+ }
+
+ inline bool is_network(){
+ return _dev_type == DEV_TYPE_NETWORK;
}
inline bool is_virtual(){
diff --git a/DSView/pv/dialogs/decoderoptionsdlg.cpp b/DSView/pv/dialogs/decoderoptionsdlg.cpp
index bf0b8e9..4820d02 100644
--- a/DSView/pv/dialogs/decoderoptionsdlg.cpp
+++ b/DSView/pv/dialogs/decoderoptionsdlg.cpp
@@ -297,18 +297,15 @@ DsComboBox* DecoderOptionsDlg::create_probe_selector(
DsComboBox *selector = new DsComboBox(parent);
selector->addItem("-", QVariant::fromValue(-1));
- int dex = 0;
const int binded_index = decoder->binded_probe_index(pdch);
for(auto s : sigs)
{
- dex++;
-
if (s->signal_type() == SR_CHANNEL_LOGIC && s->enabled()){
selector->addItem(s->get_name(),QVariant::fromValue(s->get_index()));
if (binded_index == s->get_index()){
- selector->setCurrentIndex(dex);
+ selector->setCurrentIndex(selector->count() - 1); // Индекс берётся из списка доступных входов, поэтому отключённые каналы не меняют сохранённую привязку при обычном OK.
}
}
}
@@ -500,8 +497,7 @@ void DecoderOptionsDlg::commit_decoder_probes(data::decode::Decoder *dec)
for(auto &p : _probe_selectors)
{
- if(p._decoder != dec)
- break;
+ if (p._decoder != dec) continue; // Пропускаем селекторы других уровней стека, чтобы неизменённый диалог не очищал входы вложенного декодера.
const int selection = p._combo->itemData(p._combo->currentIndex()).value<int>();
diff --git a/DSView/pv/dock/protocoldock.cpp b/DSView/pv/dock/protocoldock.cpp
index 4654789..ad1fd6f 100644
--- a/DSView/pv/dock/protocoldock.cpp
+++ b/DSView/pv/dock/protocoldock.cpp
@@ -27,6 +27,8 @@
#include "../dialogs/protocollist.h"
#include "../dialogs/protocolexp.h"
#include "../view/view.h"
+#include "../view/header.h" // Нативная проверка отправляет события мыши настоящему заголовку дорожек.
+#include <QMouseEvent> // Проверяем перетаскивание через обработчики Qt, а не прямой вызов изменения высоты.
#include <QObject>
#include <QHBoxLayout>
@@ -349,6 +351,99 @@ void ProtocolDock::check_host_decoders()
if (file.open(QIODevice::WriteOnly | QIODevice::Append))
file.write((message + "\n").toUtf8());
};
+ if (qEnvironmentVariableIsSet("DSVIEW_HOST_CHECK_DISPLAY_CACHE")) { // Отдельный нативный сценарий проверяет реальные диалоги поверх полностью рассчитанной записи.
+ auto timer = new QTimer(this); // Ожидаем окончания рабочего потока без блокирования интерфейса и декодирования.
+ int attempts = 0; // Ограничиваем ожидание шестьюдесятью секундами при интервале 50 миллисекунд.
+ connect(timer, &QTimer::timeout, this, [this, timer, attempts, log, report]() mutable { // Состояние ожидания принадлежит только текущему проверочному запуску.
+ if (++attempts > 1200) { log("FAIL display cache timeout"); timer->stop(); timer->deleteLater(); return; } // Неполный анализ не может дать ложный успешный результат.
+ const auto traces = _session->get_decode_signals(); // Проверяем анализатор, восстановленный из обычного файла DSL.
+ if (traces.empty()) return; // Восстановление списка декодеров может завершиться после появления окна.
+ auto trace = traces.front(); // Одного трёхфазного анализатора достаточно для проверки изменения видимости и параметров.
+ auto stack = trace->decoder(); // Готовые аннотации должны остаться в том же стеке после принятия диалога.
+ if (stack->IsRunning() || stack->get_result_count() == 0 || stack->options_changed()) return; // Переключаем поля только после фактического окончания исходного расчёта.
+ timer->stop(); // Вложенный цикл модального диалога не должен повторно запускать этот сценарий.
+ const auto before = stack->get_result_count(); // Очистка стека при ошибочном повторном анализе будет видна по числу результатов.
+ bool ok = true; // Каждая фаза обязана подтвердить сохранение расчётных данных.
+ for (int phase = 0; phase < 3; ++phase) { // Проверяем скрытие строки, неизменённый OK и переключение вида графиков PWM.
+ bool edited = phase == 1; // Во второй фазе намеренно принимаем диалог без каких-либо изменений.
+ QTimer::singleShot(30, this, [phase, &edited]() { // Меняем настоящие элементы штатного Decoder Options после открытия диалога.
+ auto dialog = qobject_cast<dialogs::DecoderOptionsDlg*>(QApplication::activeModalWidget()); // Получаем именно модальное окно, открытое create_popup().
+ if (!dialog) return; // Отсутствующее окно приведёт к тайм-ауту внешнего нативного сценария.
+ if (phase == 0) for (auto button : dialog->findChildren<QPushButton*>()) { // Кнопки строк используют штатное свойство index, в отличие от кнопки видимости всего декодера.
+ if (button->property("index").isValid() && button->property("index").toInt() == 0) { button->click(); edited = true; break; } // Скрываем первую строку тем же сигналом, что пользовательское нажатие.
+ } // Кнопки OK, Cancel и скрытия декодера не затрагиваются.
+ if (phase == 2) for (auto combo : dialog->findChildren<QComboBox*>()) { // Параметр раскладки является представлением готовых данных, а не вычислительной настройкой.
+ const int combined = combo->findText("combined"), split = combo->findText("split"); // Находим список по двум штатным значениям, не по порядковому номеру виджета.
+ if (combined >= 0 && split >= 0) { combo->setCurrentIndex(combo->currentIndex() == combined ? split : combined); edited = true; break; } // Переключаем split/combined через настоящий редактор свойств.
+ } // Другие параметры анализа остаются прежними.
+ QMetaObject::invokeMethod(dialog, "on_accept", Qt::DirectConnection); // Вызываем штатный слот OK с проверкой диапазона; create_popup() затем применяет настройки после Accepted.
+ }); // Таймер срабатывает в штатном вложенном цикле событий модального окна.
+ const bool reanalyze = trace->create_popup(false); // Это тот же признак, по которому SigSession очищает аннотации и ставит повторную задачу.
+ ok = ok && edited && !reanalyze && stack->get_result_count() == before && !stack->options_changed(); // Данные и очередь расчёта должны сохраниться во всех трёх фазах.
+ log(QString("display cache phase %1: %2").arg(phase).arg(ok ? "ok" : "FAIL")); // Журнал различает видимость, неизменённый диалог и раскладку графиков.
+ } // После всех принятий остаются результаты единственного исходного декодирования.
+ auto decoder = stack->stack().front(); // Контрольное изменение реального параметра должно по-прежнему требовать нового анализа.
+ if (QString::fromUtf8(decoder->decoder()->id) == "pwm_waveform") { // Проверка использует математический параметр сглаживания штатного PWM-декодера.
+ decoder->set_option("smoothing_ms", g_variant_new_double(1.0)); // Отличие на одну миллисекунду меняет вычисляемую кривую, поэтому игнорировать его нельзя.
+ ok = decoder->commit() && ok; // Новое вычислительное значение обязано инвалидировать прежние результаты.
+ decoder->set_option("smoothing_ms", g_variant_new_double(0.0)); // Возвращаем стандартный режим без сглаживания для проверочной записи.
+ ok = decoder->commit() && ok; // Возврат математической настройки также требует пересчёта.
+ decoder->set_option("smoothing_ms", g_variant_new_double(0.0)); // Новый объект GVariant с прежним содержимым не является изменением настройки.
+ ok = !decoder->commit() && ok; // Сравнение значений предотвращает ложную инвалидизацию по адресу объекта.
+ } // Изменения параметров выполняются после проверки кэша и не запускают дополнительный расчёт тестовой записи.
+ _view.signals_changed(nullptr); // Готовим окончательную компоновку после изменения видимых строк и вида графиков.
+ _view.grab().save(report + ".display-cache.png"); // Сохраняем артефакт настоящего рендерера для проверки фиксированных промежутков.
+ log(ok ? "PASS" : "FAIL display cache"); // Внешний сценарий требует явного успеха каждой проверки.
+ timer->deleteLater(); // Освобождаем проверочный таймер штатно в потоке интерфейса.
+ }); // Обычный запуск без переменной окружения не создаёт проверочные диалоги и таймеры.
+ timer->start(50); // Интервал 50 миллисекунд сохраняет отзывчивость и быстро замечает завершение расчёта.
+ return; // Проверка кэша не смешивается с независимыми сценариями удаления анализаторов и растягивания дорожек.
+ } // Остальные нативные проверки продолжают работать по прежнему пути.
+ auto header = _view.findChild<view::Header*>(); // Проверяем тот же виджет, который принимает перетаскивание пользователем.
+ bool resize_ok = header && _view.can_resize_logic_rows(); // Режим Demo должен поддерживать высоту наравне с файловым и сетевым источником.
+ std::vector<view::Trace*> traces; // Сохраняем независимые размеры всех дорожек для проверки отсутствия побочных изменений.
+ _view.get_traces(TIME_VIEW, traces); // В список входят цифровые каналы и анализаторы текущей сессии.
+ std::map<view::Trace*, int> original; // Ключом служит объект, поэтому одинаковые индексы канала и анализаторов не смешиваются.
+ for (auto *trace : traces) original[trace] = trace->custom_row_height(); // После проверки возвращаем исходную компоновку пользователя.
+ auto drag = [this, header](view::Trace *trace, int delta) { // Воспроизводим полный жест на нижней границе выбранной дорожки.
+ const int bottom = trace->get_v_offset() + trace->get_totalHeight() / 2 + view::View::SignalMargin; // Берём границу в координатах содержимого, учитывая текущую высоту.
+ _view.verticalScrollBar()->setValue(qMax(0, bottom - header->height() + 20)); // Подводим ручку к видимой части заголовка, в том числе после появления прокрутки.
+ const QPoint start(header->width() / 2, trace->get_y() + trace->get_totalHeight() / 2 + view::View::SignalMargin); // Середина заголовка проверяет захват по всей ширине, а не только у номера канала.
+ const QPoint finish = start + QPoint(0, delta); // Положительное смещение растягивает дорожку, отрицательное сжимает.
+ QMouseEvent press(QEvent::MouseButtonPress, start, header->mapToGlobal(start), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); // Нажимаем левую кнопку на разделителе.
+ QApplication::sendEvent(header, &press); // Запускаем обычный обработчик выбора дорожки для изменения высоты.
+ QMouseEvent move(QEvent::MouseMove, finish, header->mapToGlobal(finish), Qt::NoButton, Qt::LeftButton, Qt::NoModifier); // Держим кнопку нажатой при перемещении курсора.
+ QApplication::sendEvent(header, &move); // Высота и соседние дорожки должны обновиться ещё до отпускания.
+ QMouseEvent release(QEvent::MouseButtonRelease, finish, header->mapToGlobal(finish), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); // Завершаем жест даже за пределами исходной строки.
+ QApplication::sendEvent(header, &release); // Проверяем завершающую ветку обработчика и сброс состояния захвата.
+ }; // Тот же жест используется для каналов и декодеров.
+ for (int type : {SR_CHANNEL_LOGIC, SR_CHANNEL_DECODER}) { // Оба типа обязаны поддерживать независимое изменение высоты.
+ view::Trace *selected = nullptr; // Для каждого типа достаточно одной реальной дорожки с её собственными строками.
+ for (auto *trace : traces) if (trace->get_type() == type && trace->enabled() && trace->resize_rows_size() > 0) { selected = trace; break; } // Пропускаем выключенные входы и текстовые анализаторы с фиксированной геометрией.
+ if (!selected || !resize_ok) continue; // Обычный файл может не содержать анализаторов; каналы при этом проверяются полностью.
+ std::map<view::Trace*, int> before; // Запоминаем полные высоты до жеста, чтобы обнаружить коллизии индексов.
+ for (auto *trace : traces) before[trace] = trace->get_totalHeight(); // Проверяем также все остальные анализаторы и входы.
+ const int rows = selected->resize_rows_size(); // Смешанный анализатор растягивает только графики, сохраняя промежутки текстовых полей.
+ const int fixed = selected->layout_height() - rows * selected->get_row_height(); // Постоянная часть не должна исчезать при сжатии графиков до минимума.
+ drag(selected, 48 * rows); // Увеличиваем каждую строку на сорок восемь пикселей, заметно больше зоны захвата.
+ resize_ok = selected->get_totalHeight() == before[selected] + 48 * rows; // Полная высота должна соответствовать жесту с учётом числа строк.
+ for (auto *trace : traces) if (trace != selected) resize_ok = resize_ok && trace->get_totalHeight() == before[trace]; // Размер остальных дорожек должен остаться прежним.
+ _view.signals_changed(nullptr); // Имитируем перераскладку при изменении окна или обновлении результатов декодирования.
+ resize_ok = resize_ok && selected->get_totalHeight() == before[selected] + 48 * rows; // Ручная высота должна переживать обновление представления.
+ drag(selected, -selected->get_totalHeight() - 100); // Проверяем сжатие далеко за верхнюю границу дорожки.
+ resize_ok = resize_ok && selected->get_totalHeight() == fixed + 12 * rows; // Минимум двенадцать пикселей на графическую строку сохраняет фиксированные текстовые поля.
+ drag(selected, 5000 * rows); // Проверяем большой жест и появление вертикальной прокрутки.
+ resize_ok = resize_ok && selected->get_totalHeight() == fixed + 1000 * rows; // Предел в тысячу пикселей применяется только к графикам и не увеличивает текстовые интервалы.
+ selected->set_row_height(80); // Для снимка оставляем удобную высоту восемьдесят пикселей на строку.
+ _view.signals_changed(nullptr); // Готовим кадр, показывающий независимо растянутый канал и анализатор.
+ log(QString("resize %1: %2").arg(type == SR_CHANNEL_LOGIC ? "channel" : "analyzer").arg(resize_ok ? "ok" : "FAIL")); // Отчёт различает проверенные типы дорожек.
+ } // Один и тот же код жестов обязан работать в Demo, файловом и сетевом режиме.
+ _view.verticalScrollBar()->setValue(0); // На контрольном кадре показываем начало списка дорожек.
+ _view.grab().save(report + ".row-resize.png"); // Сохраняем реальную отрисовку после независимого растягивания.
+ for (auto entry : original) entry.first->set_row_height(entry.second); // Возвращаем размеры до запуска проверки, не меняя пользовательский профиль.
+ _view.signals_changed(nullptr); // Восстанавливаем исходные позиции перед штатными тестами диалогов.
+ if (!resize_ok) { log("FAIL row resizing"); return; } // Ошибка жеста не должна маскироваться успешной проверкой декодеров.
+ if (qEnvironmentVariableIsSet("DSVIEW_HOST_CHECK_RESIZE_ONLY")) { log("PASS"); return; } // Отдельный запуск позволяет проверить обычный DSL без предустановленных анализаторов.
auto timer = new QTimer(this);
int phase = 0;
connect(timer, &QTimer::timeout, this, [this, timer, phase, log, report]() mutable {
diff --git a/DSView/pv/dock/triggerdock.cpp b/DSView/pv/dock/triggerdock.cpp
index 8280cdc..5716485 100644
--- a/DSView/pv/dock/triggerdock.cpp
+++ b/DSView/pv/dock/triggerdock.cpp
@@ -270,11 +270,15 @@ void TriggerDock::device_updated()
_session->get_device()->get_config_bool(SR_CONF_STREAM, stream);
sample_limits = _session->get_device()->get_sample_limit();
- _adv_radioButton->setEnabled(!stream);
- _position_spinBox->setEnabled(!stream);
- _position_slider->setEnabled(!stream);
-
- if (stream)
+ bool zynq_advanced = false;
+ if (_session->get_device()->is_network())
+ _session->get_device()->get_config_bool(SR_CONF_ZYNQ_ADVANCED, zynq_advanced);
+ const bool simple_only = stream || (_session->get_device()->is_network() && !zynq_advanced);
+ _adv_radioButton->setEnabled(!simple_only);
+ _position_spinBox->setEnabled(!simple_only);
+ _position_slider->setEnabled(!simple_only);
+
+ if (simple_only)
maxRange = 1;
else if (hw_depth >= sample_limits)
maxRange = DS_MAX_TRIG_PERCENT;
@@ -284,7 +288,7 @@ void TriggerDock::device_updated()
_position_spinBox->setRange(MinTrigPosition, maxRange);
_position_slider->setRange(MinTrigPosition, maxRange);
- if (_session->get_device()->is_virtual() || stream) {
+ if (_session->get_device()->is_virtual() || simple_only) {
_simple_radioButton->setChecked(true);
simple_trigger();
}
@@ -305,6 +309,8 @@ void TriggerDock::device_updated()
bool TriggerDock::commit_trigger()
{
+ // The standard editors always contain at least 16 positions.
+ const int editor_channels = qMax(_cur_ch_num, TriggerProbes);
// trigger position update
ds_trigger_set_pos(_position_slider->value());
@@ -334,7 +340,7 @@ bool TriggerDock::commit_trigger()
value0_str = _value0_lineEdit_list.at(i)->text();
value1_str = _value1_lineEdit_list.at(i)->text();
}
- ds_trigger_stage_set_value(i, _cur_ch_num, value0_str.toLocal8Bit().data(),
+ ds_trigger_stage_set_value(i, editor_channels, value0_str.toLocal8Bit().data(),
value1_str.toLocal8Bit().data());
}
} else if(_adv_tabWidget->currentIndex() == 1){
@@ -350,9 +356,9 @@ bool TriggerDock::commit_trigger()
edge_str = _serial_edge_lineEdit->text();
comp_str = _value1_lineEdit_list.at(1)->text();
}
- ds_trigger_stage_set_value(0, _cur_ch_num, start_str.toLocal8Bit().data(),
+ ds_trigger_stage_set_value(0, editor_channels, start_str.toLocal8Bit().data(),
stop_str.toLocal8Bit().data());
- ds_trigger_stage_set_value(1, _cur_ch_num, edge_str.toLocal8Bit().data(),
+ ds_trigger_stage_set_value(1, editor_channels, edge_str.toLocal8Bit().data(),
comp_str.toLocal8Bit().data());
//_serial_data_comboBox
diff --git a/DSView/pv/mainwindow.cpp b/DSView/pv/mainwindow.cpp
--- a/DSView/pv/mainwindow.cpp
+++ b/DSView/pv/mainwindow.cpp
@@ -21,6 +21,11 @@
*/
#include <QAction>
+#include <QFrame> // Предупреждение о сетевом обрыве живёт внутри панели анализатора вместо отдельного диалога.
+#include <QHBoxLayout> // Горизонтальная компоновка сохраняет видимыми причину ошибки и кнопку закрытия.
+#include <QLabel> // Текст и значок предупреждения показываются обычными дочерними виджетами.
+#include <QPushButton> // Явная кнопка позволяет убрать сообщение без закрытия приложения.
+#include <QStyle> // Значок предупреждения берётся из текущей темы Qt.
#include <QButtonGroup>
#include <QFileDialog>
#include <QFileInfo>
@@ -468,6 +473,7 @@
void MainWindow::on_session_error()
{
+ if (_session->get_error() == SigSession::No_err) return; // Уже обработанные сигналы из очереди не должны создавать пустые повторные окна.
QString title;
QString details;
QString ch_status = "";
@@ -492,7 +498,13 @@
title = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_PACKET_ERROR), "Packet Error");
details = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_PACKET_ERROR_DET),
"the content of received packet are not expected!");
- _session->refresh(0);
+ if (_device_agent->is_network()) {
+ QString reason;
+ if (_device_agent->get_config_string(SR_CONF_ZYNQ_LAST_ERROR, reason) && !reason.isEmpty())
+ details = reason;
+ }
+ if (_device_agent->is_network()) _session->stop_capture(); // Потеря Ethernet завершает работу и таймеры повтора до показа уведомления, сохраняя принятые данные.
+ else _session->refresh(0); // Для USB сохраняем штатное восстановление после повреждённого пакета.
break;
case SigSession::Data_overflow:
dsv_info("MainWindow::on_session_error(),Data_overflow, stop capture");
@@ -507,6 +519,38 @@
break;
}
+ if (_device_agent->is_network() && _session->get_error() == SigSession::Pkt_data_err) { // Сетевой обрыв завершает захват, оставляя доступными остальные команды приложения.
+ _session->clear_error(); // Запоздавшие сигналы уже обработанной ошибки не создают повторные уведомления.
+ auto *notice = findChild<QFrame *>(QStringLiteral("networkCaptureError")); // Каждая сессия показывает не более одной панели с причиной обрыва.
+ if (notice != nullptr) { // Повторная ошибка обновляет существующее предупреждение без отдельного окна.
+ notice->findChild<QLabel *>(QStringLiteral("networkCaptureErrorText"))->setText(details); // Пользователь видит последнюю причину ошибки драйвера RJ2.
+ return; // Существующая панель уже содержит доступную кнопку закрытия.
+ } // Новую панель создаём только при отсутствии предыдущей.
+ notice = new QFrame(_central_widget); // Обычный дочерний виджет скрывается вместе с DSView и не остаётся над соседними вкладками SETGUI.
+ notice->setObjectName(QStringLiteral("networkCaptureError")); // Устойчивое имя позволяет обновлять диагностику без дубликатов.
+ notice->setAttribute(Qt::WA_DeleteOnClose); // Явное закрытие освобождает виджет и убирает его из компоновки.
+ notice->setFrameShape(QFrame::StyledPanel); // Штатная рамка темы отделяет предупреждение от панели управления и графика.
+ notice->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); // Панель занимает доступную ширину и только высоту, необходимую для текста.
+ auto *row = new QHBoxLayout(notice); // Значок, причина ошибки и кнопка располагаются в одной адаптивной строке.
+ row->setContentsMargins(12, 8, 12, 8); // Отступы в логических пикселях оставляют текст читаемым при масштабировании Windows.
+ auto *icon = new QLabel(notice); // Значок предупреждения не подменяет текстовую диагностику.
+ icon->setPixmap(style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(24, 24)); // Штатный значок Qt размером 24 логических пикселя сохраняет оформление предупреждения.
+ row->addWidget(icon); // Значок располагается слева от объяснения сетевой ошибки.
+ auto *message = new QLabel(details, notice); // Причина обрыва видна непосредственно внутри панели DSView.
+ message->setObjectName(QStringLiteral("networkCaptureErrorText")); // Именованный текст обновляется при следующей ошибке той же сессии.
+ message->setTextFormat(Qt::PlainText); // Диагностика драйвера отображается как текст без интерпретации разметки.
+ message->setWordWrap(true); // Длинный адрес или причина ошибки переносятся без обрезания содержимого.
+ message->setTextInteractionFlags(Qt::TextSelectableByMouse); // Пользователь может скопировать причину ошибки для диагностики связи.
+ row->addWidget(message, 1); // Растягиваемая область текста оставляет кнопку закрытия видимой справа.
+ auto *dismiss = new QPushButton(QStringLiteral("Закрыть"), notice); // Явная кнопка убирает предупреждение без подтверждения или перезапуска приложения.
+ connect(dismiss, &QPushButton::clicked, notice, &QWidget::close); // Нажатие закрывает только предупреждение, сохраняя запись и состояние анализатора.
+ row->addWidget(dismiss); // Кнопка участвует в компоновке и не может остаться скрытой во вложенном диалоге.
+ connect(_session->device_event_object(), SIGNAL(device_updated()), notice, SLOT(deleteLater())); // Переподключение убирает устаревшее предупреждение предыдущей сессии.
+ _vertical_layout->insertWidget(_vertical_layout->indexOf(_view), notice); // Размещаем уведомление перед графиком, сохраняя доступ к панели Start и Stop.
+ notice->show(); // Неблокирующий дочерний виджет не создаёт отдельного окна и вложенного цикла событий.
+ return; // Остальные ошибки продолжают использовать существующие диалоги ниже.
+ } // Особое поведение ограничено ошибкой сетевого захвата.
+
pv::dialogs::DSMessageBox msg(this, title);
msg.mBox()->setText(details);
msg.mBox()->setStandardButtons(QMessageBox::Ok);
@@ -602,7 +646,7 @@
void MainWindow::checkHostDecoders()
{
- on_protocol(true);
+ if (!qEnvironmentVariableIsSet("DSVIEW_HOST_CHECK_DISPLAY_CACHE")) on_protocol(true); // Проверка повторного использования данных открывает свойства напрямую, не заполняя панель списком миллионов результатов.
_protocol_widget->check_host_decoders();
}
@@ -1988,6 +2032,12 @@
{
update_toolbar_view_status();
_protocol_widget->update_view_status();
+ if (_session->show_zynq_recording() || _session->has_zynq_disk_view()) {
+ _view->signals_changed(nullptr);
+ _view->auto_set_max_scale();
+ _view->set_scale_offset(_view->scale(), 0);
+ _view->update_view_port();
+ }
break;
}
case DSV_MSG_CURRENT_DEVICE_CHANGE_PREV:
@@ -2074,6 +2124,8 @@
{
_trigger_widget->device_updated();
_measure_widget->reload();
+ if (_view->can_resize_logic_rows())
+ _view->signals_changed(nullptr);
_view->check_calibration();
break;
}
diff --git a/DSView/pv/sigsession.cpp b/DSView/pv/sigsession.cpp
--- a/DSView/pv/sigsession.cpp
+++ b/DSView/pv/sigsession.cpp
@@ -315,9 +315,7 @@
std::string file_name = pv::path::ToUnicodePath(name);
dsv_info("Load file: \"%s\"", file_name.c_str());
- std::string file_str = name.toUtf8().toStdString();
-
- if (ds_device_from_file(file_str.c_str()) != SR_OK)
+ if (ds_device_from_file(name.toUtf8().constData()) != SR_OK)
{
dsv_err("Load file error!");
return false;
@@ -453,6 +451,29 @@
{
assert(samplelimits != 0);
_capture_data->_cur_samplelimits = samplelimits;
+ }
+
+ bool SigSession::show_zynq_recording()
+ {
+ QString file;
+ if (!_device_agent.is_network() ||
+ !_device_agent.get_config_string(SR_CONF_ZYNQ_LAST_RECORDING, file) || file.isEmpty())
+ return false;
+ _device_agent.set_config_string(SR_CONF_ZYNQ_LAST_RECORDING, "");
+ clear_all_decode_task2();
+ auto *logic = _capture_data->get_logic();
+ if (!logic->load_zynq_recording(file))
+ return false;
+ set_cur_samplelimits(logic->get_sample_count());
+ _view_data = _capture_data;
+ attach_data_to_signal(_view_data);
+ _callback->trigger_message(DSV_MSG_DATA_POOL_CHANGED);
+ return true;
+ }
+
+ bool SigSession::has_zynq_disk_view() const
+ {
+ return _view_data->get_logic()->is_disk_recording();
}
void SigSession::capture_init()
@@ -1223,7 +1244,10 @@
if (_capture_data->get_logic()->last_ended())
{
- _capture_data->get_logic()->set_loop(is_loop_mode());
+ bool live_recording = false;
+ if (_device_agent.is_network())
+ _device_agent.get_config_bool(SR_CONF_ZYNQ_LIVE_RECORDING, live_recording);
+ _capture_data->get_logic()->set_loop(is_loop_mode() || live_recording);
bool bNotFree = _is_decoding && _view_data == _capture_data;
@@ -1433,6 +1457,18 @@
assert(!_is_task_end);
feed_in_logic(*(const sr_datafeed_logic *)packet->payload);
break;
+
+ case SR_DF_ZYNQ_DISK:
+ {
+ auto *logic = _capture_data->get_logic();
+ if (logic->load_zynq_recording(QString::fromUtf8(static_cast<const char *>(packet->payload)), true)) {
+ set_cur_samplelimits(logic->get_sample_count());
+ } else {
+ _error = Pkt_data_err;
+ _callback->session_error();
+ }
+ break;
+ }
case SR_DF_DSO:
assert(packet->payload);
@@ -1812,7 +1848,15 @@
void SigSession::nodata_timeout()
{
- int flag;
+ bool disk_buffer = false;
+ if (_device_agent.is_network() &&
+ _device_agent.get_config_bool(SR_CONF_ZYNQ_DISK_BUFFER, disk_buffer) && disk_buffer) {
+ bool triggered;
+ int progress;
+ if (get_capture_status(triggered, progress) && triggered)
+ return;
+ }
+ int flag = -1;
_device_agent.get_config_byte(SR_CONF_TRIGGER_SOURCE, flag);
if (flag != DSO_TRIGGER_AUTO)
{
@@ -2109,7 +2153,7 @@
dsv_err("The collected data is error!");
// trig next collect
- if (is_repeat_mode() && _is_working && event == DS_EV_COLLECT_TASK_END)
+ if (is_repeat_mode() && _is_working && _error == No_err && event == DS_EV_COLLECT_TASK_END) // Повреждённая запись не запускается повторно, даже когда драйвер завершил цикл с обычным событием END.
{
_callback->trigger_message(DSV_MSG_TRIG_NEXT_COLLECT);
}
@@ -2247,7 +2291,7 @@
{
if (_device_agent.get_work_mode() == LOGIC)
{
- bool bAddDecoder = false;
+ bool bAddDecoder = show_zynq_recording() || has_zynq_disk_view();
bool bSwapBuffer = false;
if (is_single_mode())
diff --git a/DSView/pv/sigsession.h b/DSView/pv/sigsession.h
index 3f000e3..9c34692 100644
--- a/DSView/pv/sigsession.h
+++ b/DSView/pv/sigsession.h
@@ -167,6 +167,8 @@ public:
bool set_default_device();
bool set_device(ds_device_handle dev_handle);
bool set_file(QString name);
+ bool show_zynq_recording();
+ bool has_zynq_disk_view() const;
void close_file(ds_device_handle dev_handle);
bool start_capture(bool instant);
bool stop_capture();
diff --git a/DSView/pv/toolbars/samplingbar.cpp b/DSView/pv/toolbars/samplingbar.cpp
index d82a3b5..80f9ffc 100644
--- a/DSView/pv/toolbars/samplingbar.cpp
+++ b/DSView/pv/toolbars/samplingbar.cpp
@@ -22,6 +22,14 @@
#include "samplingbar.h"
#include <assert.h>
#include <QAction>
+#include <QInputDialog>
+#include <QFileDialog>
+#include <QDateTime>
+#include <QDir>
+#include <QFileInfo>
+#include <QStandardPaths>
+#include <QStorageInfo>
+#include <QSettings>
#include <QLabel>
#include <QAbstractItemView>
#include <math.h>
@@ -101,6 +109,32 @@ namespace pv
_device_type.setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
addWidget(&_device_type);
addWidget(&_device_selector);
+ _action_zynq = addAction(tr("Zynq Ethernet..."));
+ connect(_action_zynq, &QAction::triggered, this, [this]() {
+ if (_session->is_working())
+ return;
+
+ QSettings settings;
+ bool accepted = false;
+ const auto address = QInputDialog::getText(this, tr("Connect Zynq"),
+ tr("Board IPv4 address"), QLineEdit::Normal,
+ settings.value("Zynq/IPv4", "192.168.1.68").toString(), &accepted).trimmed();
+ if (!accepted)
+ return;
+
+ ds_device_handle handle = 0;
+ const auto result = ds_add_zynq_device(address.toLatin1().constData(), &handle);
+ if (result != SR_OK) {
+ MsgBox::Show(result == SR_ERR_ARG ? tr("Enter a valid IPv4 address.") :
+ tr("No Zynq analyzer replied at %1 (UDP 37575 or TCP 37574/37572).").arg(address));
+ return;
+ }
+ settings.setValue("Zynq/IPv4", address);
+ update_device_list();
+ const int index = _device_selector.findData(QVariant::fromValue(handle));
+ if (index >= 0)
+ _device_selector.setCurrentIndex(index);
+ });
_configure_button.setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
addWidget(&_configure_button);
@@ -268,6 +302,10 @@ namespace pv
if (ret == QDialog::Accepted)
{
+ bool disk_buffer = false;
+ if (_device_agent->is_network() &&
+ _device_agent->get_config_bool(SR_CONF_ZYNQ_DISK_BUFFER, disk_buffer) && disk_buffer)
+ _session->set_collect_mode(COLLECT_SINGLE);
if (_session->have_view_data() == false)
this->commit_settings();
@@ -472,6 +510,7 @@ namespace pv
void SamplingBar::update_sample_count_selector()
{
bool stream_mode = false;
+ bool disk_buffer = false;
uint64_t hw_depth = 0;
uint64_t sw_depth;
uint64_t rle_depth = 0;
@@ -496,6 +535,8 @@ namespace pv
_updating_sample_count = true;
_device_agent->get_config_bool(SR_CONF_STREAM, stream_mode);
+ if (_device_agent->is_network())
+ _device_agent->get_config_bool(SR_CONF_ZYNQ_DISK_BUFFER, disk_buffer);
_device_agent->get_config_uint64(SR_CONF_HW_DEPTH, hw_depth);
int mode = _device_agent->get_work_mode();
@@ -533,15 +574,36 @@ namespace pv
_sample_count.currentIndex())
.value<double>();
_sample_count.clear();
+ if (disk_buffer) {
+ _sample_count.addItem(QStringLiteral("До Stop"), QVariant::fromValue(double(0)));
+ _sample_count.setCurrentIndex(0);
+ _sample_count.setEnabled(false);
+ _updating_sample_count = false;
+ connect(&_sample_count, SIGNAL(currentIndexChanged(int)), this, SLOT(on_samplecount_sel(int)));
+ return;
+ }
const uint64_t samplerate = _sample_rate.itemData(
_sample_rate.currentIndex())
.value<uint64_t>();
const double hw_duration = hw_depth / (samplerate * (1.0 / SR_SEC(1)));
+ uint64_t record_samples;
+ const bool timed_rj2 = stream_mode && _session->is_single_mode() &&
+ _device_agent->is_network() &&
+ _device_agent->get_config_uint64(SR_CONF_ZYNQ_RECORD_SAMPLES, record_samples);
if (mode == DSO)
duration = max_timebase;
+ else if (timed_rj2) {
+ if (pre_duration <= 0 && samplerate)
+ pre_duration = double(record_samples) / samplerate * SR_SEC(1);
+ for (int minutes = 60; minutes >= 1; minutes--)
+ _sample_count.addItem(QStringLiteral("%1 мин").arg(minutes),
+ QVariant::fromValue(double(SR_MIN(minutes))));
+ duration = SR_SEC(50);
+ }
else if (stream_mode)
- duration = sw_depth / (samplerate * (1.0 / SR_SEC(1)));
+ duration = (_device_agent->is_network() ? min(hw_depth, sw_depth) : sw_depth)
+ / (samplerate * (1.0 / SR_SEC(1)));
else if (rle_support)
duration = rle_depth / (samplerate * (1.0 / SR_SEC(1)));
else
@@ -615,7 +677,8 @@ namespace pv
}
_updating_sample_count = false;
- update_sample_count_selector_value();
+ if (!timed_rj2)
+ update_sample_count_selector_value();
on_samplecount_sel(_sample_count.currentIndex());
connect(&_sample_count, SIGNAL(currentIndexChanged(int)), this, SLOT(on_samplecount_sel(int)));
@@ -643,7 +706,12 @@ namespace pv
}
else
{
- if (_device_agent->get_config_uint64(SR_CONF_LIMIT_SAMPLES, v))
+ if (_session->is_single_mode() && _device_agent->is_network() &&
+ _device_agent->get_config_uint64(SR_CONF_ZYNQ_RECORD_SAMPLES, v))
+ {
+ duration = (double)v;
+ }
+ else if (_device_agent->get_config_uint64(SR_CONF_LIMIT_SAMPLES, v))
{
duration = (double)v;
}
@@ -801,7 +869,10 @@ namespace pv
SR_CONF_SAMPLERATE,
sample_rate);
- if (_device_agent->get_work_mode() != DSO)
+ bool disk_buffer = false;
+ if (_device_agent->is_network())
+ _device_agent->get_config_bool(SR_CONF_ZYNQ_DISK_BUFFER, disk_buffer);
+ if (_device_agent->get_work_mode() != DSO && !disk_buffer)
{
const uint64_t sample_count = ((uint64_t)ceil(sample_duration / SR_SEC(1) *
sample_rate) +
@@ -853,6 +924,50 @@ namespace pv
commit_settings();
+ uint64_t recording_samples;
+ bool disk_buffer = false;
+ if (_device_agent->is_network())
+ _device_agent->get_config_bool(SR_CONF_ZYNQ_DISK_BUFFER, disk_buffer);
+ const bool record_rj2 = (disk_buffer || _session->is_single_mode()) &&
+ _device_agent->is_network() &&
+ _device_agent->get_config_uint64(SR_CONF_ZYNQ_RECORD_SAMPLES, recording_samples);
+ if (record_rj2) {
+ const auto rate = _device_agent->get_sample_rate();
+ const uint64_t samples = disk_buffer ? 0 : llround(get_hori_res() / SR_SEC(1) * rate);
+ unsigned channels = _session->get_ch_num(SR_CHANNEL_LOGIC);
+ if (!channels || !rate)
+ return false;
+ QSettings settings;
+ QDir directory(settings.value("Zynq/RecordDirectory",
+ QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).toString());
+ const QString suggested = directory.filePath(QStringLiteral("RJ2-%1.dsl")
+ .arg(QDateTime::currentDateTime().toString("yyyyMMdd-HHmmss-zzz")));
+ QString path = QFileDialog::getSaveFileName(this,
+ QStringLiteral("Сохранить захват RJ2"), suggested, "DSView (*.dsl)");
+ if (path.isEmpty())
+ return false;
+ if (!path.endsWith(".dsl", Qt::CaseInsensitive))
+ path += ".dsl";
+ QFileInfo info(path);
+ if (info.exists()) {
+ MsgBox::Show(QStringLiteral("Выберите имя нового файла."));
+ return false;
+ }
+ const QStorageInfo storage(info.absolutePath());
+ const uint64_t bytes = (samples + 7) / 8 * channels;
+ const bool rle = channels > 4 && rate > SR_MHZ(100);
+ if (!disk_buffer && !rle && storage.isReady() && storage.bytesAvailable() >= 0 &&
+ uint64_t(storage.bytesAvailable()) < bytes + bytes / 100 + 1048576) {
+ MsgBox::Show(QStringLiteral("Для выбранной длительности нужно около %1 ГБ свободного места.")
+ .arg(bytes / 1e9, 0, 'f', 1));
+ return false;
+ }
+ if ((!disk_buffer && !_device_agent->set_config_uint64(SR_CONF_ZYNQ_RECORD_SAMPLES, samples)) ||
+ !_device_agent->set_config_string(SR_CONF_ZYNQ_RECORD_FILE, path.toUtf8().constData()))
+ return false;
+ settings.setValue("Zynq/RecordDirectory", info.absolutePath());
+ }
+
if (_device_agent->get_work_mode() == DSO)
{
bool zero;
@@ -884,6 +999,8 @@ namespace pv
_is_run_as_instant = false;
bool ret = _session->start_capture(false);
+ if (!ret && record_rj2)
+ _device_agent->set_config_string(SR_CONF_ZYNQ_RECORD_FILE, "");
return ret;
}
@@ -1017,6 +1134,9 @@ namespace pv
void SamplingBar::reload()
{
QString iconPath = GetIconPath();
+ bool disk_buffer = false;
+ if (_device_agent->is_network())
+ _device_agent->get_config_bool(SR_CONF_ZYNQ_DISK_BUFFER, disk_buffer);
_action_loop->setVisible(false);
@@ -1029,7 +1149,7 @@ namespace pv
else
{
update_mode_icon();
- _mode_action->setVisible(true);
+ _mode_action->setVisible(!disk_buffer);
_action_repeat->setVisible(true);
if (_session->is_loop_mode() && _device_agent->is_stream_mode() == false
@@ -1041,7 +1161,7 @@ namespace pv
_action_loop->setVisible(true);
}
_run_stop_action->setVisible(true);
- _instant_action->setVisible(true);
+ _instant_action->setVisible(!disk_buffer);
}
else if (mode == ANALOG)
{
@@ -1112,6 +1232,10 @@ namespace pv
}
update_mode_icon();
+ uint64_t record_samples;
+ if (_device_agent->is_network() &&
+ _device_agent->get_config_uint64(SR_CONF_ZYNQ_RECORD_SAMPLES, record_samples))
+ update_sample_count_selector();
}
void SamplingBar::update_device_list()
@@ -1172,11 +1296,15 @@ namespace pv
{
int bEnable = _session->is_working() == false;
int mode = _session->get_device()->get_work_mode();
+ bool disk_buffer = false;
+ if (_device_agent->is_network())
+ _device_agent->get_config_bool(SR_CONF_ZYNQ_DISK_BUFFER, disk_buffer);
_device_type.setEnabled(bEnable);
_mode_button.setEnabled(bEnable);
_configure_button.setEnabled(bEnable);
_device_selector.setEnabled(bEnable);
+ _action_zynq->setEnabled(bEnable);
_action_loop->setVisible(false);
if (_session->get_device()->is_file()){
@@ -1237,6 +1365,13 @@ namespace pv
}
update_mode_icon();
+ if (disk_buffer)
+ {
+ _sample_count.setEnabled(false);
+ if (_sample_count.count())
+ _sample_count.setItemText(0, bEnable ? QStringLiteral("До Stop") :
+ QStringLiteral("Запись до Stop"));
+ }
if (_session->get_device()->is_demo() && bEnable)
{
diff --git a/DSView/pv/toolbars/samplingbar.h b/DSView/pv/toolbars/samplingbar.h
index fde2169..724b535 100644
--- a/DSView/pv/toolbars/samplingbar.h
+++ b/DSView/pv/toolbars/samplingbar.h
@@ -162,6 +162,7 @@ namespace pv
QMenu *_mode_menu;
QAction *_action_repeat;
+ QAction *_action_zynq;
QAction *_action_single;
QAction *_action_loop;
diff --git a/DSView/pv/view/decodetrace.cpp b/DSView/pv/view/decodetrace.cpp
index fd0f68c..bd87394 100644
--- a/DSView/pv/view/decodetrace.cpp
+++ b/DSView/pv/view/decodetrace.cpp
@@ -161,7 +161,7 @@ void DecodeTrace::paint_back(QPainter &p, int left, int right, QColor fore, QCol
QPen pen(backFore);
pen.setStyle(Qt::DotLine);
p.setPen(pen);
- const double sigY = get_y() - (_totalHeight - _view->get_signalHeight())*0.5;
+ const double sigY = get_y() - (_totalHeight - get_row_height())*0.5; // Ось анализатора следует за его индивидуальной высотой, а не за размером цифровых входов.
p.drawLine(left, sigY, right, sigY);
// --draw decode region control
@@ -186,31 +186,6 @@ void DecodeTrace::paint_back(QPainter &p, int left, int right, QColor fore, QCol
p.drawPolygon(start_points, countof(start_points));
p.drawPolygon(end_points, countof(end_points));
- // --draw headings
- const int row_height = _view->get_signalHeight();
- for (size_t i = 0; i < _cur_row_headings.size(); i++)
- {
- const int y = i * row_height + get_y() - _totalHeight * 0.5;
-
- p.setPen(QPen(Qt::NoPen));
- p.setBrush(QApplication::palette().brush(QPalette::WindowText));
-
- const QRect r(left + ArrowSize * 2, y,
- right - left, row_height / 2);
- const QString h(_cur_row_headings[i]);
- const int f = Qt::AlignLeft | Qt::AlignVCenter |
- Qt::TextDontClip;
- const QPointF points[] = {
- QPointF(left, r.center().y() - ArrowSize),
- QPointF(left + ArrowSize, r.center().y()),
- QPointF(left, r.center().y() + ArrowSize)
- };
- p.drawPolygon(points, countof(points));
-
- // Draw the text
- p.setPen(fore);
- p.drawText(r, f, h);
- }
}
static bool pwm_combined(pv::data::decode::Decoder *decoder) // Режим берётся из штатных настроек конкретного экземпляра декодера.
@@ -238,6 +213,7 @@ void DecodeTrace::paint_mid(QPainter &p, int left, int right, QColor fore, QColo
double samplerate = _decoder_stack->samplerate();
_cur_row_headings.clear();
+ _cur_row_heading_offsets.clear(); // Координаты и подписи пересоздаются вместе после изменения видимых строк декодера.
// Show sample rate as 1Hz when it is unknown
if (samplerate == 0.0)
@@ -260,7 +236,10 @@ void DecodeTrace::paint_mid(QPainter &p, int left, int right, QColor fore, QColo
if (end_sample < start_sample)
return;
- const int annotation_height = _view->get_signalHeight();
+ const int annotation_height = get_row_height(); // Ручной масштаб определяет только высоту графических областей PWM.
+ const int text_height = (text_row_height() - 6) / 2 + 2; // Цветное поле имеет высоту шрифта и по одному пикселю внутреннего отступа сверху и снизу.
+ const int text_step = text_row_height(); // Шаг включает заголовок, поле и фиксированные промежутки по 2 пикселя.
+ int row_top = 0; // Относительная верхняя граница учитывает разную высоту текстовых полей и графиков.
// Iterate through the rows
assert(_view);
@@ -305,7 +284,9 @@ void DecodeTrace::paint_mid(QPainter &p, int left, int right, QColor fore, QColo
p.drawLines(lines); // Пакетная отрисовка не создаёт отдельный вызов QPainter на каждый период длинной записи.
p.restore(); // Цвет, сглаживание и отсечение не переходят на числовые строки.
if (allocate) { // Число заголовков и вертикальный шаг должны совпадать с rows_size().
- y += annotation_height * 3; // Область PWM занимает три высоты строки независимо от количества совмещённых фаз.
+ _cur_row_heading_offsets.push_back(row_top); // Заголовок графика привязывается к его фактической верхней границе.
+ row_top += annotation_height * 3; // Масштабируемая область сохраняет прежнюю высоту из трёх строк на график.
+ y += annotation_height * 3; // Следующая графическая область начинается сразу после текущей.
QString title = row.title(); // Раздельный график сохраняет прежнее название своего канала.
if (row_combined) { // Общая подпись перечисляет только основные видимые фазы, сохраняя собственные шкалы θ и несущей.
QStringList channels; // Список нужен для записи с одним, двумя или тремя видимыми входами.
@@ -317,13 +298,13 @@ void DecodeTrace::paint_mid(QPainter &p, int left, int right, QColor fore, QColo
title = QString::fromUtf8(dec->decoder()->name) + ": " + channels.join(" / ") + ": -1 .. +1"; // Общая шкала остаётся нормированной для всех видимых каналов.
} // Значения PWM и числовые результаты не изменяются при переключении вида.
_cur_row_headings.push_back(title); // Единственный заголовок относится ко всей выделенной области.
- _cur_row_headings.push_back(QString()); // Вторая строка резервирует место для верхней и нижней полуволн.
- _cur_row_headings.push_back(QString()); // Третья строка завершает прежнюю высоту области PWM.
} // Повторная совмещённая фаза не сдвигает последующие аннотации.
wave_shown = wave_shown || phase_wave; // Отдельный θ или треугольник не заменяет первую видимую фазу общей области.
continue; // Числовой рендерер не должен рисовать служебный контракт PWM1 поверх кривой.
} // Остальные строки проходят штатную отрисовку текстовых аннотаций.
+ _cur_row_heading_offsets.push_back(row_top); // Каждая текстовая строка получает собственную компактную позицию заголовка.
+ y = get_y() - _totalHeight / 2 + row_top + text_height + text_height / 2; // После заголовка высотой text_height−2 оставляем ровно 2 пикселя до цветного поля.
const uint64_t min_annotation =
_decoder_stack->get_min_annotation(row);
const double min_annWidth = min_annotation / samples_per_pixel;
@@ -344,24 +325,28 @@ void DecodeTrace::paint_mid(QPainter &p, int left, int right, QColor fore, QColo
for(Annotation *a : annotations){
draw_annotation(*a, p, get_text_colour(),
- annotation_height, left, right,
+ text_height, left, right, // Фон текстовой аннотации не зависит от высоты графика или цифрового входа.
samples_per_pixel, pixels_offset, y,
0, min_annWidth, fore, back, last_x);
}
}
}
else {
- draw_nodetail(p, annotation_height, left, right, y, 0, fore, back);
+ draw_nodetail(p, text_height, left, right, y, 0, fore, back); // При обзорном масштабе подсказка занимает ту же фиксированную высоту, что обычное поле.
}
- y += annotation_height;
+ row_top += text_step; // Между нижним краем поля и заголовком следующего блока остаётся ровно 2 пикселя.
+ y = get_y() - (_totalHeight - annotation_height) / 2 + row_top; // Восстанавливаем начало возможного следующего графика после компактной текстовой строки.
_cur_row_headings.push_back(row.title());
}
}
}
} else {
- draw_unshown_row(p, y, annotation_height, left, right, L_S(STR_PAGE_DLG, S_ID(IDS_DLG_UNSHOWN), "Unshown"), fore, back);
- y += annotation_height;
+ _cur_row_heading_offsets.push_back(row_top); // Скрытый декодер использует такую же геометрию заголовка, как текстовые строки.
+ y = get_y() - _totalHeight / 2 + row_top + text_height + text_height / 2; // Подсказка скрытого декодера располагается под заголовком с зазором 2 пикселя.
+ draw_unshown_row(p, y, text_height, left, right, L_S(STR_PAGE_DLG, S_ID(IDS_DLG_UNSHOWN), "Unshown"), fore, back); // Скрытая строка не растягивается вместе с графиком PWM.
+ row_top += text_step; // Следующий блок начинается после фиксированного двухпиксельного промежутка.
+ y = get_y() - (_totalHeight - annotation_height) / 2 + row_top; // Следующая графическая область получает собственную исходную координату.
_cur_row_headings.push_back(dec->decoder()->name);
}
}
@@ -369,13 +354,22 @@ void DecodeTrace::paint_mid(QPainter &p, int left, int right, QColor fore, QColo
void DecodeTrace::paint_fore(QPainter &p, int left, int right, QColor fore, QColor back)
{
- using namespace pv::data::decode;
-
- (void)p;
- (void)left;
- (void)right;
- (void)fore;
- (void)back;
+ (void)back; // Подписи используют передний цвет, а фон уже отрисован предыдущими слоями.
+ const int heading_height = (text_row_height() - 6) / 2; // Два внешних зазора по 2 пикселя и внутренние поля аннотации по 1 пикселю исключаются из высоты заголовка.
+ for (size_t i = 0; i < _cur_row_headings.size(); ++i) { // paint_mid() уже подготовил координаты текущего кадра после изменения видимости и раскладки.
+ const int y = _cur_row_heading_offsets[i] + get_y() - _totalHeight * 0.5; // Заголовок располагается над собственной текстовой или графической областью без задержки на один кадр.
+ p.setPen(QPen(Qt::NoPen)); // Стрелка заголовка заполняется без дополнительной рамки.
+ p.setBrush(QApplication::palette().brush(QPalette::WindowText)); // Цвет стрелки соответствует штатной палитре приложения.
+ const QRect r(left + ArrowSize * 2, y, right - left, heading_height); // Двойной размер стрелки сохраняет прежний горизонтальный отступ, высота определяется только шрифтом.
+ const QPointF points[] = { // Три вершины образуют прежний указатель строки слева от подписи.
+ QPointF(left, r.center().y() - ArrowSize), // Верхняя вершина центрируется относительно фиксированной высоты заголовка.
+ QPointF(left + ArrowSize, r.center().y()), // Острие указывает на область данных соответствующей строки.
+ QPointF(left, r.center().y() + ArrowSize) // Нижняя вершина сохраняет симметричную форму указателя.
+ }; // Геометрия не зависит от ручного масштаба графика или высоты входных каналов.
+ p.drawPolygon(points, countof(points)); // Рисуем указатель рядом с текущим заголовком.
+ p.setPen(fore); // Текст получает контрастный цвет штатной темы анализатора.
+ p.drawText(r, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip, _cur_row_headings[i]); // Подпись рисуется после данных с актуальной позицией уже в первом кадре после переключения полей.
+ } // Следующий заголовок использует собственное смещение, включая компактные текстовые строки.
}
void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
@@ -612,7 +606,7 @@ void DecodeTrace::on_new_decode_data()
if (_view && _view->session().is_stopped_status())
_view->data_updated();
- if (_totalHeight/_view->get_signalHeight() != rows_size())
+ if (_totalHeight != layout_height()) // Сравниваем реальную смешанную высоту, чтобы фиксированный текст не вызывал повторную перераскладку на каждом обновлении.
_view->signals_changed(NULL);
}
@@ -659,6 +653,38 @@ int DecodeTrace::rows_size()
return size == 0 ? 1 : size;
}
+int DecodeTrace::text_row_height() const // Компактная геометрия использует тот же размер шрифта, что Viewport::doPaint().
+{ // Метрика вычисляется заново, чтобы изменение шрифта сохраняло читаемость подписей и полей.
+ QFont font = QApplication::font(); // Базовый шрифт совпадает со шрифтом обычного виджета временной области.
+ font.setPointSizeF(std::min(10.0, double(AppConfig::Instance().appOptions.fontSize))); // Ограничение десятью пунктами повторяет штатную настройку отрисовки Viewport.
+ return QFontMetrics(font).height() * 2 + 6; // Заголовок и аннотация занимают по высоте шрифта; 2 пикселя внутри поля и два внешних зазора по 2 пикселя дают ещё 6.
+} // Высота не зависит от Max Height, размера входных каналов и перетаскивания графика.
+
+int DecodeTrace::resize_rows_size() // Только фактически показанные кривые участвуют в ручном масштабировании анализатора.
+{ // Отбор строк повторяет условия rows_size() и paint_mid(), включая совмещение фаз.
+ int size = 0; // Текстовый анализатор без графиков не имеет масштабируемой области.
+ const auto rows = _decoder_stack->get_rows_gshow(); // Используем тот же порядок видимых строк, что нативный рендерер.
+ for (auto dec : _decoder_stack->stack()) { // У каждого экземпляра стека собственная настройка совмещения фаз.
+ if (!dec->shown() || QString::fromUtf8(dec->decoder()->id) != "pwm_waveform") continue; // Скрытые и обычные текстовые декодеры не меняют фиксированную геометрию.
+ bool phase_shown = false; // Совмещённая область A/B/C учитывается отдельно от самостоятельных графиков θ и несущей.
+ for (const auto &entry : rows) { // Неподключённые и скрытые каналы исключаются до выделения высоты.
+ if (entry.first.decoder() != dec->decoder() || !entry.second || !entry.first.row() || !_decoder_stack->has_annotations(entry.first)) continue; // Должны совпасть экземпляр декодера, наличие данных и разрешение показа строки.
+ if (!QString::fromUtf8(entry.first.row()->id).startsWith("wave_")) continue; // Числовые duty и frequency остаются компактными текстовыми полями.
+ const bool phase_wave = QString::fromUtf8(entry.first.row()->id).size() == 6; // Только идентификаторы wave_a…wave_c обозначают основные фазные осциллограммы.
+ if (phase_wave && pwm_combined(dec) && phase_shown) continue; // Повторная основная фаза не увеличивает общую область A/B/C.
+ size += 3; // Каждый самостоятельный график, включая θ и расчётную несущую, занимает три масштабируемые строки.
+ phase_shown = phase_shown || phase_wave; // Дополнительные графики не влияют на наличие основной совмещённой области.
+ } // Следующий экземпляр декодера получает независимую область графика.
+ } // Сумма учитывает только графики, которые действительно выводятся.
+ return size; // Ноль отключает ручку масштабирования у чисто текстового анализатора.
+} // Изменение видимости фаз автоматически меняет число масштабируемых строк.
+
+int DecodeTrace::layout_height() // Общая высота должна совпадать с последовательностью областей в paint_mid().
+{ // Текстовые строки сохраняют размер и промежутки даже при сильном растяжении графика.
+ const int scalable = resize_rows_size(); // Графики уже учитывают режимы split и combined и скрытые фазы.
+ return scalable * get_row_height() + (rows_size() - scalable) * text_row_height(); // Остальные строки, включая подсказку скрытого декодера, занимают фиксированную высоту.
+} // View использует эту величину для положения соседних дорожек и вертикальной прокрутки.
+
void DecodeTrace::paint_type_options(QPainter &p, int right, const QPoint pt, QColor fore)
{
(void)pt;
@@ -780,7 +806,8 @@ bool DecodeTrace::create_popup(bool isnew)
}
}
- return ret;
+ if (!ret && _view) _view->signals_changed(nullptr); // Изменение видимости или вида графиков обновляет компоновку из готовых аннотаций без очистки и повторного декодирования.
+ return ret; // Только вычислительные изменения разрешают SigSession сбросить результаты и запустить анализ заново.
}
} // namespace view
diff --git a/DSView/pv/view/decodetrace.h b/DSView/pv/view/decodetrace.h
index 29ebe57..5ff3596 100644
--- a/DSView/pv/view/decodetrace.h
+++ b/DSView/pv/view/decodetrace.h
@@ -127,6 +127,9 @@ public:
void paint_fore(QPainter &p, int left, int right, QColor fore, QColor back);
int rows_size();
+ int layout_height() override; // Полная высота складывается из фиксированных текстовых полей и масштабируемых областей графиков.
+ int resize_rows_size() override; // Возвращаем только число масштабируемых строк PWM, чтобы жест не менял текстовые интервалы.
+ int text_row_height() const; // Высота заголовка, поля и двух зазоров по два пикселя определяется тем же шрифтом, что отрисовка.
QRectF get_rect(DecodeSetRegions type, int y, int right);
@@ -192,6 +195,7 @@ private:
uint64_t _decode_cursor2;
std::vector<QString> _cur_row_headings;
+ std::vector<int> _cur_row_heading_offsets; // Относительные координаты заголовков учитывают разную высоту текстовых и графических строк.
};
diff --git a/DSView/pv/view/header.cpp b/DSView/pv/view/header.cpp
index 0fab4b6..bf49b6f 100644
--- a/DSView/pv/view/header.cpp
+++ b/DSView/pv/view/header.cpp
@@ -28,6 +28,7 @@
#include <QMouseEvent>
#include <QPainter>
#include <QRect>
+#include <QScrollBar>
#include <QStyleOption>
#include <QApplication>
#include <assert.h>
@@ -112,6 +113,25 @@ pv::view::Trace* Header::get_mTrace(int &action, const QPoint &pt)
return NULL;
}
+static int handle_y(Trace *trace)
+{
+ return trace->get_y() + trace->get_totalHeight() / 2 + View::SignalMargin;
+}
+
+Trace* Header::resize_handle_at(const QPoint &point)
+{
+ if (!_view.can_resize_logic_rows()) // Одинаковая граница доступна всем цифровым источникам, включая обычные файлы и Demo.
+ return nullptr;
+ std::vector<Trace*> traces;
+ _view.get_traces(TIME_VIEW, traces);
+ for (auto *trace : traces) {
+ if ((trace->get_type() == SR_CHANNEL_LOGIC || trace->get_type() == SR_CHANNEL_DECODER || trace->get_type() == 100) && trace->enabled() && trace->resize_rows_size() > 0 && // Ручка изменяет цифровые входы, графики и эмулятор осциллографа с локальным типом 100; текстовые поля сохраняют фиксированный интервал.
+ qAbs(point.y() - handle_y(trace)) <= 5) // Полоса шириной десять пикселей облегчает захват нижней границы по всей ширине заголовка.
+ return trace;
+ }
+ return nullptr;
+}
+
void Header::paintEvent(QPaintEvent*)
{
using pv::view::Trace;
@@ -141,6 +161,19 @@ void Header::paintEvent(QPaintEvent*)
t->paint_label(painter, w, dragging ? QPoint(-1, -1) : _mouse_point, fore);
}
+ if (_view.can_resize_logic_rows()) {
+ auto grip = fore;
+ grip.setAlpha(125);
+ painter.setPen(grip);
+ for (auto *trace : traces) {
+ if ((trace->get_type() == SR_CHANNEL_LOGIC || trace->get_type() == SR_CHANNEL_DECODER || trace->get_type() == 100) && trace->enabled()) { // Показываем одинаковый разделитель для цифровых каналов, декодеров и эмулятора осциллографа с локальным типом 100.
+ const int y = handle_y(trace);
+ painter.drawLine(trace->get_leftWidth(), y, w - 5, y); // Нижняя граница проходит под названием и номером, чтобы её можно было захватить в любом месте.
+ painter.drawLine(trace->get_leftWidth(), y + 2, w - 5, y + 2); // Вторая линия с отступом два пикселя отличает ручку высоты от оси сигнала.
+ }
+ }
+ }
+
painter.end();
}
@@ -175,6 +208,17 @@ void Header::mousePressEvent(QMouseEvent *event)
{
assert(event);
+ if (event->button() == Qt::LeftButton) {
+ if (auto *trace = resize_handle_at(event->pos())) {
+ _resize_trace = trace; // Адрес различает анализаторы и каналы даже при одинаковом номере входа.
+ _resize_start_y = event->globalY();
+ _resize_start_height = trace->get_totalHeight();
+ setCursor(Qt::SizeVerCursor);
+ event->accept();
+ return;
+ }
+ }
+
_mouse_is_down = true;
std::vector<Trace*> traces;
@@ -210,7 +254,7 @@ void Header::mousePressEvent(QMouseEvent *event)
if (~QApplication::keyboardModifiers() & Qt::ControlModifier)
_drag_traces.clear();
- _drag_traces.push_back(make_pair(mTrace, mTrace->get_zero_vpos()));
+ _drag_traces.push_back(make_pair(mTrace, mTrace->get_zero_vpos() + _view.vertical_offset()));
mTrace->set_old_v_offset(mTrace->get_v_offset());
}
@@ -239,6 +283,14 @@ void Header::mouseReleaseEvent(QMouseEvent *event)
{
assert(event);
+ if (_resize_trace && event->button() == Qt::LeftButton) { // Завершаем только начатое изменение высоты, не запуская перенос дорожки.
+ _view.resize_trace_row(_resize_trace, _resize_start_height + event->globalY() - _resize_start_y); // Разность экранных координат даёт изменение полной высоты в пикселях.
+ _resize_trace = nullptr; // Освобождаем выбранную дорожку после отпускания мыши.
+ unsetCursor();
+ event->accept();
+ return;
+ }
+
_mouse_is_down = false;
// judge for color / name / trigger / move
@@ -302,6 +354,13 @@ void Header::mouseReleaseEvent(QMouseEvent *event)
void Header::wheelEvent(QWheelEvent *event)
{
assert(event);
+ auto *scroll = _view.verticalScrollBar();
+ const int vertical_delta = event->angleDelta().y();
+ if (scroll->maximum() > 0 && vertical_delta) {
+ scroll->setValue(scroll->value() - vertical_delta / 3);
+ event->accept();
+ return;
+ }
int x = 0;
int y = 0;
@@ -414,6 +473,19 @@ void Header::mouseMoveEvent(QMouseEvent *event)
{
assert(event);
+ if (_resize_trace) { // Удержание нижней границы меняет высоту выбранной дорожки независимо от типа источника.
+ if (event->buttons() & Qt::LeftButton) {
+ _view.resize_trace_row(_resize_trace, _resize_start_height + event->globalY() - _resize_start_y); // Перестраиваем соседние дорожки при каждом перемещении, не допуская их наложения.
+ event->accept();
+ return;
+ }
+ _resize_trace = nullptr; // Сбрасываем захват, если отпускание кнопки произошло за пределами окна.
+ }
+ if (_drag_traces.empty() && resize_handle_at(event->pos()))
+ setCursor(Qt::SizeVerCursor);
+ else
+ unsetCursor();
+
if (_view.session().is_working() && _view.session().get_device()->get_work_mode() == LOGIC){
//Disable the hover status of trig button on left pannel.
return;
@@ -470,6 +542,8 @@ void Header::mouseMoveEvent(QMouseEvent *event)
void Header::leaveEvent(QEvent*)
{
+ if (!_resize_trace) // Сохраняем курсор изменения высоты при выходе за заголовок с удерживаемой мышью.
+ unsetCursor();
_mouse_point = QPoint(-1, -1);
update();
}
diff --git a/DSView/pv/view/header.h b/DSView/pv/view/header.h
index 51f6164..661928a 100644
--- a/DSView/pv/view/header.h
+++ b/DSView/pv/view/header.h
@@ -49,6 +49,7 @@ public:
private:
pv::view::Trace* get_mTrace(int &action, const QPoint &pt);
+ Trace* resize_handle_at(const QPoint &point);
private:
void retranslateUi();
@@ -101,6 +102,9 @@ private:
std::list<std::pair<Trace*, int> > _drag_traces;
Trace *_context_trace;
bool _mouse_is_down;
+ Trace *_resize_trace = nullptr; // Запоминаем саму дорожку: номера каналов и разных анализаторов могут совпадать.
+ int _resize_start_y = 0;
+ int _resize_start_height = 0;
};
} // namespace view
diff --git a/DSView/pv/view/trace.cpp b/DSView/pv/view/trace.cpp
index 5540899..6b60e8c 100644
--- a/DSView/pv/view/trace.cpp
+++ b/DSView/pv/view/trace.cpp
@@ -85,6 +85,7 @@ Trace::Trace(const Trace &t) :
_sec_index(t._sec_index),
_old_v_offset(t._old_v_offset),
_totalHeight(t._totalHeight),
+ _custom_row_height(t._custom_row_height), // При явном копировании дорожки сохраняем выбранную пользователем высоту строки.
_typeWidth(t._typeWidth),
_text_size(t._text_size)
{
@@ -127,9 +128,19 @@ void Trace::set_index_list(const std::list<int> &index_list)
int Trace::get_zero_vpos()
{
- return _v_offset;
+ return get_y();
}
+int Trace::get_y() const
+{
+ return _v_offset - (_view ? _view->vertical_offset() : 0);
+}
+
+int Trace::get_row_height() const // Единая геометрия используется и раскладкой, и отрисовкой многострочных анализаторов.
+{ // Выбор размера не зависит от сетевого, файлового или демонстрационного источника.
+ return _custom_row_height > 0 ? _custom_row_height : (_view ? _view->get_signalHeight() : 20); // До привязки к View используем двадцать пикселей, соответствующих закрытой константе View::MaxHeightUnit.
+} // Ручное изменение влияет только на эту дорожку.
+
void Trace::resize()
{
}
@@ -138,7 +149,7 @@ void Trace::set_view(pv::view::View *view)
{
assert(view);
_view = view;
- connect(_view, SIGNAL(resize()), this, SLOT(resize()));
+ connect(_view, SIGNAL(resize()), this, SLOT(resize()), Qt::UniqueConnection);
}
void Trace::set_viewport(pv::view::Viewport *viewport)
diff --git a/DSView/pv/view/trace.h b/DSView/pv/view/trace.h
index d05c8fb..6ceafa9 100644
--- a/DSView/pv/view/trace.h
+++ b/DSView/pv/view/trace.h
@@ -142,6 +142,11 @@ public:
inline int get_totalHeight(){
return _totalHeight;
}
+ int get_row_height() const; // Возвращает высоту одной строки этой дорожки, включая индивидуальный масштаб анализатора.
+ virtual int layout_height() { return get_row_height() * rows_size(); } // Обычная дорожка занимает произведение масштаба строки на число строк; анализатор уточняет смешанную геометрию.
+ virtual int resize_rows_size() { return rows_size(); } // У цифрового сигнала все строки масштабируются, а текст анализатора исключается переопределением.
+ inline int custom_row_height() const { return _custom_row_height; } // Ноль означает штатную высоту из Options, положительное значение задаётся перетаскиванием.
+ inline void set_row_height(int height) { _custom_row_height = height; } // Храним размер в объекте дорожки, чтобы удалённый анализатор не передал его новому с тем же индексом.
/**
* Sets the height of this signal.
@@ -239,9 +244,7 @@ public:
/**
* Gets the y-offset of the axis.
*/
- inline int get_y(){
- return _v_offset;
- }
+ int get_y() const;
/**
* Determines if a point is in the header rect.
@@ -341,6 +344,7 @@ protected:
int _sec_index;
int _old_v_offset;
int _totalHeight;
+ int _custom_row_height = 0; // Индивидуальная высота строки в пикселях; новая дорожка начинает со штатного масштаба.
int _typeWidth;
int _view_index;
diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp
index c35f8f8..44c35b0 100644
--- a/DSView/pv/view/view.cpp
+++ b/DSView/pv/view/view.cpp
@@ -578,6 +578,9 @@ void View::set_search_pos(uint64_t search_pos, bool hit)
void View::normalize_layout()
{
+ if (can_resize_logic_rows())
+ return;
+
int v_min = INT_MAX;
std::vector<Trace*> traces;
get_traces(ALL_VIEW, traces);
@@ -635,8 +638,10 @@ void View::update_scroll()
_updating_scroll = false;
// Set the vertical scrollbar
- verticalScrollBar()->setPageStep(areaSize.height());
- verticalScrollBar()->setRange(0,0);
+ const int visible_height = _time_viewport->height();
+ verticalScrollBar()->setPageStep(visible_height);
+ verticalScrollBar()->setRange(0, can_resize_logic_rows() ?
+ max(0, _logic_content_height - visible_height) : 0);
}
void View::update_scale_offset()
@@ -673,12 +678,39 @@ void View::mode_changed()
_scale = max(min(_scale, _maxscale), _minscale);
}
+bool View::can_resize_logic_rows() const
+{
+ return _device_agent->get_work_mode() == LOGIC; // Все цифровые источники используют одинаковые размеры дорожек, включая USB, Demo и любые записи DSL.
+}
+
+int View::vertical_offset() const
+{
+ return verticalScrollBar()->value();
+}
+
+void View::resize_trace_row(Trace *trace, int height) // Получаем полную желаемую высоту дорожки после движения мыши.
+{ // Храним индивидуальный размер в самой дорожке, без коллизий индексов анализаторов.
+ if (!can_resize_logic_rows() || !trace) return; // Осциллограф сохраняет собственное управление масштабом, а пустой захват не обрабатывается.
+ std::vector<Trace*> traces; // Текущий список позволяет проверить, что анализатор не был удалён во время перетаскивания.
+ get_traces(TIME_VIEW, traces); // Меняем только дорожки временной области, не затрагивая FFT.
+ if (std::find(traces.begin(), traces.end(), trace) == traces.end()) return; // Не обращаемся к объекту, которого больше нет в текущей сессии.
+ const int rows = trace->resize_rows_size(); // Текстовые поля исключаются из числа растягиваемых строк анализатора.
+ if (rows == 0) return; // Полностью текстовый анализатор всегда сохраняет фиксированные промежутки.
+ const int fixed = trace->layout_height() - rows * trace->get_row_height(); // Постоянная часть включает заголовки и текстовые поля между графиками.
+ const int requested = max(12, min(50 * MaxHeightUnit, (height - fixed + rows / 2) / rows)); // Высота 12–1000 пикселей относится только к графикам; неподвижная текстовая часть вычитается до деления.
+ if (trace->get_row_height() == requested) return; // Повторное событие мыши не должно запускать лишнюю перераскладку.
+ trace->set_row_height(requested); // Изменение анализатора не меняет высоту его входных каналов или других декодеров.
+ signals_changed(nullptr); // Пересчитываем положения дорожек и диапазон вертикальной прокрутки.
+} // Рендерер анализатора читает тот же размер через get_row_height().
+
void View::signals_changed(const Trace* eventTrace)
{
double actualMargin = SignalMargin;
int total_rows = 0;
int label_size = 0;
uint8_t max_height = MaxHeightUnit;
+ const bool resizable_logic = can_resize_logic_rows();
+ _logic_content_height = 0;
std::vector<Trace*> time_traces;
std::vector<Trace*> fft_traces;
std::vector<Trace*> traces;
@@ -762,7 +794,15 @@ void View::signals_changed(const Trace* eventTrace)
max_height = (v + 1) * MaxHeightUnit;
}
- if (height < 2*actualMargin) {
+ if (resizable_logic) {
+ // Use the selected multiplier even when the tracks need scrolling.
+ if (_logic_max_height != max_height) {
+ for (auto *trace : time_traces) trace->set_row_height(0); // Явное изменение Options → Max Height сбрасывает индивидуальные размеры всех дорожек.
+ _logic_max_height = max_height;
+ }
+ _signalHeight = max_height;
+ }
+ else if (height < 2*actualMargin) {
actualMargin /= 2;
_signalHeight = max(1.0, (_time_viewport->height()
- 2 * actualMargin * label_size) * 1.0 / total_rows);
@@ -815,7 +855,7 @@ void View::signals_changed(const Trace* eventTrace)
if (t->rows_size() == 0)
continue;
- const double traceHeight = _signalHeight*t->rows_size();
+ const int traceHeight = resizable_logic ? t->layout_height() : _signalHeight * t->rows_size(); // Анализатор суммирует фиксированные текстовые поля и масштабируемые графики, остальные режимы сохраняют штатную высоту.
t->set_totalHeight((int)traceHeight);
t->set_v_offset(next_v_offset + 0.5 * traceHeight + actualMargin);
next_v_offset += traceHeight + 2 * actualMargin;
@@ -831,6 +871,7 @@ void View::signals_changed(const Trace* eventTrace)
sig->set_scale(sig->get_totalHeight());
}
}
+ _logic_content_height = next_v_offset;
_time_viewport->clear_measure();
_session->update_dso_data_scale();
}
@@ -969,6 +1010,7 @@ void View::v_scroll_value_changed(int value)
{
(void)value;
_header->update();
+ set_update(_time_viewport, true);
viewport_update();
}
@@ -1367,6 +1409,12 @@ void View::splitterMoved(int pos, int index)
void View::reload()
{
+ std::vector<Trace*> traces; // При смене устройства сбрасываем размеры ещё существующих дорожек перед обновлением сессии.
+ get_traces(TIME_VIEW, traces); // Временная область содержит цифровые каналы и анализаторы текущего источника.
+ for (auto *trace : traces) trace->set_row_height(0); // Новая конфигурация источника начинает со штатных высот.
+ _logic_max_height = 0;
+ _logic_content_height = 0;
+ verticalScrollBar()->setValue(0);
clear();
/*
diff --git a/DSView/pv/view/view.h b/DSView/pv/view/view.h
index aa738ec..731d5fb 100644
--- a/DSView/pv/view/view.h
+++ b/DSView/pv/view/view.h
@@ -25,6 +25,7 @@
#define DSVIEW_PV_VIEW_VIEW_H
#include <stdint.h>
+#include <map>
#include <set>
#include <vector>
@@ -192,6 +193,9 @@ public:
inline int get_signalHeight(){
return _signalHeight;
}
+ bool can_resize_logic_rows() const;
+ void resize_trace_row(Trace *trace, int height); // Полная высота в пикселях относится к конкретному каналу или анализатору, а не к номеру входа.
+ int vertical_offset() const;
int headerWidth();
@@ -459,6 +463,8 @@ private:
int64_t _preOffset;
int _spanY;
int _signalHeight;
+ int _logic_max_height = 0;
+ int _logic_content_height = 0;
bool _updating_scroll;
// trigger position fix
diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp
index dea3b91..a9e6550 100644
--- a/DSView/pv/view/viewport.cpp
+++ b/DSView/pv/view/viewport.cpp
@@ -600,7 +600,18 @@ void Viewport::paintProgress(QPainter &p, QColor fore, QColor back)
QRect status_rect = QRect(cenPos.x() - radius, cenPos.y() + radius * 0.4, radius * 2, radius * 0.5);
- if (triggered) {
+ auto *device = _view.session().get_device();
+ bool disk_buffer = false;
+ if (device->is_network() &&
+ device->get_config_bool(SR_CONF_ZYNQ_DISK_BUFFER, disk_buffer) && disk_buffer) {
+ uint64_t tenths = 0;
+ device->get_config_uint64(SR_CONF_ZYNQ_RECORD_TENTHS, tenths);
+ const QString status = triggered
+ ? QStringLiteral("Запись в файл · %1 с · Stop для завершения").arg(tenths / 10.0, 0, 'f', 1)
+ : QStringLiteral("Ожидание триггера");
+ p.drawText(status_rect, Qt::AlignCenter | Qt::AlignVCenter, status);
+ }
+ else if (triggered) {
p.drawText(status_rect,
Qt::AlignCenter | Qt::AlignVCenter,
L_S(STR_PAGE_DLG, S_ID(IDS_DLG_TRIGGERED), "Triggered! ") + QString::number(captured_progress)
diff --git a/libsigrok4DSL/lib_main.c b/libsigrok4DSL/lib_main.c
index 054cddd..3efd5fe 100644
--- a/libsigrok4DSL/lib_main.c
+++ b/libsigrok4DSL/lib_main.c
@@ -21,6 +21,7 @@
#include "libsigrok-internal.h"
#include "log.h"
+#include <gio/gio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
@@ -336,6 +337,54 @@ SR_API int ds_get_device_list(struct ds_device_base_info **out_list, int *out_co
return SR_OK;
}
+SR_API int ds_add_zynq_device(const char *address, ds_device_handle *preferred)
+{
+ if (!address || !preferred || ds_is_collecting())
+ return SR_ERR_ARG;
+
+ GInetAddress *ip = g_inet_address_new_from_string(address);
+ if (!ip)
+ return SR_ERR_ARG;
+ const gboolean ipv4 = g_inet_address_get_family(ip) == G_SOCKET_FAMILY_IPV4;
+ g_object_unref(ip);
+ if (!ipv4)
+ return SR_ERR_ARG;
+
+ *preferred = 0;
+ for (enum zynq_transport mode = ZYNQ_PL_STREAM; mode <= ZYNQ_TCP_BUFFER; mode++) {
+ struct sr_dev_inst *candidate = sr_zynq_probe(address, mode);
+ if (!candidate) {
+ if (mode == ZYNQ_PL_BUFFER && *preferred)
+ break;
+ continue;
+ }
+
+ pthread_mutex_lock(&lib_ctx.mutext);
+ struct sr_dev_inst *existing = NULL;
+ for (const GSList *entry = lib_ctx.device_list; entry; entry = entry->next) {
+ struct sr_dev_inst *device = entry->data;
+ if (device->dev_type == DEV_TYPE_NETWORK &&
+ strcmp(device->name, candidate->name) == 0) {
+ existing = device;
+ break;
+ }
+ }
+
+ if (existing) {
+ sr_dev_inst_free(candidate);
+ candidate = existing;
+ } else {
+ lib_ctx.device_list = g_slist_append(lib_ctx.device_list, candidate);
+ }
+ if (!*preferred)
+ *preferred = candidate->handle;
+ pthread_mutex_unlock(&lib_ctx.mutext);
+ if (mode == ZYNQ_PL_STREAM || mode == ZYNQ_PL_BUFFER)
+ break;
+ }
+ return *preferred ? SR_OK : SR_ERR;
+}
+
/**
* Active a device.
*/
diff --git a/libsigrok4DSL/libsigrok-internal.h b/libsigrok4DSL/libsigrok-internal.h
index a5534c0..4676313 100644
--- a/libsigrok4DSL/libsigrok-internal.h
+++ b/libsigrok4DSL/libsigrok-internal.h
@@ -249,6 +249,9 @@ struct ds_trigger {
};
+enum zynq_transport { ZYNQ_PL_STREAM, ZYNQ_PL_BUFFER, ZYNQ_TCP_STREAM, ZYNQ_TCP_BUFFER };
+SR_PRIV struct sr_dev_inst *sr_zynq_probe(const char *address, enum zynq_transport transport);
+
/*--- device.c --------------------------------------------------------------*/
SR_PRIV struct sr_channel *sr_channel_new(uint16_t index, int type, gboolean enabled, const char *name);
diff --git a/libsigrok4DSL/libsigrok.h b/libsigrok4DSL/libsigrok.h
index 28d8a0c..a55d387 100644
--- a/libsigrok4DSL/libsigrok.h
+++ b/libsigrok4DSL/libsigrok.h
@@ -174,6 +174,7 @@ enum sr_device_type{
DEV_TYPE_FILELOG = 2,
DEV_TYPE_USB = 3,
DEV_TYPE_SERIAL = 4,
+ DEV_TYPE_NETWORK = 5,
};
/** Data types used by sr_config_info(). */
@@ -202,6 +203,7 @@ enum sr_datafeed_packet_type {
SR_DF_FRAME_BEGIN,
SR_DF_FRAME_END,
SR_DF_OVERFLOW,
+ SR_DF_ZYNQ_DISK,
};
/** Values for sr_datafeed_analog.mq. */
@@ -323,6 +325,7 @@ enum DSLOGIC_OPERATION_MODE
LO_OP_EXTEST = 3,
/** SDRAM loopback test mode */
LO_OP_LPTEST = 4,
+ LO_OP_ZYNQ_SNAPSHOT = 100,
};
enum DSO_MEASURE_TYPE {
@@ -933,6 +936,14 @@ enum sr_config_option_id
/** Stream */
SR_CONF_STREAM = 30052,
+ SR_CONF_ZYNQ_RECORD_FILE = 30990,
+ SR_CONF_ZYNQ_RECORD_SAMPLES = 30991,
+ SR_CONF_ZYNQ_LAST_ERROR = 30992,
+ SR_CONF_ZYNQ_ADVANCED = 30993,
+ SR_CONF_ZYNQ_DISK_BUFFER = 30994,
+ SR_CONF_ZYNQ_LAST_RECORDING = 30995,
+ SR_CONF_ZYNQ_LIVE_RECORDING = 30996,
+ SR_CONF_ZYNQ_RECORD_TENTHS = 30997,
/** DSO Roll */
SR_CONF_ROLL = 30053,
@@ -1409,6 +1420,7 @@ SR_API void ds_set_user_data_dir(const char *dir);
* User need call g_free() to release the buffer. If the list is empty, the out_list is null.
*/
SR_API int ds_get_device_list(struct ds_device_base_info** out_list, int *out_count);
+SR_API int ds_add_zynq_device(const char *address, ds_device_handle *preferred);
/**
* Active a device.
@@ -1436,6 +1448,24 @@ SR_API int ds_have_actived_device();
*/
SR_API int ds_device_from_file(const char *file_path);
+struct ds_zynq_record_info {
+ uint64_t samples;
+ unsigned clock, divider, mask, leaf_samples;
+ gboolean indexed;
+};
+SR_API int ds_zynq_record_info(const char *path, struct ds_zynq_record_info *info);
+SR_API int ds_zynq_record_window(const char *source, const char *target, unsigned first_leaf);
+struct ds_zynq_disk;
+struct ds_zynq_tile {
+ uint8_t first, last, changes;
+};
+#define DS_ZYNQ_TILE_SAMPLES 262144u
+SR_API struct ds_zynq_disk *ds_zynq_disk_open(const char *path, struct ds_zynq_record_info *info);
+SR_API void ds_zynq_disk_close(struct ds_zynq_disk *disk);
+SR_API const struct ds_zynq_tile *ds_zynq_disk_tiles(const struct ds_zynq_disk *disk);
+SR_API int ds_zynq_disk_leaf(struct ds_zynq_disk *disk, unsigned leaf, unsigned channel,
+ void *data, unsigned capacity);
+
/**
* Remove one device from the list, and destory it.
* User need to call ds_get_device_list() to get the new list.
diff --git a/libsigrok4DSL/session_driver.c b/libsigrok4DSL/session_driver.c
index 3b298d4..90d0ff1 100644
--- a/libsigrok4DSL/session_driver.c
+++ b/libsigrok4DSL/session_driver.c
@@ -1356,6 +1356,22 @@ static int dev_status_get(const struct sr_dev_inst *sdi, struct sr_status *statu
}
}
+static int receive_disk_recording(int fd, int revents, const struct sr_dev_inst *sdi)
+{
+ (void)fd;
+ if (revents != -1) {
+ const struct sr_datafeed_packet disk = {
+ .type = SR_DF_ZYNQ_DISK, .status = SR_PKT_OK, .payload = sdi->path
+ };
+ ds_data_forward(sdi, &disk);
+ }
+ const struct sr_datafeed_packet end = {.type = SR_DF_END, .status = SR_PKT_OK};
+ ds_data_forward(sdi, &end);
+ sr_session_source_remove(-1);
+ close_archive(sdi->priv);
+ return FALSE;
+}
+
static int dev_acquisition_start(struct sr_dev_inst *sdi, void *cb_data)
{
(void)cb_data;
@@ -1399,6 +1415,14 @@ static int dev_acquisition_start(struct sr_dev_inst *sdi, void *cb_data)
return SR_ERR;
}
+ if (sdi->mode == LOGIC && unzLocateFile(vdev->archive, "zynq_recording.json", 0) == UNZ_OK) {
+ std_session_send_df_header(sdi, LOG_PREFIX);
+ int result = sr_session_source_add(-1, 0, 0, receive_disk_recording, sdi);
+ if (result != SR_OK)
+ close_archive(vdev);
+ return result;
+ }
+
if (vdev->version == 1)
{
if (unzLocateFile(vdev->archive, "data", 0) != UNZ_OK)
diff --git a/DSView/pv/dock/measuredock.cpp b/DSView/pv/dock/measuredock.cpp
--- a/DSView/pv/dock/measuredock.cpp
+++ b/DSView/pv/dock/measuredock.cpp
@@ -771,10 +771,11 @@
{
QString border_width = isCursor ? "0px" : "1px";
QString hoverColor = isCursor ? cursorColor.darker().name() : bkColor.name();
+ QString horizontal_padding = "; padding-left:4px; padding-right:4px"; // По четыре логических пикселя с каждой стороны соответствуют запасу +8 в расчёте ширины номерных кнопок и перекрывают слишком широкие отступы темы SETGUI.
QString normal = "{background-color:" + cursorColor.name() +
- "; color:black" + "; border-width:" + border_width + ";}";
+ "; color:black" + "; border-width:" + border_width + horizontal_padding + ";}"; // Номер получает всю рассчитанную область текста без обрезания отступами общей темы.
QString hover = "{background-color:" + hoverColor +
- "; color:black" + "; border-width:" + border_width + ";}";
+ "; color:black" + "; border-width:" + border_width + horizontal_padding + ";}"; // Номер получает всю рассчитанную область текста без обрезания отступами общей темы.
QString style = "QPushButton:hover" + hover +
"QPushButton" + normal;
btn->setStyleSheet(style);