From f5f7492f8acaf999b30b2f874ec123e84111dd0a Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Tue, 30 Jun 2026 23:25:52 +0200 Subject: [PATCH 01/34] Added trace scaling, improved font size scaling, added german lang --- DSView/DSView.qrc | 1 + DSView/icons/German.svg | 6 + DSView/pv/config/appconfig.cpp | 33 +- DSView/pv/config/appconfig.h | 6 + DSView/pv/dialogs/decoderoptionsdlg.cpp | 15 +- DSView/pv/dock/protocoldock.cpp | 6 + DSView/pv/mainwindow.cpp | 2 +- DSView/pv/storesession.cpp | 24 +- DSView/pv/toolbars/logobar.cpp | 27 +- DSView/pv/toolbars/logobar.h | 2 + DSView/pv/toolbars/samplingbar.cpp | 1 + DSView/pv/ui/langresource.h | 7 +- DSView/pv/view/header.cpp | 12 +- DSView/pv/view/logicsignal.cpp | 103 +-- DSView/pv/view/ruler.cpp | 4 +- DSView/pv/view/trace.cpp | 49 +- DSView/pv/view/trace.h | 24 +- DSView/pv/view/view.cpp | 55 +- DSView/pv/view/view.h | 23 + DSView/pv/view/viewport.cpp | 51 +- lang/cn/dlg.json | 32 + lang/cn/toolbar.json | 4 + lang/de/dlg.json | 838 ++++++++++++++++++++++++ lang/de/msg.json | 390 +++++++++++ lang/de/toolbar.json | 190 ++++++ lang/en/dlg.json | 28 + lang/en/toolbar.json | 6 +- 27 files changed, 1821 insertions(+), 118 deletions(-) create mode 100644 DSView/icons/German.svg create mode 100644 lang/de/dlg.json create mode 100644 lang/de/msg.json create mode 100644 lang/de/toolbar.json diff --git a/DSView/DSView.qrc b/DSView/DSView.qrc index 222f82110..0d6855453 100644 --- a/DSView/DSView.qrc +++ b/DSView/DSView.qrc @@ -104,6 +104,7 @@ icons/Chinese.svg icons/data.svg icons/English.svg + icons/German.svg icons/usb2.svg icons/usb3.svg icons/light/moder.svg diff --git a/DSView/icons/German.svg b/DSView/icons/German.svg new file mode 100644 index 000000000..d10c8174b --- /dev/null +++ b/DSView/icons/German.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/DSView/pv/config/appconfig.cpp b/DSView/pv/config/appconfig.cpp index faf7fdd88..65838989c 100644 --- a/DSView/pv/config/appconfig.cpp +++ b/DSView/pv/config/appconfig.cpp @@ -124,6 +124,7 @@ static void _loadApp(AppOptions &o, QSettings &st) getFiled("fontSize", st, o.fontSize, 9.0); getFiled("autoScrollLatestData", st, o.autoScrollLatestData, true); getFiled("verticalScrollIsZoom", st, o.verticalScrollIsZoom, true); + getFiled("traceHeightFactor", st, o.traceHeightFactor, 1.0); getFiled("version", st, o.version, 1); getFiled("rulerTimeUnits", st, o.rulerTimeUnits, "Time"); getFiled("antialias", st, o.antialias, true); @@ -147,7 +148,12 @@ static void _loadApp(AppOptions &o, QSettings &st) { o.fontSize = (maxSize + minSize) / 2; } - + + if (o.traceHeightFactor < 0.1 || o.traceHeightFactor > 20.0) + { + o.traceHeightFactor = 1.0; + } + st.endGroup(); } @@ -167,6 +173,7 @@ static void _saveApp(AppOptions &o, QSettings &st) setFiled("fontSize", st, o.fontSize); setFiled("autoScrollLatestData", st, o.autoScrollLatestData); setFiled("verticalScrollIsZoom", st, o.verticalScrollIsZoom); + setFiled("traceHeightFactor", st, o.traceHeightFactor); setFiled("version", st, APP_CONFIG_VERSION); setFiled("rulerTimeUnits", st, o.rulerTimeUnits); setFiled("antialias", st, o.antialias); @@ -224,14 +231,16 @@ static void _loadFrame(FrameOptions &o, QSettings &st) o.windowState = st.value("windowState", QByteArray()).toByteArray(); st.endGroup(); - if (o.language == -1 || (o.language != LAN_CN && o.language != LAN_EN)){ + if (o.language == -1 || (o.language != LAN_CN && o.language != LAN_EN && o.language != LAN_DE)){ //get local language QLocale locale; if (QLocale::languageToString(locale.language()) == "Chinese") - o.language = LAN_CN; + o.language = LAN_CN; + else if (QLocale::languageToString(locale.language()) == "German") + o.language = LAN_DE; else - o.language = LAN_EN; + o.language = LAN_EN; } } @@ -421,6 +430,20 @@ std::string AppConfig::GetProtocolFormat(const std::string &protocolName) return ""; } +float AppConfig::GetTraceFontSize() +{ + float minSize = 0; + float maxSize = 0; + GetFontSizeRange(&minSize, &maxSize); + + float size = appOptions.fontSize; + if (size < minSize) + size = minSize; + if (size > maxSize) + size = maxSize; + return size; +} + void AppConfig::GetFontSizeRange(float *minSize, float *maxSize) { assert(minSize); @@ -433,7 +456,7 @@ void AppConfig::GetFontSizeRange(float *minSize, float *maxSize) #ifdef Q_OS_LINUX *minSize = 8; - *maxSize = 14; + *maxSize = 16; #endif #ifdef Q_OS_DARWIN diff --git a/DSView/pv/config/appconfig.h b/DSView/pv/config/appconfig.h index b4fa86819..5b122387f 100644 --- a/DSView/pv/config/appconfig.h +++ b/DSView/pv/config/appconfig.h @@ -29,6 +29,7 @@ #define LAN_CN 25 #define LAN_EN 31 +#define LAN_DE 7 #define THEME_STYLE_DARK "dark" #define THEME_STYLE_LIGHT "light" @@ -76,6 +77,7 @@ struct AppOptions bool autoScrollLatestData; bool verticalScrollIsZoom; float fontSize; + float traceHeightFactor; QString rulerTimeUnits; bool antialias; int minDecoderFontWidthPercent; @@ -167,6 +169,10 @@ class AppConfig static void GetFontSizeRange(float *minSize, float *maxSize); + // The configured application font size, clamped to the valid range. Used + // for trace-area text so it honours the user's font-size setting. + float GetTraceFontSize(); + bool IsDarkStyle(); QColor GetStyleColor(); diff --git a/DSView/pv/dialogs/decoderoptionsdlg.cpp b/DSView/pv/dialogs/decoderoptionsdlg.cpp index bf0b8e933..6765f3ba6 100644 --- a/DSView/pv/dialogs/decoderoptionsdlg.cpp +++ b/DSView/pv/dialogs/decoderoptionsdlg.cpp @@ -212,10 +212,19 @@ void DecoderOptionsDlg::load_options_view() int real_content_width = _content_width; int content_height = _contentHeight; - // scroll + // scroll QSize tsize = dlg->sizeHint(); - int w = tsize.width(); - int other_height = 190 + h_ex2; + int w = tsize.width(); + int other_height = 190 + h_ex2; + + // The 190 constant was tuned for a small font; with larger fonts the + // non-scroll widgets (cursor combos, buttons, ...) grow taller. Derive the + // reserved height from the dialog's actual size hint so the scroll area is + // not over-sized, which would otherwise push the bottom widgets off-dialog. + int dynamic_other = tsize.height() - _contentHeight + 10; + if (dynamic_other > other_height) + other_height = dynamic_other; + content_height += 20; int cursor_line_width = lb1->sizeHint().width() + _start_comboBox->sizeHint().width(); diff --git a/DSView/pv/dock/protocoldock.cpp b/DSView/pv/dock/protocoldock.cpp index 132474bd8..f7c73438a 100644 --- a/DSView/pv/dock/protocoldock.cpp +++ b/DSView/pv/dock/protocoldock.cpp @@ -1073,6 +1073,12 @@ void ProtocolDock::UpdateFont() ui::set_form_font(this, font); _table_view->setFont(font); + // The decoded-results model provides no size hint, so the table keeps its + // default row height and clips larger fonts. Grow the rows with the font, + // leaving room for cell margins and descenders. + QFontMetrics cell_fm(font); + _table_view->verticalHeader()->setDefaultSectionSize(qRound(cell_fm.height() * 1.5)); + for(auto lay : _protocol_lay_items){ lay->update_font(); } diff --git a/DSView/pv/mainwindow.cpp b/DSView/pv/mainwindow.cpp index 52c6c00f4..e80ae9453 100644 --- a/DSView/pv/mainwindow.cpp +++ b/DSView/pv/mainwindow.cpp @@ -1402,7 +1402,7 @@ namespace pv _myTrans.load(":/my_" + QString::number(language)); qApp->installTranslator(&_myTrans); } - else if (language == LAN_EN) + else if (language == LAN_EN || language == LAN_DE) { qApp->removeTranslator(&_qtTrans); qApp->removeTranslator(&_myTrans); diff --git a/DSView/pv/storesession.cpp b/DSView/pv/storesession.cpp index fe0ff219a..931a32b18 100644 --- a/DSView/pv/storesession.cpp +++ b/DSView/pv/storesession.cpp @@ -316,7 +316,7 @@ void StoreSession::save_logic(pv::data::LogicSnapshot *logic_snapshot) block_buf = (uint8_t *)malloc(block_size); if (block_buf == NULL) { _has_error = true; - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_SAVEPROC_ERROR1), + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_SAVEPROC_ERROR1), "Failed to create zip file. Malloc error."); } else { @@ -330,7 +330,7 @@ void StoreSession::save_logic(pv::data::LogicSnapshot *logic_snapshot) if (ret != SR_OK) { if (!_has_error) { _has_error = true; - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_SAVEPROC_ERROR2), + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_SAVEPROC_ERROR2), "Failed to create zip file. Please check write permission of this path."); } progress_updated(); @@ -410,7 +410,7 @@ void StoreSession::save_analog(pv::data::AnalogSnapshot *analog_snapshot) uint8_t *tmp = (uint8_t *)malloc(size); if (tmp == NULL) { _has_error = true; - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_SAVEPROC_ERROR1), + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_SAVEPROC_ERROR1), "Failed to create zip file. Malloc error."); } else { memcpy(tmp, buf, buf_end-buf); @@ -434,7 +434,7 @@ void StoreSession::save_analog(pv::data::AnalogSnapshot *analog_snapshot) if (ret != SR_OK) { if (!_has_error) { _has_error = true; - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_SAVEPROC_ERROR2), + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_SAVEPROC_ERROR2), "Failed to create zip file. Please check write permission of this path."); } progress_updated(); @@ -491,7 +491,7 @@ void StoreSession::save_dso(pv::data::DsoSnapshot *dso_snapshot) if (ret != SR_OK) { if (!_has_error) { _has_error = true; - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_SAVEPROC_ERROR2), + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_SAVEPROC_ERROR2), "Failed to create zip file. Please check write permission of this path."); } progress_updated(); @@ -811,11 +811,11 @@ bool StoreSession::export_start() } if (type_set.size() > 1) { - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_EXPORTSTART_ERROR1), + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_EXPORTSTART_ERROR1), "DSView does not currently support\nfile export for multiple data types."); return false; } else if (type_set.size() == 0) { - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_EXPORTSTART_ERROR2), "No data to save."); + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_EXPORTSTART_ERROR2), "No data to save."); return false; } @@ -823,12 +823,12 @@ bool StoreSession::export_start() assert(snapshot); // Check we have data if (snapshot->empty()) { - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_EXPORTSTART_ERROR2), "No data to save."); + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_EXPORTSTART_ERROR2), "No data to save."); return false; } if (_file_name == ""){ - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_EXPORTSTART_ERROR3), "No set file name."); + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_EXPORTSTART_ERROR3), "No set file name."); return false; } @@ -847,7 +847,7 @@ bool StoreSession::export_start() if (_outModule == NULL) { - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_EXPORTSTART_ERROR4), "Invalid export format."); + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_EXPORTSTART_ERROR4), "Invalid export format."); } else { @@ -895,7 +895,7 @@ void StoreSession::export_exec(data::Snapshot *snapshot) channel_type = SR_CHANNEL_ANALOG; } else { _has_error = true; - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_EXPORTPROC_ERROR1), "data type don't support."); + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_EXPORTPROC_ERROR1), "data type don't support."); return; } @@ -1077,7 +1077,7 @@ void StoreSession::export_exec(data::Snapshot *snapshot) uint8_t *xbuf = (uint8_t *)malloc(size * unitsize); if (xbuf == NULL) { _has_error = true; - _error = L_S(STR_PAGE_DLG, S_ID(IDS_MSG_STORESESS_EXPORTPROC_ERROR2), "xbuffer malloc failed."); + _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_EXPORTPROC_ERROR2), "xbuffer malloc failed."); return; } diff --git a/DSView/pv/toolbars/logobar.cpp b/DSView/pv/toolbars/logobar.cpp index 4b3b492cc..e1b1f9d4c 100644 --- a/DSView/pv/toolbars/logobar.cpp +++ b/DSView/pv/toolbars/logobar.cpp @@ -67,17 +67,22 @@ LogoBar::LogoBar(SigSession *session, QWidget *parent) : _action_en = new QAction(this); _action_en->setObjectName(QString::fromUtf8("actionEn")); - + _action_cn = new QAction(this); _action_cn->setObjectName(QString::fromUtf8("actionCn")); - + + _action_de = new QAction(this); + _action_de->setObjectName(QString::fromUtf8("actionDe")); + _language = new QMenu(this); _language->setObjectName(QString::fromUtf8("menuLanguage")); _language->addAction(_action_cn); _language->addAction(_action_en); + _language->addAction(_action_de); _action_en->setIcon(QIcon(":/icons/English.svg")); _action_cn->setIcon(QIcon(":/icons/Chinese.svg")); + _action_de->setIcon(QIcon(":/icons/German.svg")); _about = new QAction(this); _about->setObjectName(QString::fromUtf8("actionAbout")); @@ -116,6 +121,7 @@ LogoBar::LogoBar(SigSession *session, QWidget *parent) : connect(_action_en, SIGNAL(triggered()), this, SLOT(on_actionEn_triggered())); connect(_action_cn, SIGNAL(triggered()), this, SLOT(on_actionCn_triggered())); + connect(_action_de, SIGNAL(triggered()), this, SLOT(on_actionDe_triggered())); connect(_about, SIGNAL(triggered()), this, SLOT(on_actionAbout_triggered())); connect(_manual, SIGNAL(triggered()), this, SIGNAL(sig_open_doc())); connect(_issue, SIGNAL(triggered()), this, SLOT(on_actionIssue_triggered())); @@ -136,16 +142,19 @@ void LogoBar::retranslateUi() _logo_button.setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_HELP), "Help")); _language->setTitle(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_HELP_LANG), "&Language")); _action_en->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_HELP_LANG_EN), "English")); - _action_cn->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_HELP_LANG_CN), "中文")); + _action_cn->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_HELP_LANG_CN), "中文")); + _action_de->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_HELP_LANG_DE), "Deutsch")); _about->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_HELP_ABOUT), "&About...")); _manual->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_HELP_MANUAL), "&Manual...")); _issue->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_HELP_BUG), "&Bug Report")); _update->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_HELP_UPDATE), "&Update")); _log->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_HELP_LOG), "L&og Options")); - AppConfig &app = AppConfig::Instance(); + AppConfig &app = AppConfig::Instance(); if (app.frameOptions.language == LAN_CN) _language->setIcon(QIcon(":/icons/Chinese.svg")); + else if (app.frameOptions.language == LAN_DE) + _language->setIcon(QIcon(":/icons/German.svg")); else _language->setIcon(QIcon(":/icons/English.svg")); } @@ -190,7 +199,15 @@ void LogoBar::on_actionCn_triggered() _language->setIcon(QIcon::fromTheme("file", QIcon(":/icons/Chinese.svg"))); assert(_mainForm); - _mainForm->switchLanguage(LAN_CN); + _mainForm->switchLanguage(LAN_CN); +} + +void LogoBar::on_actionDe_triggered() +{ + _language->setIcon(QIcon::fromTheme("file", + QIcon(":/icons/German.svg"))); + assert(_mainForm); + _mainForm->switchLanguage(LAN_DE); } void LogoBar::on_actionAbout_triggered() diff --git a/DSView/pv/toolbars/logobar.h b/DSView/pv/toolbars/logobar.h index 19fea7fde..14a6a0495 100644 --- a/DSView/pv/toolbars/logobar.h +++ b/DSView/pv/toolbars/logobar.h @@ -72,6 +72,7 @@ class LogoBar : public QToolBar, public IUiWindow private slots: void on_actionEn_triggered(); void on_actionCn_triggered(); + void on_actionDe_triggered(); void on_actionAbout_triggered(); void on_actionManual_triggered(); void on_actionIssue_triggered(); @@ -92,6 +93,7 @@ private slots: QMenu *_language; QAction *_action_en; QAction *_action_cn; + QAction *_action_de; QAction *_about; QAction *_manual; diff --git a/DSView/pv/toolbars/samplingbar.cpp b/DSView/pv/toolbars/samplingbar.cpp index 29ba23e75..838af4fb1 100644 --- a/DSView/pv/toolbars/samplingbar.cpp +++ b/DSView/pv/toolbars/samplingbar.cpp @@ -1139,6 +1139,7 @@ namespace pv _device_selector.clear(); + int max_text_width = 0; for (int i = 0; i < dev_count; i++) { p = (array + i); diff --git a/DSView/pv/ui/langresource.h b/DSView/pv/ui/langresource.h index f8e84cd95..9755752a6 100644 --- a/DSView/pv/ui/langresource.h +++ b/DSView/pv/ui/langresource.h @@ -58,10 +58,11 @@ struct lang_page_item bool is_dynamic; }; -static const struct lang_key_item lang_id_keys[] = +static const struct lang_key_item lang_id_keys[] = { {25, "cn"}, - {31, "en"} + {31, "en"}, + {7, "de"} }; static const struct lang_page_item lange_page_keys[] = @@ -89,7 +90,7 @@ class LangResource void release_dynamic(); inline bool is_lang_en(){ - return _cur_lang == 31; + return _cur_lang == 31 || _cur_lang == 7; } private: diff --git a/DSView/pv/view/header.cpp b/DSView/pv/view/header.cpp index 9dce5247b..4d91ac45e 100644 --- a/DSView/pv/view/header.cpp +++ b/DSView/pv/view/header.cpp @@ -135,9 +135,9 @@ void Header::paintEvent(QPaintEvent*) fore.setAlpha(View::ForeAlpha); QFont font(painter.font()); - float fSize = AppConfig::Instance().appOptions.fontSize; - if (fSize > 10) - fSize = 10; + float fSize = AppConfig::Instance().GetTraceFontSize(); + // Grow channel-label text together with the trace height (logic mode). + fSize *= _view.get_trace_font_scale(); font.setPointSizeF(fSize); painter.setFont(font); @@ -389,8 +389,10 @@ void Header::changeName(QMouseEvent *event) { header_resize(); QFont font = this->font(); - float fsize = AppConfig::Instance().appOptions.fontSize; - font.setPointSizeF(fsize <= 10 ? fsize: 10); + float fsize = AppConfig::Instance().GetTraceFontSize(); + // Match the scaled label font used when painting the channel name. + fsize *= _context_trace->get_label_scale(); + font.setPointSizeF(fsize); nameEdit->setFont(font); nameEdit->setText(_context_trace->get_name()); diff --git a/DSView/pv/view/logicsignal.cpp b/DSView/pv/view/logicsignal.cpp index 24eb2f1d2..28290be58 100644 --- a/DSView/pv/view/logicsignal.cpp +++ b/DSView/pv/view/logicsignal.cpp @@ -254,38 +254,44 @@ void LogicSignal::paint_type_options(QPainter &p, int right, const QPoint pt, QC p.drawLine(posTrig_rect.left(), posTrig_rect.bottom(), edgeTrig_rect.right(), edgeTrig_rect.bottom()); - p.setPen(QPen(fore, 2, Qt::SolidLine)); + // Scale the glyph insets/stroke so the trigger symbols grow with the box. + const double sc = get_label_scale(); + const double i5 = 5 * sc; + const double i7 = 7 * sc; + const double i2 = 2 * sc; + + p.setPen(QPen(fore, max(2.0, 2 * sc), Qt::SolidLine)); p.setBrush(Qt::transparent); - p.drawLine(posTrig_rect.left() + 5, posTrig_rect.bottom() - 5, - posTrig_rect.center().x(), posTrig_rect.bottom() - 5); - p.drawLine(posTrig_rect.center().x(), posTrig_rect.bottom() - 5, - posTrig_rect.center().x(), posTrig_rect.top() + 5); - p.drawLine(posTrig_rect.center().x(), posTrig_rect.top() + 5, - posTrig_rect.right() - 5, posTrig_rect.top() + 5); - - p.drawLine(higTrig_rect.left() + 5, higTrig_rect.top() + 5, - higTrig_rect.right() - 5, higTrig_rect.top() + 5); - - p.drawLine(negTrig_rect.left() + 5, negTrig_rect.top() + 5, - negTrig_rect.center().x(), negTrig_rect.top() + 5); - p.drawLine(negTrig_rect.center().x(), negTrig_rect.top() + 5, - negTrig_rect.center().x(), negTrig_rect.bottom() - 5); - p.drawLine(negTrig_rect.center().x(), negTrig_rect.bottom() - 5, - negTrig_rect.right() - 5, negTrig_rect.bottom() - 5); - - p.drawLine(lowTrig_rect.left() + 5, lowTrig_rect.bottom() - 5, - lowTrig_rect.right() - 5, lowTrig_rect.bottom() - 5); - - p.drawLine(edgeTrig_rect.left() + 5, edgeTrig_rect.top() + 5, - edgeTrig_rect.center().x() - 2, edgeTrig_rect.top() + 5); - p.drawLine(edgeTrig_rect.center().x() + 2 , edgeTrig_rect.top() + 5, - edgeTrig_rect.right() - 5, edgeTrig_rect.top() + 5); - p.drawLine(edgeTrig_rect.center().x(), edgeTrig_rect.top() + 7, - edgeTrig_rect.center().x(), edgeTrig_rect.bottom() - 7); - p.drawLine(edgeTrig_rect.left() + 5, edgeTrig_rect.bottom() - 5, - edgeTrig_rect.center().x() - 2, edgeTrig_rect.bottom() - 5); - p.drawLine(edgeTrig_rect.center().x() + 2, edgeTrig_rect.bottom() - 5, - edgeTrig_rect.right() - 5, edgeTrig_rect.bottom() - 5); + p.drawLine(QPointF(posTrig_rect.left() + i5, posTrig_rect.bottom() - i5), + QPointF(posTrig_rect.center().x(), posTrig_rect.bottom() - i5)); + p.drawLine(QPointF(posTrig_rect.center().x(), posTrig_rect.bottom() - i5), + QPointF(posTrig_rect.center().x(), posTrig_rect.top() + i5)); + p.drawLine(QPointF(posTrig_rect.center().x(), posTrig_rect.top() + i5), + QPointF(posTrig_rect.right() - i5, posTrig_rect.top() + i5)); + + p.drawLine(QPointF(higTrig_rect.left() + i5, higTrig_rect.top() + i5), + QPointF(higTrig_rect.right() - i5, higTrig_rect.top() + i5)); + + p.drawLine(QPointF(negTrig_rect.left() + i5, negTrig_rect.top() + i5), + QPointF(negTrig_rect.center().x(), negTrig_rect.top() + i5)); + p.drawLine(QPointF(negTrig_rect.center().x(), negTrig_rect.top() + i5), + QPointF(negTrig_rect.center().x(), negTrig_rect.bottom() - i5)); + p.drawLine(QPointF(negTrig_rect.center().x(), negTrig_rect.bottom() - i5), + QPointF(negTrig_rect.right() - i5, negTrig_rect.bottom() - i5)); + + p.drawLine(QPointF(lowTrig_rect.left() + i5, lowTrig_rect.bottom() - i5), + QPointF(lowTrig_rect.right() - i5, lowTrig_rect.bottom() - i5)); + + p.drawLine(QPointF(edgeTrig_rect.left() + i5, edgeTrig_rect.top() + i5), + QPointF(edgeTrig_rect.center().x() - i2, edgeTrig_rect.top() + i5)); + p.drawLine(QPointF(edgeTrig_rect.center().x() + i2 , edgeTrig_rect.top() + i5), + QPointF(edgeTrig_rect.right() - i5, edgeTrig_rect.top() + i5)); + p.drawLine(QPointF(edgeTrig_rect.center().x(), edgeTrig_rect.top() + i7), + QPointF(edgeTrig_rect.center().x(), edgeTrig_rect.bottom() - i7)); + p.drawLine(QPointF(edgeTrig_rect.left() + i5, edgeTrig_rect.bottom() - i5), + QPointF(edgeTrig_rect.center().x() - i2, edgeTrig_rect.bottom() - i5)); + p.drawLine(QPointF(edgeTrig_rect.center().x() + i2, edgeTrig_rect.bottom() - i5), + QPointF(edgeTrig_rect.right() - i5, edgeTrig_rect.bottom() - i5)); } bool LogicSignal::measure(const QPointF &p, uint64_t &index0, uint64_t &index1, uint64_t &index2) @@ -517,33 +523,36 @@ bool LogicSignal::mouse_press(int right, const QPoint pt) QRectF LogicSignal::get_rect(LogicSetRegions type, int y, int right) { - const QSizeF name_size(right - get_leftWidth() - get_rightWidth(), SquareWidth); + const int squareWidth = get_squareWidth(); + const int squareMargin = get_squareMargin(); + const QSizeF name_size(right - get_leftWidth() - get_rightWidth(), squareWidth); + const int base_x = get_leftWidth() + name_size.width() + squareMargin; if (type == POSTRIG) return QRectF( - get_leftWidth() + name_size.width() + Margin, - y - SquareWidth / 2, - SquareWidth, SquareWidth); + base_x, + y - squareWidth / 2, + squareWidth, squareWidth); else if (type == HIGTRIG) return QRectF( - get_leftWidth() + name_size.width() + SquareWidth + Margin, - y - SquareWidth / 2, - SquareWidth, SquareWidth); + base_x + squareWidth, + y - squareWidth / 2, + squareWidth, squareWidth); else if (type == NEGTRIG) return QRectF( - get_leftWidth() + name_size.width() + 2 * SquareWidth + Margin, - y - SquareWidth / 2, - SquareWidth, SquareWidth); + base_x + 2 * squareWidth, + y - squareWidth / 2, + squareWidth, squareWidth); else if (type == LOWTRIG) return QRectF( - get_leftWidth() + name_size.width() + 3 * SquareWidth + Margin, - y - SquareWidth / 2, - SquareWidth, SquareWidth); + base_x + 3 * squareWidth, + y - squareWidth / 2, + squareWidth, squareWidth); else if (type == EDGTRIG) return QRectF( - get_leftWidth() + name_size.width() + 4 * SquareWidth + Margin, - y - SquareWidth / 2, - SquareWidth, SquareWidth); + base_x + 4 * squareWidth, + y - squareWidth / 2, + squareWidth, squareWidth); else return QRectF(0, 0, 0, 0); } diff --git a/DSView/pv/view/ruler.cpp b/DSView/pv/view/ruler.cpp index 74c750a19..bfb169e88 100644 --- a/DSView/pv/view/ruler.cpp +++ b/DSView/pv/view/ruler.cpp @@ -246,9 +246,7 @@ void Ruler::paintEvent(QPaintEvent*) style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this); QFont font = p.font(); - float fSize = AppConfig::Instance().appOptions.fontSize; - if (fSize > 10) - fSize = 10; + float fSize = AppConfig::Instance().GetTraceFontSize(); font.setPointSizeF(fSize); p.setFont(font); diff --git a/DSView/pv/view/trace.cpp b/DSView/pv/view/trace.cpp index fcd58f26a..cadaa74fa 100644 --- a/DSView/pv/view/trace.cpp +++ b/DSView/pv/view/trace.cpp @@ -98,13 +98,46 @@ Trace::Trace(const Trace &t) : int Trace::get_name_width() { QFont font; - float fSize = AppConfig::Instance().appOptions.fontSize; - font.setPointSizeF(fSize <= 10 ? fSize : 10); + float fSize = AppConfig::Instance().GetTraceFontSize(); + // Keep the reserved label width in sync with the scaled label font so + // channel names are not clipped when the trace height is scaled up. + fSize *= get_label_scale(); + font.setPointSizeF(fSize); QFontMetrics fm(font); return fm.boundingRect(get_name()).width(); } +double Trace::get_label_scale() +{ + return (_view != NULL) ? _view->get_trace_font_scale() : 1.0; +} + +int Trace::get_squareWidth() +{ + return (int)(SquareWidth * get_label_scale()); +} + +int Trace::get_squareMargin() +{ + return (int)(Margin * get_label_scale()); +} + +int Trace::get_leftWidth() +{ + return get_squareWidth() / 2 + get_squareMargin(); +} + +int Trace::get_rightWidth() +{ + return 2 * get_squareMargin() + _typeWidth * get_squareWidth() + 1.5 * get_squareWidth(); +} + +int Trace::get_headerHeight() +{ + return get_squareWidth(); +} + void Trace::set_name(QString name) { _name = name; @@ -374,10 +407,10 @@ int Trace::rows_size() QRectF Trace::get_rect(const char *s, int y, int right) { - const QSizeF color_size(get_leftWidth() - Margin, SquareWidth); - // const QSizeF name_size(right - get_leftWidth() - get_rightWidth(), SquareWidth); - const QSizeF name_size(right - get_leftWidth() - get_rightWidth(), SquareWidth); - const QSizeF label_size(SquareWidth, SquareWidth); + const int squareWidth = get_squareWidth(); + const QSizeF color_size(get_leftWidth() - get_squareMargin(), squareWidth); + const QSizeF name_size(right - get_leftWidth() - get_rightWidth(), squareWidth); + const QSizeF label_size(squareWidth, squareWidth); if (!strcmp(s, "name")) return QRectF( @@ -397,8 +430,8 @@ QRectF Trace::get_rect(const char *s, int y, int right) else return QRectF( 2, - y - SquareWidth / 2, - SquareWidth, SquareWidth); + y - squareWidth / 2, + squareWidth, squareWidth); } } // namespace view diff --git a/DSView/pv/view/trace.h b/DSView/pv/view/trace.h index c33915686..131fb15fb 100644 --- a/DSView/pv/view/trace.h +++ b/DSView/pv/view/trace.h @@ -161,17 +161,23 @@ class Trace : public SelectableItem /** * Geom */ - inline int get_leftWidth(){ - return SquareWidth/2 + Margin; - } - inline int get_rightWidth(){ - return 2 * Margin + _typeWidth * SquareWidth + 1.5 * SquareWidth; - } + /** + * Scale applied to the header-label geometry (square/box sizes, margins) + * so the labels and trigger buttons grow together with the trace height. + * Returns 1.0 when no view is attached or the trace height is not scaled. + */ + double get_label_scale(); - inline int get_headerHeight(){ - return SquareWidth; - } + // Square and margin sizes scaled by get_label_scale(). + int get_squareWidth(); + int get_squareMargin(); + + int get_leftWidth(); + + int get_rightWidth(); + + int get_headerHeight(); /** * Gets the old vertical layout offset of this signal. diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index 8c7ada50a..3a1eeacfa 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "groupsignal.h" @@ -107,6 +108,10 @@ View::View(SigSession *session, pv::toolbars::SamplingBar *sampling_bar, QWidget _session = session; _device_agent = session->get_device(); + _trace_height_factor = AppConfig::Instance().appOptions.traceHeightFactor; + if (_trace_height_factor < MinTraceHeightFactor || _trace_height_factor > MaxTraceHeightFactor) + _trace_height_factor = 1.0; + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); // setWidgetResizable(true); @@ -334,6 +339,41 @@ bool View::zoom(double steps, int offset) return ret; } +void View::vzoom(double steps) +{ + if (_device_agent->have_instance() == false) + return; + + // Vertical scaling only makes sense for the logic trace window. + if (_device_agent->get_work_mode() != LOGIC) + return; + + // Halve the step size (relative to horizontal zoom) for finer adjustment. + double factor = _trace_height_factor * std::pow(3.0/2.0, steps * 0.5); + factor = max(min(factor, MaxTraceHeightFactor), MinTraceHeightFactor); + + if (factor == _trace_height_factor) + return; + + _trace_height_factor = factor; + + AppConfig &app = AppConfig::Instance(); + app.appOptions.traceHeightFactor = factor; + app.SaveApp(); + + signals_changed(NULL); + _header->update(); + viewport_update(); + update_scroll(); +} + +double View::get_trace_font_scale() +{ + if (_device_agent->have_instance() && _device_agent->get_work_mode() == LOGIC) + return _trace_height_factor; + return 1.0; +} + void View::timebase_changed() { int width = get_view_width(); @@ -785,18 +825,29 @@ void View::signals_changed(const Trace* eventTrace) int v; bool ret; + // Minimum row height must accommodate the configured font so that + // in-trace text (e.g. decoder annotations) is not cut off. + QFont trace_font; + trace_font.setPointSizeF(AppConfig::Instance().GetTraceFontSize()); + const int min_row_height = max((int)HeightUnit, + QFontMetrics(trace_font).height() + 4); + ret = _device_agent->get_config_byte(SR_CONF_MAX_HEIGHT_VALUE, v); if (ret) { max_height = (v + 1) * HeightUnit; } if (height < 2*actualMargin) { //actualMargin /= 2; - _signalHeight = max((double)HeightUnit, (_time_viewport->height() + _signalHeight = max((double)min_row_height, (_time_viewport->height() - 2 * actualMargin * label_size) * 1.0 / total_rows); } else { - _signalHeight = max((double)HeightUnit, (height >= max_height) ? max_height : height); + _signalHeight = max((double)min_row_height, (height >= max_height) ? max_height : height); } + + // Apply the user-controlled vertical scaling so logic signals can + // grow to use the full window height (see View::vzoom). + _signalHeight = max((double)min_row_height, _signalHeight * _trace_height_factor); } else if (_device_agent->get_work_mode() == DSO) { _signalHeight = max((double)HeightUnit, (_header->height() diff --git a/DSView/pv/view/view.h b/DSView/pv/view/view.h index b342d0cff..d5b7a3266 100644 --- a/DSView/pv/view/view.h +++ b/DSView/pv/view/view.h @@ -92,6 +92,10 @@ class View : public QScrollArea, public IUiWindow static const int WellSamplesPerPixel = 2048; static constexpr double MaxViewRate = 1.0; + + // Bounds for the vertical (trace height) scaling factor. + static constexpr double MinTraceHeightFactor = 0.5; + static constexpr double MaxTraceHeightFactor = 20.0; static const int MaxPixelsPerSample = 100; static const int StatusHeight = 20; @@ -168,6 +172,13 @@ class View : public QScrollArea, public IUiWindow void zoom(double steps); bool zoom(double steps, int offset); + /** + * Scales the vertical size of the traces (logic mode) so signals can + * use more of the available window height. Positive steps grow the + * traces, negative steps shrink them. The factor is persisted. + */ + void vzoom(double steps); + /** * Sets the scale and offset. * @param scale The new view scale in seconds per pixel. @@ -201,6 +212,17 @@ class View : public QScrollArea, public IUiWindow return _signalHeight; } + inline double get_trace_height_factor(){ + return _trace_height_factor; + } + + /** + * Scale applied to trace-area text so it grows together with the trace + * height. Returns the vertical scaling factor in logic mode and 1.0 in + * every other mode (where trace height is not scaled). + */ + double get_trace_font_scale(); + int headerWidth(); inline Ruler* get_ruler(){ @@ -468,6 +490,7 @@ private slots: int64_t _preOffset; int _spanY; int _signalHeight; + double _trace_height_factor; bool _updating_scroll; // trigger position fix diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp index b72fc5177..9f5399101 100644 --- a/DSView/pv/view/viewport.cpp +++ b/DSView/pv/view/viewport.cpp @@ -173,9 +173,9 @@ void Viewport::doPaint() style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this); QFont font = p.font(); - float fSize = AppConfig::Instance().appOptions.fontSize; - if (fSize > 10) - fSize = 10; + float fSize = AppConfig::Instance().GetTraceFontSize(); + // Grow trace-area text together with the trace height (logic mode). + fSize *= _view.get_trace_font_scale(); font.setPointSizeF(fSize); p.setFont(font); @@ -1390,8 +1390,16 @@ void Viewport::wheelEvent(QWheelEvent *event) if (isVertical) { + const bool vZoom = (event->modifiers() & Qt::ControlModifier) && + (event->modifiers() & Qt::ShiftModifier); + + if (vZoom) { + // Ctrl+Shift+wheel: scale the trace height in the y axis so + // signals can use the full window height. + _view.vzoom(zoom_scale); + } // Vertical scrolling is interpreted as zooming in/out - if(doVScroll) { + else if(doVScroll) { _view.verticalScrollBar()->setValue(_view.verticalScrollBar()->value() - delta); } else { _view.zoom(zoom_scale, x); @@ -1773,6 +1781,17 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) } if (_measure_en) { + // Scale the hover info popup together with the traces/text. + const double sc = _view.get_trace_font_scale(); + + // Use an explicit (scaled, non-condensed) font so the popup text + // and its box stay consistent regardless of prior painter state. + QFont measure_font = p.font(); + measure_font.setStretch(QFont::Unstretched); + float mfSize = AppConfig::Instance().GetTraceFontSize(); + measure_font.setPointSizeF(mfSize * sc); + p.setFont(measure_font); + int typical_width = p.boundingRect(0, 0, INT_MAX, INT_MAX, Qt::AlignLeft | Qt::AlignTop, _mm_width_time).width(); typical_width = max(typical_width, p.boundingRect(0, 0, INT_MAX, INT_MAX, @@ -1781,16 +1800,19 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) Qt::AlignLeft | Qt::AlignTop, _mm_freq).width()); typical_width = max(typical_width, p.boundingRect(0, 0, INT_MAX, INT_MAX, Qt::AlignLeft | Qt::AlignTop, _mm_duty).width()); - typical_width = typical_width + 100; + typical_width = typical_width + (int)(100 * sc); const QString mm_period_samples_long = _mm_period_samples + " " + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SAMPLES), " samples"); const QString mm_width_samples_long = _mm_width_samples + " " + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SAMPLES), " samples"); + const double box_h = 140.0 * sc; + const double pad = 5.0 * sc; + const double row_h = 20.0 * sc; const double width = _view.get_view_width() - _view.verticalScrollBar()->geometry().width(); const double height = _view.get_view_height() - _view.horizontalScrollBar()->geometry().height() - View::StatusHeight; const double left = hoverpoint_x; const double top = hoverpoint_y; const double right = left + typical_width; - const double bottom = top + 140; + const double bottom = top + box_h; double hover_x, hover_y; if(right > width) { hover_x = left - typical_width - MouseEdgeClearance; @@ -1798,7 +1820,7 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) hover_x = left + MousePointerClearance; } if(bottom > height) { - hover_y = top - 140 - MousePointerClearance; + hover_y = top - box_h - MousePointerClearance; if(right <= width) { hover_x = left + MouseEdgeClearance; } @@ -1806,13 +1828,14 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) hover_y = top + MousePointerClearance; } QPointF org_pos = QPointF(hover_x, hover_y); - QRectF measure_rect = QRectF(org_pos.x(), org_pos.y(), (double)typical_width, 140.0); - QRectF measure1_rect = QRectF(org_pos.x()+5, org_pos.y()+5, (double)typical_width-10, 20.0); - QRectF measure2_rect = QRectF(org_pos.x()+5, org_pos.y()+25, (double)typical_width-10, 20.0); - QRectF measure3_rect = QRectF(org_pos.x()+5, org_pos.y()+50, (double)typical_width-10, 20.0); - QRectF measure4_rect = QRectF(org_pos.x()+5, org_pos.y()+70, (double)typical_width-10, 20.0); - QRectF measure5_rect = QRectF(org_pos.x()+5, org_pos.y()+95, (double)typical_width-10, 20.0); - QRectF measure6_rect = QRectF(org_pos.x()+5, org_pos.y()+115, (double)typical_width-10, 20.0); + const double tw = (double)typical_width - 2 * pad; + QRectF measure_rect = QRectF(org_pos.x(), org_pos.y(), (double)typical_width, box_h); + QRectF measure1_rect = QRectF(org_pos.x()+pad, org_pos.y()+pad, tw, row_h); + QRectF measure2_rect = QRectF(org_pos.x()+pad, org_pos.y()+25.0*sc, tw, row_h); + QRectF measure3_rect = QRectF(org_pos.x()+pad, org_pos.y()+50.0*sc, tw, row_h); + QRectF measure4_rect = QRectF(org_pos.x()+pad, org_pos.y()+70.0*sc, tw, row_h); + QRectF measure5_rect = QRectF(org_pos.x()+pad, org_pos.y()+95.0*sc, tw, row_h); + QRectF measure6_rect = QRectF(org_pos.x()+pad, org_pos.y()+115.0*sc, tw, row_h); p.setPen(Qt::NoPen); p.setBrush(back.black() > 0x80 ? View::TransparentLightBlue : View::TransparentLightYellow); diff --git a/lang/cn/dlg.json b/lang/cn/dlg.json index 22db6d62a..f810b7555 100644 --- a/lang/cn/dlg.json +++ b/lang/cn/dlg.json @@ -806,5 +806,37 @@ { "id": "IDS_FFT_MODE_LINEARRSM", "text": "线性 RMS" + }, + { + "id": "IDS_DLG_DECODER_DYNAMIC_FONT_WIDTH", + "text": "解码器自适应字体宽度" + }, + { + "id": "IDS_DLG_DEFAULT_FONT", + "text": "默认" + }, + { + "id": "IDS_DLG_MAX_FONT_WIDTH", + "text": "最大字体宽度: " + }, + { + "id": "IDS_DLG_MIN_FONT_WIDTH", + "text": "最小字体宽度: " + }, + { + "id": "IDS_DLG_RULER_UNITS", + "text": "标尺 / 光标单位" + }, + { + "id": "IDS_DLG_VERTICAL_SCROLL_ACTION", + "text": "垂直滚动操作" + }, + { + "id": "IDS_DLG_VERTICAL_SCROLL_ACTION_SMOOTH_ZOOM", + "text": "缩放" + }, + { + "id": "IDS_DLG_VERTICAL_SCROLL_ACTION_VERTICAL_SCROLL", + "text": "滚动" } ] diff --git a/lang/cn/toolbar.json b/lang/cn/toolbar.json index 65b94f007..e8db415a8 100644 --- a/lang/cn/toolbar.json +++ b/lang/cn/toolbar.json @@ -163,6 +163,10 @@ "id": "IDS_TOOLBAR_HELP_LANG_CN", "text": "中文(&C)" }, + { + "id": "IDS_TOOLBAR_HELP_LANG_DE", + "text": "德语(&D)" + }, { "id": "IDS_TOOLBAR_HELP_ABOUT", "text": "关于(&A)" diff --git a/lang/de/dlg.json b/lang/de/dlg.json new file mode 100644 index 000000000..9aa220499 --- /dev/null +++ b/lang/de/dlg.json @@ -0,0 +1,838 @@ +[ + { + "id": "IDS_DLG_LOG_OPTIONS", + "text": "Protokolloptionen" + }, + { + "id": "IDS_DLG_LOG_LEVEL", + "text": "Protokollstufe" + }, + { + "id": "IDS_DLG_SAVE_FILE", + "text": "In Datei speichern" + }, + { + "id": "IDS_DLG_OPEN_FILE", + "text": "Datei öffnen" + }, + { + "id": "IDS_DLG_OPEN_SEESION", + "text": "Sitzung öffnen" + }, + { + "id": "IDS_DLG_SAVE_SEESION", + "text": "Sitzung speichern" + }, + { + "id": "IDS_DLG_DOCUMENT", + "text": "Dokument" + }, + { + "id": "IDS_DLG_TRIGGER_DOCK_TITLE", + "text": "Triggereinstellung" + }, + { + "id": "IDS_DLG_PROTOCOL_DOCK_TITLE", + "text": "Dekodierer" + }, + { + "id": "IDS_DLG_MEASURE_DOCK_TITLE", + "text": "Messung" + }, + { + "id": "IDS_DLG_SEARCH_DOCK_TITLE", + "text": "Suchen" + }, + { + "id": "IDS_DLG_SAVE_AS", + "text": "Speichern unter" + }, + { + "id": "IDS_DLG_CHECK_USB_SPEED_ERROR", + "text": "Das aktuelle Gerät an einen USB 2.0 Anschluss anzuschließen beeinträchtigt die Leistung erheblich.\nBitte an einen USB 3.0 Anschluss umstecken." + }, + { + "id": "IDS_DLG_CHECK_SESSION_FILE_VERSION_ERROR", + "text": "Diese Datei liegt in einem alten Format vor.\nSie wird langsam geladen.\nSie können sie nach dem Laden erneut speichern." + }, + { + "id": "IDS_DLG_ABOUT", + "text": "Über" + }, + { + "id": "IDS_DLG_DISPLAY_OPTIONS", + "text": "Anzeigeoptionen" + }, + { + "id": "IDS_DLG_QUICK_SCROLL", + "text": "Wellenform-Scrollen mit Mausbewegung" + }, + { + "id": "IDS_DLG_SAVE", + "text": "Speichern" + }, + { + "id": "IDS_DLG_RESET", + "text": "Zurücksetzen" + }, + { + "id": "IDS_DLG_EXIT", + "text": "Beenden" + }, + { + "id": "IDS_DLG_MANUAL_CALIBRATION", + "text": "Manuelle Kalibrierung" + }, + { + "id": "IDS_DLG_CHANNEL", + "text": "Kanal" + }, + { + "id": "IDS_DLG_SAVE_CALIBRATION_RESULTS", + "text": "Kalibrierungsergebnisse werden gespeichert... Bitte warten." + }, + { + "id": "IDS_DLG_CANCEL", + "text": "Abbrechen" + }, + { + "id": "IDS_DLG_RELOAD_CALIBRATION_RESULTS", + "text": "Vorherige Kalibrierungsergebnisse werden neu geladen... Bitte warten." + }, + { + "id": "IDS_DLG_DECODER_OPTIONS", + "text": "Dekodierer-Optionen" + }, + { + "id": "IDS_DLG_CURSOR", + "text": "Cursor " + }, + { + "id": "IDS_DLG_CURSOR_FOR_DECODE_START", + "text": "Dekodierungs-Startposition" + }, + { + "id": "IDS_DLG_CURSOR_FOR_DECODE_END", + "text": "Dekodierungs-Endposition" + }, + { + "id": "IDS_DLG_DEVICE_OPTIONS", + "text": "Geräteoptionen" + }, + { + "id": "IDS_DLG_MODE", + "text": "Optionen" + }, + { + "id": "IDS_DLG_ENABLE_ALL", + "text": "Alle aktivieren" + }, + { + "id": "IDS_DLG_DISABLE_ALL", + "text": "Alle deaktivieren" + }, + { + "id": "IDS_DLG_ENABLE", + "text": "Aktivieren: " + }, + { + "id": "IDS_DLG_AUTO_CALIBRATION", + "text": "Automatische Kalibrierung" + }, + { + "id": "IDS_DLG_CALIBRATION", + "text": "Kalibrierung" + }, + { + "id": "IDS_DLG_MEASUREMENTS", + "text": "Messungen" + }, + { + "id": "IDS_DLG_FFT_ENABLE", + "text": "FFT aktivieren: " + }, + { + "id": "IDS_DLG_FFT_LENGTH", + "text": "FFT-Länge: " + }, + { + "id": "IDS_DLG_SAMPLE_INTERVAL", + "text": "Abtastintervall: " + }, + { + "id": "IDS_DLG_FFT_SOURCE", + "text": "FFT-Quelle: " + }, + { + "id": "IDS_DLG_FFT_WINDOW", + "text": "FFT-Fenster: " + }, + { + "id": "IDS_DLG_DC_IGNORED", + "text": "DC ignoriert: " + }, + { + "id": "IDS_DLG_Y-AXIS_MODE", + "text": "Y-Achsen-Modus: " + }, + { + "id": "IDS_DLG_DBV_RANGE", + "text": "DBV-Bereich: " + }, + { + "id": "IDS_DLG_FFT_OPTIONS", + "text": "FFT-Optionen" + }, + { + "id": "IDS_DLG_INTERVAL_S", + "text": "Intervall (s): " + }, + { + "id": "IDS_DLG_REPETITIVE_INTERVAL", + "text": "Wiederholungsintervall" + }, + { + "id": "IDS_DLG_X_AXIS", + "text": "X-Achse" + }, + { + "id": "IDS_DLG_Y_AXIS", + "text": "Y-Achse" + }, + { + "id": "IDS_DLG_LISSAJOUS_OPTIONS", + "text": "Lissajous-Optionen" + }, + { + "id": "IDS_DLG_ADD", + "text": "Hinzufügen" + }, + { + "id": "IDS_DLG_SUBSTRACT", + "text": "Subtrahieren" + }, + { + "id": "IDS_DLG_MULTIPLY", + "text": "Multiplizieren" + }, + { + "id": "IDS_DLG_DIVIDE", + "text": "Dividieren" + }, + { + "id": "IDS_DLG_MATH_TYPE", + "text": "Mathematiktyp" + }, + { + "id": "IDS_DLG_1ST_SOURCE", + "text": "1. Quelle" + }, + { + "id": "IDS_DLG_2ST_SOURCE", + "text": "2. Quelle" + }, + { + "id": "IDS_DLG_MATH_OPTIONS", + "text": "Mathematik-Optionen" + }, + { + "id": "IDS_DLG_EXPORT_FORMAT", + "text": "Exportformat: " + }, + { + "id": "IDS_DLG_PROTOCOL_EXPORT", + "text": "Protokoll-Export" + }, + { + "id": "IDS_DLG_EXPORT_DATA", + "text": "Daten exportieren: " + }, + { + "id": "IDS_DLG_EXPORT_PROTOCOL_LIST_RESULT", + "text": "Dekodierungsergebnisse werden exportiert... Bitte warten." + }, + { + "id": "IDS_DLG_FIT_TO_WINDOW", + "text": "An Fenstergröße anpassen" + }, + { + "id": "IDS_DLG_FIXED", + "text": "Fest" + }, + { + "id": "IDS_DLG_MAP_ZOOM", + "text": "Sprungzoom: " + }, + { + "id": "IDS_DLG_DECODED_PROTOCOLS", + "text": "Dekodierte Protokolle: " + }, + { + "id": "IDS_DLG_PROTOCOL_LIST_VIEWER", + "text": "Dekodierungsergebnisse" + }, + { + "id": "IDS_DLG_REGION", + "text": "Bereich" + }, + { + "id": "IDS_DLG_SEARCH_LABEL", + "text": "X: Beliebig\n0: Niedriger Pegel\n1: Hoher Pegel\nR: Steigende Flanke\nF: Fallende Flanke\nC: Steigende/Fallende Flanke" + }, + { + "id": "IDS_DLG_SEARCH_OPTIONS", + "text": "Suchoptionen" + }, + { + "id": "IDS_DLG_SAVING", + "text": "Speichern..." + }, + { + "id": "IDS_DLG_ORIGINAL_DATA", + "text": "Originaldaten" + }, + { + "id": "IDS_DLG_COMPRESSED_DATA", + "text": "Komprimierte Daten" + }, + { + "id": "IDS_DLG_EXPORT", + "text": "Exportieren" + }, + { + "id": "IDS_DLG_EXPORTING", + "text": "Exportieren..." + }, + { + "id": "IDS_DLG_DONT_CONNECT_PROBES", + "text": "Keine Sonden anschließen!" + }, + { + "id": "IDS_DLG_LOAD_CURRENT_SETTING", + "text": "Aktuelle Einstellungen werden geladen... Bitte warten." + }, + { + "id": "IDS_DLG_WAITING", + "text": "Warten" + }, + { + "id": "IDS_DLG_FINISHED", + "text": "Fertig!" + }, + { + "id": "IDS_DLG_US", + "text": "µs" + }, + { + "id": "IDS_DLG_MS", + "text": "ms" + }, + { + "id": "IDS_DLG_S", + "text": "s" + }, + { + "id": "IDS_DLG_TRIGGER_POSITION", + "text": "Triggerposition: " + }, + { + "id": "IDS_DLG_HOLD_OFF_TIME", + "text": "Haltezeit: " + }, + { + "id": "IDS_DLG_NOISE_SENSITIVITY", + "text": "Trigger-Empfindlichkeit: " + }, + { + "id": "IDS_DLG_TRIGGER_SOURCES", + "text": "Triggerquellen: " + }, + { + "id": "IDS_DLG_TRIGGER_TYPES", + "text": "Triggertypen: " + }, + { + "id": "IDS_DLG_RISING_EDGE", + "text": "Steigende Flanke" + }, + { + "id": "IDS_DLG_FALLING_EDGE", + "text": "Fallende Flanke" + }, + { + "id": "IDS_DLG_AUTO", + "text": "Automatisch" + }, + { + "id": "IDS_DLG_CHANNEL_0", + "text": "Kanal 0" + }, + { + "id": "IDS_DLG_CHANNEL_1", + "text": "Kanal 1" + }, + { + "id": "IDS_DLG_CHANNEL_0_AND_1", + "text": "Kanal 0 && 1" + }, + { + "id": "IDS_DLG_CHANNEL_0_OR_1", + "text": "Kanal 0 | 1" + }, + { + "id": "IDS_DLG_KEY_DECODER_SEARCH", + "text": "Dekodierer suchen..." + }, + { + "id": "IDS_DLG_TIME_SAMPLES", + "text": "Zeit / Abtastwerte" + }, + { + "id": "IDS_DLG_MOUSE_MEASUREMENT", + "text": "Mausmessung" + }, + { + "id": "IDS_DLG_ENABLE_FLOATING_MEASUREMENT", + "text": "Schwebende Messung aktivieren" + }, + { + "id": "IDS_DLG_CURSOR_DISTANCE", + "text": "Cursorabstand" + }, + { + "id": "IDS_DLG_EDGES", + "text": "Flanken" + }, + { + "id": "IDS_DLG_CURSORS", + "text": "Cursor" + }, + { + "id": "IDS_DLG_RIS_OR_FAL_EDGE", + "text": "Steigende/Fallende Flanken" + }, + { + "id": "IDS_DLG_W", + "text": "Breite: " + }, + { + "id": "IDS_DLG_P", + "text": "Periode: " + }, + { + "id": "IDS_DLG_F", + "text": "Freq.: " + }, + { + "id": "IDS_DLG_D", + "text": "Tast: " + }, + { + "id": "IDS_DLG_SEARCH", + "text": "Suchen" + }, + { + "id": "IDS_DLG_MATCHING_ITEMS", + "text": "Treffer:" + }, + { + "id": "IDS_DLG_OUT_OF_MEMORY", + "text": "Kein Speicher mehr" + }, + { + "id": "IDS_DLG_SEARCHING", + "text": "Suchen..." + }, + { + "id": "IDS_DLG_SEARCH_PREVIOUS", + "text": "Vorherigen suchen..." + }, + { + "id": "IDS_DLG_SEARCH_NEXT", + "text": "Nächsten suchen..." + }, + { + "id": "IDS_DLG_SIMPLE_TRIGGER", + "text": "Einfacher Trigger" + }, + { + "id": "IDS_DLG_ADVANCED_TRIGGER", + "text": "Erweiterter Trigger" + }, + { + "id": "IDS_DLG_TOTAL_TRIGGER_STAGES", + "text": "Gesamte Triggerstufen: " + }, + { + "id": "IDS_DLG_START_FLAG", + "text": "Startflag: " + }, + { + "id": "IDS_DLG_STOP_FLAG", + "text": "Stoppflag: " + }, + { + "id": "IDS_DLG_CLOCK_FLAG", + "text": "Taktflag: " + }, + { + "id": "IDS_DLG_DATA_CHANNEL", + "text": "Datenkanal: " + }, + { + "id": "IDS_DLG_DATA_VALUE", + "text": "Datenwert: " + }, + { + "id": "IDS_DLG_SERIAL_TRIGGER", + "text": "Serieller Trigger" + }, + { + "id": "IDS_DLG_STAGE_TRIGGER", + "text": "Stufentrigger" + }, + { + "id": "IDS_DLG_SERIAL_NOTE_LABEL", + "text": "X: Beliebig\n0: Niedriger Pegel\n1: Hoher Pegel\nR: Steigende Flanke\nF: Fallende Flanke\nC: Steigende/Fallende Flanke" + }, + { + "id": "IDS_DLG_DATA_BITS", + "text": "Datenbits" + }, + { + "id": "IDS_DLG_INV", + "text": "Inv" + }, + { + "id": "IDS_DLG_COUNTER", + "text": "Zähler" + }, + { + "id": "IDS_DLG_CONTIGUOUS", + "text": "Zusammenhängend" + }, + { + "id": "IDS_DLG_STAGE", + "text": "Stufe" + }, + { + "id": "IDS_DLG_OR", + "text": "Oder" + }, + { + "id": "IDS_DLG_AND", + "text": "Und" + }, + { + "id": "IDS_DLG_HZ", + "text": "Hz" + }, + { + "id": "IDS_DLG_UNSHOWN", + "text": "Ausgeblendet" + }, + { + "id": "ZOOM_IN_FOR_DETAILS", + "text": "Für Details hineinzoomen" + }, + { + "id": "IDS_DLG_DECODETRACE_ERROR1", + "text": "Fehler: " + }, + { + "id": "IDS_DLG_DECODETRACE_ERROR2", + "text": "Fehler: ..." + }, + { + "id": "IDS_DLG_ADD_GROUP", + "text": "Gruppe hinzufügen" + }, + { + "id": "IDS_DLG_DEL_GROUP", + "text": "Gruppe entfernen" + }, + { + "id": "IDS_DLG_SET_CHANNEL_COLOUR", + "text": "Kanalfarbe festlegen" + }, + { + "id": "IDS_DLG_LISSAJOUS_FIGURE", + "text": "Lissajous-Figur" + }, + { + "id": "IDS_DLG_DATA_SOURCE_ERROR", + "text": "Datenquellenfehler." + }, + { + "id": "IDS_DLG_ADD_X_CURSOR", + "text": "X-Cursor hinzufügen" + }, + { + "id": "IDS_DLG_ADD_Y_CURSOR", + "text": "Y-Cursor hinzufügen" + }, + { + "id": "IDS_DLG_AUTO_ROLL", + "text": "Auto(Rollen)" + }, + { + "id": "IDS_DLG_WAITING_TRIG", + "text": "Warte auf Trigger" + }, + { + "id": "IDS_DLG_TRIG_D", + "text": "Getriggert" + }, + { + "id": "IDS_DLG_TRIGGERED", + "text": "Getriggert! " + }, + { + "id": "IDS_DLG_CAPTURED", + "text": "% Erfasst" + }, + { + "id": "IDS_DLG_WAITING_FOR_TRIGGER", + "text": "Warte auf Trigger! " + }, + { + "id": "IDS_DLG_RISING", + "text": "Steigend: " + }, + { + "id": "IDS_DLG_FALLING", + "text": "Fallend: " + }, + { + "id": "IDS_DLG_Edges_1", + "text": "Flanken: " + }, + { + "id": "IDS_DLG_WIDTH", + "text": "Breite: " + }, + { + "id": "IDS_DLG_PERIOD", + "text": "Periode: " + }, + { + "id": "IDS_DLG_FREQUENCY", + "text": "Frequenz: " + }, + { + "id": "IDS_DLG_DUTY_CYCLE", + "text": "Tastverhältnis: " + }, + { + "id": "IDS_DLG_SAMPLES_MEAS", + "text": "Abtastwerte: " + }, + { + "id": "IDS_DLG_SAMPLES", + "text": "Abtastwerte" + }, + { + "id": "IDS_DLG_TIME", + "text": "Zeit" + }, + { + "id": "IDS_DLG_RULER_UNITS", + "text": "Lineal / Cursor-Einheiten" + }, + { + "id": "IDS_DLG_MEASURE", + "text": "Messen" + }, + { + "id": "IDS_DLG_TRIGGER_TIME", + "text": "Triggerzeit: " + }, + { + "id": "IDS_DLG_SAMPLES_CAPTURED", + "text": "Abtastwerte erfasst!" + }, + { + "id": "IDS_DLG_FILE_THRESHOLD", + "text": "Schwellenwert: " + }, + { + "id": "IDS_DLG_VIEW_CAPTURE", + "text": "Aufnehmen" + }, + { + "id": "IDS_DLG_CHAN_NUM_ERR2", + "text": "Benötigt die Daten von zwei Kanälen." + }, + { + "id": "IDS_DLG_PATH_NAME", + "text": "Pfad" + }, + { + "id": "IDS_DLG_DECODER_IF_TRANS", + "text": "Parameternamen übersetzen" + }, + { + "id": "IDS_DLG_LOG_PATH", + "text": "Dateipfad" + }, + { + "id": "IDS_DLG_OPEN", + "text": "Öffnen" + }, + { + "id": "IDS_DLG_CLEARE", + "text": "Löschen" + }, + { + "id": "IDS_DLG_APPEND_MODE", + "text": "Anhängemodus" + }, + { + "id": "IDS_DLG_TRIG_DISPLAY_MIDDLE", + "text": "Triggerpos. in der Mitte anzeigen" + }, + { + "id": "IDS_DLG_DISPLAY_PROFILE_IN_BAR", + "text": "Profil in der Titelleiste anzeigen" + }, + { + "id": "IDS_DLG_DISPLAY_ANTIALIAS", + "text": "Kantenglättung" + }, + { + "id": "IDS_DLG_SERIAL_HEX", + "text": "Hex:" + }, + { + "id": "IDS_DLG_SERIAL_INPUT_AS_HEX", + "text": "Eingabe im Hex-Format" + }, + { + "id": "IDS_DLG_USE_ABORT_DATA_REPEAT", + "text": "Neueste Daten beim Stopp im Wiederholmodus aktualisieren" + }, + { + "id": "IDS_DLG_FONT_SIZE", + "text": "Schriftgröße" + }, + { + "id": "IDS_DLG_GROUP_LOGIC", + "text": "Logikanalysator" + }, + { + "id": "IDS_DLG_GROUP_DSO", + "text": "Oszilloskop" + }, + { + "id": "IDS_DLG_GROUP_UI", + "text": "Benutzeroberfläche" + }, + { + "id": "IDS_DLG_ABORT", + "text": "Abbrechen" + }, + { + "id": "IDS_DLG_AUTO_SCROLL_LATEAST_DATA", + "text": "Automatisch zu den neuesten Daten scrollen" + }, + { + "id": "IDS_DLG_DATA_OUT_OFF_RANGE", + "text": "Daten außerhalb des Bereichs" + }, + { + "id": "IDS_DLG_START_CURSOR", + "text": "Start" + }, + { + "id": "IDS_DLG_END_CURSOR", + "text": "Ende" + }, + { + "id": "IDS_DSO_CTR_EN", + "text": "EIN" + }, + { + "id": "IDS_DSO_CTR_DIS", + "text": "AUS" + }, + { + "id": "IDS_DSO_CTR_GND", + "text": "GND" + }, + { + "id": "IDS_DSO_CTR_DC", + "text": "DC" + }, + { + "id": "IDS_DSO_CTR_AC", + "text": "AC" + }, + { + "id": "IDS_DSO_CTR_AUTO", + "text": "AUTO" + }, + { + "id": "IDS_CALIB_VGAIN", + "text": "VGAIN" + }, + { + "id": "IDS_CALIB_VOFF", + "text": "VOFF" + }, + { + "id": "IDS_CALIB_VCOMB", + "text": "VCOMB" + }, + { + "id": "IDS_FFT_WINDOW_RECTANGLE", + "text": "Rechteck" + }, + { + "id": "IDS_FFT_WINDOW_HANN", + "text": "Hann" + }, + { + "id": "IDS_FFT_WINDOW_HAMMING", + "text": "Hamming" + }, + { + "id": "IDS_FFT_WINDOW_BLACKMAN", + "text": "Blackman" + }, + { + "id": "IDS_FFT_WINDOW_FLATTOP", + "text": "Flache Spitze" + }, + { + "id": "IDS_FFT_MODE_LINEARRSM", + "text": "Linear RMS" + }, + { + "id": "IDS_DLG_DECODER_DYNAMIC_FONT_WIDTH", + "text": "Adaptive Dekodierer-Schriftbreite" + }, + { + "id": "IDS_DLG_DEFAULT_FONT", + "text": "Standard" + }, + { + "id": "IDS_DLG_MAX_FONT_WIDTH", + "text": "Max. Schriftbreite:" + }, + { + "id": "IDS_DLG_MIN_FONT_WIDTH", + "text": "Min. Schriftbreite:" + }, + { + "id": "IDS_DLG_VERTICAL_SCROLL_ACTION", + "text": "Vertikale Scroll-Aktion" + }, + { + "id": "IDS_DLG_VERTICAL_SCROLL_ACTION_SMOOTH_ZOOM", + "text": "Zoomen" + }, + { + "id": "IDS_DLG_VERTICAL_SCROLL_ACTION_VERTICAL_SCROLL", + "text": "Scrollen" + } +] diff --git a/lang/de/msg.json b/lang/de/msg.json new file mode 100644 index 000000000..8e01d10ef --- /dev/null +++ b/lang/de/msg.json @@ -0,0 +1,390 @@ +[ + { + "id": "IDS_MSG_OK", + "text": "Ok" + }, + { + "id": "IDS_MSG_SKIP", + "text": "Überspringen" + }, + { + "id": "IDS_MSG_CANCEL", + "text": "Abbrechen" + }, + { + "id": "IDS_MSG_ATTENTION", + "text": "Achtung" + }, + { + "id": "IDS_MSG_ERROR", + "text": "Fehler" + }, + { + "id": "IDS_MSG_INFORMATION", + "text": "Information" + }, + { + "id": "IDS_MSG_CONTINUE", + "text": "Weiter" + }, + { + "id": "IDS_MSG_AUTO_CALIB_START", + "text": "Die automatische Kalibrierung wird gestartet. Keine Sonden anschließen. \nDies kann einen Moment dauern!" + }, + { + "id": "IDS_MSG_AUTO_CALIB", + "text": "Automatische Kalibrierung" + }, + { + "id": "IDS_MSG_ADJUST_SAVE", + "text": "Bitte Nullpunkt-Offset anpassen und das Ergebnis speichern" + }, + { + "id": "IDS_MSG_SAVE_CAPDATE", + "text": "Aufgezeichnete Daten speichern?" + }, + { + "id": "IDS_MSG_NOT_FOND_DEFAULT_PROFILE", + "text": "Keine Standardsitzung für dieses Gerät gefunden!" + }, + { + "id": "IDS_MSG_OPEN_FILE_ERROR", + "text": "Fehler beim Öffnen der Datei!" + }, + { + "id": "IDS_MSG_PROFILE_NOT_COMPATIBLE", + "text": "Sitzung ist nicht kompatibel mit dem aktuellen Gerät oder Modus!" + }, + { + "id": "IDS_MSG_RESTORE_WINDOW_ERROR", + "text": "Fehler beim Wiederherstellen des Fensterstatus!" + }, + { + "id": "IDS_MSG_DECODE_INVAILD_CURSOR", + "text": "Ungültiger Cursorindex für den Abtastbereich!" + }, + { + "id": "IDS_MSG_SEL_FILENAME", + "text": "Bitte einen Dateinamen auswählen." + }, + { + "id": "IDS_MSG_DECODER_REPEAT", + "text": "Doppelte ID oder Name in den Dekodierern gefunden:" + }, + { + "id": "IDS_MSG_DECODER_LIST_EMPTY", + "text": "Dekodierliste ist leer!" + }, + { + "id": "IDS_MSG_NO_SEL_DECODER", + "text": "Bitte einen Dekodierer auswählen!" + }, + { + "id": "IDS_MSG_FIND_BASE_DECODER_ERROR", + "text": "Kein entsprechender Basis-Dekodierer gefunden!" + }, + { + "id": "IDS_MSG_NO_DECODER_DEL", + "text": "Kein Dekodierer zum Entfernen vorhanden!" + }, + { + "id": "IDS_MSG_DECODER_CONFIRM_DEL_ALL", + "text": "Alle Dekodierer wirklich entfernen?" + }, + { + "id": "IDS_MSG_DECODER_CONFIRM_DEL", + "text": "Diesen Dekodierer wirklich entfernen?" + }, + { + "id": "IDS_MSG_CLOSE_DEVICE", + "text": "Dieses Gerät wirklich entfernen?" + }, + { + "id": "IDS_MSG_SET_DEF_CAL_SETTING", + "text": "Alle Kalibrierungseinstellungen werden als Standard gespeichert!" + }, + { + "id": "IDS_MSG_ALL_CHANNEL_DISABLE", + "text": "Alle Kanäle deaktiviert! Bitte mindestens einen Kanal aktivieren." + }, + { + "id": "IDS_MSG_MAX_CHANNEL_COUNT_WARNING", + "text": "Aktueller Modus unterstützt maximal {0} Kanäle!" + }, + { + "id": "IDS_MSG_TRI_SET_ISSUE", + "text": "Problem mit der Triggereinstellung" + }, + { + "id": "IDS_MSG_CHANGE_HOR_TRI_POS_FAIL", + "text": "Änderung der horizontalen Triggerposition fehlgeschlagen!" + }, + { + "id": "IDS_MSG_CHANGE_TRI_HOLDOFF_TIME_FAIL", + "text": "Änderung der Trigger-Haltezeit fehlgeschlagen!" + }, + { + "id": "IDS_MSG_CHANGE_SENSITIVITY_FAIL", + "text": "Änderung der Trigger-Empfindlichkeit fehlgeschlagen!" + }, + { + "id": "IDS_MSG_CHANGE_SOURCE_FAIL", + "text": "Änderung der Triggerquelle fehlgeschlagen!" + }, + { + "id": "IDS_MSG_CHANGE_CHANNEL_FAIL", + "text": "Änderung des Triggerkanals fehlgeschlagen!" + }, + { + "id": "IDS_MSG_CHANGE_TYPE_FAIL", + "text": "Änderung des Triggertyps fehlgeschlagen!" + }, + { + "id": "IDS_MSG_PLEASE_INSERT_CURSOR", + "text": "Bitte zuerst einen Cursor einfügen, um die Cursormessung zu verwenden." + }, + { + "id": "IDS_MSG_SEARCH", + "text": "Suchen" + }, + { + "id": "IDS_MSG_NO_SAMPLE_DATA", + "text": "Keine Abtastdaten vorhanden!" + }, + { + "id": "IDS_MSG_SEARCH_AT_START", + "text": "Suchcursor an Anfangsposition!" + }, + { + "id": "IDS_MSG_PATTERN_NOT_FOUND", + "text": "Muster nicht gefunden!" + }, + { + "id": "IDS_MSG_SEARCH_AT_END", + "text": "Suchcursor an Endposition!" + }, + { + "id": "IDS_MSG_TRIGGER", + "text": "Trigger" + }, + { + "id": "IDS_MSG_STREAM_NO_AD_TRIGGER", + "text": "Stream-Modus unterstützt keinen erweiterten Trigger!" + }, + { + "id": "IDS_MSG_AD_TRIGGER_NEED_HARDWARE", + "text": "Erweiterter Trigger benötigt DSLogic-Hardware-Unterstützung!" + }, + { + "id": "IDS_MSG_SET_TRI_MULTI_CHANNEL", + "text": "Trigger auf mehreren Kanälen gesetzt!\nAufnahme wird nur ausgelöst, wenn alle gesetzten Kanäle zur gleichen Abtastzeit erfüllt sind" + }, + { + "id": "IDS_MSG_NOT_SHOW_AGAIN", + "text": "Nicht mehr anzeigen" + }, + { + "id": "IDS_MSG_CLEAR_TRIG", + "text": "Trigger löschen" + }, + { + "id": "IDS_MSG_IGNORE", + "text": "Ignorieren" + }, + { + "id": "IDS_MSG_OPEN", + "text": "Öffnen" + }, + { + "id": "IDS_MSG_FAIL_TO_LOAD", + "text": "Laden fehlgeschlagen" + }, + { + "id": "IDS_MSG_HARDWARE_ERROR", + "text": "Hardwarefehler" + }, + { + "id": "IDS_MSG_HARDWARE_ERROR_DET", + "text": "Bitte Gerät erneut einstecken, um die Hardwarekonfiguration zu aktualisieren!" + }, + { + "id": "IDS_MSG_MALLOC_ERROR", + "text": "Speicherreservierungsfehler" + }, + { + "id": "IDS_MSG_MALLOC_ERROR_DET", + "text": "Nicht genug Speicher für diese Abtastung!\nBitte Abtasttiefe reduzieren!" + }, + { + "id": "IDS_MSG_PACKET_ERROR", + "text": "Paketfehler" + }, + { + "id": "IDS_MSG_PACKET_ERROR_DET", + "text": "Das empfangene Paketformat ist falsch!" + }, + { + "id": "IDS_MSG_DATA_OVERFLOW", + "text": "Datenüberlauf" + }, + { + "id": "IDS_MSG_DATA_OVERFLOW_DET", + "text": "USB-Bandbreite kann die aktuelle Abtastrate nicht unterstützen!\nBitte Abtastrate reduzieren!" + }, + { + "id": "IDS_MSG_UNDEFINED_ERROR", + "text": "Unbekannter Fehler" + }, + { + "id": "IDS_MSG_UNDEFINED_ERROR_DET", + "text": "Unerwarteter Fehler!" + }, + { + "id": "IDS_MSG_STORESESS_SAVESTART_ERROR1", + "text": "DSView unterstützt derzeit keine\nDateispeicherung für mehrere Datentypen." + }, + { + "id": "IDS_MSG_STORESESS_SAVESTART_ERROR2", + "text": "Keine Daten zum Speichern." + }, + { + "id": "IDS_MSG_STORESESS_SAVESTART_ERROR3", + "text": "Kein Dateiname." + }, + { + "id": "IDS_MSG_STORESESS_SAVESTART_ERROR4", + "text": "Erstellen der temporären Dateidaten fehlgeschlagen." + }, + { + "id": "IDS_MSG_STORESESS_SAVESTART_ERROR5", + "text": "Erstellen der Dekodierer-Dateidaten fehlgeschlagen." + }, + { + "id": "IDS_MSG_STORESESS_SAVESTART_ERROR6", + "text": "Erstellen der Sitzungs-Dateidaten fehlgeschlagen." + }, + { + "id": "IDS_MSG_STORESESS_SAVESTART_ERROR7", + "text": "Erstellen der ZIP-Datei fehlgeschlagen." + }, + { + "id": "IDS_MSG_STORESESS_SAVEPROC_ERROR1", + "text": "ZIP-Datei konnte nicht erstellt werden, Speicherreservierungsfehler." + }, + { + "id": "IDS_MSG_STORESESS_SAVEPROC_ERROR2", + "text": "ZIP-Datei konnte nicht erstellt werden, bitte Schreibberechtigung für diesen Pfad prüfen." + }, + { + "id": "IDS_MSG_STORESESS_EXPORTSTART_ERROR1", + "text": "DSView unterstützt derzeit keinen\nDateiexport für mehrere Datentypen." + }, + { + "id": "IDS_MSG_STORESESS_EXPORTSTART_ERROR2", + "text": "Keine Daten zum Speichern." + }, + { + "id": "IDS_MSG_STORESESS_EXPORTSTART_ERROR3", + "text": "Kein Dateiname gesetzt." + }, + { + "id": "IDS_MSG_STORESESS_EXPORTSTART_ERROR4", + "text": "Ungültiges Exportformat." + }, + { + "id": "IDS_MSG_STORESESS_EXPORTPROC_ERROR1", + "text": "Datentyp wird nicht unterstützt." + }, + { + "id": "IDS_MSG_STORESESS_EXPORTPROC_ERROR2", + "text": "Pufferspeicher fehlgeschlagen." + }, + { + "id": "IDS_MSG_SAVE_FILE", + "text": "Datei speichern" + }, + { + "id": "IDS_MSG_EXPORT_DATA", + "text": "Daten exportieren" + }, + { + "id": "IDS_MSG_DECODERSTACK_DECODE_WORK_ERROR", + "text": "Ein oder mehrere erforderliche Kanäle wurden nicht angegeben!" + }, + { + "id": "IDS_MSG_DECODERSTACK_DECODE_DATA_ERROR", + "text": "Mindestens einer der ausgewählten Kanäle ist nicht aktiviert." + }, + { + "id": "IDS_MSG_DECODERSTACK_DECODE_STACK_ERROR", + "text": "Dekodierer-Instanz konnte nicht erstellt werden" + }, + { + "id": "IDS_MSG_MESSAGE", + "text": "Nachricht" + }, + { + "id": "IDS_MSG_BOX_CONFIRM", + "text": "Bestätigen" + }, + { + "id": "IDS_MSG_NO_DATA", + "text": "Keine Daten vorhanden!" + }, + { + "id": "IDS_MSG_TO_SWITCH_DEVICE", + "text": "Zum neuen Gerät wechseln?" + }, + { + "id": "IDS_MSG_DISABLED_CHANNEL_TRIG", + "text": "Deaktivierte Kanäle können nicht für den Trigger verwendet werden!" + }, + { + "id": "IDS_MSG_TO_RECONNECT_FOR_FIRMWARE", + "text": "Die Firmware-Version ist falsch, bitte Gerät erneut einstecken!" + }, + { + "id": "IDS_MSG_DEVICE_BUSY_SWITCH_FAILED", + "text": "Das Gerät ist belegt, Wechsel fehlgeschlagen!" + }, + { + "id": "IDS_MSG_DEVICE_NO_DRIVER", + "text": "Der Gerätetreiber ist möglicherweise nicht installiert!" + }, + { + "id": "IDS_MSG_NO_ENABLED_CHANNEL", + "text": "Keine Kanäle aktiviert!" + }, + { + "id": "IDS_MSG_FILE_NOT_EXIST", + "text": "Datei existiert nicht!" + }, + { + "id": "IDS_MSG_TO_CLEAR_LOG", + "text": "Protokolldatei wirklich löschen?" + }, + { + "id": "IDS_MSG_DEVICE_SPEED_TOO_LOW", + "text": "Fehler: USB-Port-Geschwindigkeit ist zu niedrig (<480 Mbit/s)!" + }, + { + "id": "IDS_MSG_FIRMWARE_NOT_EXIST", + "text": "Fehler: Firmware-Datei existiert nicht!" + }, + { + "id": "IDS_MSG_DEVICE_USB_IO_ERROR", + "text": "Fehler: USB-IO-Fehler!" + }, + { + "id": "IDS_MSG_DATA_RANGE_ERROR", + "text": "Datenbereich ist fehlerhaft!" + }, + { + "id": "IDS_MSG_DATA_RANGE_HAVE_NO_DATA", + "text": "Keine Daten im Datenbereich!" + }, + { + "id": "IDS_MSG_NO_DECODED_RESULT", + "text": "Keine Daten zum Exportieren!" + } +] diff --git a/lang/de/toolbar.json b/lang/de/toolbar.json new file mode 100644 index 000000000..97e1edc80 --- /dev/null +++ b/lang/de/toolbar.json @@ -0,0 +1,190 @@ +[ + { + "id": "IDS_DEVICE_MODE_LOGIC", + "text": "&Logikanalysator" + }, + { + "id": "IDS_DEVICE_MODE_ANALOG", + "text": "&Datenerfassung" + }, + { + "id": "IDS_DEVICE_MODE_DSO", + "text": "&Oszilloskop" + }, + { + "id": "IDS_TOOLBAR_DEVICE_TYPE_DEMO", + "text": "Demo" + }, + { + "id": "IDS_TOOLBAR_DEVICE_TYPE_FILE", + "text": "Datei" + }, + { + "id": "IDS_TOOLBAR_DEVICE_OPTION", + "text": "&Optionen" + }, + { + "id": "IDS_TOOLBAR_CAPTURE_MODE", + "text": "&Modus" + }, + { + "id": "IDS_TOOLBAR_CAPTURE_MODE_SINGLE", + "text": "&Einzeln" + }, + { + "id": "IDS_TOOLBAR_CAPTURE_MODE_REPEAT", + "text": "&Wiederholen" + }, + { + "id": "IDS_TOOLBAR_CAPTURE_MODE_LOOP", + "text": "&Endlos" + }, + { + "id": "IDS_TOOLBAR_RUN_START", + "text": "&Starten" + }, + { + "id": "IDS_TOOLBAR_RUN_STOP", + "text": "S&topp" + }, + { + "id": "IDS_TOOLBAR_ONE_SINGLE", + "text": "E&inzel" + }, + { + "id": "IDS_TOOLBAR_ONE_INSTANT", + "text": "&Sofort" + }, + { + "id": "IDS_TOOLBAR_ONE_STOP", + "text": "S&topp" + }, + { + "id": "IDS_TOOLBAR_TRIGGER", + "text": "&Trigger" + }, + { + "id": "IDS_TOOLBAR_DECODE", + "text": "&Dekodieren" + }, + { + "id": "IDS_TOOLBAR_MEASURE", + "text": "&Messen" + }, + { + "id": "IDS_TOOLBAR_SEARCH", + "text": "&Suchen" + }, + { + "id": "IDS_TOOLBAR_FUNCTION", + "text": "&Funktion" + }, + { + "id": "IDS_TOOLBAR_FUNCTION_FFT", + "text": "&FFT" + }, + { + "id": "IDS_TOOLBAR_FUNCTION_MATH", + "text": "&Mathematik" + }, + { + "id": "IDS_TOOLBAR_DISPLAY", + "text": "&Anzeige" + }, + { + "id": "IDS_TOOLBAR_DISPLAY_THEMES", + "text": "&Designs" + }, + { + "id": "IDS_TOOLBAR_DISPLAY_LISSAJOUS", + "text": "&Lissajous" + }, + { + "id": "IDS_TOOLBAR_DISPLAY_THEMES_DARK", + "text": "&Dunkel" + }, + { + "id": "IDS_TOOLBAR_DISPLAY_THEMES_LIGHT", + "text": "&Hell" + }, + { + "id": "IDS_TOOLBAR_DISPLAY_OPTIONS", + "text": "&Optionen" + }, + { + "id": "IDS_TOOLBAR_FILE", + "text": "&Datei" + }, + { + "id": "IDS_TOOLBAR_FILE_CONFIG", + "text": "&Konfiguration" + }, + { + "id": "IDS_TOOLBAR_FILE_CONFIG_LOAD", + "text": "&Sitzung laden" + }, + { + "id": "IDS_TOOLBAR_FILE_CONFIG_STORE", + "text": "&Sitzung speichern" + }, + { + "id": "IDS_TOOLBAR_FILE_CONFIG_DEFAULT", + "text": "&Standardsitzung laden" + }, + { + "id": "IDS_TOOLBAR_FILE_OPEN", + "text": "&Öffnen..." + }, + { + "id": "IDS_TOOLBAR_FILE_SAVE", + "text": "&Speichern..." + }, + { + "id": "IDS_TOOLBAR_FILE_EXPORT", + "text": "&Exportieren..." + }, + { + "id": "IDS_TOOLBAR_FILE_CAPTURE", + "text": "&Aufnehmen..." + }, + { + "id": "IDS_TOOLBAR_HELP", + "text": "&Hilfe" + }, + { + "id": "IDS_TOOLBAR_HELP_LANG", + "text": "&Sprache" + }, + { + "id": "IDS_TOOLBAR_HELP_LANG_EN", + "text": "&Englisch" + }, + { + "id": "IDS_TOOLBAR_HELP_LANG_CN", + "text": "Chinesisch(&C)" + }, + { + "id": "IDS_TOOLBAR_HELP_LANG_DE", + "text": "&Deutsch" + }, + { + "id": "IDS_TOOLBAR_HELP_ABOUT", + "text": "&Über..." + }, + { + "id": "IDS_TOOLBAR_HELP_MANUAL", + "text": "&Handbuch..." + }, + { + "id": "IDS_TOOLBAR_HELP_BUG", + "text": "&Fehler melden" + }, + { + "id": "IDS_TOOLBAR_HELP_UPDATE", + "text": "&Aktualisieren" + }, + { + "id": "IDS_TOOLBAR_HELP_LOG", + "text": "&Protokolloptionen" + } +] diff --git a/lang/en/dlg.json b/lang/en/dlg.json index 2959ac107..d2c8414eb 100644 --- a/lang/en/dlg.json +++ b/lang/en/dlg.json @@ -810,5 +810,33 @@ { "id": "IDS_FFT_MODE_LINEARRSM", "text": "Linear RMS" + }, + { + "id": "IDS_DLG_DECODER_DYNAMIC_FONT_WIDTH", + "text": "Decoder adaptive font width" + }, + { + "id": "IDS_DLG_DEFAULT_FONT", + "text": "Default" + }, + { + "id": "IDS_DLG_MAX_FONT_WIDTH", + "text": "Max font width:" + }, + { + "id": "IDS_DLG_MIN_FONT_WIDTH", + "text": "Min font width:" + }, + { + "id": "IDS_DLG_VERTICAL_SCROLL_ACTION", + "text": "Vertical Scroll Action" + }, + { + "id": "IDS_DLG_VERTICAL_SCROLL_ACTION_SMOOTH_ZOOM", + "text": "Zoom" + }, + { + "id": "IDS_DLG_VERTICAL_SCROLL_ACTION_VERTICAL_SCROLL", + "text": "Scroll" } ] diff --git a/lang/en/toolbar.json b/lang/en/toolbar.json index ed511f43e..1fb7fae2f 100644 --- a/lang/en/toolbar.json +++ b/lang/en/toolbar.json @@ -162,7 +162,11 @@ { "id": "IDS_TOOLBAR_HELP_LANG_CN", "text": "中文(&C)" - }, + }, + { + "id": "IDS_TOOLBAR_HELP_LANG_DE", + "text": "&Deutsch" + }, { "id": "IDS_TOOLBAR_HELP_ABOUT", "text": "&About..." From 00b154d02650e5e69a8ccd776a7c728308e3bd37 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Tue, 30 Jun 2026 23:45:37 +0200 Subject: [PATCH 02/34] Fix most build warnings --- CMakeLists.txt | 22 ++++++++++---------- DSView/main.cpp | 6 +++++- DSView/pv/config/appconfig.cpp | 2 +- DSView/pv/data/decode/annotationrestable.cpp | 7 ++++--- DSView/pv/dialogs/calibration.cpp | 2 +- DSView/pv/dialogs/deviceoptions.cpp | 2 +- DSView/pv/dialogs/storeprogress.cpp | 6 ++---- DSView/pv/dock/keywordlineedit.cpp | 2 -- DSView/pv/dock/measuredock.cpp | 2 -- DSView/pv/dock/searchdock.cpp | 2 -- DSView/pv/mainframe.cpp | 12 ++++------- DSView/pv/mainwindow.cpp | 4 ++-- DSView/pv/storesession.cpp | 3 --- DSView/pv/toolbars/titlebar.cpp | 6 +++--- DSView/pv/view/cursor.cpp | 2 +- DSView/pv/view/decodetrace.cpp | 5 +---- DSView/pv/view/dsldial.cpp | 2 +- DSView/pv/view/ruler.cpp | 1 - DSView/pv/view/view.cpp | 3 ++- DSView/pv/view/viewport.cpp | 9 ++++---- common/minizip/crypt.h | 1 + common/minizip/ioapi.c | 10 +++++++++ common/minizip/zip.c | 5 +++-- libsigrok4DSL/hardware/DSL/dscope.c | 9 ++++---- libsigrok4DSL/hardware/DSL/dslogic.c | 5 +++-- libsigrok4DSL/hardware/demo/demo.c | 10 ++++----- libsigrok4DSL/output/csv.c | 10 +++------ 27 files changed, 73 insertions(+), 77 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e5bf5b27a..01173bafd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,8 +31,8 @@ set(DS_TITLE DSView) set(DS_DESCRIPTION "A GUI for instruments of DreamSourceLab") set(DS_VERSION_MAJOR 1) -set(DS_VERSION_MINOR 4) -set(DS_VERSION_MICRO 2) +set(DS_VERSION_MINOR 5) +set(DS_VERSION_MICRO 0) set(DS_VERSION_STRING ${DS_VERSION_MAJOR}.${DS_VERSION_MINOR}.${DS_VERSION_MICRO}) configure_file ( @@ -46,8 +46,8 @@ configure_file ( include(FindPkgConfig) include(GNUInstallDirs) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake") -#list(APPEND CMAKE_PREFIX_PATH "xxx.cmake find path") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake") +#list(APPEND CMAKE_PREFIX_PATH "xxx.cmake find path") find_package(PkgConfig) @@ -135,7 +135,7 @@ else() endif() endif() endif() - + #=============================================================================== #= FFTW #------------------------------------------------------------------------------- @@ -322,7 +322,7 @@ set(DSView_SOURCES DSView/pv/dialogs/protocolexp.cpp DSView/pv/dialogs/fftoptions.cpp DSView/pv/data/mathstack.cpp - DSView/pv/view/mathtrace.cpp + DSView/pv/view/mathtrace.cpp DSView/pv/toolbars/titlebar.cpp DSView/pv/mainframe.cpp DSView/pv/widgets/border.cpp @@ -443,7 +443,7 @@ set(DSView_HEADERS DSView/pv/view/signal.h DSView/pv/view/logicsignal.h DSView/pv/view/analogsignal.h - DSView/pv/view/dsosignal.h + DSView/pv/view/dsosignal.h DSView/pv/dock/protocoldock.h DSView/pv/data/decoderstack.h DSView/pv/view/decodetrace.h @@ -471,7 +471,7 @@ set(DSView_HEADERS DSView/pv/ui/fn.h DSView/pv/ui/xtoolbutton.h ) - + #=============================================================================== #= libsigrok4DSL source #------------------------------------------------------------------------------- @@ -514,7 +514,7 @@ set(libsigrok4DSL_HEADERS libsigrok4DSL/hardware/DSL/command.h libsigrok4DSL/hardware/DSL/dsl.h ) - + #=============================================================================== #= libsigrokdecode4DSL source #------------------------------------------------------------------------------- @@ -690,7 +690,7 @@ if(WIN32) install(DIRECTORY DSView/res DESTINATION .) install(DIRECTORY DSView/demo DESTINATION .) install(DIRECTORY lang DESTINATION .) - install(DIRECTORY libsigrokdecode4DSL/decoders + install(DIRECTORY libsigrokdecode4DSL/decoders DESTINATION . PATTERN "__pycache__" EXCLUDE) install(DIRECTORY cross-compile-windows/python-setup/python-dist/. DESTINATION .) @@ -703,7 +703,7 @@ else() install(FILES DSView/icons/logo.svg DESTINATION share/icons/hicolor/scalable/apps RENAME dsview.svg) install(FILES DSView/icons/logo.svg DESTINATION share/pixmaps RENAME dsview.svg) - if(CMAKE_SYSTEM_NAME MATCHES "Linux") + if(CMAKE_SYSTEM_NAME MATCHES "Linux") install(FILES DSView/DSView.desktop DESTINATION /usr/share/applications RENAME dsview.desktop) add_compile_definitions(_DEFAULT_SOURCE) diff --git a/DSView/main.cpp b/DSView/main.cpp index f8fe25212..ee60bc705 100644 --- a/DSView/main.cpp +++ b/DSView/main.cpp @@ -159,11 +159,15 @@ bool bHighScale = true; bHighScale = false; } #endif +#if QT_VERSION < QT_VERSION_CHECK(6,0,0) if (bHighScale){ QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); } -#endif +#else + (void)bHighScale; +#endif +#endif //----------------------init app QApplication a(argcFinal, argvFinal); diff --git a/DSView/pv/config/appconfig.cpp b/DSView/pv/config/appconfig.cpp index 65838989c..17b1c01e6 100644 --- a/DSView/pv/config/appconfig.cpp +++ b/DSView/pv/config/appconfig.cpp @@ -566,7 +566,7 @@ QString GetDecodeScriptDir() // ./decoders if (dir1.exists(path)) { - return path; QColor GetStyleColor(); + return path; } QDir dir(QCoreApplication::applicationDirPath()); diff --git a/DSView/pv/data/decode/annotationrestable.cpp b/DSView/pv/data/decode/annotationrestable.cpp index 6f2ec96ab..9c248edef 100644 --- a/DSView/pv/data/decode/annotationrestable.cpp +++ b/DSView/pv/data/decode/annotationrestable.cpp @@ -21,8 +21,9 @@ #include "annotationrestable.h" #include -#include +#include #include +#include #include "../../log.h" #include "../../dsvdef.h" @@ -292,7 +293,7 @@ const char* AnnotationResTable::format_numberic(const char *hex_str, int fmt) return hex_str; } - strncpy(all_wr, sub_str, sublen); + memcpy(all_wr, sub_str, sublen); all_wr += sublen; sub_wr = sub_buf; //reset write buffer } @@ -320,7 +321,7 @@ const char* AnnotationResTable::format_numberic(const char *hex_str, int fmt) return hex_str; } - strncpy(all_wr, sub_str, sublen); + memcpy(all_wr, sub_str, sublen); all_wr += sublen; } diff --git a/DSView/pv/dialogs/calibration.cpp b/DSView/pv/dialogs/calibration.cpp index 9ddd066cb..be5e60be9 100644 --- a/DSView/pv/dialogs/calibration.cpp +++ b/DSView/pv/dialogs/calibration.cpp @@ -201,7 +201,7 @@ void Calibration::update_device_info() for (const GSList *l = _device_agent->get_channels(); l; l = l->next) { sr_channel *const probe = (sr_channel*)l->data; dex++; - assert(dex < _params.size()); + assert((size_t)dex < _params.size()); auto *form = &_params[dex]; assert(form->probe == probe); diff --git a/DSView/pv/dialogs/deviceoptions.cpp b/DSView/pv/dialogs/deviceoptions.cpp index baa7198b5..f2d4b1c92 100644 --- a/DSView/pv/dialogs/deviceoptions.cpp +++ b/DSView/pv/dialogs/deviceoptions.cpp @@ -705,7 +705,7 @@ void DeviceOptions::analog_probes(QGridLayout &layout) probe_widget->setLayout(probe_layout); bool ch_enabled = probe->enabled; - if (ch_dex < _lst_probe_enabled_status.size()){ + if ((size_t)ch_dex < _lst_probe_enabled_status.size()){ ch_enabled = _lst_probe_enabled_status[ch_dex]; } diff --git a/DSView/pv/dialogs/storeprogress.cpp b/DSView/pv/dialogs/storeprogress.cpp index bb2685a2c..5341a46e2 100644 --- a/DSView/pv/dialogs/storeprogress.cpp +++ b/DSView/pv/dialogs/storeprogress.cpp @@ -179,8 +179,6 @@ void StoreProgress::accept() uint64_t start_index = 0; uint64_t end_index = 0; - auto &cursor_list = _view->get_cursorList(); - int dex1 = _start_cursor->currentIndex(); int dex2 = _end_cursor->currentIndex(); @@ -279,7 +277,7 @@ void StoreProgress::save_run(ISessionDataGetter *getter) auto &cursor_list = _view->get_cursorList(); - for (int i=0; iget_cursorList(); - for (int i=0; isetFixedSize(editline->size()); this->setFixedSize(editline->size()); - QPoint pt = mapToGlobal(editline->rect().bottomLeft()); - QPoint p1 = editline->pos(); QPoint p2 = editline->mapToGlobal(p1); int x = p2.x() - p1.x(); diff --git a/DSView/pv/dock/measuredock.cpp b/DSView/pv/dock/measuredock.cpp index a08de5727..07866763a 100644 --- a/DSView/pv/dock/measuredock.cpp +++ b/DSView/pv/dock/measuredock.cpp @@ -646,8 +646,6 @@ void MeasureDock::update_dist() { auto &cursor_list = _view.get_cursorList(); - QColor bkColor = AppConfig::Instance().GetStyleColor(); - auto mode_rows = get_mode_rows(); for (auto &inf : mode_rows->_dist_row_list) diff --git a/DSView/pv/dock/searchdock.cpp b/DSView/pv/dock/searchdock.cpp index 17e52268f..9d2f04938 100644 --- a/DSView/pv/dock/searchdock.cpp +++ b/DSView/pv/dock/searchdock.cpp @@ -182,7 +182,6 @@ void SearchDock::on_previous() _is_busy = false; }); - Qt::WindowFlags flags = Qt::CustomizeWindowHint; QString title = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SEARCH_PREVIOUS), "Search Previous..."); QString cancelText = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CANCEL), "Cancel"); EdgeSearchProgressDialog dlg(this, title, cancelText); @@ -245,7 +244,6 @@ void SearchDock::on_next() _is_busy = false; }); - Qt::WindowFlags flags = Qt::CustomizeWindowHint; QString title = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SEARCH_NEXT), "Search Next..."); QString cancelText = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CANCEL), "Cancel"); EdgeSearchProgressDialog dlg(this, title, cancelText); diff --git a/DSView/pv/mainframe.cpp b/DSView/pv/mainframe.cpp index acc3a5611..f43b78cf8 100644 --- a/DSView/pv/mainframe.cpp +++ b/DSView/pv/mainframe.cpp @@ -251,7 +251,7 @@ void MainFrame::OnParentNativeEvent(ParentNativeEvent msg) void MainFrame::OnParentNaitveWindowEvent(int msg) { - + (void)msg; #ifdef _WIN32 if (_parentNativeWidget != NULL && msg == PARENT_EVENT_DISPLAY_CHANGED){ @@ -408,12 +408,8 @@ bool MainFrame::eventFilter(QObject *object, QEvent *event) { const QEvent::Type type = event->type(); const QMouseEvent *const mouse_event = (QMouseEvent*)event; - int newWidth = 0; - int newHeight = 0; - int newLeft = 0; - int newTop = 0; -#ifdef _WIN32 +#ifdef _WIN32 if (_parentNativeWidget != NULL){ return QFrame::eventFilter(object, event); } @@ -468,7 +464,7 @@ bool MainFrame::eventFilter(QObject *object, QEvent *event) QPoint pt; int k = 1; - pt = mouse_event->globalPos(); + pt = mouse_event->globalPosition().toPoint(); int datX = pt.x() - _clickPos.x(); int datY = pt.y() - _clickPos.y(); @@ -572,7 +568,7 @@ bool MainFrame::eventFilter(QObject *object, QEvent *event) _bDraging = true; _timer.start(50); - _clickPos = mouse_event->globalPos(); + _clickPos = mouse_event->globalPosition().toPoint(); _dragStartRegion = GetFormRegion(); } else if (type == QEvent::MouseButtonRelease) { diff --git a/DSView/pv/mainwindow.cpp b/DSView/pv/mainwindow.cpp index e80ae9453..eb67f179b 100644 --- a/DSView/pv/mainwindow.cpp +++ b/DSView/pv/mainwindow.cpp @@ -1397,9 +1397,9 @@ namespace pv if (language == LAN_CN) { - _qtTrans.load(":/qt_" + QString::number(language)); + (void)_qtTrans.load(":/qt_" + QString::number(language)); qApp->installTranslator(&_qtTrans); - _myTrans.load(":/my_" + QString::number(language)); + (void)_myTrans.load(":/my_" + QString::number(language)); qApp->installTranslator(&_myTrans); } else if (language == LAN_EN || language == LAN_DE) diff --git a/DSView/pv/storesession.cpp b/DSView/pv/storesession.cpp index 931a32b18..42bf0e490 100644 --- a/DSView/pv/storesession.cpp +++ b/DSView/pv/storesession.cpp @@ -555,7 +555,6 @@ bool StoreSession::meta_gen(data::Snapshot *snapshot, std::string &str) struct sr_channel *probe; int probecnt; char *s; - struct sr_status status; char meta[300] = {0}; sprintf(meta, "%s", "[version]\n"); str += meta; @@ -1190,8 +1189,6 @@ void StoreSession::export_exec(data::Snapshot *snapshot) void* data_buffer = analog_snapshot->get_data(); unsigned int usize = 8192; struct sr_datafeed_analog ap; - - unsigned char* read_buf = (unsigned char*)data_buffer; const uint64_t ring_start = analog_snapshot->get_ring_start(); diff --git a/DSView/pv/toolbars/titlebar.cpp b/DSView/pv/toolbars/titlebar.cpp index 79ef01b55..292fcbde0 100644 --- a/DSView/pv/toolbars/titlebar.cpp +++ b/DSView/pv/toolbars/titlebar.cpp @@ -227,7 +227,7 @@ void TitleBar::mousePressEvent(QMouseEvent* event) if (!bTopWidow || bClick ){ _is_draging = true; - _clickPos = event->globalPos(); + _clickPos = event->globalPosition().toPoint(); if (_titleParent != NULL){ _oldPos = _titleParent->GetParentPos(); @@ -252,8 +252,8 @@ void TitleBar::mouseMoveEvent(QMouseEvent *event) int datX = 0; int datY = 0; - datX = (event->globalPos().x() - _clickPos.x()); - datY = (event->globalPos().y() - _clickPos.y()); + datX = (event->globalPosition().toPoint().x() - _clickPos.x()); + datY = (event->globalPosition().toPoint().y() - _clickPos.y()); int x = _oldPos.x() + datX; int y = _oldPos.y() + datY; diff --git a/DSView/pv/view/cursor.cpp b/DSView/pv/view/cursor.cpp index 1c3e707d1..eec060188 100644 --- a/DSView/pv/view/cursor.cpp +++ b/DSView/pv/view/cursor.cpp @@ -49,7 +49,7 @@ const int Cursor::CloseSize = 10; Cursor::Cursor(View &view, int order, uint64_t sampleIndex) : TimeMarker(view, sampleIndex) { - _order = _order; + _order = order; } QRect Cursor::get_label_rect(const QRect &rect, bool &visible, bool has_hoff) diff --git a/DSView/pv/view/decodetrace.cpp b/DSView/pv/view/decodetrace.cpp index e79c0b2df..abddb49da 100644 --- a/DSView/pv/view/decodetrace.cpp +++ b/DSView/pv/view/decodetrace.cpp @@ -487,6 +487,7 @@ void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p, double end, int y, QColor fore, QColor back) { (void)fore; + (void)back; AppConfig &app = AppConfig::Instance(); bool fontStretch = app.appOptions.decoderDynamicFontWidth; @@ -531,9 +532,7 @@ void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p, // Try to find an annotation that will fit QString best_annotation; - int best_width = 0; int max_width = rect.width(); - bool isCondensed = false; QFontMetrics fm = p.fontMetrics(); QFont condensed_font = p.font(); double MIN_STRETCH = fontStretch @@ -562,8 +561,6 @@ void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p, if (stretch >= MIN_STRETCH) { // fitting annotation found with condensing best_annotation = a; - best_width = ceil((double)w * (stretch / 100.0)); - isCondensed = true; break; } } diff --git a/DSView/pv/view/dsldial.cpp b/DSView/pv/view/dsldial.cpp index 0453b3e08..aefb9bd02 100644 --- a/DSView/pv/view/dsldial.cpp +++ b/DSView/pv/view/dsldial.cpp @@ -99,7 +99,7 @@ void dslDial::paint(QPainter &p, QRectF dialRect, QColor dialColor, const QPoint displayIndex++; } - assert(displayIndex < _unit.count()); + assert((qsizetype)displayIndex < _unit.count()); pText = QString::number(displayValue) + _unit[displayIndex] + "/div"; diff --git a/DSView/pv/view/ruler.cpp b/DSView/pv/view/ruler.cpp index bfb169e88..dacbfbe47 100644 --- a/DSView/pv/view/ruler.cpp +++ b/DSView/pv/view/ruler.cpp @@ -757,7 +757,6 @@ void Ruler::draw_cursor_sel(QPainter &p) if (!cursor_list.empty()) { int index = 1; - auto i = cursor_list.begin(); for (auto curosr : cursor_list) { const QRectF cursorRect = get_cursor_sel_rect(index); diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index 3a1eeacfa..df8abccf8 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -935,7 +935,7 @@ bool View::eventFilter(QObject *object, QEvent *event) else _hover_point = mouse_event->pos(); } else if (object == _header) - _hover_point = QPoint(0, mouse_event->y()); + _hover_point = QPoint(0, mouse_event->pos().y()); else _hover_point = QPoint(-1, -1); @@ -1136,6 +1136,7 @@ void View::make_cursors_order() void View::add_cursor(QColor color, uint64_t sampleIndex) { + (void)color; Cursor *newCursor = new Cursor(*this, -1, sampleIndex); get_cursorList().push_back(newCursor); make_cursors_order(); diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp index 9f5399101..d735d2c24 100644 --- a/DSView/pv/view/viewport.cpp +++ b/DSView/pv/view/viewport.cpp @@ -673,7 +673,6 @@ void Viewport::mousePressEvent(QMouseEvent *event) else if (_view.session().get_device()->get_work_mode() == DSO) { if (_hover_hit) { const int64_t index = _view.pixel2index(event->pos().x()); - auto &cursor_list = _view.get_cursorList(); _view.add_cursor(index); _view.show_cursors(true); } @@ -983,7 +982,6 @@ void Viewport::set_action(ActionType action) void Viewport::onLogicMouseRelease(QMouseEvent *event) { bool quickScroll = AppConfig::Instance().appOptions.quickScroll; - bool isMaxWindow = AppControl::Instance()->TopWindowIsMaximized(); switch (_action_type) { @@ -1177,12 +1175,14 @@ void Viewport::onDsoMouseRelease(QMouseEvent *event) } break; } + default: + break; } } void Viewport::onAnalogMouseRelease(QMouseEvent *event) { - + (void)event; } void Viewport::mouseReleaseEvent(QMouseEvent *event) @@ -1290,7 +1290,6 @@ void Viewport::mouseDoubleClickEvent(QMouseEvent *event) index = _view.pixel2index(curX); } - auto &cursor_list = _view.get_cursorList(); _view.add_cursor(index); _view.show_cursors(true); } @@ -1320,7 +1319,6 @@ void Viewport::mouseDoubleClickEvent(QMouseEvent *event) uint64_t index; const double curX = event->pos().x(); index = _view.pixel2index(curX); - auto &cursor_list = _view.get_cursorList(); _view.add_cursor(index); _view.show_cursors(true); } @@ -1553,6 +1551,7 @@ void Viewport::set_receive_len(quint64 length) void Viewport::update(int event) { + (void)event; QWidget::update(); } diff --git a/common/minizip/crypt.h b/common/minizip/crypt.h index 1e9e8200b..d22a7d817 100644 --- a/common/minizip/crypt.h +++ b/common/minizip/crypt.h @@ -37,6 +37,7 @@ static int decrypt_byte(unsigned long* pkeys, const z_crc_t* pcrc_32_tab) unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an * unpredictable manner on 16-bit systems; not a problem * with any known compiler so far, though */ + (void)pcrc_32_tab; temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2; return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); diff --git a/common/minizip/ioapi.c b/common/minizip/ioapi.c index 3c5c1dbf6..86d4b5f34 100644 --- a/common/minizip/ioapi.c +++ b/common/minizip/ioapi.c @@ -98,6 +98,7 @@ static int ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream)); static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode) { + (void)opaque; FILE* file = NULL; const char* mode_fopen = NULL; if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) @@ -116,6 +117,7 @@ static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, in static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode) { + (void)opaque; #ifdef _WIN32 FILE* file = NULL; const wchar_t* mode_fopen = NULL; @@ -164,6 +166,7 @@ static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size) { + (void)opaque; uLong ret; ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream); return ret; @@ -171,6 +174,7 @@ static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size) { + (void)opaque; uLong ret; ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream); return ret; @@ -178,6 +182,7 @@ static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const voi static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream) { + (void)opaque; long ret; ret = ftell((FILE *)stream); return ret; @@ -186,6 +191,7 @@ static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream) static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream) { + (void)opaque; ZPOS64_T ret; ret = FTELLO_FUNC((FILE *)stream); return ret; @@ -193,6 +199,7 @@ static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream) static long ZCALLBACK fseek_file_func (voidpf opaque, voidpf stream, uLong offset, int origin) { + (void)opaque; int fseek_origin=0; long ret; switch (origin) @@ -216,6 +223,7 @@ static long ZCALLBACK fseek_file_func (voidpf opaque, voidpf stream, uLong offs static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) { + (void)opaque; int fseek_origin=0; long ret; switch (origin) @@ -242,6 +250,7 @@ static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream) { + (void)opaque; int ret; ret = fclose((FILE *)stream); return ret; @@ -249,6 +258,7 @@ static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream) static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream) { + (void)opaque; int ret; ret = ferror((FILE *)stream); return ret; diff --git a/common/minizip/zip.c b/common/minizip/zip.c index 44e88a9cb..360aecf8e 100644 --- a/common/minizip/zip.c +++ b/common/minizip/zip.c @@ -518,15 +518,16 @@ local ZPOS64_T zip64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_f if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) break; - for (i=(int)uReadSize-3; (i--)>0;) + for (i=(int)uReadSize-3; (i--)>0;) { if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) { uPosFound = uReadPos+i; break; } + } - if (uPosFound!=0) + if (uPosFound!=0) break; } TRYFREE(buf); diff --git a/libsigrok4DSL/hardware/DSL/dscope.c b/libsigrok4DSL/hardware/DSL/dscope.c index 1b5d1c01f..af9179b93 100644 --- a/libsigrok4DSL/hardware/DSL/dscope.c +++ b/libsigrok4DSL/hardware/DSL/dscope.c @@ -208,10 +208,11 @@ static GSList *scan(GSList *options) num = 0; is_speed_not_match = 0; - if (options != NULL) + if (options != NULL) { sr_info("Scan DSCope device with options."); - else + } else { sr_info("Scan DSCope device..."); + } conn = NULL; for (l = options; l; l = l->next) { @@ -429,7 +430,7 @@ static uint64_t dso_preoff(const struct sr_channel* ch) static uint64_t dso_offset(const struct sr_dev_inst *sdi, const struct sr_channel* ch) { uint64_t pwm_off = 0; - int offset_coarse, offset_fine; + int offset_coarse = 0, offset_fine = 0; int trans_coarse, trans_fine; struct DSL_context *devc = sdi->priv; const double offset_mid = (1 << (ch->bits - 1)); @@ -1853,7 +1854,7 @@ static int dev_open(struct sr_dev_inst *sdi) gboolean fpga_done; int ret; GSList *l; - gboolean zeroed; + gboolean zeroed = TRUE; struct DSL_context *devc = sdi->priv; if ((ret = dsl_dev_open(di, sdi, &fpga_done)) == SR_OK) { diff --git a/libsigrok4DSL/hardware/DSL/dslogic.c b/libsigrok4DSL/hardware/DSL/dslogic.c index 2382ba9f3..52f48846e 100644 --- a/libsigrok4DSL/hardware/DSL/dslogic.c +++ b/libsigrok4DSL/hardware/DSL/dslogic.c @@ -296,10 +296,11 @@ static GSList *scan(GSList *options) num = 0; is_speed_not_match = 0; - if (options != NULL) + if (options != NULL) { sr_info("Scan DSLogic device with options."); - else + } else { sr_info("Scan DSLogic device..."); + } conn = NULL; for (l = options; l; l = l->next) { diff --git a/libsigrok4DSL/hardware/demo/demo.c b/libsigrok4DSL/hardware/demo/demo.c index 877ae31d1..42ebabe92 100644 --- a/libsigrok4DSL/hardware/demo/demo.c +++ b/libsigrok4DSL/hardware/demo/demo.c @@ -72,13 +72,13 @@ static int b_load_directory = 0; static char* demo_mode_names[3] = {"logic", "dso", "analog"}; static const struct DEMO_channels logic_channel_modes[] = { - {DEMO_LOGIC125x16, LOGIC, SR_CHANNEL_LOGIC, 16, 1, SR_MHZ(1), SR_Mn(1), + {(enum DEMO_CHANNEL_ID)DEMO_LOGIC125x16, LOGIC, SR_CHANNEL_LOGIC, 16, 1, SR_MHZ(1), SR_Mn(1), SR_KHZ(50), SR_MHZ(125), "Use 16 Channels (Max 125MHz)"}, - {DEMO_LOGIC250x12, LOGIC, SR_CHANNEL_LOGIC, 12, 1, SR_MHZ(1), SR_Mn(1), + {(enum DEMO_CHANNEL_ID)DEMO_LOGIC250x12, LOGIC, SR_CHANNEL_LOGIC, 12, 1, SR_MHZ(1), SR_Mn(1), SR_KHZ(50), SR_MHZ(250), "Use 12 Channels (Max 250MHz)"}, - {DEMO_LOGIC500x6, LOGIC, SR_CHANNEL_LOGIC, 6, 1, SR_MHZ(1), SR_Mn(1), + {(enum DEMO_CHANNEL_ID)DEMO_LOGIC500x6, LOGIC, SR_CHANNEL_LOGIC, 6, 1, SR_MHZ(1), SR_Mn(1), SR_KHZ(50), SR_MHZ(500), "Use 6 Channels (Max 500MHz)"}, - {DEMO_LOGIC1000x3, LOGIC, SR_CHANNEL_LOGIC, 3, 1, SR_MHZ(1), SR_Mn(1), + {(enum DEMO_CHANNEL_ID)DEMO_LOGIC1000x3, LOGIC, SR_CHANNEL_LOGIC, 3, 1, SR_MHZ(1), SR_Mn(1), SR_KHZ(50), SR_GHZ(1), "Use 3 Channels (Max 1GHz)"}, }; @@ -1135,7 +1135,7 @@ static int config_set(int id, GVariant *data, struct sr_dev_inst *sdi, if(logic_channel_modes[i].id == (enum DEMO_CHANNEL_ID)nv) { vdev->logic_ch_mode_index = i; - vdev->logic_ch_mode = (enum DEMO_CHANNEL_ID)nv; + vdev->logic_ch_mode = (enum DEMO_LOGIC_CHANNEL_ID)nv; load_virtual_device_session(sdi); vdev->channel_mode_change = TRUE; break; diff --git a/libsigrok4DSL/output/csv.c b/libsigrok4DSL/output/csv.c index 0bff430b6..09bb21155 100644 --- a/libsigrok4DSL/output/csv.c +++ b/libsigrok4DSL/output/csv.c @@ -70,7 +70,6 @@ static int init(struct sr_output *o, GHashTable *options) GSList *l; int i; float range; - int ch_num; if (!o || !o->sdi) return SR_ERR_ARG; @@ -87,7 +86,6 @@ static int init(struct sr_output *o, GHashTable *options) ctx->mask = 0; ctx->index = 0; ctx->type = g_variant_get_int16(g_hash_table_lookup(options, "type")); - ch_num = 0; if (o->start_sample_index > 0){ ctx->index = o->start_sample_index; @@ -147,16 +145,14 @@ static GString *gen_header(const struct sr_output *o) struct sr_channel *ch; GString *header; GSList *l; - time_t t; int num_channels, i; ctx = o->priv; header = g_string_sized_new(512); /* Some metadata */ - t = time(NULL); g_string_append_printf(header, "; CSV, generated by DSView, sampled on %s\n", - o->time_string); //ctime(&t) + o->time_string); /* Columns / channels */ if (ctx->type == SR_CHANNEL_LOGIC) @@ -298,7 +294,7 @@ static int receive(const struct sr_output *o, const struct sr_datafeed_packet *p pFlagCheck++; } - g_string_append_printf(*out, tmp_buffer); + g_string_append_printf(*out, "%s", tmp_buffer); } for (j = 0; j < ctx->num_enabled_channels; j++) { @@ -365,7 +361,7 @@ static int receive(const struct sr_output *o, const struct sr_datafeed_packet *p for (i = 0; i < (uint64_t)analog->num_samples; i++) { ch_cfg_dex = 0; - for (j = 0; j < ch_num; j++) { + for (j = 0; j < (uint64_t)ch_num; j++) { if (enalbe_channel_flags[j] == 0){ continue; From e537640324e15af19a4aa3c41065dc5f987f078f Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 1 Jul 2026 10:20:43 +0200 Subject: [PATCH 03/34] Fix viewport size --- DSView/pv/view/viewport.cpp | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp index d735d2c24..614d28cfe 100644 --- a/DSView/pv/view/viewport.cpp +++ b/DSView/pv/view/viewport.cpp @@ -1799,7 +1799,25 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) Qt::AlignLeft | Qt::AlignTop, _mm_freq).width()); typical_width = max(typical_width, p.boundingRect(0, 0, INT_MAX, INT_MAX, Qt::AlignLeft | Qt::AlignTop, _mm_duty).width()); - typical_width = typical_width + (int)(100 * sc); + + // Measure the (localized) row labels too - a flat padding guess + // isn't enough once translations run longer than English (e.g. + // German "Tastverhältnis: " for "Duty Cycle: "), which is what + // caused the label and value text to overlap in the same row. + const QString label_width_str = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_WIDTH), "Width: "); + const QString label_period_str = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_PERIOD), "Period: "); + const QString label_freq_str = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_FREQUENCY), "Frequency: "); + const QString label_duty_str = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_DUTY_CYCLE), "Duty Cycle: "); + int label_width = p.boundingRect(0, 0, INT_MAX, INT_MAX, + Qt::AlignLeft | Qt::AlignTop, label_width_str).width(); + label_width = max(label_width, p.boundingRect(0, 0, INT_MAX, INT_MAX, + Qt::AlignLeft | Qt::AlignTop, label_period_str).width()); + label_width = max(label_width, p.boundingRect(0, 0, INT_MAX, INT_MAX, + Qt::AlignLeft | Qt::AlignTop, label_freq_str).width()); + label_width = max(label_width, p.boundingRect(0, 0, INT_MAX, INT_MAX, + Qt::AlignLeft | Qt::AlignTop, label_duty_str).width()); + + typical_width = typical_width + label_width + (int)(40 * sc); const QString mm_period_samples_long = _mm_period_samples + " " + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SAMPLES), " samples"); const QString mm_width_samples_long = _mm_width_samples + " " + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SAMPLES), " samples"); @@ -1841,21 +1859,17 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) p.drawRect(measure_rect); p.setPen(active_color); - p.drawText(measure1_rect, Qt::AlignLeft | Qt::AlignVCenter, - L_S(STR_PAGE_DLG, S_ID(IDS_DLG_WIDTH), "Width: ")); + p.drawText(measure1_rect, Qt::AlignLeft | Qt::AlignVCenter, label_width_str); p.drawText(measure1_rect, Qt::AlignRight | Qt::AlignVCenter,_mm_width_time); p.drawText(measure2_rect, Qt::AlignRight | Qt::AlignVCenter,mm_width_samples_long); p.setPen(active_color2); - p.drawText(measure3_rect, Qt::AlignLeft | Qt::AlignVCenter, - L_S(STR_PAGE_DLG, S_ID(IDS_DLG_PERIOD), "Period: ")); + p.drawText(measure3_rect, Qt::AlignLeft | Qt::AlignVCenter, label_period_str); p.drawText(measure3_rect, Qt::AlignRight | Qt::AlignVCenter, _mm_period_time); p.drawText(measure4_rect, Qt::AlignRight | Qt::AlignVCenter, mm_period_samples_long); p.setPen(active_color); - p.drawText(measure5_rect, Qt::AlignLeft | Qt::AlignVCenter, - L_S(STR_PAGE_DLG, S_ID(IDS_DLG_FREQUENCY), "Frequency: ")); + p.drawText(measure5_rect, Qt::AlignLeft | Qt::AlignVCenter, label_freq_str); p.drawText(measure5_rect, Qt::AlignRight | Qt::AlignVCenter, _mm_freq); - p.drawText(measure6_rect, Qt::AlignLeft | Qt::AlignVCenter, - L_S(STR_PAGE_DLG, S_ID(IDS_DLG_DUTY_CYCLE), "Duty Cycle: ")); + p.drawText(measure6_rect, Qt::AlignLeft | Qt::AlignVCenter, label_duty_str); p.drawText(measure6_rect, Qt::AlignRight | Qt::AlignVCenter, _mm_duty); } } From f883c95e29202b87f4a12446243229740cec9180 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 1 Jul 2026 12:46:18 +0200 Subject: [PATCH 04/34] Fix font size scaling for oscilloscope mode --- DSView/pv/view/dsosignal.cpp | 3 +++ DSView/pv/view/trace.cpp | 16 ++++++++++++++-- DSView/pv/view/trace.h | 4 ++++ DSView/pv/view/view.cpp | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/DSView/pv/view/dsosignal.cpp b/DSView/pv/view/dsosignal.cpp index 7b700f8d9..08451a72b 100644 --- a/DSView/pv/view/dsosignal.cpp +++ b/DSView/pv/view/dsosignal.cpp @@ -1220,6 +1220,9 @@ QRectF DsoSignal::get_rect(DsoSetRegions type, int y, int right) { (void)right; + const int SquareWidth = get_squareWidth(); + const int Margin = get_squareMargin(); + if (type == DSO_VDIAL) return QRectF( get_leftWidth() + SquareWidth*0.5 + Margin, diff --git a/DSView/pv/view/trace.cpp b/DSView/pv/view/trace.cpp index cadaa74fa..955fa4e00 100644 --- a/DSView/pv/view/trace.cpp +++ b/DSView/pv/view/trace.cpp @@ -113,14 +113,26 @@ double Trace::get_label_scale() return (_view != NULL) ? _view->get_trace_font_scale() : 1.0; } +// Ratio between the currently configured trace font size and the font size +// the Margin/SquareWidth pixel constants were tuned against. Without this, +// the per-channel config boxes (AC/DC, AUTO, x1/x10/x100, etc.) stayed a +// fixed pixel size regardless of the font size setting, while the text +// drawn inside them (sized off the same GetTraceFontSize() value, see +// Viewport::paintEvent) grew - so bigger font settings just clipped the +// box text instead of growing the box with it. +static double square_font_ratio() +{ + return AppConfig::Instance().GetTraceFontSize() / Trace::BaseFontSize; +} + int Trace::get_squareWidth() { - return (int)(SquareWidth * get_label_scale()); + return (int)(SquareWidth * square_font_ratio() * get_label_scale()); } int Trace::get_squareMargin() { - return (int)(Margin * get_label_scale()); + return (int)(Margin * square_font_ratio() * get_label_scale()); } int Trace::get_leftWidth() diff --git a/DSView/pv/view/trace.h b/DSView/pv/view/trace.h index 131fb15fb..9c3cd605f 100644 --- a/DSView/pv/view/trace.h +++ b/DSView/pv/view/trace.h @@ -51,6 +51,10 @@ class Trace : public SelectableItem static const int LabelHitPadding; public: + // The font size (in points) the Margin/SquareWidth pixel constants + // were tuned against - used to scale the boxes with AppConfig's font + // size setting, not just the (independent) trace height factor. + static constexpr double BaseFontSize = 9.0; static const int SquareWidth = 20; static const int COLOR = 1; static const int NAME = 2; diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index df8abccf8..5f68f8dc4 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -369,7 +369,7 @@ void View::vzoom(double steps) double View::get_trace_font_scale() { - if (_device_agent->have_instance() && _device_agent->get_work_mode() == LOGIC) + if (_device_agent->have_instance()) return _trace_height_factor; return 1.0; } From a24b7adf5700650fe29d886a8ae30d66e752d5cd Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 1 Jul 2026 13:14:20 +0200 Subject: [PATCH 05/34] Fix measurement bar overlap --- DSView/pv/view/view.cpp | 28 ++++++++++++++++++++++------ DSView/pv/view/view.h | 11 ++++++++++- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index 5f68f8dc4..49a2e53e9 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -164,13 +164,13 @@ View::View(SigSession *session, pv::toolbars::SamplingBar *sampling_bar, QWidget layout->setContentsMargins(0,0,0,0); _viewcenter->setLayout(layout); layout->addWidget(_vsplitter, 0, 0); - QVBoxLayout* statusLayout = new QVBoxLayout(this); - statusLayout->setSpacing(0); - statusLayout->setContentsMargins(0,0,verticalScrollBar()->geometry().width()+2, horizontalScrollBar()->geometry().height()+1); + _statusLayout = new QVBoxLayout(this); + _statusLayout->setSpacing(0); + _statusLayout->setContentsMargins(0,0,verticalScrollBar()->geometry().width()+2, horizontalScrollBar()->geometry().height()+1); _viewbottom = new ViewStatus(_session, *this); _viewbottom->setFixedHeight(StatusHeight); - setLayout(statusLayout); - statusLayout->addWidget(_viewbottom, 0, Qt::AlignBottom); + setLayout(_statusLayout); + _statusLayout->addWidget(_viewbottom, 0, Qt::AlignBottom); #ifdef Q_OS_DARWIN QWidget *lineSpan = new QWidget(this); @@ -1090,7 +1090,22 @@ void View::update_margins() _ruler->setGeometry(_viewcenter->x(), 0, width, _viewcenter->y()); _header->setGeometry(0, _viewcenter->y(), _viewcenter->x(), _viewcenter->height()); _devmode->setGeometry(0, 0, _viewcenter->x(), _viewcenter->y()); - } + } +} + +void View::update_status_margins() +{ + // The bottom/right margins were only ever computed once, at + // construction time, before the scrollbars had a real on-screen + // geometry - and never refreshed afterwards. That was mostly hidden in + // LOGIC mode (a fixed, short StatusHeight), but became visibly wrong in + // DSO mode once _viewbottom grows to DsoStatusHeight for its two-row + // measurement layout: the reserved strip stayed sized for the stale + // scrollbar height, so the lower measurement row overlapped the real + // horizontal scrollbar. Recompute with the scrollbars' current geometry. + _statusLayout->setContentsMargins(0, 0, + verticalScrollBar()->geometry().width() + 2, + horizontalScrollBar()->geometry().height() + 1); } void View::header_updated() @@ -1488,6 +1503,7 @@ void View::reconstruct() _viewbottom->setFixedHeight(DsoStatusHeight); else _viewbottom->setFixedHeight(StatusHeight); + update_status_margins(); _viewbottom->reload(); } diff --git a/DSView/pv/view/view.h b/DSView/pv/view/view.h index d5b7a3266..18b536d7b 100644 --- a/DSView/pv/view/view.h +++ b/DSView/pv/view/view.h @@ -32,6 +32,7 @@ #include #include #include +#include #include "../toolbars/samplingbar.h" @@ -380,7 +381,14 @@ class View : public QScrollArea, public IUiWindow static bool compare_trace_v_offsets( const Trace *a, const Trace *b); void get_scroll_layout(int64_t &length, int64_t &offset); void update_scroll(); - void update_margins(); + void update_margins(); + // Re-reserves bottom/right space in _statusLayout for the real + // scrollbars, using their current (not construction-time) geometry - + // the ViewStatus (_viewbottom) widget can grow taller (DSO's 2-row + // measurement layout) after construction, so the scrollbar-clearance + // margin has to be refreshed alongside it, or the lower measurement + // row overlaps the horizontal scrollbar. + void update_status_margins(); void set_scale(double scale); void clear(); @@ -466,6 +474,7 @@ private slots: QWidget *_viewcenter; ViewStatus *_viewbottom; + QVBoxLayout *_statusLayout; QSplitter *_vsplitter; Viewport *_time_viewport; Viewport *_fft_viewport; From 11d84c3691172966b468a4b54fb815ba3bfa39ae Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 1 Jul 2026 14:10:56 +0200 Subject: [PATCH 06/34] Added catppuccin themes --- DSView/pv/config/appconfig.cpp | 24 +- DSView/pv/config/appconfig.h | 2 + DSView/pv/toolbars/trigbar.cpp | 43 +- DSView/pv/toolbars/trigbar.h | 4 + DSView/themes/breeze.qrc | 2 + DSView/themes/frappe.qss | 1662 +++++++++++++++++++++++++++++++ DSView/themes/latte.qss | 1695 ++++++++++++++++++++++++++++++++ lang/cn/toolbar.json | 10 +- lang/de/toolbar.json | 8 + lang/en/toolbar.json | 10 +- 10 files changed, 3452 insertions(+), 8 deletions(-) create mode 100644 DSView/themes/frappe.qss create mode 100644 DSView/themes/latte.qss diff --git a/DSView/pv/config/appconfig.cpp b/DSView/pv/config/appconfig.cpp index 17b1c01e6..e7f938748 100644 --- a/DSView/pv/config/appconfig.cpp +++ b/DSView/pv/config/appconfig.cpp @@ -467,7 +467,11 @@ void AppConfig::GetFontSizeRange(float *minSize, float *maxSize) bool AppConfig::IsDarkStyle() { - if (frameOptions.style == THEME_STYLE_DARK){ + // Frappe is Catppuccin's dark flavor - treat it like Dark for anything + // that only distinguishes light/dark (icon set selection, background + // luminosity-dependent painting, etc), rather than the exact color + // scheme in use. + if (frameOptions.style == THEME_STYLE_DARK || frameOptions.style == THEME_STYLE_FRAPPE){ return true; } return false; @@ -475,7 +479,13 @@ bool AppConfig::IsDarkStyle() QColor AppConfig::GetStyleColor() { - if (IsDarkStyle()){ + if (frameOptions.style == THEME_STYLE_FRAPPE){ + return QColor(0x30, 0x34, 0x46); // Catppuccin Frappe "Base" + } + else if (frameOptions.style == THEME_STYLE_LATTE){ + return QColor(0xef, 0xf1, 0xf5); // Catppuccin Latte "Base" + } + else if (IsDarkStyle()){ return QColor(38, 38, 38); } else{ @@ -486,11 +496,19 @@ QColor AppConfig::GetStyleColor() //-------------api QString GetIconPath() -{ +{ QString style = AppConfig::Instance().frameOptions.style; if (style == ""){ style = THEME_STYLE_DARK; } + // Latte/Frappe don't have their own icon sets - they reuse whichever of + // the light/dark icon sets already matches their background darkness. + if (AppConfig::Instance().IsDarkStyle()){ + style = THEME_STYLE_DARK; + } + else{ + style = THEME_STYLE_LIGHT; + } return ":/icons/" + style; } diff --git a/DSView/pv/config/appconfig.h b/DSView/pv/config/appconfig.h index 5b122387f..4c542b0b6 100644 --- a/DSView/pv/config/appconfig.h +++ b/DSView/pv/config/appconfig.h @@ -33,6 +33,8 @@ #define THEME_STYLE_DARK "dark" #define THEME_STYLE_LIGHT "light" +#define THEME_STYLE_LATTE "latte" +#define THEME_STYLE_FRAPPE "frappe" #define APP_NAME "DSView" diff --git a/DSView/pv/toolbars/trigbar.cpp b/DSView/pv/toolbars/trigbar.cpp index f79793ffd..c437f4e8f 100644 --- a/DSView/pv/toolbars/trigbar.cpp +++ b/DSView/pv/toolbars/trigbar.cpp @@ -72,14 +72,22 @@ TrigBar::TrigBar(SigSession *session, QWidget *parent) : _dark_style = new QAction(this); _dark_style->setObjectName(QString::fromUtf8("actionDark")); - + _light_style = new QAction(this); _light_style->setObjectName(QString::fromUtf8("actionLight")); - + + _latte_style = new QAction(this); + _latte_style->setObjectName(QString::fromUtf8("actionLatte")); + + _frappe_style = new QAction(this); + _frappe_style->setObjectName(QString::fromUtf8("actionFrappe")); + _themes = new QMenu(this); _themes->setObjectName(QString::fromUtf8("menuThemes")); _themes->addAction(_light_style); _themes->addAction(_dark_style); + _themes->addAction(_latte_style); + _themes->addAction(_frappe_style); _action_dispalyOptions = new QAction(this); @@ -119,6 +127,8 @@ TrigBar::TrigBar(SigSession *session, QWidget *parent) : connect(_action_lissajous, SIGNAL(triggered()), this, SLOT(on_actionLissajous_triggered())); connect(_dark_style, SIGNAL(triggered()), this, SLOT(on_actionDark_triggered())); connect(_light_style, SIGNAL(triggered()), this, SLOT(on_actionLight_triggered())); + connect(_latte_style, SIGNAL(triggered()), this, SLOT(on_actionLatte_triggered())); + connect(_frappe_style, SIGNAL(triggered()), this, SLOT(on_actionFrappe_triggered())); connect(_action_dispalyOptions, SIGNAL(triggered()), this, SLOT(on_display_setting())); ADD_UI(this); @@ -144,6 +154,8 @@ void TrigBar::retranslateUi() _dark_style->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_DISPLAY_THEMES_DARK), "Dark")); _light_style->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_DISPLAY_THEMES_LIGHT), "Light")); + _latte_style->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_DISPLAY_THEMES_LATTE), "Latte")); + _frappe_style->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_DISPLAY_THEMES_FRAPPE), "Frappé")); _action_fft->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_FUNCTION_FFT), "FFT")); _action_math->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_FUNCTION_MATH), "Math")); @@ -167,11 +179,18 @@ void TrigBar::reStyle() _action_lissajous->setIcon(QIcon(iconPath+"/lissajous.svg")); _dark_style->setIcon(QIcon(iconPath+"/dark.svg")); _light_style->setIcon(QIcon(iconPath+"/light.svg")); + // Latte/Frappe have no dedicated glyph asset - reuse whichever of the + // light/dark menu icons matches their background darkness. + _latte_style->setIcon(QIcon(iconPath+"/light.svg")); + _frappe_style->setIcon(QIcon(iconPath+"/dark.svg")); _action_dispalyOptions->setIcon(QIcon(iconPath+"/gear.svg")); AppConfig &app = AppConfig::Instance(); - QString icon_fname = iconPath +"/"+ app.frameOptions.style +".svg"; + // The Themes menu's own icon: fall back to the dark/light glyph for + // color schemes (Latte/Frappe) that don't have a same-named icon file. + QString icon_style = app.IsDarkStyle() ? THEME_STYLE_DARK : THEME_STYLE_LIGHT; + QString icon_fname = iconPath +"/"+ icon_style +".svg"; _themes->setIcon(QIcon(icon_fname)); } @@ -305,6 +324,24 @@ void TrigBar::on_actionLight_triggered() _themes->setIcon(QIcon(icon)); } +void TrigBar::on_actionLatte_triggered() +{ + sig_setTheme(THEME_STYLE_LATTE); + // Latte has no dedicated glyph asset - it reuses the "light" icon set, + // so use that icon set's "light.svg" glyph for the menu icon too. + QString icon = GetIconPath() + "/" + THEME_STYLE_LIGHT + ".svg"; + _themes->setIcon(QIcon(icon)); +} + +void TrigBar::on_actionFrappe_triggered() +{ + sig_setTheme(THEME_STYLE_FRAPPE); + // Frappe has no dedicated glyph asset - it reuses the "dark" icon set, + // so use that icon set's "dark.svg" glyph for the menu icon too. + QString icon = GetIconPath() + "/" + THEME_STYLE_DARK + ".svg"; + _themes->setIcon(QIcon(icon)); +} + void TrigBar::on_actionLissajous_triggered() { pv::dialogs::LissajousOptions lissajous_dlg(_session, this); diff --git a/DSView/pv/toolbars/trigbar.h b/DSView/pv/toolbars/trigbar.h index 95d18380f..efa2f54a7 100644 --- a/DSView/pv/toolbars/trigbar.h +++ b/DSView/pv/toolbars/trigbar.h @@ -72,6 +72,8 @@ class TrigBar : public QToolBar, public IUiWindow private slots: void on_actionDark_triggered(); void on_actionLight_triggered(); + void on_actionLatte_triggered(); + void on_actionFrappe_triggered(); void on_actionLissajous_triggered(); void on_actionFft_triggered(); void on_actionMath_triggered(); @@ -108,6 +110,8 @@ public slots: QAction *_action_dispalyOptions; QAction *_dark_style; QAction *_light_style; + QAction *_latte_style; + QAction *_frappe_style; QAction *_action_lissajous; }; diff --git a/DSView/themes/breeze.qrc b/DSView/themes/breeze.qrc index 620630a51..3ec5c203e 100644 --- a/DSView/themes/breeze.qrc +++ b/DSView/themes/breeze.qrc @@ -62,6 +62,8 @@ dark/undock-hover.svg light.qss dark.qss + latte.qss + frappe.qss light/checkbox_checked.svg light/checkbox_checked_disabled.svg light/checkbox_checked-hover.svg diff --git a/DSView/themes/frappe.qss b/DSView/themes/frappe.qss new file mode 100644 index 000000000..98cced8cf --- /dev/null +++ b/DSView/themes/frappe.qss @@ -0,0 +1,1662 @@ +/* + * Catppuccin Frappe theme for DSView + * Generated from dark.qss via a systematic + * Catppuccin color substitution - see gen_theme.py in this session's + * scratchpad for the exact mapping used. + */ +/* + * DSView dark stylesheet. + * --------------------------------------------------------------------- + * The MIT License (MIT) + * + * Copyright (c) <2013-2014> + * Copyright (C) 2019 DreamSourceLab + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * --------------------------------------------------------------------- + */ + +QToolTip +{ + border: 1px solid #c6d0f5; + background-color: #303446; + alternate-background-color: #414559; + color: #c6d0f5; + padding: 1px; + opacity: 200; +} + +QWidget +{ + color: #c6d0f5; + background-color: #303446; + selection-background-color:#8caaee; + selection-color: #c6d0f5; + background-clip: border; + image: none; + border: 0px transparent; + outline: 0; +} + +/* +QWidget:item:hover +{ + background-color: #8caaee; + color: #c6d0f5; +} + +QWidget:item:selected +{ + background-color: #8caaee; +} +*/ + +QPushButton#flat{ + text-align:left; + border:none; +} + +QPushButton#flat:hover +{ + background-color: #85c1dc; + color: #ca9ee6; +} + +QCheckBox +{ + spacing: 0px; + outline: none; + color: #c6d0f5; + margin-bottom: 2px; + opacity: 200; +} + +QCheckBox:disabled +{ + color: #414559; +} + +QGroupBox::indicator +{ + width: 18px; + height: 18px; + margin-left: 2px; +} + +QCheckBox::indicator:unchecked, +QCheckBox::indicator:unchecked:focus +{ + image: url(:/dark/checkbox_unchecked.svg); +} + +QCheckBox::indicator:unchecked:hover, +QCheckBox::indicator:unchecked:pressed, +QGroupBox::indicator:unchecked:hover, +QGroupBox::indicator:unchecked:focus, +QGroupBox::indicator:unchecked:pressed +{ + border: none; + image: url(:/dark/checkbox_unchecked-hover.svg); +} + +QCheckBox::indicator:checked +{ + image: url(:/dark/checkbox_checked.svg); +} + +QCheckBox::indicator:checked:hover, +QCheckBox::indicator:checked:focus, +QCheckBox::indicator:checked:pressed, +QGroupBox::indicator:checked:hover, +QGroupBox::indicator:checked:focus, +QGroupBox::indicator:checked:pressed +{ + border: none; + image: url(:/dark/checkbox_checked-hover.svg); +} + +QCheckBox::indicator:indeterminate +{ + image: url(:/dark/checkbox_indeterminate.svg); +} + +QCheckBox::indicator:indeterminate:focus, +QCheckBox::indicator:indeterminate:hover +QCheckBox::indicator:indeterminate:pressed +{ + image: url(:/dark/checkbox_indeterminate-hover.svg); +} + +QCheckBox::indicator:indeterminate:disabled +{ + image: url(:/dark/checkbox_indeterminate_disabled.svg); +} + +QCheckBox::indicator:checked:disabled, +QGroupBox::indicator:checked:disabled +{ + image: url(:/dark/checkbox_checked_disabled.svg); +} + +QCheckBox::indicator:unchecked:disabled, +QGroupBox::indicator:unchecked:disabled +{ + image: url(:/dark/checkbox_unchecked_disabled.svg); +} + +QRadioButton +{ + spacing: 5px; + outline: none; + color: #c6d0f5; + margin-bottom: 2px; +} + +QRadioButton:disabled +{ + color: #414559; +} +QRadioButton::indicator +{ + width: 16px; + height: 16px; +} + +QRadioButton::indicator:unchecked, +QRadioButton::indicator:unchecked:focus +{ + image: url(:/dark/radio_unchecked.svg); +} + + +QRadioButton::indicator:unchecked:hover, +QRadioButton::indicator:unchecked:pressed +{ + border: none; + outline: none; + image: url(:/dark/radio_unchecked-hover.svg); +} + + +QRadioButton::indicator:checked +{ + border: none; + outline: none; + image: url(:/dark/radio_checked.svg); +} + +QRadioButton::indicator:checked:hover, +QRadioButton::indicator:checked:focus, +QRadioButton::indicator:checked:pressed +{ + border: none; + outline: none; + image: url(:/dark/radio_checked-hover.svg); +} + +QRadioButton::indicator:checked:disabled +{ + outline: none; + image: url(:/dark/radio_checked_disabled.svg); +} + +QRadioButton::indicator:unchecked:disabled +{ + image: url(:/dark/radio_unchecked_disabled.svg); +} + +QMenuBar +{ + background-color: #303446; + color: #c6d0f5; +} + +QMenuBar::item +{ + background: transparent; +} + +QMenuBar::item:selected +{ + background: transparent; + border: 1px transparent; +} + +QMenuBar::item:pressed +{ + border: 1px transparent; + background-color: #8caaee; + color: #c6d0f5; + margin-bottom: -1px; + padding-bottom: 1px; +} + +QMenu +{ + border: 1px transparent; + color: #c6d0f5; + margin: 0px; +} + +QMenu::item +{ + padding: 5px 30px 5px 30px; + margin-left: 2px; + border: 1px solid transparent; /* reserve space for selection border */ +} + +QMenu::item:selected +{ + background-color: #8caaee; + color: #c6d0f5; +} + +QMenu::separator +{ + height: 2px; + background: lightblue; + margin-left: 10px; + margin-right: 5px; +} + +QMenu::indicator { + width: 18px; + height: 18px; +} + +/* non-exclusive indicator = check box style indicator + (see QActionGroup::setExclusive) */ +QMenu::indicator:non-exclusive:unchecked +{ + image: url(:/dark/checkbox_unchecked_disabled.svg); +} + +QMenu::indicator:non-exclusive:unchecked:selected +{ + image: url(:/dark/checkbox_unchecked_disabled.svg); +} + +QMenu::indicator:non-exclusive:checked +{ + image: url(:/dark/checkbox_checked.svg); +} + +QMenu::indicator:non-exclusive:checked:selected +{ + image: url(:/dark/checkbox_checked.svg); +} + +/* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */ +QMenu::indicator:exclusive:unchecked +{ + image: url(:/dark/radio_unchecked_disabled.svg); +} + +QMenu::indicator:exclusive:unchecked:selected +{ + image: url(:/dark/radio_unchecked_disabled.svg); +} + +QMenu::indicator:exclusive:checked +{ + image: url(:/dark/radio_checked.svg); +} + +QMenu::indicator:exclusive:checked:selected +{ + image: url(:/dark/radio_checked.svg); +} + +QMenu::right-arrow +{ + margin: 5px; + image: url(:/dark/right_arrow.svg); +} + + +QWidget:disabled +{ + color: #414559; + background-color: #303446; +} + +QAbstractItemView +{ + alternate-background-color: #414559; + color: #c6d0f5; + border: 1px transparent; + border-radius: 2px; + padding: 1px +} + +QTabWidget:focus, +QCheckBox:focus, +QRadioButton:focus, +QSlider:focus +{ + border: none; +} + +QLineEdit +{ + background-color: #232634; + padding: 2px; + border-style: solid; + border: 1px solid #414559; + border-radius: 2px; + color: #c6d0f5; +} + +QTextEdit +{ + background-color: #232634; + padding: 2px; + border-style: solid; + border: 1px solid #414559; + border-radius: 2px; + color: #c6d0f5; +} + +QGroupBox +{ + border: 1px solid #414559; + border-radius: 2px; + margin-top: 20px; +} + +QGroupBox::title +{ + subcontrol-origin: margin; + subcontrol-position: top center; + padding-left: 10px; + padding-right: 10px; + padding-top: 10px; +} + +QScrollBar:horizontal +{ + height: 24px; + margin: 3px 12px 3px 12px; + border: 1px transparent; + border-radius: 9px; + background-color: #232634; +} + +QScrollBar::handle:horizontal +{ + background-color: #737994; + min-width: 20px; + border-radius: 9px; +} + +QScrollBar::add-line:horizontal +{ + margin: 0px 3px 0px 3px; + image: url(:/dark/right_arrow_disabled.svg); + width: 10px; + height: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal +{ + margin: 0px 3px 0px 3px; + image: url(:/dark/left_arrow_disabled.svg); + width: 10px; + height: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::add-line:horizontal:hover, +QScrollBar::add-line:horizontal:on +{ + image: url(:/dark/right_arrow.svg); + width: 10px; + height: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + + +QScrollBar::sub-line:horizontal:hover, +QScrollBar::sub-line:horizontal:on +{ + image: url(:/dark/left_arrow.svg); + width: 10px; + height: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:horizontal, +QScrollBar::down-arrow:horizontal +{ + background: none; +} + + +QScrollBar::add-page:horizontal, +QScrollBar::sub-page:horizontal +{ + background: none; +} + +QScrollBar:vertical +{ + background-color: #232634; + width: 24px; + margin: 12px 3px 12px 3px; + border: 1px transparent; + border-radius: 9px; +} + +QScrollBar::handle:vertical +{ + background-color: #737994; + min-height: 20px; + border-radius: 9px; +} + +QScrollBar::sub-line:vertical +{ + margin: 3px 0px 3px 0px; + image: url(:/dark/up_arrow_disabled.svg); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QScrollBar::add-line:vertical +{ + margin: 3px 0px 3px 0px; + image: url(:/dark/down_arrow_disabled.svg); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:vertical:hover, +QScrollBar::sub-line:vertical:on +{ + + image: url(:/dark/up_arrow.svg); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + + +QScrollBar::add-line:vertical:hover, +QScrollBar::add-line:vertical:on +{ + image: url(:/dark/down_arrow.svg); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:vertical, +QScrollBar::down-arrow:vertical +{ + background: none; +} + + +QScrollBar::add-page:vertical, +QScrollBar::sub-page:vertical +{ + background: none; +} + +QTextEdit +{ + background-color: #232634; + color: #c6d0f5; + border: 1px solid #414559; + margin: 0; +} + +QPlainTextEdit +{ + background-color: #232634;; + color: #c6d0f5; + border-radius: 2px; + border: 1px solid #414559; +} + +QHeaderView::section +{ + background-color: #737994; + color: #c6d0f5; + padding-left: 4px; + border: 1px solid #737994; +} + +QSizeGrip +{ + image: url(:/dark/sizegrip.svg); + width: 12px; + height: 12px; +} + +QMenu::separator +{ + height: 1px; + background-color: #737994; + color: white; + padding-left: 4px; + margin-left: 10px; + margin-right: 5px; +} + +QFrame +{ + border-radius: 2px; + border: 1px solid #414559; +} + +QFrame[frameShape="0"] +{ + border-radius: 2px; + border: 1px transparent; +} + +QStackedWidget +{ + border: 1px transparent; +} + +QToolBar +{ + border: 1px transparent; + background: 1px solid #303446; + padding: 0px; +} + +QToolBar::handle:horizontal +{ + image: url(:/dark/hmovetoolbar.svg); + width = 16px; + height = 64px; +} + +QToolBar::handle:vertical +{ + image: url(:/dark/vmovetoolbar.svg); + width = 54px; + height = 10px; +} + +QToolBar::separator:horizontal +{ + image: url(:/dark/hsepartoolbar.svg); + width = 7px; + height = 63px; +} + +QToolBar::separator:vertical +{ + image: url(:/dark/vsepartoolbars.svg); + width = 63px; + height = 7px; +} + +QPushButton +{ + color: #c6d0f5; + background-color: #303446; + border-width: 1px; + border-color: #232634; + border-style: solid; + padding: 5px; + border-radius: 5px; + outline: none; +} + +QPushButton:disabled +{ + background-color: #303446; + border-width: 1px; + border-color: #414559; + border-style: solid; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 10px; + padding-right: 10px; + border-radius: 5px; + color: #414559; +} + +QPushButton:focus +{ + color: white; +} + +QPushButton:pressed +{ + background-color: #303446; + padding-top: -15px; + padding-bottom: -17px; +} + + +QPushButton:checked{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #303446, + stop: 0.5 #737994, + stop: 1 #303446); +} + +QPushButton:hover +{ + background-color: #303446; + border: 1px solid #8caaee; + color: #c6d0f5; + padding-top: 6px; + padding-bottom: 4px; +} + +QPushButton:checked:hover +{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #303446, + stop: 0.5 #737994, + stop: 1 #303446); + border: 1px solid #8caaee; + color: #c6d0f5; + padding-top: 6px; + padding-bottom: 4px; +} + +QComboBox:hover, +QAbstractSpinBox:hover, +QLineEdit:hover, +QTextEdit:hover, +QPlainTextEdit:hover, +QAbstractView:hover, +QTreeView:hover +{ + border: 1px solid #8caaee; + color: #c6d0f5; +} + +QComboBox:on +{ + background-color: #626880; + padding-top: 3px; + padding-left: 4px; + selection-background-color: #51576d; +} + +QComboBox +{ + selection-background-color: #85c1dc; + background-color: #232634; + border-style: solid; + border: 1px solid #414559; + border-radius: 2px; + padding: 2px; + min-width: 30px; +} + + +QComboBox::drop-down +{ + subcontrol-origin: padding; + subcontrol-position: top right; + width: 10px; + + border-left-width: 0px; + border-left-color: darkgray; + border-left-style: solid; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +QComboBox::down-arrow +{ + image: url(:/dark/down_arrow_disabled.svg); +} + +QComboBox::down-arrow:on, +QComboBox::down-arrow:hover, +QComboBox::down-arrow:focus +{ + image: url(:/dark/down_arrow.svg); +} + +QAbstractSpinBox +{ + padding: 2px; + border: 1px solid #737994; + background-color: #232634; + color: #c6d0f5; + border-radius: 2px; + min-width: 60px; +} + +QAbstractSpinBox:up-button +{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center right; +} + +QAbstractSpinBox:down-button +{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center left; +} + +QAbstractSpinBox::up-arrow, +QAbstractSpinBox::up-arrow:disabled, +QAbstractSpinBox::up-arrow:off +{ + image: url(:/dark/up_arrow_disabled.svg); + width: 10px; + height: 10px; +} + +QAbstractSpinBox::up-arrow:hover +{ + image: url(:/dark/up_arrow.svg); +} + +QAbstractSpinBox::down-arrow, +QAbstractSpinBox::down-arrow:disabled, +QAbstractSpinBox::down-arrow:off +{ + image: url(:/dark/down_arrow_disabled.svg); + width: 10px; + height: 10px; +} + +QAbstractSpinBox::down-arrow:hover +{ + image: url(:/dark/down_arrow.svg); +} + +QLabel +{ + border: 0px solid black; + margin-left: 2px; + margin-right: 2px; +} + +/* BORDERS */ +QTabWidget::pane +{ + padding: 0px; + padding-right: 3px; + margin: 0px; +} + +QTabWidget::pane:top +{ + border: 1px transparent; + top: -1px; +} + +QTabWidget::pane:bottom +{ + border: 1px transparent; + bottom: -1px; +} + +QTabWidget::pane:left +{ + border: 1px transparent; + right: -1px; +} + +QTabWidget::pane:right +{ + border: 1px transparent; + left: -1px; +} + + +QTabBar +{ + qproperty-drawBase: 0; + margin: 0px; + padding: 0px; + border-radius: 3px; +} + +QTabBar:focus +{ + border: 0px transparent; +} + +QTabBar::close-button +{ + image: url(:/dark/close.svg); + background: transparent; +} + +QTabBar::close-button:hover +{ + image: url(:/dark/close-hover.svg); + width: 12px; + height: 12px; + background: transparent; +} + +QTabBar::close-button:pressed +{ + image: url(:/dark/close-pressed.svg); + width: 12px; + height: 12px; + background: transparent; +} + +QTabBar::tab +{ + color: white; + background-color: #85c1dc; + padding: 5px; +} + +QTabBar::tab:!selected +{ + color: white; + background-color: #303446; +} + +QTabBar::tab:disabled +{ + color: #414559; + background-color: #303446; +} + +/* TOP TABS */ +QTabBar::tab:top +{ + border: 1px solid #51576d; + border-bottom: 1px transparent; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +QTabBar::tab:top:!selected +{ + border: 1px solid #51576d; + border-bottom: 1px transparent; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +QTabBar::tab:top:hover +{ + border: 1px solid #8caaee; + border-bottom: 1px transparent; +} + + +/* BOTTOM TABS */ +QTabBar::tab:bottom +{ + border: 1px solid #51576d; + border-top: 1px transparent; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:bottom:!selected +{ + border: 1px solid #51576d; + border-top: 1px transparent; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:bottom:hover +{ + border: 1px solid #8caaee; + border-top: 1px transparent; +} + + +/* LEFT TABS */ +QTabBar::tab:left +{ + border: 1px solid #51576d; + border-left: 1px transparent; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:left:!selected +{ + border: 1px solid #51576d; + border-left: 1px transparent; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:left:hover +{ + border: 1px solid #8caaee; + border-left: 1px transparent; +} + + +/* RIGHT TABS */ +QTabBar::tab:right +{ + border: 1px solid #51576d; + border-right: 1px transparent; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} + +QTabBar::tab:right:!selected +{ + border: 1px solid #51576d; + border-right: 1px transparent; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} + +QTabBar::tab:right:hover +{ + border: 1px solid #8caaee; + border-right: 1px transparent; +} + + +QTabBar QToolButton::right-arrow:enabled +{ + image: url(:/dark/right_arrow.svg); +} + +QTabBar QToolButton::left-arrow:enabled +{ + image: url(:/dark/left_arrow.svg); +} + +QTabBar QToolButton::right-arrow:disabled +{ + image: url(:/dark/right_arrow_disabled.svg); +} + +QTabBar QToolButton::left-arrow:disabled +{ + image: url(:/dark/left_arrow_disabled.svg); +} + +QDockWidget +{ + background: #303446; + border: 1px transparent; + titlebar-close-icon: url(:/dark/transparent.svg); + titlebar-normal-icon: url(:/dark/transparent.svg); +} + +QDockWidget::title { + background-color: #232634; + padding-top: 7px; + border: 1px solid #292c3c; +} + +QDockWidget::close-button, +QDockWidget::float-button +{ + border: 1px solid transparent; + border-radius: 2px; + background: transparent; +} + +QDockWidget::float-button +{ + image: url(:/dark/undock.svg); +} + +QDockWidget::float-button:hover +{ + image: url(:/dark/undock-hover.svg) ; +} + +QDockWidget::close-button +{ + image: url(:/dark/close.svg) ; +} + +QDockWidget::close-button:hover +{ + image: url(:/dark/close-hover.svg) ; +} + +QDockWidget::close-button:pressed +{ + image: url(:/dark/close-pressed.svg) ; +} + +QTreeView, +QListView +{ + border: 1px solid #737994; + background-color: #303446; +} + +QTreeView::branch:has-siblings:!adjoins-item +{ + image: url(:/dark/stylesheet-vline.svg) 0; +} + +QTreeView::branch:has-siblings:adjoins-item +{ + image: url(:/dark/stylesheet-branch-more.svg) 0; +} + +QTreeView::branch:!has-children:!has-siblings:adjoins-item +{ + image: url(:/dark/stylesheet-branch-end.svg) 0; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings +{ + image: url(:/dark/stylesheet-branch-end-closed.svg) 0; + image: url(:/dark/branch_closed.svg); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings +{ + image: url(:/dark/stylesheet-branch-end-open.svg) 0; + image: url(:/dark/branch_open.svg); +} + + +QSlider::groove:horizontal +{ + border: 1px solid #303446; + height: 4px; + background: #838ba7; + margin: 0px; + border-radius: 1px; +} + +QSlider::handle:horizontal +{ + background: #303446; + border: 1px solid #8caaee; + width: 16px; + margin: -7px 0; + border-radius: 8px; +} + +QSlider::groove:vertical +{ + border: 1px solid #303446; + width: 4px; + background: #838ba7; + margin: 0px; + border-radius: 3px; +} + +QSlider::handle:vertical +{ + background: #303446; + border: 1px solid #626880; + width: 16px; + height: 16px; + margin: 0 -8px; + border-radius: 9px; +} + +QSlider::handle:horizontal:hover, +QSlider::handle:vertical:hover +{ + background: #8caaee; +} + +QSlider::sub-page:horizontal, +QSlider::add-page:vertical +{ + background: #8caaee; + border-radius: 1px; +} + +QSlider::add-page:horizontal, +QSlider::sub-page:vertical +{ + background: #626880; + border-radius: 1px; +} + +QSlider::handle:disabled +{ + background: #303446; + border: 1px solid #737994; +} + +QSlider::groove:disabled +{ + background: #737994; + border: 1px solid #737994; +} + +QSlider::add-page:disabled, +QSlider::sub-page:disabled +{ + background: #737994; + border-radius: 1px; +} + +QToolButton#MaximizeButton { + background-color: transparent; + border-left: 1px solid QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #303446, stop: 0.3 #626880, + stop: 0.5 #737994, + stop: 0.7 #626880, stop: 1 #303446); + border-right: 1px solid QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #303446, stop: 0.3 #626880, + stop: 0.5 #737994, + stop: 0.7 #626880, stop: 1 #303446); + border-radius: 0px; + margin: 0px; + padding: 0px; +} + +QToolButton#MinimizeButton, +QToolButton#CloseButton { + background-color: transparent; + border: 1px transparent; + border-radius: 0px; + margin: 0px; + padding: 0px; +} + +QToolButton#FileCloseButton { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #303446, stop: 0.1 #e78284, + stop: 0.5 #e78284, + stop: 0.9 #e78284, stop: 1 #303446); + border: 1px transparent; + border-radius: 0px; + margin: 0px; + padding: 0px; +} + +QToolButton#MinimizeButton:hover, QToolButton#MinimizeButton::menu-button:hover, +QToolButton#MaximizeButton:hover, QToolButton#MaximizeButton::menu-button:hover{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #303446, stop: 0.4 #51576d, + stop: 0.5 #51576d, + stop: 0.6 #51576d, stop: 1 #303446); +} + +QToolButton#CloseButton:hover, QToolButton#CloseButton::menu-button:hover { +background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #303446, stop: 0.2 #ea999c, + stop: 0.5 #e78284, + stop: 0.8 #ea999c, stop: 1 #303446); +} + +QToolButton#FileCloseButton:hover, QToolButton#FileCloseButton::menu-button:hover { + padding-top: 2px; +} + +QToolButton +{ + background-color: transparent; + border: 0px; + border-radius: 2px; + margin: 0px; + padding-top: 0px; + padding-left: 2px; + padding-right: 2px; + padding-bottom: 2px; +} + +QToolButton[popupMode="1"] /* only for MenuButtonPopup */ +{ + padding-right: 20px; /* make way for the popup button */ + border: 0px; + border-radius: 5px; + margin: 0px; + padding-top: 0px; + padding-left: 2px; + padding-right: 2px; + padding-bottom: 2px; +} + +QToolButton[popupMode="2"] /* only for InstantPopup */ +{ + padding-right: 10px; /* make way for the popup button */ + border: 0px; + margin: 0px; + padding-top: 0px; + padding-left: 2px; + padding-right: 2px; + padding-bottom: 2px; +} + +QToolButton:hover, +QToolButton:checked:hover, +QToolButton::menu-button:hover +QToolButton::menu-button:checked:hover +{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #303446, stop: 0.4 #51576d, + stop: 0.5 #51576d, + stop: 0.6 #51576d, stop: 1 #303446); + padding-top: 1px; + padding-left: 2px; + padding-right: 2px; + padding-bottom: 1px; +} + +QToolButton:checked, QToolButton:pressed, +QToolButton::menu-button:pressed { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #303446, + stop: 0.5 #51576d, + stop: 1.0 #303446); + padding-top: 2px; + padding-left: 2px; + padding-right: 2px; + padding-bottom: 0px; +} + + +/* the subcontrols below are used only in the MenuButtonPopup mode */ +QToolButton::menu-button +{ + border: 0px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + /* 16px width + 4px for border + no text = 20px allocated above */ + width: 16px; + outline: none; +} + +QToolButton::menu-arrow +{ + image: url(:/dark/down_arrow.svg); +} + +QToolButton::menu-arrow:open +{ + border: 1px solid #737994; +} + +QToolButton#ModeButton::menu-arrow +{ + /*image: url(:/dark/mode_down_arrow.svg);*/ + image:none; +} + +/* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */ +QToolButton::menu-indicator +{ + image: url(:/dark/down_arrow.svg); + top: -20px; + left: 0px; + width:8px; + height:10px; +} + +QToolButton{ + +} + +QToolButton#ModeButton::menu-indicator +{ + image: url(:/dark/mode_down_arrow.svg); + top: 0px; + left: 0px; + width:12px; + height:12px; +} +QToolButton#ModeButton::menu-indicator:hover +{ + top: 0px; +} + +QPushButton::menu-indicator +{ + subcontrol-origin: padding; + subcontrol-position: bottom right; + left: 0px; +} + +QTableView +{ + border: 1px transparent; + gridline-color: #737994; + background-color: #232634; +} + + +QTableView, +QHeaderView +{ + border-radius: 0px; +} + +QTableView::item, +QListView::item, +QTreeView::item +{ + padding: 3px; +} + +QTableView::item:pressed, +QListView::item:pressed, +QTreeView::item:pressed +{ + background: #8caaee; + color: #c6d0f5; +} + +QTableView::item:selected:active, +QTreeView::item:selected:active, +QListView::item:selected:active +{ + background: #8caaee; + color: #c6d0f5; +} + +QTableView::item:hover, +QListView::item:hover, +QTreeView::item:hover +{ + border: 1px solid #8caaee; +} + +QHeaderView +{ + border: 1px transparent; + border-radius: 2px; + margin: 0px; + padding: 0px; +} + +QHeaderView::section +{ + background-color: #303446; + color: #c6d0f5; + padding: 4px; + border: 1px transparent; + border-radius: 0px; + text-align: center; +} + +QHeaderView::section::vertical::first, +QHeaderView::section::vertical::only-one +{ + border-top: 1px transparent; +} + +QHeaderView::section::vertical +{ + border-top: transparent; +} + +QHeaderView::section::horizontal::first, +QHeaderView::section::horizontal::only-one +{ + border-left: 1px transparent; +} + +QHeaderView::section::horizontal +{ + background-color: #626880; + border-left: transparent; +} + + +QHeaderView::section:checked +{ + color: white; + background-color: #51576d; +} + + /* style the sort indicator */ +QHeaderView::down-arrow +{ + image: url(:/dark/down_arrow.svg); +} + +QHeaderView::up-arrow +{ + image: url(:/dark/up_arrow.svg); +} + +QTableCornerButton::section +{ + background-color: #303446; + border: 1px transparent; + border-radius: 2px; +} + +QToolBox +{ + padding: 3px; + border: 1px transparent; +} + +QToolBox:selected +{ + background-color: #303446; + border-color: #8caaee; +} + +QToolBox:hover +{ + border-color: #8caaee; +} + +QStatusBar::item +{ + border: 0px transparent; +} + +QFrame[height="3"], +QFrame[width="3"] +{ + background-color: #737994; +} + +QAbstractScrollArea +{ + border-radius: 2px; + border: 0px; + background-color: #303446; +} + +QSplitter::handle:horizontal, +QMainWindow::separator +{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #303446, + stop: 0.4 #292c3c, + stop: 0.5 #414559, + stop: 0.6 #292c3c, + stop: 1 #303446); + color: white; + padding-left: 0px; + spacing: 0px; + width: 3px; + border: 0px solid #303446; +} + +QSplitter::handle:horizontal:hover, +QMainWindow::separator:hover +{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #303446, + stop: 0.1 #292c3c, + stop: 0.5 #414559, + stop: 0.9 #292c3c, + stop: 1 #303446); + color: white; + padding-left: 0px; + spacing: 0px; + width: 3px; + border: 0px solid #303446; +} + +QSplitter::handle:vertical { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, + stop: 0.0 #303446, + stop: 0.3 #51576d, + stop: 0.5 #626880, + stop: 0.7 #51576d, + stop: 1 #303446); + height: 3px; +} + +QSplitter::handle:vertical:hover { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, + stop: 0.0 #303446, + stop: 0.1 #51576d, + stop: 0.5 #626880, + stop: 0.8 #51576d, + stop: 1 #303446); + height: 3px; +} + + +QProgressBar:horizontal +{ + background-color: #626880; + border: 1px solid #303446; + border-radius: 3px; + height: 5px; + text-align: right; + margin-top: 5px; + margin-bottom: 5px; + margin-right: 50px; + padding: 1px; +} + +QProgressBar::chunk:horizontal +{ + background-color: #8caaee; + border: 1px transparent; + border-radius: 3px; +} + +QSpinBox, +QDoubleSpinBox +{ + padding-right: 0px; + background-color: #232634; + border-style: solid; + border: 1px solid #414559; + border-radius: 2px; + color: #c6d0f5; +} + +QSpinBox::up-button, +QDoubleSpinBox::up-button +{ + subcontrol-origin: content; + subcontrol-position: right top; + + width: 16px; + border-width: 1px; +} + +QSpinBox::up-arrow, +QDoubleSpinBox::up-arrow +{ + image: url(:/dark/up_arrow.svg); + width: 9px; + height: 6px; +} + +QSpinBox::up-arrow:hover, +QSpinBox::up-arrow:pressed, +QDoubleSpinBox::up-arrow:hover, +QDoubleSpinBox::up-arrow:pressed +{ + image: url(:/dark/up_arrow-hover.svg); + width: 9px; + height: 6px; +} + +QSpinBox::up-arrow:disabled, +QSpinBox::up-arrow:off, +QDoubleSpinBox::up-arrow:disabled, +QDoubleSpinBox::up-arrow:off +{ + image: url(:/dark/up_arrow_disabled.svg); +} + +QSpinBox::down-button, +QDoubleSpinBox::down-button +{ + subcontrol-origin: content; + subcontrol-position: right bottom; + + width: 16px; + border-width: 1px; +} + +QSpinBox::down-arrow, +QDoubleSpinBox::down-arrow +{ + image: url(:/dark/down_arrow.svg); + width: 9px; + height: 6px; +} + +QSpinBox::down-arrow:hover, +QSpinBox::down-arrow:pressed, +QDoubleSpinBox::down-arrow:hover, +QDoubleSpinBox::down-arrow:pressed +{ + image: url(:/dark/down_arrow-hover.svg); + width: 9px; + height: 6px; +} + +QSpinBox::down-arrow:disabled, +QSpinBox::down-arrow:off, +QDoubleSpinBox::down-arrow:disabled, +QDoubleSpinBox::down-arrow:off +{ + image: url(:/dark/down_arrow_disabled.svg); +} + +QTextBrowser:hover +{ + border: 1px transparent; +} + +QLineEdit#PathLine{ + color:#c6d0f5; +} +QTextEdit#PathLine{ + color:#c6d0f5; +} diff --git a/DSView/themes/latte.qss b/DSView/themes/latte.qss new file mode 100644 index 000000000..a0ee36826 --- /dev/null +++ b/DSView/themes/latte.qss @@ -0,0 +1,1695 @@ +/* + * Catppuccin Latte theme for DSView + * Generated from light.qss via a systematic + * Catppuccin color substitution - see gen_theme.py in this session's + * scratchpad for the exact mapping used. + */ +/* + * DSView light stylesheet. + * --------------------------------------------------------------------- + * The MIT License (MIT) + * + * Copyright (c) <2013-2014> + * Copyright (C) 2019 DreamSourceLab + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * --------------------------------------------------------------------- + */ + +QToolTip +{ + background-color: black; + color: white; + padding: 5px; +} + +QWidget +{ + color: #4c4f69; + background-color: #eff1f5; + selection-background-color:#1e66f5; + selection-color: #4c4f69; + background-clip: border; + image: none; + border: 0px transparent; + outline: 0; +} + +/* +QWidget:item:hover +{ + background-color: #1e66f5; + color: #4c4f69; +} + +QWidget:item:selected +{ + background-color: #1e66f5; +} +*/ + +QPushButton#flat{ + text-align:left; + border:none; +} + +QPushButton#flat:hover +{ + background-color: #209fb5; + color: #8839ef; +} + +QCheckBox +{ + spacing: 0px; + outline: none; + color: #4c4f69; + margin-bottom: 2px; + opacity: 200; +} + +QCheckBox:disabled +{ + color: #acb0be; +} + +QGroupBox::indicator +{ + width: 18px; + height: 18px; + margin-left: 2px; +} + +QCheckBox::indicator:unchecked, +QCheckBox::indicator:unchecked:focus +{ + image: url(:/light/checkbox_unchecked.svg); +} + +QCheckBox::indicator:unchecked:hover, +QCheckBox::indicator:unchecked:pressed, +QGroupBox::indicator:unchecked:hover, +QGroupBox::indicator:unchecked:focus, +QGroupBox::indicator:unchecked:pressed +{ + border: none; + image: url(:/light/checkbox_unchecked-hover.svg); +} + +QCheckBox::indicator:checked +{ + image: url(:/light/checkbox_checked.svg); +} + +QCheckBox::indicator:checked:hover, +QCheckBox::indicator:checked:focus, +QCheckBox::indicator:checked:pressed, +QGroupBox::indicator:checked:hover, +QGroupBox::indicator:checked:focus, +QGroupBox::indicator:checked:pressed +{ + border: none; + image: url(:/light/checkbox_checked-hover.svg); +} + +QCheckBox::indicator:indeterminate +{ + image: url(:/light/checkbox_indeterminate.svg); +} + +QCheckBox::indicator:indeterminate:focus, +QCheckBox::indicator:indeterminate:hover +QCheckBox::indicator:indeterminate:pressed +{ + image: url(:/light/checkbox_indeterminate-hover.svg); +} + +QCheckBox::indicator:indeterminate:disabled +{ + image: url(:/light/checkbox_indeterminate_disabled.svg); +} + +QCheckBox::indicator:checked:disabled, +QGroupBox::indicator:checked:disabled +{ + image: url(:/light/checkbox_checked_disabled.svg); +} + +QCheckBox::indicator:unchecked:disabled, +QGroupBox::indicator:unchecked:disabled +{ + image: url(:/light/checkbox_unchecked_disabled.svg); +} + +QRadioButton +{ + spacing: 5px; + outline: none; + color: #4c4f69; + margin-bottom: 2px; +} + +QRadioButton:disabled +{ + color: #acb0be; +} +QRadioButton::indicator +{ + width: 16px; + height: 16px; +} + +QRadioButton::indicator:unchecked, +QRadioButton::indicator:unchecked:focus +{ + image: url(:/light/radio_unchecked.svg); +} + +QRadioButton::indicator:unchecked:hover, +QRadioButton::indicator:unchecked:pressed +{ + border: none; + outline: none; + image: url(:/light/radio_unchecked-hover.svg); +} + +QRadioButton::indicator:checked +{ + border: none; + outline: none; + image: url(:/light/radio_checked.svg); +} + +QRadioButton::indicator:checked:hover, +QRadioButton::indicator:checked:focus, +QRadioButton::indicator:checked:pressed +{ + border: none; + outline: none; + image: url(:/light/radio_checked-hover.svg); +} + +QRadioButton::indicator:checked:disabled +{ + outline: none; + image: url(:/light/radio_checked_disabled.svg); +} + +QRadioButton::indicator:unchecked:disabled +{ + image: url(:/light/radio_unchecked_disabled.svg); +} + +QMenuBar +{ + background-color: #eff1f5; + color: #4c4f69; +} + +QMenuBar::item +{ + background: transparent; +} + +QMenuBar::item:selected +{ + background: transparent; + border: 1px transparent; +} + +QMenuBar::item:pressed +{ + border: 1px transparent; + background-color: #1e66f5; + color: #4c4f69; + margin-bottom: -1px; + padding-bottom: 1px; +} + +QMenu +{ + border: 1px transparent; + color: #4c4f69; + margin: 0px; +} + +QMenu::item +{ + padding: 5px 30px 5px 30px; + margin-left: 2px; + border: 1px solid transparent; /* reserve space for selection border */ +} + +QMenu::item:selected +{ + background-color: #1e66f5; + color: #4c4f69; +} + +QMenu::separator +{ + height: 2px; + background: lightblue; + margin-left: 10px; + margin-right: 5px; +} + +QMenu::indicator { + width: 18px; + height: 18px; +} + +/* non-exclusive indicator = check box style indicator + (see QActionGroup::setExclusive) */ +QMenu::indicator:non-exclusive:unchecked +{ + image: url(:/light/checkbox_unchecked_disabled.svg); +} + +QMenu::indicator:non-exclusive:unchecked:selected +{ + image: url(:/light/checkbox_unchecked_disabled.svg); +} + +QMenu::indicator:non-exclusive:checked +{ + image: url(:/light/checkbox_checked.svg); +} + +QMenu::indicator:non-exclusive:checked:selected +{ + image: url(:/light/checkbox_checked.svg); +} + +/* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */ +QMenu::indicator:exclusive:unchecked +{ + image: url(:/light/radio_unchecked_disabled.svg); +} + +QMenu::indicator:exclusive:unchecked:selected +{ + image: url(:/light/radio_unchecked_disabled.svg); +} + +QMenu::indicator:exclusive:checked +{ + image: url(:/light/radio_checked.svg); +} + +QMenu::indicator:exclusive:checked:selected +{ + image: url(:/light/radio_checked.svg); +} + +QMenu::right-arrow +{ + margin: 5px; + image: url(:/light/right_arrow.svg); +} + + +QWidget:disabled +{ + color: #acb0be; + background-color: #eff1f5; +} + +QAbstractItemView +{ + alternate-background-color: #dce0e8; + color: #4c4f69; + border: 1px transparent; + border-radius: 2px; + padding: 1px +} + +QTabWidget:focus, +QCheckBox:focus, +QRadioButton:focus, +QSlider:focus +{ + border: none; +} + +QLineEdit +{ + background-color: #eff1f5; + padding: 2px; + border-style: solid; + border: 1px solid #4c4f69; + border-radius: 2px; + color: #4c4f69; +} + +QTextEdit +{ + background-color: #eff1f5; + padding: 2px; + border-style: solid; + border: 1px solid #4c4f69; + border-radius: 2px; + color: #4c4f69; +} + +QGroupBox +{ + border: 1px solid #4c4f69; + border-radius: 2px; + margin-top: 20px; +} + +QGroupBox:disabled +{ + border: 1px solid #acb0be; +} + +QGroupBox::title +{ + subcontrol-origin: margin; + subcontrol-position: top center; + padding-left: 10px; + padding-right: 10px; + padding-top: 10px; +} + +QScrollBar:horizontal +{ + height: 24px; + margin: 3px 12px 3px 12px; + border: 1px transparent; + border-radius: 9px; + background-color: #eff1f5; +} + +QScrollBar::handle:horizontal +{ + background-color: #6c6f85; + min-width: 20px; + border-radius: 9px; +} + +QScrollBar::add-line:horizontal +{ + margin: 0px 3px 0px 3px; + image: url(:/light/right_arrow_disabled.svg); + width: 10px; + height: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal +{ + margin: 0px 3px 0px 3px; + image: url(:/light/left_arrow_disabled.svg); + width: 10px; + height: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::add-line:horizontal:hover, +QScrollBar::add-line:horizontal:on +{ + image: url(:/light/right_arrow.svg); + width: 10px; + height: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + + +QScrollBar::sub-line:horizontal:hover, +QScrollBar::sub-line:horizontal:on +{ + image: url(:/light/left_arrow.svg); + width: 10px; + height: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:horizontal, +QScrollBar::down-arrow:horizontal +{ + background: none; +} + + +QScrollBar::add-page:horizontal, +QScrollBar::sub-page:horizontal +{ + background: none; +} + +QScrollBar:vertical +{ + background-color: #eff1f5; + width: 24px; + margin: 12px 3px 12px 3px; + border: 1px transparent; + border-radius: 9px; +} + +QScrollBar::handle:vertical +{ + background-color: #6c6f85; + min-height: 20px; + border-radius: 9px; +} + +QScrollBar::sub-line:vertical +{ + margin: 3px 0px 3px 0px; + image: url(:/light/up_arrow_disabled.svg); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QScrollBar::add-line:vertical +{ + margin: 3px 0px 3px 0px; + image: url(:/light/down_arrow_disabled.svg); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:vertical:hover, +QScrollBar::sub-line:vertical:on +{ + + image: url(:/light/up_arrow.svg); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + + +QScrollBar::add-line:vertical:hover, +QScrollBar::add-line:vertical:on +{ + image: url(:/light/down_arrow.svg); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:vertical, +QScrollBar::down-arrow:vertical +{ + background: none; +} + + +QScrollBar::add-page:vertical, +QScrollBar::sub-page:vertical +{ + background: none; +} + +QTextEdit +{ + background-color: #eff1f5; + color: #4c4f69; + border: 1px solid #4c4f69; + margin: 0; +} + +QPlainTextEdit +{ + background-color: #eff1f5; + color: #4c4f69; + border-radius: 2px; + border: 1px solid #4c4f69; +} + +QHeaderView::section +{ + background-color: #4c4f69; + color: #4c4f69; + padding-left: 4px; + border: 1px solid #4c4f69; +} + +QSizeGrip +{ + image: url(:/light/sizegrip.svg); + width: 12px; + height: 12px; +} + +QMenu::separator +{ + height: 1px; + background-color: #4c4f69; + color: white; + padding-left: 4px; + margin-left: 10px; + margin-right: 5px; +} + +QFrame +{ + border-radius: 2px; + border: 1px transparent; +} + +QFrame[frameShape="0"] +{ + border-radius: 2px; + border: 1px transparent; +} + +QStackedWidget +{ + border: 1px transparent; +} + +QToolBar +{ + border: 1px transparent; + background: transparent; + padding: 0px; +} + +QToolBar::handle:horizontal +{ + image: url(:/light/hmovetoolbar.svg); + width = 16px; + height = 64px; +} + +QToolBar::handle:vertical +{ + image: url(:/light/vmovetoolbar.svg); + width = 54px; + height = 10px; +} + +QToolBar::separator:horizontal +{ + image: url(:/light/hsepartoolbar.svg); + width = 7px; + height = 63px; +} + +QToolBar::separator:vertical +{ + image: url(:/light/vsepartoolbars.svg); + width = 63px; + height = 7px; +} + +QPushButton +{ + color: #4c4f69; + background-color: #eff1f5; + border-width: 1px; + border-color: #4c4f69; + border-style: solid; + padding: 5px; + border-radius: 5px; + outline: none; +} + +QPushButton:disabled +{ + background-color: #dce0e8; + border-width: 1px; + border-color: #acb0be; + border-style: solid; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 10px; + padding-right: 10px; + border-radius: 5px; + color: #acb0be; +} + +QPushButton:focus +{ + color: black; +} + +QPushButton:pressed +{ + background-color: #eff1f5; + padding-top: -15px; + padding-bottom: -17px; +} + + +QPushButton:checked{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #eff1f5, + stop: 0.5 #9ca0b0, + stop: 1 #eff1f5); +} + +QPushButton:hover +{ + background-color: #eff1f5; + border: 1px solid #1e66f5; + color: #4c4f69; + padding-top: 6px; + padding-bottom: 4px; +} + +QPushButton:checked:hover +{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #eff1f5, + stop: 0.5 #9ca0b0, + stop: 1 #eff1f5); + border: 1px solid #1e66f5; + color: #4c4f69; + padding-top: 6px; + padding-bottom: 4px; +} + +QComboBox:hover, +QAbstractSpinBox:hover, +QLineEdit:hover, +QTextEdit:hover, +QPlainTextEdit:hover, +QAbstractView:hover, +QTreeView:hover +{ + border: 1px solid #1e66f5; + color: #4c4f69; +} + +QComboBox:hover:pressed, +QPushButton:hover:pressed, +QAbstractSpinBox:hover:pressed, +QLineEdit:hover:pressed, +QTextEdit:hover:pressed, +QPlainTextEdit:hover:pressed, +QAbstractView:hover:pressed, +QTreeView:hover:pressed +{ + background-color: #eff1f5; +} + +QComboBox:disabled, +QAbstractSpinBox:disabled, +QLineEdit:disabled, +QTextEdit:disabled, +QPlainTextEdit:disabled, +QAbstractView:disabled, +QTreeView:disabled +{ + border: 1px solid #acb0be; +} + +QComboBox:on +{ + padding-top: 3px; + padding-left: 4px; + selection-background-color: #5c5f77; +} + +QComboBox +{ + selection-background-color: #1e66f5; + background-color: #eff1f5; + border-style: solid; + border: 1px solid #4c4f69; + border-radius: 2px; + padding: 2px; + min-width: 30px; +} + +QComboBox QAbstractItemView +{ + background-color: #eff1f5; + border-radius: 2px; + border: 1px solid #4c4f69; + selection-background-color: #1e66f5; +} + +QComboBox::drop-down +{ + subcontrol-origin: padding; + subcontrol-position: top right; + width: 10px; + + border-left-width: 0px; + border-left-color: darkgray; + border-left-style: solid; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +QComboBox::down-arrow +{ + image: url(:/light/down_arrow_disabled.svg); +} + +QComboBox::down-arrow:on, +QComboBox::down-arrow:hover, +QComboBox::down-arrow:focus +{ + image: url(:/light/down_arrow.svg); +} + +QAbstractSpinBox +{ + padding: 2px; + border: 1px solid #4c4f69; + background-color: #e6e9ef; + color: #4c4f69; + border-radius: 2px; + min-width: 60px; +} + +QAbstractSpinBox:up-button +{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center right; +} + +QAbstractSpinBox:down-button +{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center left; +} + +QAbstractSpinBox::up-arrow, +QAbstractSpinBox::up-arrow:disabled, +QAbstractSpinBox::up-arrow:off +{ + image: url(:/light/up_arrow_disabled.svg); + width: 10px; + height: 10px; +} + +QAbstractSpinBox::up-arrow:hover +{ + image: url(:/light/up_arrow.svg); +} + +QAbstractSpinBox::down-arrow, +QAbstractSpinBox::down-arrow:disabled, +QAbstractSpinBox::down-arrow:off +{ + image: url(:/light/down_arrow_disabled.svg); + width: 10px; + height: 10px; +} + +QAbstractSpinBox::down-arrow:hover +{ + image: url(:/light/down_arrow.svg); +} + +QLabel +{ + border: 0px solid black; + margin-left: 2px; + margin-right: 2px; +} + +/* BORDERS */ +QTabWidget::pane +{ + padding: 0px; + padding-right: 3px; + margin: 0px; +} + +QTabWidget::pane:top +{ + border: 1px transparent; + top: -1px; +} + +QTabWidget::pane:bottom +{ + border: 1px transparent; + bottom: -1px; +} + +QTabWidget::pane:left +{ + border: 1px transparent; + right: -1px; +} + +QTabWidget::pane:right +{ + border: 1px transparent; + left: -1px; +} + +QTabBar +{ + qproperty-drawBase: 0; + margin: 0px; + padding: 0px; + border-radius: 3px; +} + +QTabBar:focus +{ + border: 0px transparent; +} + +QTabBar::close-button +{ + image: url(:/light/close.svg); + background: transparent; +} + +QTabBar::close-button:hover +{ + image: url(:/light/close-hover.svg); + width: 12px; + height: 12px; + background: transparent; +} + +QTabBar::close-button:pressed +{ + image: url(:/light/close-pressed.svg); + width: 12px; + height: 12px; + background: transparent; +} + +QTabBar::tab +{ + color: 2A2A2A; + background-color: #209fb5; + padding: 5px; +} + +QTabBar::tab:!selected +{ + color: 2A2A2A; + background-color: #eff1f5; +} + +QTabBar::tab:disabled +{ + color: #acb0be; + background-color: #eff1f5; +} + +/* TOP TABS */ +QTabBar::tab:top +{ + border: 1px solid #acb0be; + border-bottom: 1px transparent; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +QTabBar::tab:top:!selected +{ + border: 1px solid #acb0be; + border-bottom: 1px transparent; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +QTabBar::tab:top:hover +{ + border: 1px solid #1e66f5; + border-bottom: 1px transparent; +} + + +/* BOTTOM TABS */ +QTabBar::tab:bottom +{ + border: 1px solid #acb0be; + border-top: 1px transparent; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:bottom:!selected +{ + border: 1px solid #acb0be; + border-top: 1px transparent; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:bottom:hover +{ + border: 1px solid #1e66f5; + border-top: 1px transparent; +} + + +/* LEFT TABS */ +QTabBar::tab:left +{ + border: 1px solid #acb0be; + border-left: 1px transparent; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:left:!selected +{ + border: 1px solid #acb0be; + border-left: 1px transparent; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:left:hover +{ + border: 1px solid #1e66f5; + border-left: 1px transparent; +} + + +/* RIGHT TABS */ +QTabBar::tab:right +{ + border: 1px solid #acb0be; + border-right: 1px transparent; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} + +QTabBar::tab:right:!selected +{ + border: 1px solid #acb0be; + border-right: 1px transparent; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} + +QTabBar::tab:right:hover +{ + border: 1px solid #1e66f5; + border-right: 1px transparent; +} + + +QTabBar QToolButton::right-arrow:enabled +{ + image: url(:/light/right_arrow.svg); +} + +QTabBar QToolButton::left-arrow:enabled +{ + image: url(:/light/left_arrow.svg); +} + +QTabBar QToolButton::right-arrow:disabled +{ + image: url(:/light/right_arrow_disabled.svg); +} + +QTabBar QToolButton::left-arrow:disabled +{ + image: url(:/light/left_arrow_disabled.svg); +} + +QDockWidget +{ + background: #eff1f5; + border: 1px transparent; + titlebar-close-icon: url(:/light/transparent.svg); + titlebar-normal-icon: url(:/light/transparent.svg); +} + +QDockWidget::close-button, +QDockWidget::float-button +{ + border: 1px solid transparent; + border-radius: 2px; + background: transparent; +} + +QDockWidget::float-button +{ + image: url(:/dark/undock.svg); +} + +QDockWidget::float-button:hover +{ + image: url(:/dark/undock-hover.svg) ; +} + +QDockWidget::close-button +{ + image: url(:/dark/close.svg) ; +} + +QDockWidget::close-button:hover +{ + image: url(:/dark/close-hover.svg) ; +} + +QDockWidget::close-button:pressed +{ + image: url(:/dark/close-pressed.svg) ; +} + +QTreeView, +QListView +{ + border: 1px solid #4c4f69; + background-color: #eff1f5; +} + + +QTreeView::branch:has-siblings:!adjoins-item +{ + image: url(:/light/stylesheet-vline.svg) 0; +} + +QTreeView::branch:has-siblings:adjoins-item +{ + image: url(:/light/stylesheet-branch-more.svg) 0; +} + +QTreeView::branch:!has-children:!has-siblings:adjoins-item +{ + image: url(:/light/stylesheet-branch-end.svg) 0; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings +{ + image: url(:/light/stylesheet-branch-end-closed.svg) 0; + image: url(:/light/branch_closed.svg); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings +{ + image: url(:/light/stylesheet-branch-end-open.svg) 0; + image: url(:/light/branch_open.svg); +} + +QTableView::item, +QListView::item, +QTreeView::item +{ + padding: 3px; +} + +QTableView::item:!selected:hover, +QListView::item:!selected:hover, +QTreeView::item:!selected:hover +{ + background-color: rgba(61, 173, 232, 0.1); + outline: 0; + color: #4c4f69; + padding: 3px; +} + +QSlider::groove:horizontal +{ + border: 1px solid #eff1f5; + height: 4px; + background: #6c6f85; + margin: 0px; + border-radius: 1px; +} + +QSlider::handle:horizontal +{ + background: #eff1f5; + border: 1px solid #1e66f5; + width: 16px; + margin: -7px 0; + border-radius: 8px; +} + +QSlider::groove:vertical +{ + border: 1px solid #eff1f5; + width: 4px; + background: #6c6f85; + margin: 0px; + border-radius: 3px; +} + +QSlider::handle:vertical +{ + background: #eff1f5; + border: 1px solid #1e66f5; + width: 16px; + height: 16px; + margin: 0 -8px; + border-radius: 9px; +} + +QSlider::handle:horizontal:hover, +QSlider::handle:vertical:hover +{ + background: #1e66f5; +} + +QSlider::sub-page:horizontal, +QSlider::add-page:vertical +{ + background: #1e66f5; + border-radius: 1px; +} + +QSlider::add-page:horizontal, +QSlider::sub-page:vertical +{ + background: #6c6f85; + border-radius: 1px; +} + +QSlider::handle:disabled +{ + background: #eff1f5; + border: 1px solid #acb0be; +} + +QSlider::groove:disabled +{ + background: #acb0be; + border: 1px solid #acb0be; +} + +QSlider::add-page:disabled, +QSlider::sub-page:disabled +{ + background: #acb0be; + border-radius: 1px; +} + +QToolButton#MaximizeButton { + background-color: transparent; + border-left: 1px solid QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #eff1f5, stop: 0.3 #5c5f77, + stop: 0.5 #8c8fa1, + stop: 0.7 #5c5f77, stop: 1 #eff1f5); + border-right: 1px solid QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #eff1f5, stop: 0.3 #5c5f77, + stop: 0.5 #8c8fa1, + stop: 0.7 #5c5f77, stop: 1 #eff1f5); + border-radius: 0px; + margin: 0px; + padding: 0px; +} + +QToolButton#MinimizeButton, +QToolButton#CloseButton { + background-color: transparent; + border: 1px transparent; + border-radius: 0px; + margin: 0px; + padding: 0px; +} + +QToolButton#FileCloseButton { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #eff1f5, stop: 0.1 #d20f39, + stop: 0.5 #d20f39, + stop: 0.9 #d20f39, stop: 1 #eff1f5); + border: 1px transparent; + border-radius: 0px; + margin: 0px; + padding: 0px; +} + +QToolButton#MinimizeButton:hover, QToolButton#MinimizeButton::menu-button:hover, +QToolButton#MaximizeButton:hover, QToolButton#MaximizeButton::menu-button:hover{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #eff1f5, stop: 0.4 #5c5f77, + stop: 0.5 #5c5f77, + stop: 0.6 #5c5f77, stop: 1 #eff1f5); +} + +QToolButton#CloseButton:hover, QToolButton#CloseButton::menu-button:hover { +background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #eff1f5, stop: 0.1 #d20f39, + stop: 0.5 #d20f39, + stop: 0.9 #d20f39, stop: 1 #eff1f5); +} + +QToolButton#FileCloseButton:hover, QToolButton#FileCloseButton::menu-button:hover { + padding-top: 2px; +} + +QToolButton +{ + background-color: transparent; + border: 0px; + border-radius: 0px; + margin: 0px; + padding-top: 0px; + padding-left: 2px; + padding-right: 2px; + padding-bottom: 2px; +} + +QToolButton[popupMode="1"] /* only for MenuButtonPopup */ +{ + padding-right: 20px; /* make way for the popup button */ + border: 0px; + border-radius: 0px; + margin: 0px; + padding-top: 0px; + padding-left: 2px; + padding-right: 2px; + padding-bottom: 2px; +} + +QToolButton[popupMode="2"] /* only for InstantPopup */ +{ + padding-right: 10px; /* make way for the popup button */ + border: 0px; + margin: 0px; + padding-top: 0px; + padding-left: 2px; + padding-right: 2px; + padding-bottom: 2px; +} + +QToolButton:hover, +QToolButton::menu-button:hover +{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #eff1f5, stop: 0.9 #eff1f5, + stop: 0.95 #1e66f5, stop: 1 #1e66f5); + padding-top: 1px; + padding-left: 2px; + padding-right: 2px; + padding-bottom: 1px; +} + +QToolButton:checked, QToolButton:pressed, +QToolButton::menu-button:pressed { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #eff1f5, stop: 0.9 #eff1f5, + stop: 0.95 #1e66f5, stop: 1 #1e66f5); + padding-top: 2px; + padding-left: 2px; + padding-right: 2px; + padding-bottom: 0px; +} + + +/* the subcontrols below are used only in the MenuButtonPopup mode */ +QToolButton::menu-button +{ + border: 0px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + /* 16px width + 4px for border + no text = 20px allocated above */ + width: 16px; + outline: none; +} + +QToolButton::menu-arrow +{ + image: url(:/light/down_arrow.svg); +} +QToolButton::menu-arrow:open +{ + border: 1px solid #4c4f69; +} + +QToolButton#ModeButton::menu-arrow +{ + /*image: url(:/light/mode_down_arrow.svg);*/ + image:none; +} + +/* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */ +QToolButton::menu-indicator +{ + image: url(:/light/down_arrow.svg); + top: -20px; + left: 0px; + width:8px; + height:10px; +} + +QToolButton{ + +} + +QToolButton#ModeButton::menu-indicator +{ + image: url(:/light/mode_down_arrow.svg); + top: 0px; + left: 0px; + width:12px; + height:12px; +} +QToolButton#ModeButton::menu-indicator:hover +{ + top: 0px; +} + +QPushButton::menu-indicator +{ + subcontrol-origin: padding; + subcontrol-position: bottom right; + left: 0px; +} + +QTableView +{ + border: 1px transparent; + gridline-color: #4c4f69; + background-color: #eff1f5; +} + + +QTableView, +QHeaderView +{ + border-radius: 0px; +} + +QTableView::item:pressed +{ + background: #1e66f5; + color: #4c4f69; +} + +QTableView::item:selected:active +{ + background: #1e66f5; + color: #4c4f69; +} + +QTableView::item:selected:hover +{ + background-color: #04a5e5; + color: #4c4f69; +} + +QListView::item:pressed, +QTreeView::item:pressed +{ + background: #1e66f5; + color: #4c4f69; +} + +QTreeView::item:selected:active, +QListView::item:selected:active +{ + background: #1e66f5; + color: #4c4f69; +} + +QTableView::item:hover, +QListView::item:hover, +QTreeView::item:hover +{ + border: 1px solid #1e66f5; +} + + +QHeaderView +{ + border: 1px transparent; + border-radius: 2px; + margin: 0px; + padding: 0px; +} + +QHeaderView::section +{ + background-color: #eff1f5; + color: #4c4f69; + padding: 4px; + border: 1px transparent; + border-radius: 0px; + text-align: center; +} + +QHeaderView::section::vertical::first, +QHeaderView::section::vertical::only-one +{ + border-top: 1px transparent; +} + +QHeaderView::section::vertical +{ + border-top: transparent; +} + +QHeaderView::section::horizontal::first, +QHeaderView::section::horizontal::only-one +{ + border-left: 1px transparent; +} + +QHeaderView::section::horizontal +{ + background-color: #ccd0da; + border-left: transparent; +} + + +QHeaderView::section:checked + + { + color: black; + background-color: #99d1db; + } + + /* style the sort indicator */ +QHeaderView::down-arrow +{ + image: url(:/light/down_arrow.svg); +} + +QHeaderView::up-arrow +{ + image: url(:/light/up_arrow.svg); +} + +QTableCornerButton::section +{ + background-color: #eff1f5; + border: 1px transparent; + border-radius: 0px; +} + +QToolBox +{ + padding: 3px; + border: 1px transparent; +} + +QToolBox:selected +{ + background-color: #eff1f5; + border-color: #1e66f5; +} + +QToolBox:hover +{ + border-color: #1e66f5; +} + +QStatusBar::item +{ + border: 0px transparent; +} + +QSplitter::handle:horizontal +QMainWindow::separator +{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #eff1f5, + stop: 0.4 #ccd0da, + stop: 0.5 #6c6f85, + stop: 0.6 #ccd0da, + stop: 1 #eff1f5); + color: white; + padding-left: 0px; + spacing: 0px; + width: 2px; + border: 0px solid #4c4f69; +} + +QSplitter::handle:horizontal:hover, +QMainWindow::separator:hover +{ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0.0 #eff1f5, + stop: 0.1 #ccd0da, + stop: 0.5 #6c6f85, + stop: 0.9 #ccd0da, + stop: 1 #eff1f5); + color: white; + padding-left: 0px; + spacing: 0px; + width: 2px; + border: 0px solid #4c4f69; +} + +QSplitter::handle:vertical { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, + stop: 0.0 #eff1f5, + stop: 0.3 #ccd0da, + stop: 0.5 #6c6f85, + stop: 0.7 #ccd0da, + stop: 1 #eff1f5); + height: 3px; +} + +QSplitter::handle:vertical:hover { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, + stop: 0.0 #eff1f5, + stop: 0.1 #ccd0da, + stop: 0.5 #6c6f85, + stop: 0.8 #ccd0da, + stop: 1 #eff1f5); + height: 3px; +} + + +QProgressBar:horizontal +{ + background-color: #bcc0cc; + border: 1px solid #eff1f5; + border-radius: 3px; + height: 5px; + text-align: right; + margin-top: 5px; + margin-bottom: 5px; + margin-right: 50px; + padding: 1px; +} + +QProgressBar::chunk:horizontal +{ + background-color: #1e66f5; + border: 1px transparent; + border-radius: 3px; +} + +QAbstractSpinBox +{ + background-color: #eff1f5; +} + +QSpinBox, +QDoubleSpinBox +{ + padding-right: 0px; +} + +QSpinBox::up-button, +QDoubleSpinBox::up-button +{ + subcontrol-origin: content; + subcontrol-position: right top; + + width: 16px; + border-width: 1px; +} + +QSpinBox::up-arrow, +QDoubleSpinBox::up-arrow +{ + image: url(:/light/up_arrow.svg); + width: 9px; + height: 6px; +} + +QSpinBox::up-arrow:hover, +QSpinBox::up-arrow:pressed, +QDoubleSpinBox::up-arrow:hover, +QDoubleSpinBox::up-arrow:pressed +{ + image: url(:/light/up_arrow-hover.svg); + width: 9px; + height: 6px; +} + +QSpinBox::up-arrow:disabled, +QSpinBox::up-arrow:off, +QDoubleSpinBox::up-arrow:disabled, +QDoubleSpinBox::up-arrow:off +{ + image: url(:/light/up_arrow_disabled.svg); +} + +QSpinBox::down-button, +QDoubleSpinBox::down-button +{ + subcontrol-origin: content; + subcontrol-position: right bottom; + + width: 16px; + border-width: 1px; +} + +QSpinBox::down-arrow, +QDoubleSpinBox::down-arrow +{ + image: url(:/light/down_arrow.svg); + width: 9px; + height: 6px; +} + +QSpinBox::down-arrow:hover, +QSpinBox::down-arrow:pressed, +QDoubleSpinBox::down-arrow:hover, +QDoubleSpinBox::down-arrow:pressed +{ + image: url(:/light/down_arrow-hover.svg); + width: 9px; + height: 6px; +} + +QSpinBox::down-arrow:disabled, +QSpinBox::down-arrow:off, +QDoubleSpinBox::down-arrow:disabled, +QDoubleSpinBox::down-arrow:off +{ + image: url(:/light/down_arrow_disabled.svg); +} + +QTextBrowser:hover +{ + border: 1px transparent; +} + +QLineEdit#PathLine{ + color:#4c4f69; +} + +QTextEdit#PathLine{ + color:#4c4f69; +} diff --git a/lang/cn/toolbar.json b/lang/cn/toolbar.json index e8db415a8..30d767b3d 100644 --- a/lang/cn/toolbar.json +++ b/lang/cn/toolbar.json @@ -106,7 +106,15 @@ { "id": "IDS_TOOLBAR_DISPLAY_THEMES_LIGHT", "text": "清新(&L)" - }, + }, + { + "id": "IDS_TOOLBAR_DISPLAY_THEMES_LATTE", + "text": "拿铁(&T)" + }, + { + "id": "IDS_TOOLBAR_DISPLAY_THEMES_FRAPPE", + "text": "冰沙(&P)" + }, { "id": "IDS_TOOLBAR_DISPLAY_OPTIONS", "text": "选项(&O)" diff --git a/lang/de/toolbar.json b/lang/de/toolbar.json index 97e1edc80..bbea75dbd 100644 --- a/lang/de/toolbar.json +++ b/lang/de/toolbar.json @@ -107,6 +107,14 @@ "id": "IDS_TOOLBAR_DISPLAY_THEMES_LIGHT", "text": "&Hell" }, + { + "id": "IDS_TOOLBAR_DISPLAY_THEMES_LATTE", + "text": "La&tte" + }, + { + "id": "IDS_TOOLBAR_DISPLAY_THEMES_FRAPPE", + "text": "Fra&ppé" + }, { "id": "IDS_TOOLBAR_DISPLAY_OPTIONS", "text": "&Optionen" diff --git a/lang/en/toolbar.json b/lang/en/toolbar.json index 1fb7fae2f..19ff22708 100644 --- a/lang/en/toolbar.json +++ b/lang/en/toolbar.json @@ -106,7 +106,15 @@ { "id": "IDS_TOOLBAR_DISPLAY_THEMES_LIGHT", "text": "&Light" - }, + }, + { + "id": "IDS_TOOLBAR_DISPLAY_THEMES_LATTE", + "text": "La&tte" + }, + { + "id": "IDS_TOOLBAR_DISPLAY_THEMES_FRAPPE", + "text": "Fra&ppé" + }, { "id": "IDS_TOOLBAR_DISPLAY_OPTIONS", "text": "&Options" From afa5fd88e3989373c1a69d6f8a04f29ec03ac5ff Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 1 Jul 2026 14:19:28 +0200 Subject: [PATCH 07/34] Add option to skip save dialog --- DSView/pv/config/appconfig.cpp | 2 ++ DSView/pv/config/appconfig.h | 1 + DSView/pv/dialogs/applicationpardlg.cpp | 9 +++++++++ DSView/pv/mainwindow.cpp | 12 +++++++++--- lang/cn/dlg.json | 4 ++++ lang/de/dlg.json | 4 ++++ lang/en/dlg.json | 4 ++++ 7 files changed, 33 insertions(+), 3 deletions(-) diff --git a/DSView/pv/config/appconfig.cpp b/DSView/pv/config/appconfig.cpp index e7f938748..d07f00a38 100644 --- a/DSView/pv/config/appconfig.cpp +++ b/DSView/pv/config/appconfig.cpp @@ -131,6 +131,7 @@ static void _loadApp(AppOptions &o, QSettings &st) getFiled("decoderDynamicFontWidth", st, o.decoderDynamicFontWidth, false); getFiled("maxDecoderFontWidthPercent", st, o.maxDecoderFontWidthPercent, 125); getFiled("minDecoderFontWidthPercent", st, o.minDecoderFontWidthPercent, 75); + getFiled("dontAskSaveOnExit", st, o.dontAskSaveOnExit, false); o.warnofMultiTrig = true; @@ -180,6 +181,7 @@ static void _saveApp(AppOptions &o, QSettings &st) setFiled("decoderDynamicFontWidth", st, o.decoderDynamicFontWidth); setFiled("maxDecoderFontWidthPercent", st, o.maxDecoderFontWidthPercent); setFiled("minDecoderFontWidthPercent", st, o.minDecoderFontWidthPercent); + setFiled("dontAskSaveOnExit", st, o.dontAskSaveOnExit); QString fmt = FormatArrayToString(o.m_protocolFormats); setFiled("protocalFormats", st, fmt); diff --git a/DSView/pv/config/appconfig.h b/DSView/pv/config/appconfig.h index 4c542b0b6..1dc53bbae 100644 --- a/DSView/pv/config/appconfig.h +++ b/DSView/pv/config/appconfig.h @@ -85,6 +85,7 @@ struct AppOptions int minDecoderFontWidthPercent; int maxDecoderFontWidthPercent; bool decoderDynamicFontWidth; + bool dontAskSaveOnExit; std::vector m_protocolFormats; }; diff --git a/DSView/pv/dialogs/applicationpardlg.cpp b/DSView/pv/dialogs/applicationpardlg.cpp index b12fbf502..9559d42b3 100644 --- a/DSView/pv/dialogs/applicationpardlg.cpp +++ b/DSView/pv/dialogs/applicationpardlg.cpp @@ -151,6 +151,9 @@ bool ApplicationParamDlg::ShowDlg(QWidget *parent) QCheckBox *ck_antialias = new QCheckBox(); ck_antialias->setChecked(app.appOptions.antialias); + QCheckBox *ck_dontAskSaveOnExit = new QCheckBox(); + ck_dontAskSaveOnExit->setChecked(app.appOptions.dontAskSaveOnExit); + QComboBox *ftCbSize = new DsComboBox(); ftCbSize->setFixedWidth(50); bind_font_size_list(ftCbSize, app.appOptions.fontSize); @@ -328,6 +331,8 @@ bool ApplicationParamDlg::ShowDlg(QWidget *parent) uiLay->addWidget(ftCbSize, 1, 1, Qt::AlignRight); uiLay->addWidget(new QLabel(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_DISPLAY_ANTIALIAS), "Antialiasing")), 2, 0, Qt::AlignLeft); uiLay->addWidget(ck_antialias, 2, 1, Qt::AlignRight); + uiLay->addWidget(new QLabel(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_DONT_ASK_SAVE_ON_EXIT), "Do not ask to save captured data")), 3, 0, Qt::AlignLeft); + uiLay->addWidget(ck_dontAskSaveOnExit, 3, 1, Qt::AlignRight); lay->addWidget(uiGroup); dlg.layout()->addLayout(lay); @@ -381,6 +386,10 @@ bool ApplicationParamDlg::ShowDlg(QWidget *parent) app.appOptions.antialias = ck_antialias->isChecked(); bAppChanged = true; } + if (app.appOptions.dontAskSaveOnExit != ck_dontAskSaveOnExit->isChecked()){ + app.appOptions.dontAskSaveOnExit = ck_dontAskSaveOnExit->isChecked(); + bAppChanged = true; + } if (app.appOptions.decoderDynamicFontWidth != ck_decoderDynamicFontWidth->isChecked()) { app.appOptions.decoderDynamicFontWidth = ck_decoderDynamicFontWidth->isChecked(); bAppChanged = true; diff --git a/DSView/pv/mainwindow.cpp b/DSView/pv/mainwindow.cpp index eb67f179b..aceea7a35 100644 --- a/DSView/pv/mainwindow.cpp +++ b/DSView/pv/mainwindow.cpp @@ -1618,12 +1618,18 @@ namespace pv } bool MainWindow::confirm_to_store_data() - { + { bool ret = false; - _is_save_confirm_msg = true; + _is_save_confirm_msg = true; + + if (AppConfig::Instance().appOptions.dontAskSaveOnExit) + { + _is_save_confirm_msg = false; + return false; + } if (_session->have_hardware_data() && _session->is_first_store_confirm()) - { + { // Only popup one time. ret = MsgBox::Confirm(L_S(STR_PAGE_MSG, S_ID(IDS_MSG_SAVE_CAPDATE), "Save captured data?")); diff --git a/lang/cn/dlg.json b/lang/cn/dlg.json index f810b7555..ebf66f594 100644 --- a/lang/cn/dlg.json +++ b/lang/cn/dlg.json @@ -699,6 +699,10 @@ "id": "IDS_DLG_DISPLAY_ANTIALIAS", "text": "抗锯齿" }, + { + "id": "IDS_DLG_DONT_ASK_SAVE_ON_EXIT", + "text": "不再询问是否保存采集数据" + }, { "id": "IDS_DLG_SERIAL_HEX", "text": "十六进制 :" diff --git a/lang/de/dlg.json b/lang/de/dlg.json index 9aa220499..c02d34b72 100644 --- a/lang/de/dlg.json +++ b/lang/de/dlg.json @@ -699,6 +699,10 @@ "id": "IDS_DLG_DISPLAY_ANTIALIAS", "text": "Kantenglättung" }, + { + "id": "IDS_DLG_DONT_ASK_SAVE_ON_EXIT", + "text": "Nicht nach Speichern der aufgezeichneten Daten fragen" + }, { "id": "IDS_DLG_SERIAL_HEX", "text": "Hex:" diff --git a/lang/en/dlg.json b/lang/en/dlg.json index d2c8414eb..da90e9402 100644 --- a/lang/en/dlg.json +++ b/lang/en/dlg.json @@ -703,6 +703,10 @@ "id": "IDS_DLG_DISPLAY_ANTIALIAS", "text": "Antialiasing" }, + { + "id": "IDS_DLG_DONT_ASK_SAVE_ON_EXIT", + "text": "Do not ask to save captured data" + }, { "id": "IDS_DLG_SERIAL_HEX", "text": "Hex :" From 8827e456eb8e14d8d120eeba3144ae66c13a6951 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 1 Jul 2026 17:32:03 +0200 Subject: [PATCH 08/34] Added workflow --- .github/workflows/build.yml | 126 ++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 4 +- 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..c68e4fdae --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,126 @@ +name: Build + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + workflow_dispatch: + +jobs: + linux: + name: Linux (deb + AppImage) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential cmake git pkg-config \ + qt6-base-dev libqt6svg6-dev libgl1-mesa-dev libxkbcommon-dev libvulkan-dev \ + libglib2.0-dev zlib1g-dev libusb-1.0-0-dev libboost-dev libfftw3-dev \ + python3-dev libudev-dev dpkg-dev + + - name: Configure + run: | + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCPACK_PACKAGE_CONTACT="pat.felix92@gmail.com" \ + -DCPACK_DEBIAN_PACKAGE_SHLIBDEPS=ON + + - name: Build + run: cmake --build build -j "$(nproc)" + + - name: Build .deb package + run: | + cd build + cpack -G DEB + + - name: Upload .deb artifact + uses: actions/upload-artifact@v4 + with: + name: DSView-linux-deb + path: build/*.deb + + - name: Install into AppDir + run: | + rm -rf AppDir + DESTDIR="${GITHUB_WORKSPACE}/AppDir" cmake --install build + + - name: Download linuxdeployqt + run: | + wget -q -O linuxdeployqt \ + "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" + chmod +x linuxdeployqt + + - name: Build AppImage + run: | + export QMAKE="$(which qmake6)" + ./linuxdeployqt --appimage-extract-and-run \ + "${GITHUB_WORKSPACE}/AppDir/usr/share/applications/dsview.desktop" \ + -qmake="${QMAKE}" \ + -appimage + + - name: Upload AppImage artifact + uses: actions/upload-artifact@v4 + with: + name: DSView-linux-appimage + path: '*.AppImage' + + windows: + name: Windows (MSYS2) + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: >- + git + mingw-w64-x86_64-toolchain + mingw-w64-x86_64-cmake + mingw-w64-x86_64-ninja + mingw-w64-x86_64-qt5-base + mingw-w64-x86_64-qt5-svg + mingw-w64-x86_64-qt5-winextras + mingw-w64-x86_64-glib2 + mingw-w64-x86_64-zlib + mingw-w64-x86_64-libusb + mingw-w64-x86_64-boost + mingw-w64-x86_64-fftw + mingw-w64-x86_64-python + mingw-w64-x86_64-pkg-config + + - name: Configure + run: | + cmake -S . -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=install + + - name: Build + run: cmake --build build -j "$(nproc)" + + - name: Install + run: cmake --install build + + - name: Package as zip + run: | + cd build/install + 7z a -r "${GITHUB_WORKSPACE}/DSView-windows-x86_64.zip" . + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: DSView-windows + path: DSView-windows-x86_64.zip diff --git a/CMakeLists.txt b/CMakeLists.txt index 01173bafd..7223a735e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -693,7 +693,9 @@ if(WIN32) install(DIRECTORY libsigrokdecode4DSL/decoders DESTINATION . PATTERN "__pycache__" EXCLUDE) - install(DIRECTORY cross-compile-windows/python-setup/python-dist/. DESTINATION .) + if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cross-compile-windows/python-setup/python-dist) + install(DIRECTORY cross-compile-windows/python-setup/python-dist/. DESTINATION .) + endif() else() # Install the executable. install(TARGETS ${PROJECT_NAME} DESTINATION bin) From ab81d9ad542fcd9ba16de68f6f530ad8053e08b2 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 1 Jul 2026 20:56:40 +0200 Subject: [PATCH 09/34] Updated ubuntu build version --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c68e4fdae..b68cbd544 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ on: jobs: linux: name: Linux (deb + AppImage) - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 From 7a733ba08c1e454c0f622b38d87223e3e4ea616d Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 1 Jul 2026 21:03:18 +0200 Subject: [PATCH 10/34] Fix build --- .github/workflows/build.yml | 62 +++++++++++++++++++++++++++------ DSView/pv/mainframe.cpp | 10 +++++- DSView/pv/toolbars/titlebar.cpp | 15 ++++++-- 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b68cbd544..fe945700d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install build dependencies run: | @@ -22,7 +22,7 @@ jobs: build-essential cmake git pkg-config \ qt6-base-dev libqt6svg6-dev libgl1-mesa-dev libxkbcommon-dev libvulkan-dev \ libglib2.0-dev zlib1g-dev libusb-1.0-0-dev libboost-dev libfftw3-dev \ - python3-dev libudev-dev dpkg-dev + python3-dev libudev-dev dpkg-dev rsync - name: Configure run: | @@ -41,7 +41,7 @@ jobs: cpack -G DEB - name: Upload .deb artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: DSView-linux-deb path: build/*.deb @@ -60,13 +60,52 @@ jobs: - name: Build AppImage run: | export QMAKE="$(which qmake6)" + # Deploy Qt/shared-lib dependencies into AppDir, but don't build the + # final .AppImage yet - we still need to bundle the Python stdlib + # (DSView embeds Python for its protocol decoders, and linuxdeployqt + # only picks up libpython*.so via ldd, not the stdlib data files it + # loads at runtime, e.g. the `encodings` module). ./linuxdeployqt --appimage-extract-and-run \ "${GITHUB_WORKSPACE}/AppDir/usr/share/applications/dsview.desktop" \ - -qmake="${QMAKE}" \ - -appimage + -qmake="${QMAKE}" + + # linuxdeployqt doesn't reliably pick up libpython*.so (it isn't a + # direct Qt dependency), so make sure it's actually in AppDir/usr/lib + # by copying whatever the just-installed binary is linked against. + ldd "${GITHUB_WORKSPACE}/AppDir/usr/bin/DSView" \ + | awk '/libpython/ {print $3}' \ + | xargs -r -I{} cp -Lv {} "${GITHUB_WORKSPACE}/AppDir/usr/lib/" + + PYVER="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')" + PYLIBDIR="/usr/lib/python${PYVER}" + DEST="${GITHUB_WORKSPACE}/AppDir/usr/lib/python${PYVER}" + mkdir -p "${DEST}" + rsync -a \ + --exclude 'test' --exclude 'idlelib' --exclude 'tkinter' --exclude '__pycache__' \ + "${PYLIBDIR}/" "${DEST}/" + + # linuxdeployqt's AppRun only sets up the Qt/library environment. + # Wrap it with our own AppRun that also points the bundled Python + # interpreter at the bundled stdlib, instead of falling back to + # whatever python3 (and version) happens to be on the host. + mv "${GITHUB_WORKSPACE}/AppDir/AppRun" "${GITHUB_WORKSPACE}/AppDir/AppRun.qt" + cat > "${GITHUB_WORKSPACE}/AppDir/AppRun" <- git + zip mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja @@ -106,7 +146,7 @@ jobs: run: | cmake -S . -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=install + -DCMAKE_INSTALL_PREFIX=dsview-dist - name: Build run: cmake --build build -j "$(nproc)" @@ -116,11 +156,11 @@ jobs: - name: Package as zip run: | - cd build/install - 7z a -r "${GITHUB_WORKSPACE}/DSView-windows-x86_64.zip" . + cd dsview-dist + zip -r "${GITHUB_WORKSPACE}/DSView-windows-x86_64.zip" . - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: DSView-windows path: DSView-windows-x86_64.zip diff --git a/DSView/pv/mainframe.cpp b/DSView/pv/mainframe.cpp index f43b78cf8..07bf539ff 100644 --- a/DSView/pv/mainframe.cpp +++ b/DSView/pv/mainframe.cpp @@ -464,7 +464,11 @@ bool MainFrame::eventFilter(QObject *object, QEvent *event) QPoint pt; int k = 1; - pt = mouse_event->globalPosition().toPoint(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + pt = mouse_event->globalPosition().toPoint(); +#else + pt = mouse_event->globalPos(); +#endif int datX = pt.x() - _clickPos.x(); int datY = pt.y() - _clickPos.y(); @@ -568,7 +572,11 @@ bool MainFrame::eventFilter(QObject *object, QEvent *event) _bDraging = true; _timer.start(50); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) _clickPos = mouse_event->globalPosition().toPoint(); +#else + _clickPos = mouse_event->globalPos(); +#endif _dragStartRegion = GetFormRegion(); } else if (type == QEvent::MouseButtonRelease) { diff --git a/DSView/pv/toolbars/titlebar.cpp b/DSView/pv/toolbars/titlebar.cpp index 292fcbde0..0c2f8ee55 100644 --- a/DSView/pv/toolbars/titlebar.cpp +++ b/DSView/pv/toolbars/titlebar.cpp @@ -227,7 +227,11 @@ void TitleBar::mousePressEvent(QMouseEvent* event) if (!bTopWidow || bClick ){ _is_draging = true; - _clickPos = event->globalPosition().toPoint(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + _clickPos = event->globalPosition().toPoint(); +#else + _clickPos = event->globalPos(); +#endif if (_titleParent != NULL){ _oldPos = _titleParent->GetParentPos(); @@ -252,8 +256,13 @@ void TitleBar::mouseMoveEvent(QMouseEvent *event) int datX = 0; int datY = 0; - datX = (event->globalPosition().toPoint().x() - _clickPos.x()); - datY = (event->globalPosition().toPoint().y() - _clickPos.y()); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QPoint globalPos = event->globalPosition().toPoint(); +#else + QPoint globalPos = event->globalPos(); +#endif + datX = (globalPos.x() - _clickPos.x()); + datY = (globalPos.y() - _clickPos.y()); int x = _oldPos.x() + datX; int y = _oldPos.y() + datY; From 1b9a266700585f3f8656670ca0d570804a3692fd Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 1 Jul 2026 21:35:25 +0200 Subject: [PATCH 11/34] Add release job --- .github/workflows/build.yml | 59 ++++++++++++++++++++++++++++++++++ DSView/pv/toolbars/logobar.cpp | 7 +--- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe945700d..6a1d7701f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -154,6 +154,39 @@ jobs: - name: Install run: cmake --install build + - name: Bundle runtime DLLs + run: | + # Qt loads platform/image/style plugins dynamically (not visible to + # ldd); without qwindows.dll in particular, the app can't even open + # a window. Qt's default search looks for a "platforms" etc. folder + # next to the executable. Copy these first so the ldd pass below + # also picks up libraries only the plugins need (e.g. Qt5Svg.dll, + # needed by the qsvg imageformat/iconengine plugins but not linked + # directly by DSView.exe itself). + for group in platforms styles imageformats iconengines; do + mkdir -p "dsview-dist/${group}" + cp -v /mingw64/share/qt5/plugins/${group}/*.dll "dsview-dist/${group}/" + done + + # The install rules only place DSView.exe + data files; the MSYS2 + # toolchain/Qt5 runtime DLLs it's dynamically linked against still + # need to be copied in manually. Resolve the dependency closure with + # ldd and keep looping until a pass copies nothing new, since some + # transitive deps only show up once their dependents are present. + while :; do + new=0 + for dll in $(find dsview-dist -name '*.exe' -o -name '*.dll' \ + | xargs ldd 2>/dev/null \ + | grep -i '/mingw64/bin/' | awk '{print $3}' | sort -u); do + base="$(basename "$dll")" + if [ ! -f "dsview-dist/$base" ]; then + cp -v "$dll" dsview-dist/ + new=1 + fi + done + [ "$new" -eq 0 ] && break + done + - name: Package as zip run: | cd dsview-dist @@ -164,3 +197,29 @@ jobs: with: name: DSView-windows path: DSView-windows-x86_64.zip + + release: + name: Publish latest pre-release + needs: [linux, windows] + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download artifacts + uses: actions/download-artifact@v7 + with: + path: artifacts + merge-multiple: true + + - name: Publish "latest" pre-release + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release delete latest --yes --cleanup-tag --repo "${GITHUB_REPOSITORY}" || true + gh release create latest artifacts/* \ + --repo "${GITHUB_REPOSITORY}" \ + --title "Latest build" \ + --notes "Automated build from commit ${GITHUB_SHA}" \ + --prerelease \ + --target "${GITHUB_SHA}" diff --git a/DSView/pv/toolbars/logobar.cpp b/DSView/pv/toolbars/logobar.cpp index e1b1f9d4c..b8c57e060 100644 --- a/DSView/pv/toolbars/logobar.cpp +++ b/DSView/pv/toolbars/logobar.cpp @@ -229,12 +229,7 @@ void LogoBar::on_actionIssue_triggered() void LogoBar::on_action_update() { - if (AppConfig::Instance().frameOptions.language == LAN_CN){ - QDesktopServices::openUrl(QUrl(QLatin1String("https://dreamsourcelab.cn/download/"))); - } - else{ - QDesktopServices::openUrl(QUrl(QLatin1String("https://www.dreamsourcelab.com/download/"))); - } + QDesktopServices::openUrl(QUrl(QLatin1String("https://github.com/Schildkroet/DSView/releases"))); } void LogoBar::enable_toggle(bool enable) From d5781774fd64bdfa9883e5c1a22298ce2c1c1b73 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 1 Jul 2026 22:06:44 +0200 Subject: [PATCH 12/34] Updated gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 81fa4defb..9249a939b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,8 @@ moc_*.cpp moc_*.cpp_parameters DSView-prj -build* +build/* +build.dir* share .vscode qtpro From 1537768033af44c1b1bf13eebcdad5e67f4a321d Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 1 Jul 2026 22:32:01 +0200 Subject: [PATCH 13/34] Port fully to Qt6 --- .github/workflows/build.yml | 12 ++++----- CMakeLists.txt | 17 +++---------- DSView/pv/mainframe.cpp | 46 +++++++++++++++++++++++++---------- DSView/pv/mainframe.h | 13 +++++----- DSView/pv/mainwindow.cpp | 4 +-- DSView/pv/winnativewidget.cpp | 5 ++-- DSView/pv/winnativewidget.h | 2 ++ DSView/pv/winshadow.cpp | 6 ++--- DSView/pv/winshadow.h | 10 +++++++- 9 files changed, 67 insertions(+), 48 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6a1d7701f..1c7813cf1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -131,9 +131,9 @@ jobs: mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja - mingw-w64-x86_64-qt5-base - mingw-w64-x86_64-qt5-svg - mingw-w64-x86_64-qt5-winextras + mingw-w64-x86_64-qt6-base + mingw-w64-x86_64-qt6-svg + mingw-w64-x86_64-qt6-5compat mingw-w64-x86_64-glib2 mingw-w64-x86_64-zlib mingw-w64-x86_64-libusb @@ -160,16 +160,16 @@ jobs: # ldd); without qwindows.dll in particular, the app can't even open # a window. Qt's default search looks for a "platforms" etc. folder # next to the executable. Copy these first so the ldd pass below - # also picks up libraries only the plugins need (e.g. Qt5Svg.dll, + # also picks up libraries only the plugins need (e.g. Qt6Svg.dll, # needed by the qsvg imageformat/iconengine plugins but not linked # directly by DSView.exe itself). for group in platforms styles imageformats iconengines; do mkdir -p "dsview-dist/${group}" - cp -v /mingw64/share/qt5/plugins/${group}/*.dll "dsview-dist/${group}/" + cp -v /mingw64/share/qt6/plugins/${group}/*.dll "dsview-dist/${group}/" done # The install rules only place DSView.exe + data files; the MSYS2 - # toolchain/Qt5 runtime DLLs it's dynamically linked against still + # toolchain/Qt6 runtime DLLs it's dynamically linked against still # need to be copied in manually. Resolve the dependency closure with # ldd and keep looping until a pass copies nothing new, since some # transitive deps only show up once their dependents are present. diff --git a/CMakeLists.txt b/CMakeLists.txt index 7223a735e..d6838d42b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,16 +181,11 @@ include_directories(${ZLIB_INCLUDE_DIRS}) #=============================================================================== #= Qt6 or Qt5 # -# Windows build only works with Qt5 for now due to WinExtras dependency, -# therefore do not yet attempt to build with Qt6. -# -# Otherwise prioritize Qt6 for better font rendering on OSX and better Wayland -# compatibility on Linux. +# Prioritize Qt6 for better font rendering on OSX, better Wayland compatibility +# on Linux, and to stay on the actively maintained Qt release on Windows. #------------------------------------------------------------------------------- -if(NOT WIN32) - find_package(Qt6Core QUIET) -endif() +find_package(Qt6Core QUIET) if(Qt6Core_FOUND) message("----- Qt6:") @@ -220,12 +215,6 @@ if(Qt5Core_FOUND) set(QT_INCLUDE_DIRS ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) set(QT_LIBRARIES Qt5::Gui Qt5::Widgets) add_definitions(${Qt5Gui_DEFINITIONS} ${Qt5Widgets_DEFINITIONS}) - if(WIN32) - find_package(Qt5WinExtras REQUIRED) - add_definitions(${Qt5WinExtras_DEFINITIONS}) - list(APPEND QT_LIBRARIES Qt5::WinExtras) - list(APPEND QT_INCLUDE_DIRS ${Qt5WinExtras_INCLUDE_DIRS}) - endif() endif() if(NOT Qt5Core_FOUND AND NOT Qt6Core_FOUND) diff --git a/DSView/pv/mainframe.cpp b/DSView/pv/mainframe.cpp index 07bf539ff..8f02589e7 100644 --- a/DSView/pv/mainframe.cpp +++ b/DSView/pv/mainframe.cpp @@ -63,6 +63,7 @@ #ifdef _WIN32 #include "winnativewidget.h" +#include #endif namespace pv { @@ -97,7 +98,7 @@ MainFrame::MainFrame() #ifdef _WIN32 setWindowFlags(Qt::FramelessWindowHint); _is_win32_parent_window = true; - _taskBtn = NULL; + _taskbarList3 = NULL; isWin32 = true; #else setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint); @@ -179,7 +180,6 @@ MainFrame::MainFrame() } #ifdef _WIN32 - _taskBtn = new QWinTaskbarButton(this); connect(_mainWindow, SIGNAL(prgRate(int)), this, SLOT(setTaskbarProgress(int))); #endif @@ -193,9 +193,19 @@ MainFrame::MainFrame() connect(this, SIGNAL(sig_ParentNativeEvent(int)), this, SLOT(OnParentNaitveWindowEvent(int))); - + } - + +MainFrame::~MainFrame() +{ +#ifdef _WIN32 + if (_taskbarList3 != NULL) { + _taskbarList3->Release(); + _taskbarList3 = NULL; + } +#endif +} + void MainFrame::MoveWindow(int x, int y) { #ifdef _WIN32 @@ -1025,10 +1035,15 @@ void MainFrame::ReadSettings() #ifdef _WIN32 void MainFrame::showEvent(QShowEvent *event) { - // Taskbar Progress Effert for Win7 and Above - if (_taskBtn && _taskBtn->window() == NULL) { - _taskBtn->setWindow(windowHandle()); - _taskPrg = _taskBtn->progress(); + // Taskbar Progress Effert for Win7 and Above, via native ITaskbarList3 COM interface + if (_taskbarList3 == NULL) { + if (SUCCEEDED(CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, + IID_ITaskbarList3, (void**)&_taskbarList3))) { + if (FAILED(_taskbarList3->HrInit())) { + _taskbarList3->Release(); + _taskbarList3 = NULL; + } + } } event->accept(); } @@ -1037,11 +1052,16 @@ void MainFrame::showEvent(QShowEvent *event) void MainFrame::setTaskbarProgress(int progress) { #ifdef _WIN32 + if (_taskbarList3 == NULL) + return; + + HWND hwnd = (HWND)winId(); + if (progress > 0) { - _taskPrg->setVisible(true); - _taskPrg->setValue(progress); + _taskbarList3->SetProgressState(hwnd, TBPF_NORMAL); + _taskbarList3->SetProgressValue(hwnd, (ULONGLONG)progress, 100); } else { - _taskPrg->setVisible(false); + _taskbarList3->SetProgressState(hwnd, TBPF_NOPROGRESS); } #else (void)progress; @@ -1126,8 +1146,8 @@ bool MainFrame::nativeEvent(const QByteArray &eventType, void *message, MESSAGE_ case WM_NCLBUTTONDBLCLK: case WM_NCHITTEST: { - *result = long(SendMessageW(hwnd, - msg->message, msg->wParam, msg->lParam)); + *result = (MESSAGE_RESULT_TYPE)SendMessageW(hwnd, + msg->message, msg->wParam, msg->lParam); return true; } } diff --git a/DSView/pv/mainframe.h b/DSView/pv/mainframe.h index 0c2ec3ee9..18960deca 100644 --- a/DSView/pv/mainframe.h +++ b/DSView/pv/mainframe.h @@ -31,8 +31,7 @@ #include #ifdef _WIN32 -#include -#include +struct ITaskbarList3; #endif #include "toolbars/titlebar.h" @@ -40,8 +39,10 @@ #if QT_VERSION >= QT_VERSION_CHECK(6,0,0) typedef qintptr *MESSAGE_RESULT_PTR; +typedef qintptr MESSAGE_RESULT_TYPE; #else typedef long *MESSAGE_RESULT_PTR; +typedef long MESSAGE_RESULT_TYPE; #endif namespace pv { @@ -98,7 +99,8 @@ class MainFrame : public: MainFrame(); - + ~MainFrame(); + void ShowFormInit(); void ShowHelpDocAsync(); @@ -172,10 +174,9 @@ public slots: int _hit_border; QTimer _timer; bool _freezing; - // Taskbar Progress Effert for Win7 and Above + // Taskbar Progress Effert for Win7 and Above, via native ITaskbarList3 COM interface #ifdef _WIN32 - QWinTaskbarButton *_taskBtn; - QWinTaskbarProgress *_taskPrg; + ITaskbarList3 *_taskbarList3; #endif bool _is_win32_parent_window; diff --git a/DSView/pv/mainwindow.cpp b/DSView/pv/mainwindow.cpp index aceea7a35..3e398264d 100644 --- a/DSView/pv/mainwindow.cpp +++ b/DSView/pv/mainwindow.cpp @@ -539,9 +539,9 @@ namespace pv (void)x; (void)y; -#ifdef _WIN32 +#ifdef _WIN32 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - QPixmap pixmap = QGuiApplication::primaryScreen()->grabWindow(QApplication::desktop->winId(), x, y, w, h); + QPixmap pixmap = parentWidget()->grab(); #else QPixmap pixmap = QPixmap::grabWidget(parentWidget()); #endif diff --git a/DSView/pv/winnativewidget.cpp b/DSView/pv/winnativewidget.cpp index c6a2aa509..f7b5abe09 100644 --- a/DSView/pv/winnativewidget.cpp +++ b/DSView/pv/winnativewidget.cpp @@ -23,7 +23,6 @@ #include "winnativewidget.h" #include -#include #include #include #include @@ -256,13 +255,13 @@ LRESULT CALLBACK WinNativeWidget::WndProc(HWND hWnd, UINT message, WPARAM wParam case WM_KEYDOWN: { //enable the hot key. - QKeyEvent keyEvent(QEvent::KeyPress, (int)wParam, 0); + QKeyEvent keyEvent(QEvent::KeyPress, (int)wParam, Qt::NoModifier); QApplication::sendEvent(self->_childWidget->GetBodyView(), &keyEvent); break; } case WM_KEYUP: { - QKeyEvent keyEvent(QEvent::KeyRelease, (int)wParam, 0); + QKeyEvent keyEvent(QEvent::KeyRelease, (int)wParam, Qt::NoModifier); QApplication::sendEvent(self->_childWidget->GetBodyView(), &keyEvent); break; } diff --git a/DSView/pv/winnativewidget.h b/DSView/pv/winnativewidget.h index 18c12c8e3..5d0f2c191 100644 --- a/DSView/pv/winnativewidget.h +++ b/DSView/pv/winnativewidget.h @@ -24,7 +24,9 @@ #ifndef WINNATIVEWINDOW_H #define WINNATIVEWINDOW_H +#ifndef UNICODE #define UNICODE +#endif #include #include diff --git a/DSView/pv/winshadow.cpp b/DSView/pv/winshadow.cpp index 8b806d766..d72a01000 100644 --- a/DSView/pv/winshadow.cpp +++ b/DSView/pv/winshadow.cpp @@ -108,8 +108,8 @@ void WinShadow::hideShadow() QWidget::hide(); } -bool WinShadow::nativeEvent(const QByteArray &eventType, void *message, long *result) -{ +bool WinShadow::nativeEvent(const QByteArray &eventType, void *message, SHADOW_MESSAGE_RESULT_PTR result) +{ MSG *msg = static_cast(message); switch (msg->message) @@ -139,7 +139,7 @@ bool WinShadow::nativeEvent(const QByteArray &eventType, void *message, long *re case WM_NCLBUTTONDBLCLK: case WM_NCHITTEST: { - *result = long(SendMessageW(m_hwnd, msg->message, msg->wParam, msg->lParam)); + *result = (SHADOW_MESSAGE_RESULT_TYPE)SendMessageW(m_hwnd, msg->message, msg->wParam, msg->lParam); return true; } } diff --git a/DSView/pv/winshadow.h b/DSView/pv/winshadow.h index b6bc9108d..b9f4ad5f2 100644 --- a/DSView/pv/winshadow.h +++ b/DSView/pv/winshadow.h @@ -29,6 +29,14 @@ #define SHADOW_BORDER_WIDTH 11 +#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) +typedef qintptr *SHADOW_MESSAGE_RESULT_PTR; +typedef qintptr SHADOW_MESSAGE_RESULT_TYPE; +#else +typedef long *SHADOW_MESSAGE_RESULT_PTR; +typedef long SHADOW_MESSAGE_RESULT_TYPE; +#endif + namespace pv { class IShadowCallback @@ -69,7 +77,7 @@ private slots: void onCheckForeWindow(); private: - bool nativeEvent(const QByteArray &eventType, void *message, long *result) override; + bool nativeEvent(const QByteArray &eventType, void *message, SHADOW_MESSAGE_RESULT_PTR result) override; void paintEvent(QPaintEvent *event) override; QWidget *m_parent; From 76e4632acc669d36c587170b9707d15ab292933d Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Thu, 2 Jul 2026 13:34:38 +0200 Subject: [PATCH 14/34] Fix deb dependencies --- .github/workflows/build.yml | 55 ++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c7813cf1..3839229b4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: build-essential cmake git pkg-config \ qt6-base-dev libqt6svg6-dev libgl1-mesa-dev libxkbcommon-dev libvulkan-dev \ libglib2.0-dev zlib1g-dev libusb-1.0-0-dev libboost-dev libfftw3-dev \ - python3-dev libudev-dev dpkg-dev rsync + python3-dev libudev-dev dpkg-dev rsync patchelf - name: Configure run: | @@ -40,6 +40,59 @@ jobs: cd build cpack -G DEB + - name: Bundle Python (lib + stdlib) so the .deb doesn't depend on the host's Python + run: | + cd build + DEB="$(ls *.deb)" + rm -rf deb-extract + dpkg-deb -R "$DEB" deb-extract + + # DSView embeds Python (for its protocol decoders) and links + # libpython directly, so the binary has a hard DT_NEEDED on the + # exact libpythonX.Y.so the runner (Ubuntu 22.04, Python 3.10) + # built against, and the interpreter needs that same version's + # stdlib (encodings, etc.) to even bootstrap. Neither exists on + # newer distros (24.04 only ships Python 3.12), so the app would + # fail to start even though apt happily installed it. Fix: carry + # our own copy of both inside the package and point the binary at + # them via RPATH + a PYTHONHOME/PYTHONPATH wrapper, so it never + # relies on whatever Python happens to be on the host. + PYVER="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')" + BIN="deb-extract/usr/bin/DSView" + PYSO="$(ldd "$BIN" | awk '/libpython/ {print $3}')" + BUNDLE="deb-extract/usr/lib/dsview" + mkdir -p "$BUNDLE" + cp -Lv "$PYSO" "$BUNDLE/" + patchelf --set-rpath '$ORIGIN/../lib/dsview' "$BIN" + + rsync -a \ + --exclude 'test' --exclude 'idlelib' --exclude 'tkinter' --exclude '__pycache__' \ + "/usr/lib/python${PYVER}/" "$BUNDLE/python${PYVER}/" + + mv "$BIN" "${BIN}.bin" + cat > "$BIN" </dev/null || true + + dpkg-deb --root-owner-group -b deb-extract "$DEB" + - name: Upload .deb artifact uses: actions/upload-artifact@v6 with: From 52ec50aa4bf808598d17238c29b1caefe7f1b0f9 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Thu, 2 Jul 2026 20:17:45 +0200 Subject: [PATCH 15/34] Fix dragging issue on wayland --- DSView/pv/toolbars/titlebar.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/DSView/pv/toolbars/titlebar.cpp b/DSView/pv/toolbars/titlebar.cpp index 0c2f8ee55..3743bb5d3 100644 --- a/DSView/pv/toolbars/titlebar.cpp +++ b/DSView/pv/toolbars/titlebar.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "../config/appconfig.h" #include "../appcontrol.h" @@ -225,7 +226,21 @@ void TitleBar::mousePressEvent(QMouseEvent* event) bool bClick = (x >= 6 && y >= 5 && x <= width() - 6); //top window need resize hit check if (!bTopWidow || bClick ){ - _is_draging = true; + + // Wayland forbids clients from positioning themselves; manual + // move-by-delta below is a no-op there. Ask the compositor to + // perform the move instead, via the same protocol native + // titlebar dragging uses. + if (QGuiApplication::platformName().startsWith("wayland", Qt::CaseInsensitive)){ + QWindow *win = window()->windowHandle(); + if (win != NULL){ + win->startSystemMove(); + event->accept(); + return; + } + } + + _is_draging = true; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) _clickPos = event->globalPosition().toPoint(); From ac436324b563eb35d6aa1538278e613ef62c0cdd Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Thu, 2 Jul 2026 20:48:09 +0200 Subject: [PATCH 16/34] Code improvements --- DSView/pv/config/appconfig.cpp | 7 ++---- DSView/pv/data/analogsnapshot.cpp | 15 ++++++++++-- DSView/pv/data/dsosnapshot.cpp | 1 + DSView/pv/data/logicsnapshot.cpp | 14 ++++++++++- DSView/pv/deviceagent.cpp | 3 ++- DSView/pv/dialogs/deviceoptions.cpp | 9 +++++++- DSView/pv/dock/protocoldock.cpp | 2 ++ DSView/pv/prop/binding/binding.cpp | 10 +++++++- DSView/pv/prop/binding/binding.h | 2 ++ DSView/pv/sigsession.h | 21 +++++++++-------- DSView/pv/storesession.cpp | 36 +++++++++++++++++++++++------ DSView/pv/toolbars/logobar.cpp | 3 +++ DSView/pv/view/logicsignal.cpp | 3 +++ DSView/pv/view/view.cpp | 4 ++++ DSView/pv/view/viewport.cpp | 1 + DSView/pv/winnativewidget.cpp | 23 ++++++++++++------ 16 files changed, 119 insertions(+), 35 deletions(-) diff --git a/DSView/pv/config/appconfig.cpp b/DSView/pv/config/appconfig.cpp index d07f00a38..47944b11f 100644 --- a/DSView/pv/config/appconfig.cpp +++ b/DSView/pv/config/appconfig.cpp @@ -358,11 +358,8 @@ AppConfig::~AppConfig() AppConfig& AppConfig::Instance() { - static AppConfig *ins = NULL; - if (ins == NULL){ - ins = new AppConfig(); - } - return *ins; + static AppConfig ins; + return ins; } void AppConfig::LoadAll() diff --git a/DSView/pv/data/analogsnapshot.cpp b/DSView/pv/data/analogsnapshot.cpp index f9ccffb78..d9773abe4 100644 --- a/DSView/pv/data/analogsnapshot.cpp +++ b/DSView/pv/data/analogsnapshot.cpp @@ -23,11 +23,13 @@ #include #include #include +#include #include #include - + #include "analogsnapshot.h" #include "../dsvdef.h" +#include "../log.h" using namespace std; @@ -125,7 +127,16 @@ void AnalogSnapshot::first_payload(const sr_datafeed_analog &analog, uint64_t to } bool isOk = true; - uint64_t size = _total_sample_count * _channel_num * _unit_bytes + sizeof(uint64_t); + const uint64_t unit_stride = (uint64_t)_channel_num * (uint64_t)_unit_bytes; + + if (unit_stride != 0 && _total_sample_count > (UINT64_MAX - sizeof(uint64_t)) / unit_stride) { + dsv_err("AnalogSnapshot::first_payload, sample buffer size overflow."); + free_data(); + _memory_failed = true; + return; + } + + uint64_t size = _total_sample_count * unit_stride + sizeof(uint64_t); if (size != _capacity) { free_data(); diff --git a/DSView/pv/data/dsosnapshot.cpp b/DSView/pv/data/dsosnapshot.cpp index 97167fde2..c9bc1b598 100644 --- a/DSView/pv/data/dsosnapshot.cpp +++ b/DSView/pv/data/dsosnapshot.cpp @@ -526,6 +526,7 @@ bool DsoSnapshot::get_max_min_value(uint8_t &maxv, uint8_t &minv, int chan_index if (chan_index < 0 || chan_index >= (int)_ch_data.size()){ assert(false); + return false; } uint8_t *p = _ch_data[chan_index]; diff --git a/DSView/pv/data/logicsnapshot.cpp b/DSView/pv/data/logicsnapshot.cpp index b2226cd8c..ce933930a 100644 --- a/DSView/pv/data/logicsnapshot.cpp +++ b/DSView/pv/data/logicsnapshot.cpp @@ -335,8 +335,9 @@ void LogicSnapshot::append_cross_payload(const sr_datafeed_logic &logic) if (index0 >= _ch_data[0].size()){ assert(false); + return; } - + lbp = _ch_data[fill_chan_index][index0].lbp[index1]; if (lbp == NULL){ lbp = malloc(LeafBlockSpace); @@ -1293,6 +1294,10 @@ bool LogicSnapshot::pattern_search_self(int64_t start, int64_t end, int64_t &ind int channel = it->first; if (flag != 'X' && has_data(channel)){ + if (count >= CHANNEL_MAX_COUNT){ + assert(false); + break; + } flagList[count] = flag; chanIndexs[count] = channel; count++; @@ -1489,6 +1494,13 @@ 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; + + if (index >= _ch_data[order].size()){ + assert(false); + sample = 0; + return NULL; + } + uint8_t *lbp = (uint8_t*)_ch_data[order][index].lbp[pos]; if (lbp == NULL){ diff --git a/DSView/pv/deviceagent.cpp b/DSView/pv/deviceagent.cpp index 6db0e58b5..b4f9b3a11 100644 --- a/DSView/pv/deviceagent.cpp +++ b/DSView/pv/deviceagent.cpp @@ -364,7 +364,8 @@ GVariant* DeviceAgent::get_config_list(const sr_channel_group *group, int key) dsv_detail("%s%d", "WARNING: Failed to get config list, key:", key); if (data != NULL){ - dsv_warn("%s%d", "WARNING: Failed to get config list, but data is not null. key:", key); + dsv_warn("%s%d", "WARNING: Failed to get config list, but data is not null. key:", key); + g_variant_unref(data); } data = NULL; } diff --git a/DSView/pv/dialogs/deviceoptions.cpp b/DSView/pv/dialogs/deviceoptions.cpp index f2d4b1c92..269663aad 100644 --- a/DSView/pv/dialogs/deviceoptions.cpp +++ b/DSView/pv/dialogs/deviceoptions.cpp @@ -172,7 +172,11 @@ DeviceOptions::DeviceOptions(QWidget *parent) : } DeviceOptions::~DeviceOptions() -{ +{ + for(auto p : _probe_options_binding_list) { + delete p; + } + _probe_options_binding_list.clear(); } void DeviceOptions::ChannelChecked(int index, QObject *object) @@ -682,6 +686,9 @@ void DeviceOptions::analog_probes(QGridLayout &layout) using namespace Qt; _probes_checkBox_list.clear(); + for(auto p : _probe_options_binding_list) { + delete p; + } _probe_options_binding_list.clear(); _dso_channel_list.clear(); diff --git a/DSView/pv/dock/protocoldock.cpp b/DSView/pv/dock/protocoldock.cpp index f7c73438a..82dc995ef 100644 --- a/DSView/pv/dock/protocoldock.cpp +++ b/DSView/pv/dock/protocoldock.cpp @@ -480,6 +480,7 @@ void ProtocolDock::set_model() { pv::dialogs::ProtocolList *protocollist_dlg = new pv::dialogs::ProtocolList(this, _session); protocollist_dlg->exec(); + delete protocollist_dlg; resize_table_view(_session->get_decoder_model()); _model_proxy.setSourceModel(_session->get_decoder_model()); search_done(); @@ -614,6 +615,7 @@ void ProtocolDock::export_table_view() { pv::dialogs::ProtocolExp *protocolexp_dlg = new pv::dialogs::ProtocolExp(this, _session); protocolexp_dlg->exec(); + delete protocolexp_dlg; } void ProtocolDock::nav_table_view() diff --git a/DSView/pv/prop/binding/binding.cpp b/DSView/pv/prop/binding/binding.cpp index fb5f9f106..ed7af3428 100644 --- a/DSView/pv/prop/binding/binding.cpp +++ b/DSView/pv/prop/binding/binding.cpp @@ -38,7 +38,15 @@ const std::vector& Binding::properties() } Binding::Binding(){ - _row_num = 0; + _row_num = 0; +} + +Binding::~Binding() +{ + for(auto p : _properties) { + delete p; + } + _properties.clear(); } void Binding::commit() diff --git a/DSView/pv/prop/binding/binding.h b/DSView/pv/prop/binding/binding.h index 22f94c01a..1a35561c3 100644 --- a/DSView/pv/prop/binding/binding.h +++ b/DSView/pv/prop/binding/binding.h @@ -46,6 +46,8 @@ class Binding public: Binding(); + virtual ~Binding(); + const std::vector& properties(); void commit(); diff --git a/DSView/pv/sigsession.h b/DSView/pv/sigsession.h index 18d72f5fc..e6830e097 100644 --- a/DSView/pv/sigsession.h +++ b/DSView/pv/sigsession.h @@ -26,9 +26,10 @@ #include #include #include -#include +#include #include #include +#include #include #include @@ -583,13 +584,13 @@ class SigSession: int _noData_cnt; bool _data_lock; - bool _data_updated; + std::atomic _data_updated; int _data_auto_lock; QDateTime _session_time; QDateTime _trig_time; - bool _is_triged; - bool _trigger_flag; + std::atomic _is_triged; + std::atomic _trigger_flag; uint8_t _trigger_ch; bool _hw_replied; @@ -599,24 +600,24 @@ class SigSession: bool _bClose; uint64_t _save_start; - uint64_t _save_end; - volatile bool _is_working; + uint64_t _save_end; + std::atomic _is_working; double _repeat_intvl; // The progress wait timer interval. int _repeat_hold_prg; // The time sleep progress int _repeat_wait_prog_step; bool _is_saving; bool _is_instant; - volatile int _device_status; + std::atomic _device_status; int _work_time_id; - int _capture_times; + int _capture_times; int _confirm_store_time_id; uint64_t _rt_refresh_time_id; uint64_t _rt_ck_refresh_time_id; DEVICE_COLLECT_MODE _clt_mode; bool _is_stream_mode; - + bool _is_action; - uint64_t _dso_packet_count; + std::atomic _dso_packet_count; bool _is_task_end; diff --git a/DSView/pv/storesession.cpp b/DSView/pv/storesession.cpp index 42bf0e490..11d96a334 100644 --- a/DSView/pv/storesession.cpp +++ b/DSView/pv/storesession.cpp @@ -914,19 +914,36 @@ void StoreSession::export_exec(data::Snapshot *snapshot) output.start_sample_index = _start_index; } + auto release_export_params = [&](){ + g_hash_table_destroy(params); + if (filenameGVariant != NULL) + g_variant_unref(filenameGVariant); + if (typeGVariant != NULL) + g_variant_unref(typeGVariant); + }; + if(_outModule->init){ if(_outModule->init(&output, params) != SR_OK){ dsv_err("Failed to init export module."); + release_export_params(); return; } } - + QString dateTimeString = Formatting::DateTimeToString(_session->get_session_time(), TimeStrigFormatType::TIME_STR_FORMAT_ALL); strcpy(output.time_string, dateTimeString.toStdString().c_str()); - + QFile file(_file_name); - file.open(QIODevice::WriteOnly | QIODevice::Text); - QTextStream out(&file); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)){ + dsv_err("StoreSession::export_proc, failed to open file for writing."); + _has_error = true; + _error = QString("Failed to open file for writing: %1").arg(_file_name); + _outModule->cleanup(&output); + release_export_params(); + progress_updated(); + return; + } + QTextStream out(&file); encoding::set_utf8(out); //out.setGenerateByteOrderMark(true); // UTF-8 without BOM @@ -1004,6 +1021,9 @@ void StoreSession::export_exec(data::Snapshot *snapshot) if (start_index > logic_snapshot->get_ring_sample_count()){ dsv_err("ERROR:the start curosr is invalid!"); _units_stored = -1; + file.close(); + _outModule->cleanup(&output); + release_export_params(); progress_updated(); return; } @@ -1077,6 +1097,10 @@ void StoreSession::export_exec(data::Snapshot *snapshot) if (xbuf == NULL) { _has_error = true; _error = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_STORESESS_EXPORTPROC_ERROR2), "xbuffer malloc failed."); + file.close(); + _outModule->cleanup(&output); + release_export_params(); + progress_updated(); return; } @@ -1245,9 +1269,7 @@ void StoreSession::export_exec(data::Snapshot *snapshot) // optional, as QFile destructor will already do it: file.close(); _outModule->cleanup(&output); - g_hash_table_destroy(params); - if (filenameGVariant != NULL) - g_variant_unref(filenameGVariant); + release_export_params(); progress_updated(); } diff --git a/DSView/pv/toolbars/logobar.cpp b/DSView/pv/toolbars/logobar.cpp index b8c57e060..411a8faea 100644 --- a/DSView/pv/toolbars/logobar.cpp +++ b/DSView/pv/toolbars/logobar.cpp @@ -295,6 +295,9 @@ void LogoBar::on_action_setting_log() dlg.exec(); + _log_open_bt = NULL; + _log_clear_bt = NULL; + if (dlg.IsClickYes()){ bool ableSave = ckSave->isChecked(); int level = cbBox->currentIndex(); diff --git a/DSView/pv/view/logicsignal.cpp b/DSView/pv/view/logicsignal.cpp index 28290be58..ffa97565a 100644 --- a/DSView/pv/view/logicsignal.cpp +++ b/DSView/pv/view/logicsignal.cpp @@ -118,6 +118,9 @@ void LogicSignal::paint_mid_align(QPainter &p, int left, int right, QColor fore, const int y = get_y() + _totalHeight * 0.5; const double scale = _view->scale(); assert(scale > 0); + if (scale <= 0) + return; + const int64_t offset = _view->x_offset(); const int high_offset = y - _totalHeight + 0.5f; diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index 49a2e53e9..6bb2067b9 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -1534,6 +1534,10 @@ double View::index2pixel(uint64_t index, bool has_hoff) const double samples_per_pixel = rateValue * scaleValue; + if (samples_per_pixel == 0){ + return 0; + } + if (has_hoff){ pixels = index / samples_per_pixel - offsetValue + hoffValue / samples_per_pixel; } diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp index 614d28cfe..e0bb29242 100644 --- a/DSView/pv/view/viewport.cpp +++ b/DSView/pv/view/viewport.cpp @@ -2025,6 +2025,7 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) measure_line_count += 3; } p.drawLines(measure_lines, measure_line_count); + delete[] measure_lines; if (dso_xm_stage < DsoMeasureStages) { p.drawLine(x[dso_xm_stage-1], _dso_xm_y, _mouse_point.x(), _dso_xm_y); diff --git a/DSView/pv/winnativewidget.cpp b/DSView/pv/winnativewidget.cpp index f7b5abe09..8d976b7ec 100644 --- a/DSView/pv/winnativewidget.cpp +++ b/DSView/pv/winnativewidget.cpp @@ -199,7 +199,12 @@ WinNativeWidget::~WinNativeWidget() if (_hWnd){ Show(false); DestroyWindow(_hWnd); - } + } + + if (_shadow != NULL){ + delete _shadow; + _shadow = NULL; + } } void WinNativeWidget::SetChildWidget(MainFrame *w) @@ -253,16 +258,20 @@ LRESULT CALLBACK WinNativeWidget::WndProc(HWND hWnd, UINT message, WPARAM wParam break; } case WM_KEYDOWN: - { + { //enable the hot key. - QKeyEvent keyEvent(QEvent::KeyPress, (int)wParam, Qt::NoModifier); - QApplication::sendEvent(self->_childWidget->GetBodyView(), &keyEvent); + if (self->_childWidget != NULL){ + QKeyEvent keyEvent(QEvent::KeyPress, (int)wParam, Qt::NoModifier); + QApplication::sendEvent(self->_childWidget->GetBodyView(), &keyEvent); + } break; } case WM_KEYUP: - { - QKeyEvent keyEvent(QEvent::KeyRelease, (int)wParam, Qt::NoModifier); - QApplication::sendEvent(self->_childWidget->GetBodyView(), &keyEvent); + { + if (self->_childWidget != NULL){ + QKeyEvent keyEvent(QEvent::KeyRelease, (int)wParam, Qt::NoModifier); + QApplication::sendEvent(self->_childWidget->GetBodyView(), &keyEvent); + } break; } case WM_ENTERSIZEMOVE: From c47d74f6a0180e1c05f0eb7f10f03dccd8768fdc Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Fri, 3 Jul 2026 00:04:27 +0200 Subject: [PATCH 17/34] Improved UI --- DSView/DSView.qrc | 2 + DSView/icons/dark/dso-split.svg | 13 +++ DSView/icons/light/dso-split.svg | 13 +++ DSView/pv/config/appconfig.cpp | 85 ++++++++------ DSView/pv/config/appconfig.h | 2 + DSView/pv/dialogs/applicationpardlg.cpp | 35 ++++-- DSView/pv/mainframe.cpp | 15 ++- DSView/pv/mainwindow.cpp | 41 +++++-- DSView/pv/mainwindow.h | 6 + DSView/pv/toolbars/trigbar.cpp | 25 +++- DSView/pv/toolbars/trigbar.h | 6 +- DSView/pv/view/dsosignal.cpp | 57 ++++++--- DSView/pv/view/logicsignal.cpp | 44 +++++-- DSView/pv/view/logicsignal.h | 4 + DSView/pv/view/trace.cpp | 12 +- DSView/pv/view/trace.h | 6 + DSView/pv/view/view.cpp | 146 ++++++++++++++++-------- DSView/pv/view/view.h | 7 ++ DSView/pv/view/viewport.cpp | 2 + DSView/pv/view/viewport.h | 1 + 20 files changed, 380 insertions(+), 142 deletions(-) create mode 100644 DSView/icons/dark/dso-split.svg create mode 100644 DSView/icons/light/dso-split.svg diff --git a/DSView/DSView.qrc b/DSView/DSView.qrc index 0d6855453..1e19956e0 100644 --- a/DSView/DSView.qrc +++ b/DSView/DSView.qrc @@ -36,6 +36,7 @@ icons/demo.svg icons/dark/moder.svg icons/dark/modes.svg + icons/dark/dso-split.svg icons/dark/trigger.svg icons/light/about.svg icons/light/add.svg @@ -109,6 +110,7 @@ icons/usb3.svg icons/light/moder.svg icons/light/modes.svg + icons/light/dso-split.svg icons/math.svg icons/lissajous.svg icons/dark/single.svg diff --git a/DSView/icons/dark/dso-split.svg b/DSView/icons/dark/dso-split.svg new file mode 100644 index 000000000..e3eb53b3d --- /dev/null +++ b/DSView/icons/dark/dso-split.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/DSView/icons/light/dso-split.svg b/DSView/icons/light/dso-split.svg new file mode 100644 index 000000000..a59f88935 --- /dev/null +++ b/DSView/icons/light/dso-split.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/DSView/pv/config/appconfig.cpp b/DSView/pv/config/appconfig.cpp index 47944b11f..19c2ea470 100644 --- a/DSView/pv/config/appconfig.cpp +++ b/DSView/pv/config/appconfig.cpp @@ -1,7 +1,7 @@ /* * This file is part of the DSView project. * DSView is based on PulseView. - * + * * Copyright (C) 2021 DreamSourceLab * * This program is free software; you can redistribute it and/or modify @@ -19,15 +19,15 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "appconfig.h" +#include "appconfig.h" #include #include #include -#include +#include #include #include #include "../log.h" - + #define MAX_PROTOCOL_FORMAT_LIST 15 StringPair::StringPair(const std::string &key, const std::string &value) @@ -44,10 +44,10 @@ static QString FormatArrayToString(std::vector &protocolFormats) for (StringPair &o : protocolFormats){ if (!str.isEmpty()){ str += ";"; - } + } str += o.m_key.c_str(); str += "="; - str += o.m_value.c_str(); + str += o.m_value.c_str(); } return str; @@ -99,7 +99,7 @@ static void setFiled(const char *key, QSettings &st, bool f){ static void getFiled(const char *key, QSettings &st, float &f, float dv) { - f = st.value(key, dv).toInt(); + f = st.value(key, dv).toFloat(); } static void setFiled(const char *key, QSettings &st, float f) @@ -110,7 +110,7 @@ static void setFiled(const char *key, QSettings &st, float f) ///------ app static void _loadApp(AppOptions &o, QSettings &st) { - st.beginGroup("Application"); + st.beginGroup("Application"); getFiled("quickScroll", st, o.quickScroll, true); getFiled("warnofMultiTrig", st, o.warnofMultiTrig, true); getFiled("originalData", st, o.originalData, false); @@ -132,6 +132,8 @@ static void _loadApp(AppOptions &o, QSettings &st) getFiled("maxDecoderFontWidthPercent", st, o.maxDecoderFontWidthPercent, 125); getFiled("minDecoderFontWidthPercent", st, o.minDecoderFontWidthPercent, 75); getFiled("dontAskSaveOnExit", st, o.dontAskSaveOnExit, false); + getFiled("logicSignalLineWidth", st, o.logicSignalLineWidth, 1.0f); + getFiled("dsoSplitChannels", st, o.dsoSplitChannels, false); o.warnofMultiTrig = true; @@ -155,6 +157,11 @@ static void _loadApp(AppOptions &o, QSettings &st) o.traceHeightFactor = 1.0; } + if (o.logicSignalLineWidth < 1.0f || o.logicSignalLineWidth > 4.0f) + { + o.logicSignalLineWidth = 1.0f; + } + st.endGroup(); } @@ -182,10 +189,12 @@ static void _saveApp(AppOptions &o, QSettings &st) setFiled("maxDecoderFontWidthPercent", st, o.maxDecoderFontWidthPercent); setFiled("minDecoderFontWidthPercent", st, o.minDecoderFontWidthPercent); setFiled("dontAskSaveOnExit", st, o.dontAskSaveOnExit); + setFiled("logicSignalLineWidth", st, o.logicSignalLineWidth); + setFiled("dsoSplitChannels", st, o.dsoSplitChannels); QString fmt = FormatArrayToString(o.m_protocolFormats); setFiled("protocalFormats", st, fmt); - st.endGroup(); + st.endGroup(); } //-----frame @@ -212,10 +221,10 @@ static void _saveDockOptions(DockOptions &o, QSettings &st, const char *group) static void _loadFrame(FrameOptions &o, QSettings &st) { - st.beginGroup("MainFrame"); + st.beginGroup("MainFrame"); getFiled("style", st, o.style, THEME_STYLE_DARK); getFiled("language", st, o.language, -1); - getFiled("isMax", st, o.isMax, false); + getFiled("isMax", st, o.isMax, false); getFiled("left", st, o.left, 0); getFiled("top", st, o.top, 0); getFiled("right", st, o.right, 0); @@ -251,7 +260,7 @@ static void _saveFrame(FrameOptions &o, QSettings &st) st.beginGroup("MainFrame"); setFiled("style", st, o.style); setFiled("language", st, o.language); - setFiled("isMax", st, o.isMax); + setFiled("isMax", st, o.isMax); setFiled("left", st, o.left); setFiled("top", st, o.top); setFiled("right", st, o.right); @@ -262,12 +271,12 @@ static void _saveFrame(FrameOptions &o, QSettings &st) setFiled("oy", st, o.oy); setFiled("displayName", st, o.displayName); - st.setValue("windowState", o.windowState); + st.setValue("windowState", o.windowState); _saveDockOptions(o._logicDock, st, "LOGIC_DOCK"); _saveDockOptions(o._analogDock, st, "ANALOG_DOCK"); _saveDockOptions(o._dsoDock, st, "DSO_DOCK"); - + st.endGroup(); } @@ -275,28 +284,28 @@ static void _saveFrame(FrameOptions &o, QSettings &st) static void _loadHistory(UserHistory &o, QSettings &st) { st.beginGroup("UserHistory"); - getFiled("exportDir", st, o.exportDir, ""); - getFiled("saveDir", st, o.saveDir, ""); + getFiled("exportDir", st, o.exportDir, ""); + getFiled("saveDir", st, o.saveDir, ""); getFiled("showDocuments", st, o.showDocuments, true); - getFiled("screenShotPath", st, o.screenShotPath, ""); - getFiled("sessionDir", st, o.sessionDir, ""); - getFiled("openDir", st, o.openDir, ""); - getFiled("protocolExportPath", st, o.protocolExportPath, ""); - getFiled("exportFormat", st, o.exportFormat, ""); + getFiled("screenShotPath", st, o.screenShotPath, ""); + getFiled("sessionDir", st, o.sessionDir, ""); + getFiled("openDir", st, o.openDir, ""); + getFiled("protocolExportPath", st, o.protocolExportPath, ""); + getFiled("exportFormat", st, o.exportFormat, ""); st.endGroup(); } - + static void _saveHistory(UserHistory &o, QSettings &st) { st.beginGroup("UserHistory"); - setFiled("exportDir", st, o.exportDir); - setFiled("saveDir", st, o.saveDir); - setFiled("showDocuments", st, o.showDocuments); - setFiled("screenShotPath", st, o.screenShotPath); - setFiled("sessionDir", st, o.sessionDir); - setFiled("openDir", st, o.openDir); + setFiled("exportDir", st, o.exportDir); + setFiled("saveDir", st, o.saveDir); + setFiled("showDocuments", st, o.showDocuments); + setFiled("screenShotPath", st, o.screenShotPath); + setFiled("sessionDir", st, o.sessionDir); + setFiled("openDir", st, o.openDir); setFiled("protocolExportPath", st, o.protocolExportPath); - setFiled("exportFormat", st, o.exportFormat); + setFiled("exportFormat", st, o.exportFormat); st.endGroup(); } @@ -344,10 +353,10 @@ static void _saveFont(FontOptions &o, QSettings &st) //------------AppConfig AppConfig::AppConfig() -{ +{ } -AppConfig::AppConfig(AppConfig &o) +AppConfig::AppConfig(AppConfig &o) { (void)o; } @@ -363,7 +372,7 @@ AppConfig::~AppConfig() } void AppConfig::LoadAll() -{ +{ QSettings st(QApplication::organizationName(), QApplication::applicationName()); _loadApp(appOptions, st); _loadHistory(userHistory, st); @@ -398,7 +407,7 @@ void AppConfig::SetProtocolFormat(const std::string &protocolName, const std::st o.m_value = value; bChange = true; break; - } + } } if (!bChange) @@ -422,7 +431,7 @@ void AppConfig::SetProtocolFormat(const std::string &protocolName, const std::st std::string AppConfig::GetProtocolFormat(const std::string &protocolName) { for (StringPair &o : appOptions.m_protocolFormats){ - if (o.m_key == protocolName){ + if (o.m_key == protocolName){ return o.m_value; } } @@ -518,7 +527,7 @@ QString GetAppDataDir() QDir dir(QCoreApplication::applicationDirPath()); if (dir.cd("..") && dir.cd("share") && dir.cd("DSView")) { - return dir.absolutePath(); + return dir.absolutePath(); } QDir dir1("/usr/local/share/DSView"); if (dir1.exists()){ @@ -526,7 +535,7 @@ QString GetAppDataDir() } dsv_err("Data directory is not exists: ../share/DSView"); - assert(false); + assert(false); #else #ifdef Q_OS_DARWIN @@ -561,7 +570,7 @@ QString GetFirmwareDir() { return dir.absolutePath(); } - + dsv_err("%s%s", "Resource directory is not exists:", dir1.absolutePath().toUtf8().data()); return dir1.absolutePath(); } @@ -590,7 +599,7 @@ QString GetDecodeScriptDir() // ../share/libsigrokdecode4DSL/decoders if (dir.cd("..") && dir.cd("share") && dir.cd("libsigrokdecode4DSL") && dir.cd("decoders")) { - return dir.absolutePath(); + return dir.absolutePath(); } dsv_info("ERROR: the decoder directory is not exists: ../share/libsigrokdecode4DSL/decoders"); return ""; diff --git a/DSView/pv/config/appconfig.h b/DSView/pv/config/appconfig.h index 1dc53bbae..e33b96cd8 100644 --- a/DSView/pv/config/appconfig.h +++ b/DSView/pv/config/appconfig.h @@ -86,6 +86,8 @@ struct AppOptions int maxDecoderFontWidthPercent; bool decoderDynamicFontWidth; bool dontAskSaveOnExit; + float logicSignalLineWidth; + bool dsoSplitChannels; std::vector m_protocolFormats; }; diff --git a/DSView/pv/dialogs/applicationpardlg.cpp b/DSView/pv/dialogs/applicationpardlg.cpp index 9559d42b3..2b16230dc 100644 --- a/DSView/pv/dialogs/applicationpardlg.cpp +++ b/DSView/pv/dialogs/applicationpardlg.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -176,6 +177,14 @@ bool ApplicationParamDlg::ShowDlg(QWidget *parent) QCheckBox *ck_decoderDynamicFontWidth = new QCheckBox(); ck_decoderDynamicFontWidth->setChecked(fontWidthEnabled); + QDoubleSpinBox *spinBox_lineWidth = new QDoubleSpinBox(); + spinBox_lineWidth->setDecimals(1); + spinBox_lineWidth->setSingleStep(0.5); + spinBox_lineWidth->setMinimum(1.0); + spinBox_lineWidth->setMaximum(4.0); + spinBox_lineWidth->setValue(app.appOptions.logicSignalLineWidth); + spinBox_lineWidth->setSuffix(" px"); + // Logic group QGroupBox *logicGroup = new QGroupBox(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_GROUP_LOGIC), "Logic")); QGridLayout *logicLay = new QGridLayout(); @@ -291,18 +300,20 @@ bool ApplicationParamDlg::ShowDlg(QWidget *parent) logicLay->addLayout(hl_verticalScrollAction, 3, 1, Qt::AlignRight); logicLay->addWidget(new QLabel(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_RULER_UNITS), "Ruler / Cursor units")), 4, 0, Qt::AlignLeft); logicLay->addLayout(hl_units, 4, 1, Qt::AlignRight); + logicLay->addWidget(new QLabel(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SIGNAL_LINE_WIDTH), "Signal line width")), 5, 0, Qt::AlignLeft); + logicLay->addWidget(spinBox_lineWidth, 5, 1, Qt::AlignRight); // Add sliders to logic layout - logicLay->addWidget(new QLabel(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_DECODER_DYNAMIC_FONT_WIDTH), "Decoder adaptive font width")), 5, 0, Qt::AlignLeft); - logicLay->addWidget(ck_decoderDynamicFontWidth, 5, 1, Qt::AlignRight); - logicLay->addWidget(label_minFontWidth, 6, 0, Qt::AlignLeft); - logicLay->addWidget(spinBox_minFontWidth, 6, 1, Qt::AlignRight); - logicLay->addWidget(slider_minFontWidth, 7, 0, Qt::AlignJustify); - logicLay->addWidget(label_minSample, 7, 1, Qt::AlignCenter); - logicLay->addWidget(label_maxFontWidth, 8, 0, Qt::AlignLeft); - logicLay->addWidget(spinBox_maxFontWidth, 8, 1, Qt::AlignRight); - logicLay->addWidget(slider_maxFontWidth, 9, 0, Qt::AlignJustify); - logicLay->addWidget(label_maxSample, 9, 1, Qt::AlignCenter); + logicLay->addWidget(new QLabel(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_DECODER_DYNAMIC_FONT_WIDTH), "Decoder adaptive font width")), 6, 0, Qt::AlignLeft); + logicLay->addWidget(ck_decoderDynamicFontWidth, 6, 1, Qt::AlignRight); + logicLay->addWidget(label_minFontWidth, 7, 0, Qt::AlignLeft); + logicLay->addWidget(spinBox_minFontWidth, 7, 1, Qt::AlignRight); + logicLay->addWidget(slider_minFontWidth, 8, 0, Qt::AlignJustify); + logicLay->addWidget(label_minSample, 8, 1, Qt::AlignCenter); + logicLay->addWidget(label_maxFontWidth, 9, 0, Qt::AlignLeft); + logicLay->addWidget(spinBox_maxFontWidth, 9, 1, Qt::AlignRight); + logicLay->addWidget(slider_maxFontWidth, 10, 0, Qt::AlignJustify); + logicLay->addWidget(label_maxSample, 10, 1, Qt::AlignCenter); logicLay->setColumnMinimumWidth(1, wfm.horizontalAdvance("Example") + logicLay->contentsMargins().left() + logicLay->contentsMargins().right() @@ -402,6 +413,10 @@ bool ApplicationParamDlg::ShowDlg(QWidget *parent) app.appOptions.maxDecoderFontWidthPercent = slider_maxFontWidth->value(); bAppChanged = true; } + if (app.appOptions.logicSignalLineWidth != spinBox_lineWidth->value()) { + app.appOptions.logicSignalLineWidth = spinBox_lineWidth->value(); + bAppChanged = true; + } if (bAppChanged){ app.SaveApp(); AppControl::Instance()->GetSession()->broadcast_msg(DSV_MSG_APP_OPTIONS_CHANGED); diff --git a/DSView/pv/mainframe.cpp b/DSView/pv/mainframe.cpp index 8f02589e7..477720699 100644 --- a/DSView/pv/mainframe.cpp +++ b/DSView/pv/mainframe.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -701,6 +702,16 @@ void MainFrame::ShowFormInit() resize(w, h); } + // Restore the dockwidget layout only after the window has actually reached its + // final size. move()/resize()/showMaximized() above can be asynchronous (the + // window manager negotiates the real geometry later), and + // QMainWindow::restoreState() sizes the docks relative to the window's size at + // the moment it is called, so calling it too early collapses the dock widths. + MainWindow *mainWindow = _mainWindow; + QTimer::singleShot(0, this, [mainWindow](){ + mainWindow->restore_dock(); + }); + if (!_is_win32_parent_window){ QFrame::show(); return; @@ -1023,11 +1034,9 @@ void MainFrame::ReadSettings() full_rect.width(), full_rect.height()); } - dsv_info("Normal region, x:%d, y:%d, w:%d, h:%d", + dsv_info("Normal region, x:%d, y:%d, w:%d, h:%d", _normalRegion.x, _normalRegion.y, _normalRegion.w, _normalRegion.h); - // restore dockwidgets - _mainWindow->restore_dock(); _titleBar->setRestoreButton(app.frameOptions.isMax); _initWndInfo.k = k; } diff --git a/DSView/pv/mainwindow.cpp b/DSView/pv/mainwindow.cpp index 3e398264d..24a7e4b89 100644 --- a/DSView/pv/mainwindow.cpp +++ b/DSView/pv/mainwindow.cpp @@ -110,13 +110,18 @@ namespace pv namespace{ QString tmp_file; + + // Bump this when the built-in dock layout changes so a windowState + // saved under an older layout is not restored over the new one. + const int DOCK_LAYOUT_VERSION = 1; } MainWindow::MainWindow(toolbars::TitleBar *title_bar, QWidget *parent) : QMainWindow(parent) { _msg = NULL; - _frame = parent; + _frame = parent; + _restoring_dock_layout = false; assert(title_bar); assert(_frame); @@ -225,10 +230,13 @@ namespace pv _search_widget = new dock::SearchDock(_search_dock, *_view, _session); _search_dock->setWidget(_search_widget); + // Put all the right-side docks into the same tab group instead of stacking + // them on top of each other (which forced scrolling to reach the lower ones). + setTabPosition(Qt::RightDockWidgetArea, QTabWidget::North); addDockWidget(Qt::RightDockWidgetArea, _protocol_dock); - addDockWidget(Qt::RightDockWidgetArea, _trigger_dock); - addDockWidget(Qt::RightDockWidgetArea, _dso_trigger_dock); - addDockWidget(Qt::RightDockWidgetArea, _measure_dock); + tabifyDockWidget(_protocol_dock, _trigger_dock); + tabifyDockWidget(_trigger_dock, _dso_trigger_dock); + tabifyDockWidget(_dso_trigger_dock, _measure_dock); addDockWidget(Qt::BottomDockWidgetArea, _search_dock); // event filter @@ -276,6 +284,7 @@ namespace pv connect(_trig_bar, SIGNAL(sig_search(bool)), this, SLOT(on_search(bool))); connect(_trig_bar, SIGNAL(sig_setTheme(QString)), this, SLOT(switchTheme(QString))); connect(_trig_bar, SIGNAL(sig_show_lissajous(bool)), _view, SLOT(show_lissajous(bool))); + connect(_trig_bar, &toolbars::TrigBar::sig_dso_split, _view, &view::View::set_dso_split_channels); // file toolbar connect(_file_bar, SIGNAL(sig_load_file(QString)), this, SLOT(on_load_file(QString))); @@ -438,7 +447,7 @@ namespace pv save_config_to_file(sessionFile); } - app.frameOptions.windowState = saveState(); + app.frameOptions.windowState = saveState(DOCK_LAYOUT_VERSION); app.SaveFrame(); } @@ -483,6 +492,9 @@ namespace pv { _protocol_dock->setVisible(visible); + if (visible && !_restoring_dock_layout) + _protocol_dock->raise(); + if (!visible) _view->setFocus(); } @@ -494,12 +506,18 @@ namespace pv _trigger_widget->update_view(); _trigger_dock->setVisible(visible); _dso_trigger_dock->setVisible(false); + + if (visible && !_restoring_dock_layout) + _trigger_dock->raise(); } else { _dso_trigger_widget->update_view(); _trigger_dock->setVisible(false); _dso_trigger_dock->setVisible(visible); + + if (visible && !_restoring_dock_layout) + _dso_trigger_dock->raise(); } if (!visible) @@ -510,6 +528,9 @@ namespace pv { _measure_dock->setVisible(visible); + if (visible && !_restoring_dock_layout) + _measure_dock->raise(); + if (!visible) _view->setFocus(); } @@ -1206,7 +1227,9 @@ namespace pv { try { - restoreState(st); + // Versioned so a windowState saved before the right-side docks + // were tabified doesn't restore the old split (scrolling) layout. + restoreState(st, DOCK_LAYOUT_VERSION); } catch (...) { @@ -1215,8 +1238,11 @@ namespace pv } // Resotre the dock pannel. - if (_device_agent->have_instance()) + if (_device_agent->have_instance()){ + _restoring_dock_layout = true; _trig_bar->reload(); + _restoring_dock_layout = false; + } } bool MainWindow::eventFilter(QObject *object, QEvent *event) @@ -2141,6 +2167,7 @@ namespace pv case DSV_MSG_APP_OPTIONS_CHANGED: { update_title_bar_text(); + _view->viewport_update(); break; } case DSV_MSG_FONT_OPTIONS_CHANGED: diff --git a/DSView/pv/mainwindow.h b/DSView/pv/mainwindow.h index a43e04271..f1ec746cc 100644 --- a/DSView/pv/mainwindow.h +++ b/DSView/pv/mainwindow.h @@ -229,6 +229,12 @@ private slots: QDockWidget *_search_dock; dock::SearchDock *_search_widget; + // While true, on_protocol/on_trigger/on_measure won't raise() their dock when + // shown, so the tab that QMainWindow::restoreState() already made active (from + // a prior session) isn't stolen by the fixed-order visibility sync in + // TrigBar::reload() during startup. + bool _restoring_dock_layout; + QTranslator _qtTrans; QTranslator _myTrans; EventObject _event; diff --git a/DSView/pv/toolbars/trigbar.cpp b/DSView/pv/toolbars/trigbar.cpp index c437f4e8f..8f43a254d 100644 --- a/DSView/pv/toolbars/trigbar.cpp +++ b/DSView/pv/toolbars/trigbar.cpp @@ -47,7 +47,8 @@ TrigBar::TrigBar(SigSession *session, QWidget *parent) : _measure_button(this), _search_button(this), _function_button(this), - _setting_button(this) + _setting_button(this), + _dso_split_button(this) { _enable = true; @@ -107,6 +108,8 @@ TrigBar::TrigBar(SigSession *session, QWidget *parent) : _search_button.setToolButtonStyle(Qt::ToolButtonTextUnderIcon); _function_button.setToolButtonStyle(Qt::ToolButtonTextUnderIcon); _setting_button.setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + _dso_split_button.setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + _dso_split_button.setCheckable(true); _protocol_button.setContentsMargins(0,0,0,0); @@ -114,13 +117,15 @@ TrigBar::TrigBar(SigSession *session, QWidget *parent) : _protocol_action = addWidget(&_protocol_button); _measure_action = addWidget(&_measure_button); _search_action = addWidget(&_search_button); - _function_action = addWidget(&_function_button); + _function_action = addWidget(&_function_button); _display_action = addWidget(&_setting_button); //must be created + _dso_split_action = addWidget(&_dso_split_button); connect(&_trig_button, SIGNAL(clicked()),this, SLOT(trigger_clicked())); connect(&_protocol_button, SIGNAL(clicked()),this, SLOT(protocol_clicked())); connect(&_measure_button, SIGNAL(clicked()),this, SLOT(measure_clicked())); connect(&_search_button, SIGNAL(clicked()), this, SLOT(search_clicked())); + connect(&_dso_split_button, SIGNAL(clicked()), this, SLOT(dso_split_clicked())); connect(_action_fft, SIGNAL(triggered()), this, SLOT(on_actionFft_triggered())); connect(_action_math, SIGNAL(triggered()), this, SLOT(on_actionMath_triggered())); @@ -146,8 +151,9 @@ void TrigBar::retranslateUi() _measure_button.setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_MEASURE), "Measure")); _search_button.setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_SEARCH), "Search")); _function_button.setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_FUNCTION), "Function")); + _dso_split_button.setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_DSO_SPLIT), "Split")); - _setting_button.setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_DISPLAY), "Display")); + _setting_button.setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_DISPLAY), "Display")); _themes->setTitle(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_DISPLAY_THEMES), "Themes")); _action_lissajous->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_DISPLAY_LISSAJOUS), "Lissajous")); @@ -173,6 +179,7 @@ void TrigBar::reStyle() _search_button.setIcon(QIcon(iconPath+"/search-bar.svg")); _function_button.setIcon(QIcon(iconPath+"/function.svg")); _setting_button.setIcon(QIcon(iconPath+"/display.svg")); + _dso_split_button.setIcon(QIcon(iconPath+"/dso-split.svg")); _action_fft->setIcon(QIcon(iconPath+"/fft.svg")); _action_math->setIcon(QIcon(iconPath+"/math.svg")); @@ -246,6 +253,14 @@ void TrigBar::search_clicked() } } +void TrigBar::dso_split_clicked() +{ + if (_dso_split_button.isVisible() && _dso_split_button.isEnabled()) + { + sig_dso_split(_dso_split_button.isChecked()); + } +} + void TrigBar::reload() { int mode = _session->get_device()->get_work_mode(); @@ -258,6 +273,7 @@ void TrigBar::reload() _function_action->setVisible(false); _action_lissajous->setVisible(false); _action_dispalyOptions->setVisible(true); + _dso_split_action->setVisible(false); } else if (mode == ANALOG) { _trig_action->setVisible(false); @@ -267,6 +283,7 @@ void TrigBar::reload() _function_action->setVisible(false); _action_lissajous->setVisible(false); _action_dispalyOptions->setVisible(true); + _dso_split_action->setVisible(false); } else if (mode == DSO) { _trig_action->setVisible(true); @@ -276,6 +293,8 @@ void TrigBar::reload() _function_action->setVisible(true); _action_lissajous->setVisible(true); _action_dispalyOptions->setVisible(true); + _dso_split_action->setVisible(true); + _dso_split_button.setChecked(AppConfig::Instance().appOptions.dsoSplitChannels); } DockOptions *opt = getDockOptions(); diff --git a/DSView/pv/toolbars/trigbar.h b/DSView/pv/toolbars/trigbar.h index efa2f54a7..25d843b0a 100644 --- a/DSView/pv/toolbars/trigbar.h +++ b/DSView/pv/toolbars/trigbar.h @@ -68,6 +68,7 @@ class TrigBar : public QToolBar, public IUiWindow void sig_measure(bool visible);//post decode button click event,to show or hide measure property panel void sig_search(bool visible); void sig_show_lissajous(bool visible); + void sig_dso_split(bool split); //split each DSO channel into its own row private slots: void on_actionDark_triggered(); @@ -84,6 +85,7 @@ public slots: void trigger_clicked(); void measure_clicked(); void search_clicked(); + void dso_split_clicked(); private: SigSession *_session; @@ -94,12 +96,14 @@ public slots: XToolButton _search_button; XToolButton _function_button; XToolButton _setting_button; + XToolButton _dso_split_button; QAction *_trig_action; QAction *_protocol_action; QAction *_measure_action; QAction *_search_action; - QAction *_function_action; + QAction *_function_action; QAction *_display_action; + QAction *_dso_split_action; QMenu *_function_menu; QAction *_action_fft; diff --git a/DSView/pv/view/dsosignal.cpp b/DSView/pv/view/dsosignal.cpp index 08451a72b..0d0b2e26b 100644 --- a/DSView/pv/view/dsosignal.cpp +++ b/DSView/pv/view/dsosignal.cpp @@ -640,6 +640,15 @@ QString DsoSignal::get_measure(enum DSO_MEASURE_TYPE type) QRect DsoSignal::get_view_rect() { assert(_viewport); + + if (_view && _view->get_dso_split_channels()){ + const int top = get_v_offset() - get_totalHeight() / 2; + const int height = max(get_totalHeight() - UpMargin - DownMargin, 1); + return QRect(0, top + UpMargin, + _viewport->width() - RightMargin, + height); + } + return QRect(0, UpMargin, _viewport->width() - RightMargin, _viewport->height() - UpMargin - DownMargin); @@ -697,7 +706,12 @@ void DsoSignal::paint_back(QPainter &p, int left, int right, QColor fore, QColor int i, j; const int height = get_view_rect().height(); - const int width = right - left; + const int width = right - left; + // Origin of this channel's own band. In the default (overlaid) mode this + // is just UpMargin, same as before; in split mode each channel has its + // own row, so the grid must be anchored to that row instead of the + // viewport's absolute top. + const int top = get_view_rect().top(); fore.setAlpha(View::BackAlpha); @@ -705,7 +719,7 @@ void DsoSignal::paint_back(QPainter &p, int left, int right, QColor fore, QColor solidPen.setStyle(Qt::SolidLine); p.setPen(solidPen); p.setBrush(back.black() > 0x80 ? back.darker() : back.lighter()); - p.drawRect(left, UpMargin, width, height); + p.drawRect(left, top, width, height); // draw zoom region fore.setAlpha(View::ForeAlpha); @@ -718,20 +732,21 @@ void DsoSignal::paint_back(QPainter &p, int left, int right, QColor fore, QColor const double start = _view->x_offset() * samples_per_pixel; const double shown_offset = min(start / sample_len, 1.0) * width; const double shown_len = max(shown_rate * width, 6.0); - const QPointF left_edge[] = {QPoint(shown_offset + 3, UpMargin/2 - 6), - QPoint(shown_offset, UpMargin/2 - 6), - QPoint(shown_offset, UpMargin/2 + 6), - QPoint(shown_offset + 3, UpMargin/2 + 6)}; - const QPointF right_edge[] = {QPoint(shown_offset + shown_len - 3, UpMargin/2 - 6), - QPoint(shown_offset + shown_len , UpMargin/2 - 6), - QPoint(shown_offset + shown_len , UpMargin/2 + 6), - QPoint(shown_offset + shown_len - 3, UpMargin/2 + 6)}; - p.drawLine(left, UpMargin/2, shown_offset, UpMargin/2); - p.drawLine(shown_offset + shown_len, UpMargin/2, left + width, UpMargin/2); + const double markerY = top - UpMargin / 2; + const QPointF left_edge[] = {QPoint(shown_offset + 3, markerY - 6), + QPoint(shown_offset, markerY - 6), + QPoint(shown_offset, markerY + 6), + QPoint(shown_offset + 3, markerY + 6)}; + const QPointF right_edge[] = {QPoint(shown_offset + shown_len - 3, markerY - 6), + QPoint(shown_offset + shown_len , markerY - 6), + QPoint(shown_offset + shown_len , markerY + 6), + QPoint(shown_offset + shown_len - 3, markerY + 6)}; + p.drawLine(left, markerY, shown_offset, markerY); + p.drawLine(shown_offset + shown_len, markerY, left + width, markerY); p.drawPolyline(left_edge, countof(left_edge)); p.drawPolyline(right_edge, countof(right_edge)); p.setBrush(fore); - p.drawRect(shown_offset, UpMargin/2 - 3, shown_len, 6); + p.drawRect(shown_offset, markerY - 3, shown_len, 6); // draw divider fore.setAlpha(View::BackAlpha); @@ -740,7 +755,7 @@ void DsoSignal::paint_back(QPainter &p, int left, int right, QColor fore, QColor p.setPen(dashPen); const double spanY =height * 1.0 / DS_CONF_DSO_VDIVS; for (i = 1; i <= DS_CONF_DSO_VDIVS; i++) { - const double posY = spanY * i + UpMargin; + const double posY = spanY * i + top; if (i != DS_CONF_DSO_VDIVS) p.drawLine(left, posY, right, posY); const double miniSpanY = spanY / 5; @@ -753,14 +768,20 @@ void DsoSignal::paint_back(QPainter &p, int left, int right, QColor fore, QColor for (i = 1; i <= DS_CONF_DSO_HDIVS; i++) { const double posX = spanX * i; if (i != DS_CONF_DSO_HDIVS) - p.drawLine(posX, UpMargin,posX, height + UpMargin); + p.drawLine(posX, top, posX, height + top); const double miniSpanX = spanX / 5; for (j = 1; j < 5; j++) { - p.drawLine(posX - miniSpanX * j, height / 2.0f + UpMargin - 5, - posX - miniSpanX * j, height / 2.0f + UpMargin + 5); + p.drawLine(posX - miniSpanX * j, height / 2.0f + top - 5, + posX - miniSpanX * j, height / 2.0f + top + 5); } } - _view->set_back(true); + + // In overlaid mode every DSO channel shares the same background, so + // painting it once is enough (see Viewport::doPaint()'s back_ready() + // short-circuit). In split mode each channel has its own band and must + // paint its own background. + if (!_view->get_dso_split_channels()) + _view->set_back(true); } void DsoSignal::paint_mid(QPainter &p, int left, int right, QColor fore, QColor back) diff --git a/DSView/pv/view/logicsignal.cpp b/DSView/pv/view/logicsignal.cpp index ffa97565a..892fd1dae 100644 --- a/DSView/pv/view/logicsignal.cpp +++ b/DSView/pv/view/logicsignal.cpp @@ -23,11 +23,12 @@ #include #include #include "logicsignal.h" -#include "view.h" +#include "view.h" #include "../data/logicsnapshot.h" #include "view.h" #include "../dsvdef.h" #include "../log.h" +#include "../config/appconfig.h" using namespace std; @@ -44,7 +45,7 @@ LogicSignal::LogicSignal(data::LogicSnapshot *data, Signal(probe), _data(data) { - _trig = NONTRIG; + _trig = NONTRIG; _paint_align_sample_count = 0; } @@ -54,7 +55,7 @@ LogicSignal::LogicSignal(view::LogicSignal *s, Signal(*s, probe), _data(data), _trig(s->get_trig()) -{ +{ _paint_align_sample_count = 0; } @@ -64,6 +65,23 @@ LogicSignal::~LogicSignal() _cur_pulses.clear(); } +void LogicSignal::paint_back(QPainter &p, int left, int right, QColor fore, QColor back) +{ + Trace::paint_back(p, left, right, fore, back); + + // Logic channels have no fill of their own, so without an explicit + // boundary adjacent rows are hard to tell apart; draw a divider under + // each channel's row, in the middle of the gap to the next one. + QColor sep(fore); + sep.setAlpha(180); + QPen pen(sep); + pen.setWidth(2); + p.setPen(pen); + + const int bottom = get_y() + get_totalHeight() / 2 + View::SignalMargin; + p.drawLine(left, bottom, right, bottom); +} + void LogicSignal::set_trig(int trig) { if (trig > NONTRIG && trig <= EDGTRIG) @@ -77,7 +95,7 @@ bool LogicSignal::commit_trig() if (_trig == NONTRIG) { ds_trigger_probe_set(_index_list.front(), 'X', 'X'); return false; - } + } else { ds_trigger_set_en(true); if (_trig == POSTRIG) @@ -129,7 +147,7 @@ void LogicSignal::paint_mid_align(QPainter &p, int left, int right, QColor fore, double samplerate = _data->samplerate(); if (_data->empty() || samplerate == 0) return; - + if (!_data->has_data(_probe->index)) return; @@ -144,7 +162,7 @@ void LogicSignal::paint_mid_align(QPainter &p, int left, int right, QColor fore, const double end = (offset + width + 1) * samples_per_pixel; const uint64_t end_index = min(max((int64_t)floor(end), (int64_t)0), last_sample); const uint64_t start_index = max((uint64_t)floor(start), (uint64_t)0); - + if (start_index > end_index) return; @@ -161,7 +179,7 @@ void LogicSignal::paint_mid_align(QPainter &p, int left, int right, QColor fore, int preY = first_sample ? high_offset : low_offset; int x = preX; std::vector wave_lines; - + if (_cur_edges.size() < max_togs) { std::vector>::const_iterator i; for (i = _cur_edges.begin() + 1; i != _cur_edges.end() - 1; i++) { @@ -189,7 +207,9 @@ void LogicSignal::paint_mid_align(QPainter &p, int left, int right, QColor fore, wave_lines.push_back(QLine(preX, preY, x, preY)); } - p.setPen(_colour.isValid() ? _colour : fore); + QColor defaultColour = get_default_colour(); + QColor lineColour = _colour.isValid() ? _colour : (defaultColour.isValid() ? defaultColour : fore); + p.setPen(QPen(lineColour, AppConfig::Instance().appOptions.logicSignalLineWidth)); p.drawLines(wave_lines.data(), wave_lines.size()); } @@ -228,7 +248,7 @@ void LogicSignal::paint_type_options(QPainter &p, int right, const QPoint pt, QC p.setPen(Qt::NoPen); if (true) - { + { QColor color = View::Blue; if (session->is_loop_mode()){ @@ -250,7 +270,7 @@ void LogicSignal::paint_type_options(QPainter &p, int right, const QPoint pt, QC p.setBrush(edgeTrig_rect.contains(pt) ? color.lighter() : (_trig == EDGTRIG) ? color : Qt::transparent); p.drawRect(edgeTrig_rect); - } + } p.setPen(QPen(fore, 1, Qt::DashLine)); p.setBrush(Qt::transparent); @@ -313,7 +333,7 @@ bool LogicSignal::measure(const QPointF &p, uint64_t &index0, uint64_t &index1, const uint64_t end = _data->get_ring_sample_count() - 1; uint64_t index = _data->samplerate() * _view->scale() * (_view->x_offset() + p.x()); - + if (index > end){ return false; } @@ -468,7 +488,7 @@ bool LogicSignal::edges(const QPointF &p, uint64_t start, uint64_t &rising, uint } bool LogicSignal::edges(uint64_t end, uint64_t start, uint64_t &rising, uint64_t &falling) -{ +{ if (_data->empty() || !_data->has_data(_probe->index)) return false; diff --git a/DSView/pv/view/logicsignal.h b/DSView/pv/view/logicsignal.h index 2ecab7c9d..31d7f3e4a 100644 --- a/DSView/pv/view/logicsignal.h +++ b/DSView/pv/view/logicsignal.h @@ -85,6 +85,10 @@ class LogicSignal : public Signal bool commit_trig(); + // Draws the dotted zero line plus a row separator so adjacent channels + // are visually distinguishable. + void paint_back(QPainter &p, int left, int right, QColor fore, QColor back); + /** * Paints the signal with a QPainter * @param p the QPainter to paint into. diff --git a/DSView/pv/view/trace.cpp b/DSView/pv/view/trace.cpp index 955fa4e00..a8d5bed43 100644 --- a/DSView/pv/view/trace.cpp +++ b/DSView/pv/view/trace.cpp @@ -201,6 +201,15 @@ void Trace::paint_prepare() _view->set_trig_hoff(0); } +QColor Trace::get_default_colour() +{ + if (_type == SR_CHANNEL_DSO || _type == SR_CHANNEL_FFT || + _type == SR_CHANNEL_ANALOG || _type == SR_CHANNEL_MATH || _index_list.empty()) + return QColor(); + + return PROBE_COLORS[*_index_list.begin() % countof(PROBE_COLORS)]; +} + void Trace::paint_back(QPainter &p, int left, int right, QColor fore, QColor back) { (void)back; @@ -246,8 +255,9 @@ void Trace::paint_label(QPainter &p, int right, const QPoint pt, QColor fore) // Paint the ColorButton QColor foreBack = fore; foreBack.setAlpha(View::BackAlpha); + QColor defaultColour = get_default_colour(); p.setPen(Qt::transparent); - p.setBrush(enabled() ? (_colour.isValid() ? _colour : fore) : foreBack); + p.setBrush(enabled() ? (_colour.isValid() ? _colour : (defaultColour.isValid() ? defaultColour : fore)) : foreBack); p.drawRect(color_rect); if (_type == SR_CHANNEL_DSO || diff --git a/DSView/pv/view/trace.h b/DSView/pv/view/trace.h index 9c3cd605f..b820eb54b 100644 --- a/DSView/pv/view/trace.h +++ b/DSView/pv/view/trace.h @@ -100,6 +100,12 @@ class Trace : public SelectableItem _colour = colour; } + /** + * The colour to fall back to when the signal has no explicit user-set + * colour, so a channel's waveform matches the colour of its label flag. + */ + QColor get_default_colour(); + /** * Gets the vertical layout offset of this signal. */ diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index 6bb2067b9..1551b70a9 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -64,7 +64,7 @@ const int View::RulerHeight = 50; const int View::MaxScrollValue = INT_MAX / 2; const int View::HeightUnit = 20; // also serves as minimum signal height -const int View::SignalMargin = 3; +const int View::SignalMargin = 12; const int View::SignalSnapGridSize = 10; const QColor View::CursorAreaColour(220, 231, 243); @@ -100,7 +100,7 @@ View::View(SigSession *session, pv::toolbars::SamplingBar *sampling_bar, QWidget _dso_auto(true), _show_lissajous(false), _back_ready(false) -{ +{ _trig_cursor = NULL; _search_cursor = NULL; _cali = NULL; @@ -112,11 +112,13 @@ View::View(SigSession *session, pv::toolbars::SamplingBar *sampling_bar, QWidget if (_trace_height_factor < MinTraceHeightFactor || _trace_height_factor > MaxTraceHeightFactor) _trace_height_factor = 1.0; + _dso_split_channels = AppConfig::Instance().appOptions.dsoSplitChannels; + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); // setWidgetResizable(true); // setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - + // trace viewport map _trace_view_map[SR_CHANNEL_LOGIC] = TIME_VIEW; _trace_view_map[SR_CHANNEL_GROUP] = TIME_VIEW; @@ -131,19 +133,19 @@ View::View(SigSession *session, pv::toolbars::SamplingBar *sampling_bar, QWidget _ruler = new Ruler(*this); _header = new Header(*this); _devmode = new DevMode(this, session); - + setViewportMargins(headerWidth(), RulerHeight, 0, 0); // windows splitter _time_viewport = new Viewport(*this, TIME_VIEW); _time_viewport->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); _time_viewport->setMinimumHeight(100); - + _fft_viewport = new Viewport(*this, FFT_VIEW); _fft_viewport->setVisible(false); _fft_viewport->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); _fft_viewport->setMinimumHeight(100); - + _vsplitter = new QSplitter(this); _vsplitter->setOrientation(Qt::Vertical); _vsplitter->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); @@ -210,7 +212,7 @@ View::View(SigSession *session, pv::toolbars::SamplingBar *sampling_bar, QWidget connect(_fft_viewport, SIGNAL(measure_updated()), this, SLOT(on_measure_updated())); connect(_vsplitter, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved(int, int))); - + connect(_header, SIGNAL(traces_moved()),this, SLOT(on_traces_moved())); connect(_header, SIGNAL(header_updated()),this, SLOT(header_updated())); @@ -247,13 +249,13 @@ void View::capture_init() show_trig_cursor(true); else if (!_session->is_repeating()) show_trig_cursor(false); - + _maxscale = _session->cur_sampletime() / (width * MaxViewRate); if (mode == ANALOG){ set_scale_offset(_maxscale, 0); } - + status_clear(); _trig_hoff = 0; @@ -305,7 +307,7 @@ bool View::zoom(double steps, int offset) if (_device_agent->get_work_mode() != DSO) { _scale *= std::pow(3.0/2.0, -steps); _scale = max(min(_scale, _maxscale), _minscale); - } + } else { if (_session->is_running_status() && _session->is_instant()){ return ret; @@ -320,7 +322,7 @@ bool View::zoom(double steps, int offset) if (hori_res > 0) { const double scale = _session->cur_view_time() / width; _scale = max(min(scale, _maxscale), _minscale); - } + } else { ret = false; } @@ -412,7 +414,7 @@ void View::set_scale_offset(double scale, int64_t offset) } void View::set_preScale_preOffset() -{ +{ set_scale_offset(_preScale, _preOffset); } @@ -421,16 +423,16 @@ void View::get_traces(int type, std::vector &traces) assert(_session); auto &sigs = _session->get_signals(); - + const auto &decode_sigs = _session->get_decode_signals(); - + const auto &spectrums = _session->get_spectrum_traces(); - + for(auto t : sigs) { if (type == ALL_VIEW || _trace_view_map[t->get_type()] == type) traces.push_back(t); } - + for(auto t : decode_sigs) { if (type == ALL_VIEW || _trace_view_map[t->get_type()] == type) traces.push_back(t); @@ -469,11 +471,11 @@ bool View::compare_trace_v_offsets(const Trace *a, const Trace *b) if (a1->get_type() != b1->get_type()){ v1 = a1->get_type(); v2 = b1->get_type(); - } + } else if (a1->get_type() == SR_CHANNEL_DSO || a1->get_type() == SR_CHANNEL_ANALOG){ v1 = a1->get_index(); v2 = b1->get_index(); - } + } else{ v1 = a1->get_v_offset(); v2 = b1->get_v_offset(); @@ -549,7 +551,7 @@ void View::receive_end() bool ret; ret = _device_agent->get_config_bool(SR_CONF_RLE, rle); - + if (ret && rle) { ret = _device_agent->get_config_uint64(SR_CONF_ACTUAL_SAMPLES, actual_samples); if (ret) { @@ -557,7 +559,7 @@ void View::receive_end() _viewbottom->set_rle_depth(actual_samples); } } - } + } } _time_viewport->unshow_wait_trigger(); } @@ -571,7 +573,7 @@ void View::receive_trigger(quint64 trig_pos1) } void View::set_trig_cursor_posistion(uint64_t trig_pos) -{ +{ const double time = trig_pos * 1.0 / _session->cur_snap_samplerate(); _trig_cursor->set_index(trig_pos); @@ -604,7 +606,7 @@ void View::set_trig_pos(int percent) } void View::set_search_pos(uint64_t search_pos, bool hit) -{ +{ QColor fore(QWidget::palette().color(QWidget::foregroundRole())); fore.setAlpha(View::BackAlpha); @@ -625,7 +627,7 @@ void View::set_search_pos(uint64_t search_pos, bool hit) } void View::normalize_layout() -{ +{ int v_min = INT_MAX; std::vector traces; get_traces(ALL_VIEW, traces); @@ -708,18 +710,18 @@ void View::update_scroll() } void View::update_scale_offset() -{ +{ int width = get_view_width(); if (width == 0){ return; } if (_device_agent->get_work_mode() != DSO) { - _maxscale = _session->cur_sampletime() / (width * MaxViewRate); + _maxscale = _session->cur_sampletime() / (width * MaxViewRate); _minscale = (1.0 / _session->cur_snap_samplerate()) / MaxPixelsPerSample; } else { - _scale = _session->cur_view_time() / width; + _scale = _session->cur_view_time() / width; _maxscale = 1e9; _minscale = 1e-15; } @@ -806,19 +808,29 @@ void View::signals_changed(const Trace* eventTrace) if (!time_traces.empty() && _time_viewport) { for(auto t : time_traces) { - if (dynamic_cast(t) || t->enabled()) + // A disabled DSO channel still needs a slot when every channel + // shares one overlaid area (so it keeps a valid, if unused, + // band), but in split mode it shouldn't reserve a whole row of + // waveport space that nothing is drawn into. + bool isDso = (t->signal_type() == SR_CHANNEL_DSO); + bool occupiesRow = t->enabled() || (isDso && !_dso_split_channels); + if (occupiesRow) total_rows += t->rows_size(); if (t->rows_size() != 0) label_size++; } + // Every DSO channel can end up disabled at once in split mode; avoid + // a division by zero below. + total_rows = max(total_rows, 1); + const double height = (_time_viewport->height() - 2 * actualMargin * label_size) * 1.0 / total_rows; if (_device_agent->have_instance() == false){ assert(false); } - + int mode = _device_agent->get_work_mode(); if (mode == LOGIC) { @@ -860,10 +872,10 @@ void View::signals_changed(const Trace* eventTrace) _spanY = _signalHeight + 2 * actualMargin; int next_v_offset = actualMargin; - + //Make list by view-index; if (mode == LOGIC) - { + { time_traces.clear(); std::vector all_traces; @@ -879,7 +891,7 @@ void View::signals_changed(const Trace* eventTrace) time_traces.push_back(t); } - sort(all_traces.begin(), all_traces.end(), compare_trace_view_index); + sort(all_traces.begin(), all_traces.end(), compare_trace_view_index); for(auto t : all_traces){ time_traces.push_back(t); @@ -893,6 +905,16 @@ void View::signals_changed(const Trace* eventTrace) if (t->rows_size() == 0) continue; + // Mirror the total_rows accounting above: a disabled DSO channel + // in split mode gets no row of its own, so it doesn't leave a + // block of empty space where nothing is drawn. + bool isDso = (t->signal_type() == SR_CHANNEL_DSO); + if (!t->enabled() && isDso && _dso_split_channels){ + t->set_totalHeight(0); + t->set_v_offset(next_v_offset); + continue; + } + const double traceHeight = _signalHeight*t->rows_size(); t->set_totalHeight((int)traceHeight); t->set_v_offset(next_v_offset + 0.5 * traceHeight + actualMargin); @@ -901,7 +923,7 @@ void View::signals_changed(const Trace* eventTrace) if (t->signal_type() == SR_CHANNEL_DSO) { auto sig = dynamic_cast(t); - sig->set_scale(sig->get_view_rect().height()); + sig->set_scale(sig->get_view_rect().height()); } else if (t->signal_type() == SR_CHANNEL_ANALOG) { @@ -972,7 +994,7 @@ int View::headerWidth() std::vector traces; get_traces(ALL_VIEW, traces); - if (!traces.empty()) + if (!traces.empty()) { for(auto t : traces){ int w = t->get_name_width() + t->get_leftWidth() + t->get_rightWidth(); @@ -1027,7 +1049,7 @@ void View::h_scroll_value_changed(int value) const int range = horizontalScrollBar()->maximum(); if (range < MaxScrollValue) _x_offset = value; - else + else { int64_t length = 0; int64_t offset = 0; @@ -1136,7 +1158,7 @@ void View::on_traces_moved() void View::make_cursors_order() { int dex = 1; - + for (auto cursor : get_cursorList()) { cursor->set_order(dex++); @@ -1303,7 +1325,12 @@ void View::on_state_changed(bool stop) QRect View::get_view_rect() { - if (_device_agent->get_work_mode() == DSO) { + // In split mode each channel only owns its own row, so returning the + // first channel's rect here (as the overlaid case does, since every + // channel's rect is the whole viewport there) would confine cursors, + // hit-testing, and status text to that single row instead of the whole + // viewport. + if (_device_agent->get_work_mode() == DSO && !_dso_split_channels) { const auto &sigs = _session->get_signals(); if(sigs.size() > 0) { return sigs[0]->get_view_rect(); @@ -1335,7 +1362,14 @@ int View::get_view_width() int View::get_view_height() { int view_height = 0; - if (_device_agent->get_work_mode() == DSO) { + + // In split mode each DSO channel's get_view_rect() only spans its own + // row, so taking the max over channels would drastically undercount the + // actual visible area (used below to size the vertical scrollbar), + // making it look like there's a lot more content to scroll to than + // there really is. Overlaid channels all still share the full viewport + // height, so this only needs a special case for split mode. + if (_device_agent->get_work_mode() == DSO && !_dso_split_channels) { for(auto s : _session->get_signals()) { view_height = max(view_height, s->get_view_rect().height()); } @@ -1434,6 +1468,20 @@ void View::show_lissajous(bool show) signals_changed(NULL); } +void View::set_dso_split_channels(bool split) +{ + _dso_split_channels = split; + + AppConfig &app = AppConfig::Instance(); + if (app.appOptions.dsoSplitChannels != split){ + app.appOptions.dsoSplitChannels = split; + app.SaveApp(); + } + + signals_changed(NULL); + viewport_update(); +} + void View::show_region(uint64_t start, uint64_t end, bool keep) { assert(start <= end); @@ -1527,7 +1575,7 @@ double View::index2pixel(uint64_t index, bool has_hoff) { const uint64_t rateValue = session().cur_snap_samplerate(); const double scaleValue = scale(); - const int64_t offsetValue = x_offset(); + const int64_t offsetValue = x_offset(); const double hoffValue = trig_hoff(); double pixels = 0; @@ -1558,12 +1606,12 @@ double View::index2pixel(uint64_t index, bool has_hoff) } uint64_t View::pixel2index(double pixel) -{ +{ const uint64_t rateValue = session().cur_snap_samplerate(); const double scaleValue = scale(); - const int64_t offsetValue = x_offset(); + const int64_t offsetValue = x_offset(); const double hoffValue = trig_hoff(); - + const double samples_per_pixel = rateValue * scaleValue; const double index = (pixel + offsetValue) * samples_per_pixel - hoffValue; @@ -1579,7 +1627,7 @@ void View::set_receive_len(uint64_t len) { if (_time_viewport) _time_viewport->set_receive_len(len); - + if (_fft_viewport && _session->get_device()->get_work_mode() == DSO) _fft_viewport->set_receive_len(len); } @@ -1603,10 +1651,10 @@ void View::check_calibration() if (_device_agent->get_work_mode() == DSO){ bool cali = false; _device_agent->get_config_bool(SR_CONF_CALI, cali); - + if (cali) { show_calibration(); - } + } } } @@ -1636,7 +1684,7 @@ void View::auto_set_max_scale() { _maxscale = limitTime / (width * MaxViewRate); set_scale(_maxscale); - } + } } int View::get_body_width() @@ -1671,7 +1719,7 @@ void View::check_measure() } std::list& View::get_cursorList() -{ +{ if (_session->get_device()->get_work_mode() == LOGIC){ return _logic_cursors; } @@ -1701,16 +1749,16 @@ Cursor* View::get_cursor_by_index(int index) void View::UpdateLanguage() { - + } void View::UpdateTheme() { - + } void View::UpdateFont() -{ +{ update_font(); } diff --git a/DSView/pv/view/view.h b/DSView/pv/view/view.h index 18b536d7b..b0f03cdd2 100644 --- a/DSView/pv/view/view.h +++ b/DSView/pv/view/view.h @@ -217,6 +217,12 @@ class View : public QScrollArea, public IUiWindow return _trace_height_factor; } + inline bool get_dso_split_channels(){ + return _dso_split_channels; + } + + void set_dso_split_channels(bool split); + /** * Scale applied to trace-area text so it grows together with the trace * height. Returns the vertical scaling factor in logic mode and 1.0 in @@ -500,6 +506,7 @@ private slots: int _spanY; int _signalHeight; double _trace_height_factor; + bool _dso_split_channels; bool _updating_scroll; // trigger position fix diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp index e0bb29242..fee69f946 100644 --- a/DSView/pv/view/viewport.cpp +++ b/DSView/pv/view/viewport.cpp @@ -301,10 +301,12 @@ void Viewport::paintSignals(QPainter &p, QColor fore, QColor back) if (_view.scale() != _curScale || _view.x_offset() != _curOffset || _view.get_signalHeight() != _curSignalHeight || + _view.y_offset() != _curYOffset || _need_update) { _curScale = _view.scale(); _curOffset = _view.x_offset(); _curSignalHeight = _view.get_signalHeight(); + _curYOffset = _view.y_offset(); _pixmap = QPixmap(size()); _pixmap.fill(Qt::transparent); diff --git a/DSView/pv/view/viewport.h b/DSView/pv/view/viewport.h index 1a70e449c..1c293f07b 100644 --- a/DSView/pv/view/viewport.h +++ b/DSView/pv/view/viewport.h @@ -186,6 +186,7 @@ private slots: double _curScale; int64_t _curOffset; int _curSignalHeight; + int64_t _curYOffset; bool _measure_en; ActionType _action_type; From ccf1bae674a70066920b5b2d441fc1e1ba95062f Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Fri, 3 Jul 2026 12:58:29 +0200 Subject: [PATCH 18/34] Improve docks --- DSView/pv/dock/measuredock.cpp | 36 +++++++++++++++------------------- DSView/pv/dock/measuredock.h | 2 -- DSView/pv/dock/triggerdock.cpp | 3 ++- DSView/pv/view/view.cpp | 3 ++- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/DSView/pv/dock/measuredock.cpp b/DSView/pv/dock/measuredock.cpp index 07866763a..81b03c78f 100644 --- a/DSView/pv/dock/measuredock.cpp +++ b/DSView/pv/dock/measuredock.cpp @@ -54,8 +54,9 @@ MeasureDock::MeasureDock(QWidget *parent, View &view, SigSession *session) : QScrollArea(parent), _session(session), _view(view) -{ - _widget = new QWidget(this); +{ + this->setWidgetResizable(true); + _widget = new QWidget(this); _dist_pannel = NULL; _edge_pannel = NULL; @@ -156,7 +157,6 @@ MeasureDock::MeasureDock(QWidget *parent, View &view, SigSession *session) : _widget->setLayout(layout); this->setWidget(_widget); - _widget->setGeometry(0, 0, sizeHint().width(), 2000); _widget->setObjectName("measureWidget"); add_dist_measure(); @@ -958,9 +958,6 @@ void MeasureDock::UpdateFont() font.setPointSizeF(font.pointSizeF() + 1); this->parentWidget()->setFont(font); - font.setStretch(QFont::Condensed); - _condensed_font = font; - adjusLabelSize(); } @@ -984,16 +981,15 @@ void MeasureDock::adjust_form_size(QWidget *wid) o->setFixedSize(size); } - QFontMetrics fm_condensed(_condensed_font); - _width_time_label->setFont(_condensed_font); - _width_samples_label->setFont(_condensed_font); - _period_time_label->setFont(_condensed_font); - _period_samples_label->setFont(_condensed_font); - _freq_label->setFont(_condensed_font); - _duty_label->setFont(_condensed_font); - int samples_label_width = fm_condensed.horizontalAdvance("############"); - int time_label_width = fm_condensed.horizontalAdvance("+12.345678999ms"); - int duty_label_width = fm_condensed.horizontalAdvance("+100.00% / +100.00%"); + _width_time_label->setFont(font); + _width_samples_label->setFont(font); + _period_time_label->setFont(font); + _period_samples_label->setFont(font); + _freq_label->setFont(font); + _duty_label->setFont(font); + int samples_label_width = fm.horizontalAdvance("############"); + int time_label_width = fm.horizontalAdvance("+12.345678999ms"); + int duty_label_width = fm.horizontalAdvance("+100.00% / +100.00%"); _width_time_label->setMinimumWidth(time_label_width); _width_time_label->setAlignment(Qt::AlignRight); _width_samples_label->setMinimumWidth(samples_label_width); @@ -1009,13 +1005,13 @@ void MeasureDock::adjust_form_size(QWidget *wid) auto groups = wid->findChildren(); for(auto o : groups) - { - o->setFixedWidth(max_label_width + 10); + { + o->setMinimumWidth(max_label_width + 10); } QWidget *pannel = dynamic_cast(mainGroup->parent()); - if (pannel != NULL){ - pannel->setFixedWidth(max_label_width + 20); + if (pannel != NULL){ + pannel->setMinimumWidth(max_label_width + 20); } } diff --git a/DSView/pv/dock/measuredock.h b/DSView/pv/dock/measuredock.h index 8bad194e5..65b76da25 100644 --- a/DSView/pv/dock/measuredock.h +++ b/DSView/pv/dock/measuredock.h @@ -178,8 +178,6 @@ public slots: QLabel *_f_label; QLabel *_d_label; bool _bSetting; - - QFont _condensed_font; }; } // namespace dock diff --git a/DSView/pv/dock/triggerdock.cpp b/DSView/pv/dock/triggerdock.cpp index 895060734..afa98b22b 100644 --- a/DSView/pv/dock/triggerdock.cpp +++ b/DSView/pv/dock/triggerdock.cpp @@ -64,7 +64,8 @@ TriggerDock::TriggerDock(QWidget *parent, SigSession *session) : QScrollArea(parent), _session(session) { - + this->setWidgetResizable(true); + _cur_ch_num = 16; if (_session->get_device()->have_instance()) { _session->get_device()->get_config_int16(SR_CONF_TOTAL_CH_NUM, _cur_ch_num); diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index 1551b70a9..5df019a36 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -371,7 +371,8 @@ void View::vzoom(double steps) double View::get_trace_font_scale() { - if (_device_agent->have_instance()) + // Vertical scaling only applies to the logic trace window (see vzoom()). + if (_device_agent->have_instance() && _device_agent->get_work_mode() == LOGIC) return _trace_height_factor; return 1.0; } From f3e952fe17411b3c4745d8eb23c3ddfb3cc6bd5f Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 8 Jul 2026 13:07:32 +0200 Subject: [PATCH 19/34] Improved decoder colors --- .gitignore | 1 + DSView/pv/view/decodetrace.cpp | 51 +++- DSView/pv/view/decodetrace.h | 5 + .../decoders/mcp230xx/__init__.py | 25 ++ libsigrokdecode4DSL/decoders/mcp230xx/pd.py | 143 ++++++++++ .../decoders/tmp112/__init__.py | 25 ++ libsigrokdecode4DSL/decoders/tmp112/pd.py | 257 ++++++++++++++++++ 7 files changed, 506 insertions(+), 1 deletion(-) create mode 100644 libsigrokdecode4DSL/decoders/mcp230xx/__init__.py create mode 100644 libsigrokdecode4DSL/decoders/mcp230xx/pd.py create mode 100644 libsigrokdecode4DSL/decoders/tmp112/__init__.py create mode 100644 libsigrokdecode4DSL/decoders/tmp112/pd.py diff --git a/.gitignore b/.gitignore index 9249a939b..6bc68cf18 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ moc_*.cpp_parameters DSView-prj build/* build.dir* +.cache/* share .vscode qtpro diff --git a/DSView/pv/view/decodetrace.cpp b/DSView/pv/view/decodetrace.cpp index abddb49da..e9e48785a 100644 --- a/DSView/pv/view/decodetrace.cpp +++ b/DSView/pv/view/decodetrace.cpp @@ -111,6 +111,54 @@ const QColor DecodeTrace::OutlineColours[16] = { QColor(0x6B, 0x23, 0x37) }; +// Official Catppuccin Frappe accent colours +const QColor DecodeTrace::CatppuccinFrappeColours[14] = { + QColor(0xe7, 0x82, 0x84), // Red + QColor(0xea, 0x99, 0x9c), // Maroon + QColor(0xef, 0x9f, 0x76), // Peach + QColor(0xe5, 0xc8, 0x90), // Yellow + QColor(0xa6, 0xd1, 0x89), // Green + QColor(0x81, 0xc8, 0xbe), // Teal + QColor(0x99, 0xd1, 0xdb), // Sky + QColor(0x85, 0xc1, 0xdc), // Sapphire + QColor(0x8c, 0xaa, 0xee), // Blue + QColor(0xba, 0xbb, 0xf1), // Lavender + QColor(0xca, 0x9e, 0xe6), // Mauve + QColor(0xf4, 0xb8, 0xe4), // Pink + QColor(0xee, 0xbe, 0xbe), // Flamingo + QColor(0xf2, 0xd5, 0xcf) // Rosewater +}; + +// Official Catppuccin Latte accent colours +const QColor DecodeTrace::CatppuccinLatteColours[14] = { + QColor(0xd2, 0x0f, 0x39), // Red + QColor(0xe6, 0x45, 0x53), // Maroon + QColor(0xfe, 0x64, 0x0b), // Peach + QColor(0xdf, 0x8e, 0x1d), // Yellow + QColor(0x40, 0xa0, 0x2b), // Green + QColor(0x17, 0x92, 0x99), // Teal + QColor(0x04, 0xa5, 0xe5), // Sky + QColor(0x20, 0x9f, 0xb5), // Sapphire + QColor(0x1e, 0x66, 0xf5), // Blue + QColor(0x72, 0x87, 0xfd), // Lavender + QColor(0x88, 0x39, 0xef), // Mauve + QColor(0xea, 0x76, 0xcb), // Pink + QColor(0xdd, 0x78, 0x78), // Flamingo + QColor(0xdc, 0x8a, 0x78) // Rosewater +}; + +QColor DecodeTrace::get_row_base_colour(int local_row) +{ + const QString &style = AppConfig::Instance().frameOptions.style; + + if (style == THEME_STYLE_FRAPPE) + return CatppuccinFrappeColours[local_row % countof(CatppuccinFrappeColours)]; + else if (style == THEME_STYLE_LATTE) + return CatppuccinLatteColours[local_row % countof(CatppuccinLatteColours)]; + + return Colours[local_row % countof(Colours)]; +} + DecodeTrace::DecodeTrace(pv::SigSession *session, pv::data::DecoderStack *decoder_stack, int index) : @@ -365,7 +413,8 @@ void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a, const double end = min(a.end_sample() / samples_per_pixel - pixels_offset, (double)right); - const QColor &fill_base = _colour.isValid() ? _colour : fore; + const QColor fill_base = _colour.isValid() ? _colour : + get_row_base_colour(local_row); QColor fill, outline; generate_annotation_colours(fill_base, local_row, a, &fill, &outline); const QColor &text_color = get_text_colour(fill); diff --git a/DSView/pv/view/decodetrace.h b/DSView/pv/view/decodetrace.h index c521a8f64..2ec549256 100644 --- a/DSView/pv/view/decodetrace.h +++ b/DSView/pv/view/decodetrace.h @@ -79,6 +79,9 @@ class DecodeTrace : public Trace static const QColor Colours[16]; static const QColor OutlineColours[16]; + static const QColor CatppuccinFrappeColours[14]; + static const QColor CatppuccinLatteColours[14]; + static const int ControlRectWidth = 5; static const int MaxAnnType = 100; @@ -172,6 +175,8 @@ class DecodeTrace : public Trace void generate_annotation_colours(QColor baseColour, int local_row, const pv::data::decode::Annotation& a, QColor *fill, QColor *outline); + static QColor get_row_base_colour(int local_row); + signals: void decoded_progress(int progress); diff --git a/libsigrokdecode4DSL/decoders/mcp230xx/__init__.py b/libsigrokdecode4DSL/decoders/mcp230xx/__init__.py new file mode 100644 index 000000000..cee4c79e3 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mcp230xx/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Benedikt Otto +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the Microchip +8-bit MCP23008 and 16-bit MCP23017 I²C output expander protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/mcp230xx/pd.py b/libsigrokdecode4DSL/decoders/mcp230xx/pd.py new file mode 100644 index 000000000..58a1bce6d --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mcp230xx/pd.py @@ -0,0 +1,143 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Benedikt Otto +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +STATE_IDLE, STATE_ADDR, STATE_DATA, STATE_READ_ADDR, STATE_READ_DATA, STATE_STOP = range(6) +UNKNOWN, READ, WRITE = range(3) + +registers = ["IODIR", "IPOL", "GPINTEN", "DEFVAL", "INTCON", "IOCON", "GPPU", "INTF", "INTCAP", "GPIO", "OLAT"] +registers_mcp23017_bank0 = {i: (registers[i // 2] + "AB"[i % 2] if registers[i // 2] != "IOCON" else "IOCON") for i in range(22)} + +registers_mcp23017_bank1 = {(i + 5 if i > 11 else i): (registers[i % 11] + "AB"[i // 11] if registers[i % 11] != "IOCON" else "IOCON") for i in range(22)} + +registers_mcp23008 = {i: registers[i] for i in range(11)} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'mcp230xx' + name = 'MCP230XX' + longname = 'Microchip MCP230XX' + desc = 'MCP230XX 8/16-bit I²C output expanders.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['IC'] + + options = ( + {'id': 'type', 'desc': 'Type', 'default': 'MCP23017', + 'values': ('MCP23008', 'MCP23017')}, + ) + + annotations = ( + ('register_read', 'Register read'), + ('register_write', 'Register write'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('regs', 'Registers', (0, 1)), + ('warnings', 'Warnings', (2,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = STATE_IDLE + self.iocon = 0 + self.iocon_set = False + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def get_registers(self): + if self.options["type"] == "MCP23008": + return registers_mcp23008 + else: + return registers_mcp23017_bank1 if self.iocon & (1 << 7) else registers_mcp23017_bank0 + + def putx(self, ss, es, data): + self.put(ss, es, self.out_ann, data) + + def checkAddress(self, ss, es, address): + if not address in range(0x20, 0x27 + 1): + self.putx(ss, es, [2, ['Address %02X not MCP230XX compatible' % address]]) + + def handleRead(self, register, data): + if len(data) >= 1: + register = register[0] + for d in data: + registers = self.get_registers() + if not register in registers: + self.putx(d[1], d[2], [2, ['Error: Register %d not accessible' %register]]) + if not self.iocon_set: + self.iocon = d[0] + else: + register_name = registers[register] + if register_name == "IOCON": + self.iocon = d[0] + self.iocon_set = True + self.putx(d[1], d[2], [0, ["Read %s: %02X" % (register_name, d[0]), "R%02X" % d[0]]]) + register += 1 + + def handleWrite(self, data): + if len(data) >= 2: + register = data[0][0] + for d in data[1:]: + registers = self.get_registers() + if not register in registers: + self.putx(d[1], d[2], [2, ['Error: Register %d not accessible' %register]]) + if not self.iocon_set: + self.iocon = d[0] + else: + register_name = registers[register] + if register_name == "IOCON": + self.iocon = d[0] + self.iocon_set = True + self.putx(d[1], d[2], [1, ["Write %s: %02X" % (register_name, d[0]), "W%02X" % d[0]]]) + register += 1 + + def decode(self, ss, es, data): + cmd, databyte = data + if cmd in ('ACK', 'NACK', 'BITS'): # Discard 'ACK' and 'BITS'. + return + if self.state == STATE_IDLE and cmd == 'START': + self.state = STATE_ADDR + self.dataWrite = [] + self.dataRead = [] + elif self.state == STATE_ADDR and cmd == 'ADDRESS WRITE': + self.state = STATE_DATA + self.checkAddress(ss, es, databyte) + elif self.state in [STATE_DATA, STATE_STOP] and cmd == 'DATA WRITE': + self.state = STATE_STOP + self.dataWrite.append((databyte, ss, es)) + elif self.state == STATE_STOP and cmd == "START REPEAT": + self.state = STATE_READ_ADDR + elif self.state == STATE_READ_ADDR and cmd == "ADDRESS READ": + self.state = STATE_READ_DATA + self.checkAddress(ss, es, databyte) + elif self.state in [STATE_READ_DATA, STATE_STOP] and cmd == 'DATA READ': + self.state = STATE_STOP + self.dataRead.append((databyte, ss, es)) + elif self.state == STATE_STOP and cmd == 'STOP': + self.state = STATE_IDLE + if len(self.dataRead) > 0 and len(self.dataWrite) == 1: + self.handleRead(self.dataWrite[0], self.dataRead) + elif len(self.dataWrite) > 0 and self.dataRead == []: + self.handleWrite(self.dataWrite) diff --git a/libsigrokdecode4DSL/decoders/tmp112/__init__.py b/libsigrokdecode4DSL/decoders/tmp112/__init__.py new file mode 100644 index 000000000..830c10431 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/tmp112/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2026 Patrick Felixberger +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the Texas +Instruments TMP112 digital temperature sensor protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/tmp112/pd.py b/libsigrokdecode4DSL/decoders/tmp112/pd.py new file mode 100644 index 000000000..1d73c3a78 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/tmp112/pd.py @@ -0,0 +1,257 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2026 Patrick Felixberger +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# Texas Instruments TMP112 digital temperature sensor (datasheet SBOS344H). +# +# TMP112 uses a pointer register scheme: a WRITE transaction's first data +# byte selects one of four 16-bit registers (Temperature, Configuration, +# T_LOW, T_HIGH); any following data bytes in that same WRITE transaction +# are the MSB/LSB of the selected register's new value. A READ transaction +# has no pointer byte of its own -- it returns the 16-bit value of whatever +# register the pointer is currently set to (last one selected by a WRITE, +# or the Temperature register at power-up). + +import sigrokdecode as srd + +REG_TEMPERATURE = 0x00 +REG_CONFIG = 0x01 +REG_TLOW = 0x02 +REG_THIGH = 0x03 + +reg_name = { + REG_TEMPERATURE: 'Temperature', + REG_CONFIG: 'Configuration', + REG_TLOW: 'T_LOW', + REG_THIGH: 'T_HIGH', +} + +fault_queue = { + 0b00: 1, + 0b01: 2, + 0b10: 4, + 0b11: 6, +} + +conversion_rate = { + 0b00: '0.25 Hz', + 0b01: '1 Hz', + 0b10: '4 Hz', + 0b11: '8 Hz', +} + + +def twos_complement(val, bits): + if val & (1 << (bits - 1)): + val -= (1 << bits) + return val + + +class Decoder(srd.Decoder): + api_version = 3 + id = 'tmp112' + name = 'TMP112' + longname = 'Texas Instruments TMP112' + desc = 'Digital I2C temperature sensor with pointer-register addressing.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['Sensor', 'IC'] + annotations = ( + ('temperature', 'Temperature'), + ('temperature-verbose', 'Temperature (verbose)'), + ('config', 'Configuration (verbose)'), + ('config-short', 'Configuration'), + ('reg-select', 'Register select'), + ('warnings', 'Warnings'), + ) + annotation_rows = ( + ('temperature', 'Temperature', (0, 1)), + ('config', 'Configuration', (2, 3)), + ('reg-select', 'Register select', (4,)), + ('warnings', 'Warnings', (5,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.reg = REG_TEMPERATURE # Pointer register defaults to 0x00 at power-up. + self.extended_mode = False # EM bit shadow, tracked from Config register traffic. + self.rw = None + self.wbytes = [] + self.rbytes = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, ss, es, data): + self.put(ss, es, self.out_ann, data) + + def putb(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def warn_upon_invalid_slave(self, ss, es, addr): + # ADD0 pin selects the 7-bit address: GND=0x48, V+=0x49, SDA=0x4A, SCL=0x4B. + # The upstream i2c decoder's 'address_format' option controls whether + # ADDRESS WRITE/READ deliver the bare 7-bit address (shifted) or the + # full byte with the R/W bit still packed in at bit 0 (unshifted, + # the default) -- accept either encoding here. + shifted = addr in range(0x48, 0x4B + 1) + unshifted = addr in range(0x48 << 1, (0x4B << 1) + 2) + if not (shifted or unshifted): + s = 'Warning: I2C slave 0x%02x not a TMP112 compatible address.' + self.putx(ss, es, [5, [s % addr]]) + + def decode_temperature_value(self, raw16): + if self.extended_mode: + value = twos_complement(raw16 >> 3, 13) + else: + value = twos_complement(raw16 >> 4, 12) + return value * 0.0625 + + def output_temperature_reg(self, label, raw16): + celsius = self.decode_temperature_value(raw16) + fahrenheit = celsius * 9.0 / 5.0 + 32.0 + self.putb([0, ['%s: %.4f degC' % (label, celsius), + '%.4f degC' % celsius]]) + self.putb([1, ['%s: %.4f degC (%.4f degF, raw=0x%04x)' % + (label, celsius, fahrenheit, raw16)]]) + + def output_config_reg(self, raw16, from_write): + os_bit = (raw16 >> 15) & 1 + fq = (raw16 >> 11) & 0b11 + pol = (raw16 >> 10) & 1 + tm = (raw16 >> 9) & 1 + sd = (raw16 >> 8) & 1 + cr = (raw16 >> 6) & 0b11 + al = (raw16 >> 5) & 1 + em = raw16 & 1 + + self.extended_mode = bool(em) + + s = 'OS = %d: %s\n' % (os_bit, 'one-shot armed/converting' if os_bit else 'idle') + s += 'Fault queue: %d consecutive fault(s)\n' % fault_queue[fq] + s += 'ALERT polarity: active-%s\n' % ('high' if pol else 'low') + s += 'Thermostat mode: %s\n' % ('interrupt' if tm else 'comparator') + s += 'SD = %d: %s\n' % (sd, 'shutdown' if sd else 'continuous conversion') + s += 'Conversion rate: %s\n' % conversion_rate[cr] + s += 'AL (alert, read-only) = %d\n' % al + s += 'EM = %d: %s' % (em, 'extended 13-bit mode' if em else 'normal 12-bit mode') + + s2 = 'SD=%s, CR=%s, FQ=%d, POL=%s, TM=%s, EM=%s' % ( + 'shutdown' if sd else 'continuous', + conversion_rate[cr], fault_queue[fq], + 'high' if pol else 'low', + 'interrupt' if tm else 'comparator', + '13bit' if em else '12bit') + + self.putb([2, [s]]) + self.putb([3, [s2]]) + + def output_register_write(self, reg, raw16): + if reg == REG_TEMPERATURE: + self.putb([5, ['Warning: Temperature register is read-only!']]) + self.output_temperature_reg('Temperature (invalid write)', raw16) + elif reg == REG_CONFIG: + self.output_config_reg(raw16, True) + elif reg == REG_TLOW: + self.output_temperature_reg('T_LOW', raw16) + elif reg == REG_THIGH: + self.output_temperature_reg('T_HIGH', raw16) + + def output_register_read(self, reg, raw16): + if reg == REG_CONFIG: + self.output_config_reg(raw16, False) + else: + self.output_temperature_reg(reg_name.get(reg, 'Register 0x%02x' % reg), raw16) + + def decode(self, ss, es, data): + cmd, databyte = data + self.ss, self.es = ss, es + + if self.state == 'IDLE': + if cmd != 'START': + return + self.state = 'ADDR' + + elif self.state == 'ADDR': + if cmd == 'ADDRESS WRITE': + self.warn_upon_invalid_slave(ss, es, databyte) + self.rw = 'WRITE' + self.wbytes = [] + self.state = 'WRITE POINTER' + elif cmd == 'ADDRESS READ': + self.warn_upon_invalid_slave(ss, es, databyte) + self.rw = 'READ' + self.rbytes = [] + self.ss_block = None + self.state = 'READ DATA' + + elif self.state == 'WRITE POINTER': + if cmd == 'DATA WRITE': + self.reg = databyte & 0x03 + self.putx(ss, es, [4, ['Select register: %s (0x%02x)' % + (reg_name.get(self.reg, '?'), self.reg)]]) + self.ss_block = None + self.state = 'WRITE DATA' + elif cmd in ('START REPEAT', 'STOP'): + self.state = 'ADDR' if cmd == 'START REPEAT' else 'IDLE' + + elif self.state == 'WRITE DATA': + if cmd == 'DATA WRITE': + if self.ss_block is None: + self.ss_block = ss + self.wbytes.append(databyte) + self.es_block = es + if len(self.wbytes) == 2: + raw16 = (self.wbytes[0] << 8) | self.wbytes[1] + self.output_register_write(self.reg, raw16) + self.wbytes = [] + elif cmd == 'START REPEAT': + if len(self.wbytes) == 1: + self.putb([5, ['Warning: incomplete register write (1 byte)']]) + self.wbytes = [] + self.state = 'ADDR' + elif cmd == 'STOP': + if len(self.wbytes) == 1: + self.putb([5, ['Warning: incomplete register write (1 byte)']]) + self.wbytes = [] + self.state = 'IDLE' + + elif self.state == 'READ DATA': + if cmd == 'DATA READ': + if self.ss_block is None: + self.ss_block = ss + self.rbytes.append(databyte) + self.es_block = es + if len(self.rbytes) == 2: + raw16 = (self.rbytes[0] << 8) | self.rbytes[1] + self.output_register_read(self.reg, raw16) + self.rbytes = [] + elif cmd == 'START REPEAT': + if len(self.rbytes) == 1: + self.putb([5, ['Warning: incomplete register read (1 byte)']]) + self.rbytes = [] + self.state = 'ADDR' + elif cmd == 'STOP': + if len(self.rbytes) == 1: + self.putb([5, ['Warning: incomplete register read (1 byte)']]) + self.rbytes = [] + self.state = 'IDLE' From 4ad6419a9208bc250e9e69d4bc040f9c154e42a3 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 8 Jul 2026 14:01:32 +0200 Subject: [PATCH 20/34] Added dso demo device; improve text box width --- DSView/pv/prop/enum.cpp | 10 +++- DSView/pv/toolbars/samplingbar.cpp | 2 +- DSView/pv/toolbars/samplingbar.h | 2 +- libsigrok4DSL/hardware/demo/demo.c | 87 ++++++++++++++++++++---------- libsigrok4DSL/hardware/demo/demo.h | 21 +++++++- 5 files changed, 91 insertions(+), 31 deletions(-) diff --git a/DSView/pv/prop/enum.cpp b/DSView/pv/prop/enum.cpp index 78e2a6f75..1b8cb5695 100644 --- a/DSView/pv/prop/enum.cpp +++ b/DSView/pv/prop/enum.cpp @@ -70,16 +70,24 @@ QWidget* Enum::get_widget(QWidget *parent, bool auto_commit) _selector = new DsComboBox(parent); + int max_text_width = 0; + for (unsigned int i = 0; i < _values.size(); i++) { const pair &v = _values[i]; _selector->addItem(v.second, QVariant::fromValue((void*)v.first)); - + max_text_width = qMax(max_text_width, + _selector->fontMetrics().boundingRect(v.second).width()); + if (value && g_variant_compare(v.first, value) == 0) _selector->setCurrentIndex(i); } g_variant_unref(value); + _selector->setMinimumWidth(max_text_width + 40); + _selector->view()->setMinimumWidth(max_text_width + 40); + _selector->view()->setTextElideMode(Qt::ElideNone); + if (auto_commit) { connect(_selector, SIGNAL(currentIndexChanged(int)), this, SLOT(on_current_item_changed(int))); diff --git a/DSView/pv/toolbars/samplingbar.cpp b/DSView/pv/toolbars/samplingbar.cpp index 838af4fb1..73bc61d1f 100644 --- a/DSView/pv/toolbars/samplingbar.cpp +++ b/DSView/pv/toolbars/samplingbar.cpp @@ -1169,7 +1169,7 @@ namespace pv } _last_device_index = select_index; - const int width = max_text_width + 20; + const int width = max_text_width + 30; const int selector_width = min(width, ComboBoxMaxWidth); const int popup_width = width; diff --git a/DSView/pv/toolbars/samplingbar.h b/DSView/pv/toolbars/samplingbar.h index cc0de0419..4485d2813 100644 --- a/DSView/pv/toolbars/samplingbar.h +++ b/DSView/pv/toolbars/samplingbar.h @@ -65,7 +65,7 @@ namespace pv Q_OBJECT private: - static const int ComboBoxMaxWidth = 320; + static const int ComboBoxMaxWidth = 420; static const int RefreshShort = 500; static const uint64_t LogicMaxSWDepth64 = SR_GB(16); static const uint64_t LogicMaxSWDepth32 = SR_GB(8); diff --git a/libsigrok4DSL/hardware/demo/demo.c b/libsigrok4DSL/hardware/demo/demo.c index 42ebabe92..895e0af27 100644 --- a/libsigrok4DSL/hardware/demo/demo.c +++ b/libsigrok4DSL/hardware/demo/demo.c @@ -474,6 +474,9 @@ static void scan_dsl_file(struct sr_dev_inst *sdi) { struct session_vdev * vdev = sdi->priv; int dex; + int init_mode; + const char *default_file; + uint64_t mode_caps; if (b_load_directory == 0) { @@ -483,14 +486,31 @@ static void scan_dsl_file(struct sr_dev_inst *sdi) b_load_directory = 1; } - dex = get_pattern_mode_index_by_string(LOGIC, DEFAULT_LOGIC_FILE); + /* Pick the device's own default mode, so devices that don't support + * LOGIC mode (e.g. a DSO-only demo profile) don't get forced into it. */ + mode_caps = (vdev->profile != NULL) ? vdev->profile->dev_caps.mode_caps : CAPS_MODE_LOGIC; + + if (mode_caps & CAPS_MODE_LOGIC){ + init_mode = LOGIC; + default_file = DEFAULT_LOGIC_FILE; + } + else if (mode_caps & CAPS_MODE_DSO){ + init_mode = DSO; + default_file = DEFAULT_DSO_FILE; + } + else{ + init_mode = ANALOG; + default_file = DEFAULT_ANALOG_FILE; + } + + dex = get_pattern_mode_index_by_string(init_mode, default_file); if(dex == -1){ dex = PATTERN_RANDOM; } vdev->sample_generator = dex; - sdi->mode = LOGIC; + sdi->mode = init_mode; reset_dsl_path(sdi, dex); } @@ -636,50 +656,63 @@ static GSList *hw_scan(GSList *options) struct sr_dev_inst *sdi; struct session_vdev *vdev; GSList *devices; + int i; + int init_mode; (void)options; devices = NULL; - vdev = g_try_malloc0(sizeof(struct session_vdev)); - if (vdev == NULL) + for (i = 0; supported_Demo[i].vendor != 0; i++) { - sr_err("%s: sdi->priv malloc failed", __func__); - return devices; - } - memset(vdev, 0, sizeof(struct session_vdev)); + vdev = g_try_malloc0(sizeof(struct session_vdev)); + if (vdev == NULL) + { + sr_err("%s: sdi->priv malloc failed", __func__); + continue; + } + memset(vdev, 0, sizeof(struct session_vdev)); - sdi = sr_dev_inst_new(LOGIC, SR_ST_INACTIVE, - supported_Demo[0].vendor, - supported_Demo[0].model, - supported_Demo[0].model_version); - if (!sdi) - { - safe_free(vdev); - sr_err("Device instance creation failed."); - return NULL; - } + init_mode = (supported_Demo[i].dev_caps.mode_caps & CAPS_MODE_LOGIC) ? LOGIC : + (supported_Demo[i].dev_caps.mode_caps & CAPS_MODE_DSO) ? DSO : ANALOG; + + sdi = sr_dev_inst_new(init_mode, SR_ST_INACTIVE, + supported_Demo[i].vendor, + supported_Demo[i].model, + supported_Demo[i].model_version); + if (!sdi) + { + safe_free(vdev); + sr_err("Device instance creation failed."); + continue; + } + + vdev->profile = &supported_Demo[i]; - sdi->priv = vdev; - sdi->driver = di; - sdi->dev_type = DEV_TYPE_DEMO; + sdi->priv = vdev; + sdi->driver = di; + sdi->dev_type = DEV_TYPE_DEMO; - vdev->is_loop = 0; + vdev->is_loop = 0; - devices = g_slist_append(devices, sdi); + devices = g_slist_append(devices, sdi); + } return devices; } static const GSList *hw_dev_mode_list(const struct sr_dev_inst *sdi) { - (void)sdi; - + struct session_vdev *vdev; + const struct DEMO_profile *profile; GSList *l = NULL; unsigned int i; - for (i = 0; i < ARRAY_SIZE(sr_mode_list); i++) + vdev = (sdi != NULL) ? sdi->priv : NULL; + profile = (vdev != NULL && vdev->profile != NULL) ? vdev->profile : &supported_Demo[0]; + + for (i = 0; i < ARRAY_SIZE(sr_mode_list); i++) { - if (supported_Demo[0].dev_caps.mode_caps & (1 << i)){ + if (profile->dev_caps.mode_caps & (1 << i)){ l = g_slist_append(l, (gpointer)&sr_mode_list[i]); } } diff --git a/libsigrok4DSL/hardware/demo/demo.h b/libsigrok4DSL/hardware/demo/demo.h index 4d8753577..6c227399f 100644 --- a/libsigrok4DSL/hardware/demo/demo.h +++ b/libsigrok4DSL/hardware/demo/demo.h @@ -233,6 +233,8 @@ struct session_vdev enum DEMO_LOGIC_CHANNEL_INDEX logic_ch_mode_index; int is_loop; + + const struct DEMO_profile *profile; }; #define SESSION_MAX_CHANNEL_COUNT 512 @@ -425,7 +427,24 @@ static const struct DEMO_profile supported_Demo[] = { 0, vdivs10to2000, 0, - DEMO_LOGIC100x16, + DEMO_LOGIC100x16, + PATTERN_RANDOM, + SR_NS(500)} + }, + + /* + * Demo Oscilloscope + */ + {"DreamSourceLab", "Demo Oscilloscope", NULL, + {CAPS_MODE_DSO, + CAPS_FEATURE_NONE, + (1 << DEMO_DSO200x2), + SR_Kn(20), + SR_Kn(20), + 0, + vdivs10to2000, + 0, + DEMO_DSO200x2, PATTERN_RANDOM, SR_NS(500)} }, From 9baefa4e76ce024c1e4795f3043a4cfa50afc364 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 15 Jul 2026 18:11:40 +0200 Subject: [PATCH 21/34] Added y-scale reset; Added option disable channel lines --- CMakeLists.txt | 246 ++++++++++++------------ DSView/pv/config/appconfig.cpp | 2 + DSView/pv/config/appconfig.h | 1 + DSView/pv/dialogs/applicationpardlg.cpp | 29 ++- DSView/pv/mainwindow.cpp | 3 + DSView/pv/view/logicsignal.cpp | 10 +- DSView/pv/view/view.cpp | 18 +- DSView/pv/view/view.h | 14 +- DSView/pv/view/viewport.cpp | 139 ++++++++++++- DSView/pv/view/viewport.h | 15 ++ README.md | 6 +- libsigrok4DSL/hardware/demo/demo.h | 25 +-- 12 files changed, 348 insertions(+), 160 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d6838d42b..36e4c34c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,8 +36,8 @@ set(DS_VERSION_MICRO 0) set(DS_VERSION_STRING ${DS_VERSION_MAJOR}.${DS_VERSION_MINOR}.${DS_VERSION_MICRO}) configure_file ( - ${PROJECT_SOURCE_DIR}/DSView/config.h.in - ${PROJECT_BINARY_DIR}/DSView/config.h + ${PROJECT_SOURCE_DIR}/DSView/config.h.in + ${PROJECT_BINARY_DIR}/DSView/config.h ) #=============================================================================== @@ -52,7 +52,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake") find_package(PkgConfig) if(NOT PKG_CONFIG_FOUND) - message(FATAL_ERROR "Please install pkg-config!") + message(FATAL_ERROR "Please install pkg-config!") endif() #=============================================================================== @@ -65,8 +65,8 @@ set(ENABLE_COTIRE FALSE) #Enable cotire set(ENABLE_TESTS FALSE) #Enable unit tests if(WIN32) - # Windows does not support UNIX signals. - set(ENABLE_SIGNALS FALSE) + # Windows does not support UNIX signals. + set(ENABLE_SIGNALS FALSE) endif() if(NOT CMAKE_BUILD_TYPE) @@ -91,7 +91,7 @@ include_directories( pkg_search_module(GLIB glib-2.0) if(NOT GLIB_FOUND) - message(FATAL_ERROR "Please install glib!") + message(FATAL_ERROR "Please install glib!") endif() message("----- glib-2.0:") @@ -103,7 +103,6 @@ link_directories(${GLIB_LIBDIR}) #=============================================================================== #= python3 #------------------------------------------------------------------------------- - find_package(Python3 COMPONENTS Development QUIET) if (Python3_FOUND) @@ -142,7 +141,7 @@ endif() find_package(FFTW) if(NOT FFTW_FOUND) - message(FATAL_ERROR "Please install lib fftw!") + message(FATAL_ERROR "Please install lib fftw!") endif() message("----- FFTW:") @@ -156,7 +155,7 @@ include_directories(${FFTW_INCLUDE_DIRS}) find_package(libusb-1.0) if(NOT LIBUSB_1_FOUND) - message(FATAL_ERROR "Please install libusb!") + message(FATAL_ERROR "Please install libusb!") endif() message("----- libusb-1.0:") @@ -170,7 +169,7 @@ include_directories(${LIBUSB_1_INCLUDE_DIRS}) find_package(ZLIB QUIET) if(NOT ZLIB_FOUND) - message(FATAL_ERROR "Please install zlib!") + message(FATAL_ERROR "Please install zlib!") endif() message("----- zlib:") @@ -184,43 +183,42 @@ include_directories(${ZLIB_INCLUDE_DIRS}) # Prioritize Qt6 for better font rendering on OSX, better Wayland compatibility # on Linux, and to stay on the actively maintained Qt release on Windows. #------------------------------------------------------------------------------- - find_package(Qt6Core QUIET) if(Qt6Core_FOUND) - message("----- Qt6:") - message(STATUS " includes:" ${Qt6Core_INCLUDE_DIRS}) - find_package(Qt6Widgets REQUIRED) - find_package(Qt6Gui REQUIRED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt6Widgets_EXECUTABLE_COMPILE_FLAGS}") - set(QT_INCLUDE_DIRS ${Qt6Gui_INCLUDE_DIRS} ${Qt6Widgets_INCLUDE_DIRS}) - set(QT_LIBRARIES Qt6::Gui Qt6::Widgets) - if(WIN32) - find_package(Qt6Core5Compat REQUIRED) - add_definitions(${Qt6Core5Compat_DEFINITIONS}) - list(APPEND QT_LIBRARIES Qt6::Core5Compat) - list(APPEND QT_INCLUDE_DIRS ${Qt6Core5Compat_INCLUDE_DIRS}) - endif() - add_definitions(${Qt6Gui_DEFINITIONS} ${Qt6Widgets_DEFINITIONS}) + message("----- Qt6:") + message(STATUS " includes:" ${Qt6Core_INCLUDE_DIRS}) + find_package(Qt6Widgets REQUIRED) + find_package(Qt6Gui REQUIRED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt6Widgets_EXECUTABLE_COMPILE_FLAGS}") + set(QT_INCLUDE_DIRS ${Qt6Gui_INCLUDE_DIRS} ${Qt6Widgets_INCLUDE_DIRS}) + set(QT_LIBRARIES Qt6::Gui Qt6::Widgets) + if(WIN32) + find_package(Qt6Core5Compat REQUIRED) + add_definitions(${Qt6Core5Compat_DEFINITIONS}) + list(APPEND QT_LIBRARIES Qt6::Core5Compat) + list(APPEND QT_INCLUDE_DIRS ${Qt6Core5Compat_INCLUDE_DIRS}) + endif() + add_definitions(${Qt6Gui_DEFINITIONS} ${Qt6Widgets_DEFINITIONS}) else() - find_package(Qt5Core QUIET) + find_package(Qt5Core QUIET) endif() if(Qt5Core_FOUND) - message("----- Qt5:") - message(STATUS " includes:" ${Qt5Core_INCLUDE_DIRS}) - find_package(Qt5Widgets REQUIRED) - find_package(Qt5Gui REQUIRED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") - set(QT_INCLUDE_DIRS ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) - set(QT_LIBRARIES Qt5::Gui Qt5::Widgets) - add_definitions(${Qt5Gui_DEFINITIONS} ${Qt5Widgets_DEFINITIONS}) + message("----- Qt5:") + message(STATUS " includes:" ${Qt5Core_INCLUDE_DIRS}) + find_package(Qt5Widgets REQUIRED) + find_package(Qt5Gui REQUIRED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") + set(QT_INCLUDE_DIRS ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) + set(QT_LIBRARIES Qt5::Gui Qt5::Widgets) + add_definitions(${Qt5Gui_DEFINITIONS} ${Qt5Widgets_DEFINITIONS}) endif() if(NOT Qt5Core_FOUND AND NOT Qt6Core_FOUND) - message("Error!The qt5 or qt6 can't find, if them has been installed, please append the install path to CMAKE_PREFIX_PATH, the command as:") - message("list(APPEND CMAKE_PREFIX_PATH \"xxx install path\")" ) - message(FATAL_ERROR "Can't find Qt5 or Qt6!") + message("Error!The qt5 or qt6 can't find, if them has been installed, please append the install path to CMAKE_PREFIX_PATH, the command as:") + message("list(APPEND CMAKE_PREFIX_PATH \"xxx install path\")" ) + message(FATAL_ERROR "Can't find Qt5 or Qt6!") endif() #=============================================================================== @@ -229,7 +227,7 @@ endif() find_package(Boost 1.42 QUIET) if(NOT Boost_FOUND) - message(FATAL_ERROR "Please install boost!") + message(FATAL_ERROR "Please install boost!") endif() message("----- boost:") @@ -239,7 +237,6 @@ include_directories(${Boost_INCLUDE_DIRS}) #=============================================================================== #= Dependencies #------------------------------------------------------------------------------- - find_package(Threads) #=============================================================================== @@ -561,10 +558,10 @@ set(DSView_RESOURCES if(WIN32) - # Use the DSView icon for the DSView.exe executable. - set(CMAKE_RC_COMPILE_OBJECT "${CMAKE_RC_COMPILER} -O coff -I${CMAKE_CURRENT_SOURCE_DIR} ") - enable_language(RC) - # app icon + # Use the DSView icon for the DSView.exe executable. + set(CMAKE_RC_COMPILE_OBJECT "${CMAKE_RC_COMPILER} -O coff -I${CMAKE_CURRENT_SOURCE_DIR} ") + enable_language(RC) + # app icon list(APPEND DSView_SOURCES applogo.rc) list(APPEND DSView_SOURCES DSView/pv/winnativewidget.cpp @@ -577,15 +574,15 @@ if(WIN32) endif() if(Qt5Core_FOUND) - qt5_wrap_cpp(DSView_HEADERS_MOC ${DSView_HEADERS}) - qt5_wrap_ui(DSView_FORMS_HEADERS ${DSView_FORMS}) - qt5_add_resources(DSView_RESOURCES_RCC ${DSView_RESOURCES}) + qt5_wrap_cpp(DSView_HEADERS_MOC ${DSView_HEADERS}) + qt5_wrap_ui(DSView_FORMS_HEADERS ${DSView_FORMS}) + qt5_add_resources(DSView_RESOURCES_RCC ${DSView_RESOURCES}) endif() if(Qt6Core_FOUND) - qt6_wrap_cpp(DSView_HEADERS_MOC ${DSView_HEADERS}) - qt6_wrap_ui(DSView_FORMS_HEADERS ${DSView_FORMS}) - qt6_add_resources(DSView_RESOURCES_RCC ${DSView_RESOURCES}) + qt6_wrap_cpp(DSView_HEADERS_MOC ${DSView_HEADERS}) + qt6_wrap_ui(DSView_FORMS_HEADERS ${DSView_FORMS}) + qt6_add_resources(DSView_RESOURCES_RCC ${DSView_RESOURCES}) endif() @@ -601,10 +598,10 @@ set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") include_directories( - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR} - ${Boost_INCLUDE_DIRS} - ${QT_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${Boost_INCLUDE_DIRS} + ${QT_INCLUDE_DIRS} ) #=============================================================================== @@ -620,30 +617,29 @@ add_compile_options(-O3) #= Linker Configuration #------------------------------------------------------------------------------- - set(DSVIEW_LINK_LIBS - -lz - -lglib-2.0 - ${CMAKE_THREAD_LIBS_INIT} - ${QT_LIBRARIES} - ${LIBUSB_1_LIBRARIES} - ${FFTW_LIBRARIES} - ${PY_LIB} + -lz + -lglib-2.0 + ${CMAKE_THREAD_LIBS_INIT} + ${QT_LIBRARIES} + ${LIBUSB_1_LIBRARIES} + ${FFTW_LIBRARIES} + ${PY_LIB} ) if(WIN32) - # Workaround for a MinGW linking issue. - list(APPEND PULSEVIEW_LINK_LIBS "-llzma -llcms2") + # Workaround for a MinGW linking issue. + list(APPEND PULSEVIEW_LINK_LIBS "-llzma -llcms2") endif() add_executable(${PROJECT_NAME} - ${common_SOURCES} - ${DSView_SOURCES} - ${DSView_HEADERS_MOC} - ${DSView_FORMS_HEADERS} - ${DSView_RESOURCES_RCC} - ${libsigrok4DSL_SOURCES} - ${libsigrokdecode4DSL_SOURCES} + ${common_SOURCES} + ${DSView_SOURCES} + ${DSView_HEADERS_MOC} + ${DSView_FORMS_HEADERS} + ${DSView_RESOURCES_RCC} + ${libsigrok4DSL_SOURCES} + ${libsigrokdecode4DSL_SOURCES} ) target_link_libraries(${PROJECT_NAME} ${DSVIEW_LINK_LIBS}) @@ -654,8 +650,8 @@ set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-mwindows") endif() if(ENABLE_COTIRE) - include(cotire) - cotire(${PROJECT_NAME}) + include(cotire) + cotire(${PROJECT_NAME}) endif() message(STATUS "Output dir: ${CMAKE_CURRENT_SOURCE_DIR}/build.dir") @@ -666,56 +662,56 @@ set(EXECUTABLE_OUTPUT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build.dir") #------------------------------------------------------------------------------- if(WIN32) - # Settings for NSIS to create a Windows installer - set(CPACK_GENERATOR "NSIS") - set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") - set(CPACK_PACKAGE_EXECUTABLES DSView DSView) - set(CPACK_CREATE_DESKTOP_LINKS DSView) - set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/logo-win.ico") - set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/logo-win.ico") - set(CPACK_PACKAGE_VENDOR "DreamSourceLab") - set(CPACK_NSIS_INSTALLED_ICON_NAME "DSView.exe") - install(TARGETS ${PROJECT_NAME} DESTINATION .) - install(DIRECTORY DSView/res DESTINATION .) - install(DIRECTORY DSView/demo DESTINATION .) - install(DIRECTORY lang DESTINATION .) - install(DIRECTORY libsigrokdecode4DSL/decoders - DESTINATION . - PATTERN "__pycache__" EXCLUDE) - if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cross-compile-windows/python-setup/python-dist) - install(DIRECTORY cross-compile-windows/python-setup/python-dist/. DESTINATION .) - endif() + # Settings for NSIS to create a Windows installer + set(CPACK_GENERATOR "NSIS") + set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") + set(CPACK_PACKAGE_EXECUTABLES DSView DSView) + set(CPACK_CREATE_DESKTOP_LINKS DSView) + set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/logo-win.ico") + set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/logo-win.ico") + set(CPACK_PACKAGE_VENDOR "DreamSourceLab") + set(CPACK_NSIS_INSTALLED_ICON_NAME "DSView.exe") + install(TARGETS ${PROJECT_NAME} DESTINATION .) + install(DIRECTORY DSView/res DESTINATION .) + install(DIRECTORY DSView/demo DESTINATION .) + install(DIRECTORY lang DESTINATION .) + install(DIRECTORY libsigrokdecode4DSL/decoders + DESTINATION . + PATTERN "__pycache__" EXCLUDE) + if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cross-compile-windows/python-setup/python-dist) + install(DIRECTORY cross-compile-windows/python-setup/python-dist/. DESTINATION .) + endif() else() - # Install the executable. - install(TARGETS ${PROJECT_NAME} DESTINATION bin) - install(DIRECTORY DSView/res DESTINATION share/DSView) - install(DIRECTORY DSView/demo DESTINATION share/DSView) - install(FILES DSView/icons/logo.svg DESTINATION share/DSView RENAME logo.svg) - install(FILES DSView/icons/logo.svg DESTINATION share/icons/hicolor/scalable/apps RENAME dsview.svg) - install(FILES DSView/icons/logo.svg DESTINATION share/pixmaps RENAME dsview.svg) - - if(CMAKE_SYSTEM_NAME MATCHES "Linux") - install(FILES DSView/DSView.desktop DESTINATION /usr/share/applications RENAME dsview.desktop) - - add_compile_definitions(_DEFAULT_SOURCE) - - if(IS_DIRECTORY /usr/lib/udev/rules.d) - install(FILES DSView/DreamSourceLab.rules DESTINATION /usr/lib/udev/rules.d RENAME 60-dreamsourcelab.rules) - elseif(IS_DIRECTORY /lib/udev/rules.d) - install(FILES DSView/DreamSourceLab.rules DESTINATION /lib/udev/rules.d RENAME 60-dreamsourcelab.rules) - elseif(IS_DIRECTORY /etc/udev/rules.d) - install(FILES DSView/DreamSourceLab.rules DESTINATION /etc/udev/rules.d RENAME 60-dreamsourcelab.rules) - endif() - - endif() - - install(FILES NEWS25 DESTINATION share/DSView RENAME NEWS25) - install(FILES NEWS31 DESTINATION share/DSView RENAME NEWS31) - install(FILES ug25.pdf DESTINATION share/DSView RENAME ug25.pdf) - install(FILES ug31.pdf DESTINATION share/DSView RENAME ug31.pdf) - - install(DIRECTORY libsigrokdecode4DSL/decoders DESTINATION share/libsigrokdecode4DSL) - install(DIRECTORY lang DESTINATION share/DSView) + # Install the executable. + install(TARGETS ${PROJECT_NAME} DESTINATION bin) + install(DIRECTORY DSView/res DESTINATION share/DSView) + install(DIRECTORY DSView/demo DESTINATION share/DSView) + install(FILES DSView/icons/logo.svg DESTINATION share/DSView RENAME logo.svg) + install(FILES DSView/icons/logo.svg DESTINATION share/icons/hicolor/scalable/apps RENAME dsview.svg) + install(FILES DSView/icons/logo.svg DESTINATION share/pixmaps RENAME dsview.svg) + + if(CMAKE_SYSTEM_NAME MATCHES "Linux") + install(FILES DSView/DSView.desktop DESTINATION /usr/share/applications RENAME dsview.desktop) + + add_compile_definitions(_DEFAULT_SOURCE) + + if(IS_DIRECTORY /usr/lib/udev/rules.d) + install(FILES DSView/DreamSourceLab.rules DESTINATION /usr/lib/udev/rules.d RENAME 60-dreamsourcelab.rules) + elseif(IS_DIRECTORY /lib/udev/rules.d) + install(FILES DSView/DreamSourceLab.rules DESTINATION /lib/udev/rules.d RENAME 60-dreamsourcelab.rules) + elseif(IS_DIRECTORY /etc/udev/rules.d) + install(FILES DSView/DreamSourceLab.rules DESTINATION /etc/udev/rules.d RENAME 60-dreamsourcelab.rules) + endif() + + endif() + + install(FILES NEWS25 DESTINATION share/DSView RENAME NEWS25) + install(FILES NEWS31 DESTINATION share/DSView RENAME NEWS31) + install(FILES ug25.pdf DESTINATION share/DSView RENAME ug25.pdf) + install(FILES ug31.pdf DESTINATION share/DSView RENAME ug31.pdf) + + install(DIRECTORY libsigrokdecode4DSL/decoders DESTINATION share/libsigrokdecode4DSL) + install(DIRECTORY lang DESTINATION share/DSView) endif() #=============================================================================== @@ -729,7 +725,7 @@ set(CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_CURRENT_SOURCE_DIR}/DSView/README) set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/DSView/COPYING) set(CPACK_SOURCE_IGNORE_FILES ${CMAKE_CURRENT_BINARY_DIR} ".gitignore" ".git") set(CPACK_SOURCE_PACKAGE_FILE_NAME - "${CMAKE_PROJECT_NAME}-${DS_VERSION_MAJOR}.${DS_VERSION_MINOR}.${DS_VERSION_MICRO}") + "${CMAKE_PROJECT_NAME}-${DS_VERSION_MAJOR}.${DS_VERSION_MINOR}.${DS_VERSION_MICRO}") set(CPACK_SOURCE_GENERATOR "TGZ") include(CPack) @@ -739,9 +735,9 @@ include(CPack) #------------------------------------------------------------------------------- if(ENABLE_TESTS) - add_subdirectory(test) - enable_testing() - add_test(test ${CMAKE_CURRENT_BINARY_DIR}/DSView/test/DSView-test) + add_subdirectory(test) + enable_testing() + add_test(test ${CMAKE_CURRENT_BINARY_DIR}/DSView/test/DSView-test) endif(ENABLE_TESTS) diff --git a/DSView/pv/config/appconfig.cpp b/DSView/pv/config/appconfig.cpp index 19c2ea470..e6b0f35ff 100644 --- a/DSView/pv/config/appconfig.cpp +++ b/DSView/pv/config/appconfig.cpp @@ -133,6 +133,7 @@ static void _loadApp(AppOptions &o, QSettings &st) getFiled("minDecoderFontWidthPercent", st, o.minDecoderFontWidthPercent, 75); getFiled("dontAskSaveOnExit", st, o.dontAskSaveOnExit, false); getFiled("logicSignalLineWidth", st, o.logicSignalLineWidth, 1.0f); + getFiled("logicChannelDivider", st, o.logicChannelDivider, true); getFiled("dsoSplitChannels", st, o.dsoSplitChannels, false); o.warnofMultiTrig = true; @@ -190,6 +191,7 @@ static void _saveApp(AppOptions &o, QSettings &st) setFiled("minDecoderFontWidthPercent", st, o.minDecoderFontWidthPercent); setFiled("dontAskSaveOnExit", st, o.dontAskSaveOnExit); setFiled("logicSignalLineWidth", st, o.logicSignalLineWidth); + setFiled("logicChannelDivider", st, o.logicChannelDivider); setFiled("dsoSplitChannels", st, o.dsoSplitChannels); QString fmt = FormatArrayToString(o.m_protocolFormats); diff --git a/DSView/pv/config/appconfig.h b/DSView/pv/config/appconfig.h index e33b96cd8..d972cc786 100644 --- a/DSView/pv/config/appconfig.h +++ b/DSView/pv/config/appconfig.h @@ -87,6 +87,7 @@ struct AppOptions bool decoderDynamicFontWidth; bool dontAskSaveOnExit; float logicSignalLineWidth; + bool logicChannelDivider; bool dsoSplitChannels; std::vector m_protocolFormats; }; diff --git a/DSView/pv/dialogs/applicationpardlg.cpp b/DSView/pv/dialogs/applicationpardlg.cpp index 2b16230dc..d900ad425 100644 --- a/DSView/pv/dialogs/applicationpardlg.cpp +++ b/DSView/pv/dialogs/applicationpardlg.cpp @@ -139,6 +139,9 @@ bool ApplicationParamDlg::ShowDlg(QWidget *parent) QCheckBox *ck_autoScrollLatestData = new QCheckBox(); ck_autoScrollLatestData->setChecked(app.appOptions.autoScrollLatestData); + QCheckBox *ck_channelDivider = new QCheckBox(); + ck_channelDivider->setChecked(app.appOptions.logicChannelDivider); + QHBoxLayout *hl_verticalScrollAction = new QHBoxLayout(); QButtonGroup *bg_verticalScrollAction = new QButtonGroup(); QRadioButton *rb_zoom = new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_VERTICAL_SCROLL_ACTION_SMOOTH_ZOOM), "Zoom")); @@ -302,18 +305,20 @@ bool ApplicationParamDlg::ShowDlg(QWidget *parent) logicLay->addLayout(hl_units, 4, 1, Qt::AlignRight); logicLay->addWidget(new QLabel(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SIGNAL_LINE_WIDTH), "Signal line width")), 5, 0, Qt::AlignLeft); logicLay->addWidget(spinBox_lineWidth, 5, 1, Qt::AlignRight); + logicLay->addWidget(new QLabel(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CHANNEL_DIVIDER), "Channel divider line")), 6, 0, Qt::AlignLeft); + logicLay->addWidget(ck_channelDivider, 6, 1, Qt::AlignRight); // Add sliders to logic layout - logicLay->addWidget(new QLabel(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_DECODER_DYNAMIC_FONT_WIDTH), "Decoder adaptive font width")), 6, 0, Qt::AlignLeft); - logicLay->addWidget(ck_decoderDynamicFontWidth, 6, 1, Qt::AlignRight); - logicLay->addWidget(label_minFontWidth, 7, 0, Qt::AlignLeft); - logicLay->addWidget(spinBox_minFontWidth, 7, 1, Qt::AlignRight); - logicLay->addWidget(slider_minFontWidth, 8, 0, Qt::AlignJustify); - logicLay->addWidget(label_minSample, 8, 1, Qt::AlignCenter); - logicLay->addWidget(label_maxFontWidth, 9, 0, Qt::AlignLeft); - logicLay->addWidget(spinBox_maxFontWidth, 9, 1, Qt::AlignRight); - logicLay->addWidget(slider_maxFontWidth, 10, 0, Qt::AlignJustify); - logicLay->addWidget(label_maxSample, 10, 1, Qt::AlignCenter); + logicLay->addWidget(new QLabel(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_DECODER_DYNAMIC_FONT_WIDTH), "Decoder adaptive font width")), 7, 0, Qt::AlignLeft); + logicLay->addWidget(ck_decoderDynamicFontWidth, 7, 1, Qt::AlignRight); + logicLay->addWidget(label_minFontWidth, 8, 0, Qt::AlignLeft); + logicLay->addWidget(spinBox_minFontWidth, 8, 1, Qt::AlignRight); + logicLay->addWidget(slider_minFontWidth, 9, 0, Qt::AlignJustify); + logicLay->addWidget(label_minSample, 9, 1, Qt::AlignCenter); + logicLay->addWidget(label_maxFontWidth, 10, 0, Qt::AlignLeft); + logicLay->addWidget(spinBox_maxFontWidth, 10, 1, Qt::AlignRight); + logicLay->addWidget(slider_maxFontWidth, 11, 0, Qt::AlignJustify); + logicLay->addWidget(label_maxSample, 11, 1, Qt::AlignCenter); logicLay->setColumnMinimumWidth(1, wfm.horizontalAdvance("Example") + logicLay->contentsMargins().left() + logicLay->contentsMargins().right() @@ -417,6 +422,10 @@ bool ApplicationParamDlg::ShowDlg(QWidget *parent) app.appOptions.logicSignalLineWidth = spinBox_lineWidth->value(); bAppChanged = true; } + if (app.appOptions.logicChannelDivider != ck_channelDivider->isChecked()) { + app.appOptions.logicChannelDivider = ck_channelDivider->isChecked(); + bAppChanged = true; + } if (bAppChanged){ app.SaveApp(); AppControl::Instance()->GetSession()->broadcast_msg(DSV_MSG_APP_OPTIONS_CHANGED); diff --git a/DSView/pv/mainwindow.cpp b/DSView/pv/mainwindow.cpp index 24a7e4b89..902c0db66 100644 --- a/DSView/pv/mainwindow.cpp +++ b/DSView/pv/mainwindow.cpp @@ -2167,6 +2167,9 @@ namespace pv case DSV_MSG_APP_OPTIONS_CHANGED: { update_title_bar_text(); + // Recompute trace layout: the inter-channel spacing depends on + // the channel-divider option (see View::get_signal_margin()). + _view->signals_changed(NULL); _view->viewport_update(); break; } diff --git a/DSView/pv/view/logicsignal.cpp b/DSView/pv/view/logicsignal.cpp index 892fd1dae..28aff3992 100644 --- a/DSView/pv/view/logicsignal.cpp +++ b/DSView/pv/view/logicsignal.cpp @@ -69,9 +69,13 @@ void LogicSignal::paint_back(QPainter &p, int left, int right, QColor fore, QCol { Trace::paint_back(p, left, right, fore, back); - // Logic channels have no fill of their own, so without an explicit - // boundary adjacent rows are hard to tell apart; draw a divider under - // each channel's row, in the middle of the gap to the next one. + // Optional per-channel divider. Logic channels have no fill of their own, + // so without an explicit boundary adjacent rows are hard to tell apart; + // draw a divider under each channel's row, in the middle of the gap to the + // next one. Can be disabled to restore the classic borderless look. + if (!AppConfig::Instance().appOptions.logicChannelDivider) + return; + QColor sep(fore); sep.setAlpha(180); QPen pen(sep); diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index 5df019a36..604910771 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -65,8 +65,15 @@ const int View::MaxScrollValue = INT_MAX / 2; const int View::HeightUnit = 20; // also serves as minimum signal height const int View::SignalMargin = 12; +const int View::SignalMarginCompact = 3; const int View::SignalSnapGridSize = 10; +int View::get_signal_margin() +{ + return AppConfig::Instance().appOptions.logicChannelDivider + ? SignalMargin : SignalMarginCompact; +} + const QColor View::CursorAreaColour(220, 231, 243); const QSizeF View::LabelPadding(4, 4); const QString View::Unknown_Str = "########"; @@ -352,6 +359,11 @@ void View::vzoom(double steps) // Halve the step size (relative to horizontal zoom) for finer adjustment. double factor = _trace_height_factor * std::pow(3.0/2.0, steps * 0.5); + set_trace_height_factor(factor); +} + +void View::set_trace_height_factor(double factor) +{ factor = max(min(factor, MaxTraceHeightFactor), MinTraceHeightFactor); if (factor == _trace_height_factor) @@ -644,7 +656,7 @@ void View::normalize_layout() } } - const int delta = -min(v_min - (top->get_totalHeight() / 2 + 2 * SignalMargin), 0); + const int delta = -min(v_min - (top->get_totalHeight() / 2 + 2 * get_signal_margin()), 0); verticalScrollBar()->setSliderPosition(delta); v_scroll_value_changed(verticalScrollBar()->sliderPosition()); @@ -694,7 +706,7 @@ void View::update_scroll() int total_height = 0; for (auto t : traces) { if (t->enabled()) - total_height += t->get_totalHeight() + 2 * SignalMargin; + total_height += t->get_totalHeight() + 2 * get_signal_margin(); } // Make sure we can scroll the last signal past the status bar @@ -746,7 +758,7 @@ void View::mode_changed() void View::signals_changed(const Trace* eventTrace) { - double actualMargin = SignalMargin; + double actualMargin = get_signal_margin(); int total_rows = 0; int label_size = 0; uint8_t max_height = HeightUnit; diff --git a/DSView/pv/view/view.h b/DSView/pv/view/view.h index b0f03cdd2..938917c3a 100644 --- a/DSView/pv/view/view.h +++ b/DSView/pv/view/view.h @@ -84,9 +84,14 @@ class View : public QScrollArea, public IUiWindow public: //static const int SignalHeight; - static const int SignalMargin; + static const int SignalMargin; // inter-channel spacing with divider + static const int SignalMarginCompact; // original spacing without divider static const int SignalSnapGridSize; + // Effective inter-channel margin: the compact spacing is restored when the + // logic channel divider line is disabled (see AppConfig::logicChannelDivider). + static int get_signal_margin(); + static const QColor CursorAreaColour; static const QSizeF LabelPadding; static const QString Unknown_Str; @@ -180,6 +185,13 @@ class View : public QScrollArea, public IUiWindow */ void vzoom(double steps); + /** + * Sets the vertical trace-height scaling factor directly (logic mode). + * The value is clamped to [MinTraceHeightFactor, MaxTraceHeightFactor] + * and persisted. Passing 1.0 resets the y-axis zoom to its default. + */ + void set_trace_height_factor(double factor); + /** * Sets the scale and offset. * @param scale The new view scale in seconds per pixel. diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp index fee69f946..7743c9c34 100644 --- a/DSView/pv/view/viewport.cpp +++ b/DSView/pv/view/viewport.cpp @@ -76,7 +76,9 @@ Viewport::Viewport(View &parent, View_type type) : _waiting_trig(0), _dso_trig_moved(false), _curs_moved(false), - _xcurs_moved(false) + _xcurs_moved(false), + _yscale_hint_active(false), + _yscale_badge_pressed(false) { setMouseTracking(true); setAutoFillBackground(true); @@ -109,11 +111,15 @@ Viewport::Viewport(View &parent, View_type type) : QAction *xAction = _cmenu->addAction(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_ADD_X_CURSOR), "Add X-cursor")); _yAction = yAction; _xAction = xAction; - + setContextMenuPolicy(Qt::CustomContextMenu); + // Repeating tick that animates the transient y-scale badge fade-out. + _yscale_hint_timer.setSingleShot(false); + connect(&_trigger_timer, SIGNAL(timeout()),this, SLOT(on_trigger_timer())); - connect(&_drag_timer, SIGNAL(timeout()),this, SLOT(on_drag_timer())); + connect(&_drag_timer, SIGNAL(timeout()),this, SLOT(on_drag_timer())); + connect(&_yscale_hint_timer, SIGNAL(timeout()),this, SLOT(on_yscale_hint_timeout())); connect(yAction, SIGNAL(triggered(bool)), this, SLOT(add_cursor_y())); connect(xAction, SIGNAL(triggered(bool)), this, SLOT(add_cursor_x())); connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),this, SLOT(show_contextmenu(const QPoint&))); @@ -135,7 +141,7 @@ int Viewport::get_total_height() for(auto t : traces) { h += (int)(t->get_totalHeight()); } - h += 2 * View::SignalMargin; + h += 2 * View::get_signal_margin(); return h; } @@ -247,6 +253,8 @@ void Viewport::doPaint() if (_view.get_signalHeight() != _curSignalHeight) _curSignalHeight = _view.get_signalHeight(); + paintYScaleBadge(p, fore, back); + p.end(); } @@ -268,8 +276,76 @@ void Viewport::paintCursors(QPainter &p) } } +void Viewport::paintYScaleBadge(QPainter &p, QColor fore, QColor back) +{ + // Only meaningful for the logic trace window, where vzoom() applies. + if (_type != TIME_VIEW || + _view.session().get_device()->get_work_mode() != LOGIC) { + _yscale_badge_rect = QRect(); + return; + } + + // Shown transiently after a vertical zoom / reset, then fades out. + if (!_yscale_hint_active) { + _yscale_badge_rect = QRect(); + return; + } + + // Full opacity, then a linear fade over the last YScaleBadgeFadeMs. + const qint64 elapsed = _yscale_hint_clock.elapsed(); + const qint64 fade_start = YScaleBadgeDurationMs - YScaleBadgeFadeMs; + double opacity = 1.0; + if (elapsed > fade_start) + opacity = max(0.0, 1.0 - double(elapsed - fade_start) / YScaleBadgeFadeMs); + + const double factor = _view.get_trace_height_factor(); + const QString text = QString("Y-scale %1x").arg(factor, 0, 'f', 2); + const QString glyph = QString::fromUtf8(" \xE2\x9F\xB2"); // U+27F2 reset arrow + + QFont font = p.font(); + font.setPointSizeF(AppConfig::Instance().GetTraceFontSize()); + const QFontMetrics fm(font); + + const int padX = 8; + const int padY = 4; + const int textW = fm.horizontalAdvance(text + glyph); + const int textH = fm.height(); + + const QRect view_rect = _view.get_view_rect(); + const int margin = 6; + QRect badge(view_rect.right() - textW - 2 * padX - margin, + view_rect.top() + margin, + textW + 2 * padX, + textH + 2 * padY); + + p.save(); + p.setRenderHint(QPainter::Antialiasing, true); + p.setOpacity(opacity); + p.setFont(font); + + QColor bg = back; + bg.setAlpha(220); + p.setPen(QPen(View::Blue, 1)); + p.setBrush(bg); + p.drawRoundedRect(badge, 4, 4); + + fore.setAlpha(255); + p.setPen(fore); + p.drawText(QRect(badge.left() + padX, badge.top(), fm.horizontalAdvance(text) + 1, badge.height()), + Qt::AlignVCenter | Qt::AlignLeft, text); + p.setPen(View::Blue); + p.drawText(QRect(badge.left() + padX + fm.horizontalAdvance(text), badge.top(), + fm.horizontalAdvance(glyph) + padX, badge.height()), + Qt::AlignVCenter | Qt::AlignLeft, glyph); + + p.restore(); + + // Store hit-area so a click resets the y-scale (#5). + _yscale_badge_rect = badge; +} + void Viewport::paintSignals(QPainter &p, QColor fore, QColor back) -{ +{ std::vector traces; _view.get_traces(_type, traces); @@ -660,6 +736,17 @@ void Viewport::mousePressEvent(QMouseEvent *event) _drag_delta_t = 0; _drag_delta_x = 0; + // Click on the y-scale badge resets the vertical zoom (logic mode). + // The matching release is swallowed so it doesn't hit the trace below. + if (_action_type == NO_ACTION + && event->button() == Qt::LeftButton + && !_yscale_badge_rect.isEmpty() + && _yscale_badge_rect.contains(event->pos())) { + _yscale_badge_pressed = true; + reset_yscale(); + return; + } + // cancel potential ongoing MOVE action so click/drag is evaluated anew if (_action_type == LOGIC_MOVE) { set_action(NO_ACTION); @@ -1191,6 +1278,13 @@ void Viewport::mouseReleaseEvent(QMouseEvent *event) { assert(event); + // The press was consumed by the y-scale badge; ignore this release so it + // is not also interpreted as a click on the trace below the badge. + if (_yscale_badge_pressed) { + _yscale_badge_pressed = false; + return; + } + if (_type != TIME_VIEW){ update(UpdateEventType::UPDATE_EV_MS_UP); return; @@ -1397,6 +1491,7 @@ void Viewport::wheelEvent(QWheelEvent *event) // Ctrl+Shift+wheel: scale the trace height in the y axis so // signals can use the full window height. _view.vzoom(zoom_scale); + flash_yscale_badge(); } // Vertical scrolling is interpreted as zooming in/out else if(doVScroll) { @@ -2256,6 +2351,8 @@ bool Viewport::get_dso_trig_moved() void Viewport::show_contextmenu(const QPoint& pos) { + // The X/Y cursor menu is a DSO-only concept; logic mode has no context + // menu (the y-scale is reset by clicking the badge, see paintYScaleBadge). if(_cmenu && _view.session().get_device()->get_work_mode() == DSO) { @@ -2265,6 +2362,38 @@ void Viewport::show_contextmenu(const QPoint& pos) } } +void Viewport::reset_yscale() +{ + _view.set_trace_height_factor(1.0); + flash_yscale_badge(); +} + +void Viewport::on_yscale_hint_timeout() +{ + // Keep the badge fully visible while the pointer hovers over it; the + // fade only (re)starts once the mouse leaves the badge. + const bool hovered = !_yscale_badge_rect.isEmpty() && underMouse() + && _yscale_badge_rect.contains(mapFromGlobal(QCursor::pos())); + + if (hovered) { + _yscale_hint_clock.restart(); + } + else if (_yscale_hint_clock.elapsed() >= YScaleBadgeDurationMs) { + _yscale_hint_active = false; + _yscale_hint_timer.stop(); + _yscale_badge_rect = QRect(); + } + QWidget::update(); +} + +void Viewport::flash_yscale_badge() +{ + _yscale_hint_active = true; + _yscale_hint_clock.restart(); + _yscale_hint_timer.start(YScaleBadgeTickMs); + QWidget::update(); +} + void Viewport::add_cursor_y() { uint64_t index; diff --git a/DSView/pv/view/viewport.h b/DSView/pv/view/viewport.h index 1c293f07b..ffd013085 100644 --- a/DSView/pv/view/viewport.h +++ b/DSView/pv/view/viewport.h @@ -80,6 +80,9 @@ class Viewport : public QWidget, public IUiWindow static const int WaitLoopTime = 400; static const int MousePointerClearance = 25; static const int MouseEdgeClearance = 3; + static const int YScaleBadgeDurationMs = 4000; // total on-screen time + static const int YScaleBadgeFadeMs = 800; // fade-out tail duration + static const int YScaleBadgeTickMs = 40; // repaint interval while fading enum ActionType { NO_ACTION, @@ -149,6 +152,10 @@ class Viewport : public QWidget, public IUiWindow void paintProgress(QPainter& p, QColor fore, QColor back); void paintMeasure(QPainter &p, QColor fore, QColor back); void paintCursors(QPainter &p); + void paintYScaleBadge(QPainter &p, QColor fore, QColor back); + + // Briefly show the y-scale badge (logic mode) after a vertical zoom. + void flash_yscale_badge(); void start_trigger_timer(int msec); void get_captured_progress(double &progress, int &progress100); @@ -167,6 +174,8 @@ private slots: void show_contextmenu(const QPoint& pos); void add_cursor_x(); void add_cursor_y(); + void reset_yscale(); + void on_yscale_hint_timeout(); signals: void measure_updated(); @@ -256,6 +265,12 @@ private slots: int _tigger_wait_times; QAction *_yAction; QAction *_xAction; + + QTimer _yscale_hint_timer; // repaint tick while the badge fades + QElapsedTimer _yscale_hint_clock; // time since the badge was last shown + bool _yscale_hint_active; + QRect _yscale_badge_rect; // clickable reset hit-area (empty = hidden) + bool _yscale_badge_pressed; // press consumed by the badge; swallow release }; } // namespace view diff --git a/README.md b/README.md index f3fb71985..8c852c46d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![DreamSourceLab Logo](DSView/icons/dsl_logo.svg) +# DSView -# DSView DSView is a GUI program for supporting various instruments from [DreamSourceLab](http://www.dreamsourcelab.com), including logic analyzers, oscilloscopes, etc. DSView is based on the [sigrok project](https://sigrok.org). The sigrok project aims at creating a portable, cross-platform, Free/Libre/Open-Source signal analysis software suite that supports various device types (such as logic analyzers, oscilloscopes, multimeters, and more). @@ -10,6 +10,10 @@ The sigrok project aims at creating a portable, cross-platform, Free/Libre/Open- The DSView software is in a usable state and has official tarball releases. However, it is still a work in progress. Some basic functionality is available and working, but other things are always on the TODO list. +# Download + +Pre-built binaries are available on the [releases page](https://github.com/Schildkroet/DSView/releases). + # Useful links - [dreamsourcelab.com](https://www.dreamsourcelab.com) diff --git a/libsigrok4DSL/hardware/demo/demo.h b/libsigrok4DSL/hardware/demo/demo.h index 6c227399f..a0ef9bc27 100644 --- a/libsigrok4DSL/hardware/demo/demo.h +++ b/libsigrok4DSL/hardware/demo/demo.h @@ -414,37 +414,38 @@ static const gboolean default_ms_en[] = { static const struct DEMO_profile supported_Demo[] = { /* - * Demo + * Demo Oscilloscope */ - {"DreamSourceLab", "Demo Device", NULL, - {CAPS_MODE_LOGIC | CAPS_MODE_ANALOG | CAPS_MODE_DSO, + {"DreamSourceLab", "Demo Oscilloscope", NULL, + {CAPS_MODE_DSO, CAPS_FEATURE_NONE, - (1 << DEMO_LOGIC100x16) | - (1 << DEMO_ANALOG10x2) | (1 << DEMO_DSO200x2), - SR_Mn(100), + SR_Kn(20), SR_Kn(20), 0, vdivs10to2000, 0, - DEMO_LOGIC100x16, + DEMO_DSO200x2, PATTERN_RANDOM, SR_NS(500)} }, /* - * Demo Oscilloscope + * Demo Logic (listed last so it is the default device selected at launch; + * see SigSession::set_default_device(), which picks the last device). */ - {"DreamSourceLab", "Demo Oscilloscope", NULL, - {CAPS_MODE_DSO, + {"DreamSourceLab", "Demo Logic", NULL, + {CAPS_MODE_LOGIC | CAPS_MODE_ANALOG | CAPS_MODE_DSO, CAPS_FEATURE_NONE, + (1 << DEMO_LOGIC100x16) | + (1 << DEMO_ANALOG10x2) | (1 << DEMO_DSO200x2), - SR_Kn(20), + SR_Mn(100), SR_Kn(20), 0, vdivs10to2000, 0, - DEMO_DSO200x2, + DEMO_LOGIC100x16, PATTERN_RANDOM, SR_NS(500)} }, From 3b094be0abf009cc178a9239e7544c8e46dbe7ae Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Sat, 18 Jul 2026 14:25:31 +0200 Subject: [PATCH 22/34] Fix oscilloscope view scroll size --- DSView/pv/view/view.cpp | 42 +++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index 604910771..ca9915460 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -698,11 +698,15 @@ void View::update_scroll() _x_offset * 1.0 / length * MaxScrollValue); } - // Set up vertical scrollbar + // Set up vertical scrollbar. Only the time-view traces occupy the + // scrollable top pane; the FFT traces live in the fixed, splitter-sized + // _fft_viewport. Counting the FFT pane here would invent a scroll range + // that has no matching time content, so scrolling it would slide the + // whole left panel out of alignment with the FFT view. std::vector traces; - get_traces(ALL_VIEW, traces); + get_traces(TIME_VIEW, traces); - // Calculate total required height for all traces + // Calculate total required height for the time-pane traces int total_height = 0; for (auto t : traces) { if (t->enabled()) @@ -712,10 +716,15 @@ void View::update_scroll() // Make sure we can scroll the last signal past the status bar total_height += StatusHeight; + // Scroll the time pane against its own visible height (which excludes the + // FFT pane when the splitter is showing one). + const int avail_height = _fft_viewport->isVisible() + ? _time_viewport->height() : areaSize.height(); + // Enable vertical scrolling if total height exceeds viewport - if (total_height > areaSize.height()) { - verticalScrollBar()->setRange(0, total_height - areaSize.height()); - verticalScrollBar()->setPageStep(areaSize.height()); + if (total_height > avail_height) { + verticalScrollBar()->setRange(0, total_height - avail_height); + verticalScrollBar()->setPageStep(avail_height); } else { verticalScrollBar()->setRange(0, 0); } @@ -799,7 +808,11 @@ void View::signals_changed(const Trace* eventTrace) t->set_view(this); t->set_viewport(_fft_viewport); t->set_totalHeight(_fft_viewport->height()); - t->set_v_offset(_fft_viewport->geometry().bottom()); + // The header spans the whole view (both splitter panes); the FFT + // viewport's geometry is expressed in that same coordinate space, + // so anchoring the label to the pane's vertical centre keeps the + // left-panel label lined up with the FFT view it belongs to. + t->set_v_offset(_fft_viewport->geometry().center().y()); } } else { @@ -875,7 +888,13 @@ void View::signals_changed(const Trace* eventTrace) _signalHeight = max((double)min_row_height, _signalHeight * _trace_height_factor); } else if (_device_agent->get_work_mode() == DSO) { - _signalHeight = max((double)HeightUnit, (_header->height() + // Size the channels to the pane they actually live in. Using the + // full-height header would keep them sized for the whole view even + // after the FFT splitter pane has shrunk the time viewport, so the + // channels would overflow the time pane and invent vertical scroll + // range with nothing to scroll to. When no FFT pane is shown the + // time viewport fills the view, so this matches the old behaviour. + _signalHeight = max((double)HeightUnit, (_time_viewport->height() - horizontalScrollBar()->height() - 2 * actualMargin * label_size) * 1.0 / total_rows); } @@ -1083,9 +1102,12 @@ void View::v_scroll_value_changed(int value) // Track vertical offset _y_offset = value; - // Update vertical positions of all traces based on scroll value + // Only the time-view traces live in the scrollable top pane. The FFT + // traces sit in the separate, splitter-controlled _fft_viewport whose + // content is painted at a fixed viewport-relative position, so scrolling + // them here would drift their header label away from the FFT view. std::vector traces; - get_traces(ALL_VIEW, traces); + get_traces(TIME_VIEW, traces); for (auto t : traces) { if (t->enabled()) { From 73f33ac2a10e7b71a223d78e64eb03c6f01a0ed2 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Sat, 18 Jul 2026 16:43:14 +0200 Subject: [PATCH 23/34] Improved dso view, added more functionality --- CMakeLists.txt | 6 + DSView/pv/data/mathstack.cpp | 140 +++++++-- DSView/pv/data/mathstack.h | 18 ++ DSView/pv/dialogs/chanmeasure.cpp | 341 ++++++++++++++++++++++ DSView/pv/dialogs/chanmeasure.h | 67 +++++ DSView/pv/dialogs/dsohistogram.cpp | 442 +++++++++++++++++++++++++++++ DSView/pv/dialogs/dsohistogram.h | 95 +++++++ DSView/pv/dialogs/mathoptions.cpp | 60 ++-- DSView/pv/dialogs/mathoptions.h | 4 + DSView/pv/dialogs/refoptions.cpp | 123 ++++++++ DSView/pv/dialogs/refoptions.h | 70 +++++ DSView/pv/sigsession.cpp | 45 ++- DSView/pv/sigsession.h | 26 +- DSView/pv/toolbars/trigbar.cpp | 44 ++- DSView/pv/toolbars/trigbar.h | 6 + DSView/pv/view/dsosignal.cpp | 128 ++++++++- DSView/pv/view/dsosignal.h | 5 + DSView/pv/view/view.cpp | 19 +- DSView/pv/view/view.h | 11 +- DSView/pv/view/viewport.cpp | 322 +++++++++++++++------ DSView/pv/view/viewport.h | 1 + 21 files changed, 1814 insertions(+), 159 deletions(-) create mode 100644 DSView/pv/dialogs/chanmeasure.cpp create mode 100644 DSView/pv/dialogs/chanmeasure.h create mode 100644 DSView/pv/dialogs/dsohistogram.cpp create mode 100644 DSView/pv/dialogs/dsohistogram.h create mode 100644 DSView/pv/dialogs/refoptions.cpp create mode 100644 DSView/pv/dialogs/refoptions.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 36e4c34c9..1a9cf55ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -307,6 +307,9 @@ set(DSView_SOURCES DSView/pv/dialogs/protocollist.cpp DSView/pv/dialogs/protocolexp.cpp DSView/pv/dialogs/fftoptions.cpp + DSView/pv/dialogs/dsohistogram.cpp + DSView/pv/dialogs/chanmeasure.cpp + DSView/pv/dialogs/refoptions.cpp DSView/pv/data/mathstack.cpp DSView/pv/view/mathtrace.cpp DSView/pv/toolbars/titlebar.cpp @@ -424,6 +427,9 @@ set(DSView_HEADERS DSView/pv/view/spectrumtrace.h DSView/pv/data/spectrumstack.h DSView/pv/dialogs/mathoptions.h + DSView/pv/dialogs/dsohistogram.h + DSView/pv/dialogs/chanmeasure.h + DSView/pv/dialogs/refoptions.h DSView/pv/dialogs/regionoptions.h DSView/pv/view/xcursor.h DSView/pv/view/signal.h diff --git a/DSView/pv/data/mathstack.cpp b/DSView/pv/data/mathstack.cpp index 817768ad6..10cf7fa5b 100644 --- a/DSView/pv/data/mathstack.cpp +++ b/DSView/pv/data/mathstack.cpp @@ -86,6 +86,7 @@ MathStack::MathStack(pv::SigSession *session, _dsoSig1(dsoSig1), _dsoSig2(dsoSig2), _type(type), + _filter_width(10), _sample_num(0), _total_sample_num(0), _math_state(Init), @@ -181,6 +182,9 @@ uint64_t MathStack::default_vDialValue() case MATH_DIV: value = dial1_value * 1000.0 / dial2_value; break; + default: // unary operators: scale off the single source + value = dial1_value; + break; } bool bFind = false; @@ -221,6 +225,9 @@ uint64_t MathStack::default_factor() case MATH_DIV: value = factor1 / factor2; break; + default: // unary operators: single-source factor + value = factor1; + break; } if (value == 0){ @@ -276,6 +283,17 @@ view::dslDial* MathStack::get_vDial() for(int i = 0; i < vDialUnitCount; i++) vUnit.append(vDialDivUnit[i]); break; + default: // unary operators: derive the dial from the single source + for (int i = 0; i < vDialValueCount; i++) { + if (vDialValue[i] < dial1_min) + continue; + vValue.append(vDialValue[i]); + if (vDialValue[i] > dial1_max) + break; + } + for(int i = 0; i < vDialUnitCount; i++) + vUnit.append(vDialAddUnit[i]); + break; } view::dslDial *vDial = new view::dslDial(vValue.count(), vDialValueStep, vValue, vUnit, true); @@ -299,6 +317,9 @@ QString MathStack::get_unit(int level) case MATH_DIV: unit = vDialDivUnit[level]; break; + default: // unary operators use voltage-style units + unit = vDialAddUnit[level]; + break; } return unit; @@ -319,6 +340,9 @@ double MathStack::get_math_scale() case MATH_DIV: scale = 1.0 / DS_CONF_DSO_VDIVS; break; + default: // unary operators + scale = 1.0 / DS_CONF_DSO_VDIVS; + break; } return scale; @@ -371,52 +395,112 @@ void MathStack::calc_math(uint64_t mathFactor) if (data->empty() || _math.size() < _total_sample_num) return; - if (!_dsoSig1->enabled() || !_dsoSig2->enabled()) + const bool unary = is_unary(_type); + + if (!_dsoSig1->enabled()) return; - if (data->get_channel_num() < 2) + // The binary operators need a valid, enabled 2nd source; the unary ones + // (integrate/differentiate/abs/square/sqrt/filters) work off src1 alone. + if (!unary && (!_dsoSig2->enabled() || data->get_channel_num() < 2)) return; auto k1 = _dsoSig1->get_factor(); - auto k2 = _dsoSig2->get_factor(); const double scale1 = _dsoSig1->get_vDialValue() / 1000.0 * k1 * DS_CONF_DSO_VDIVS * _dsoSig1->get_scale() / _dsoSig1->get_view_rect().height(); const double delta1 = _dsoSig1->get_hw_offset() * scale1; - const double scale2 = _dsoSig2->get_vDialValue() / 1000.0 * k2 * DS_CONF_DSO_VDIVS * - _dsoSig2->get_scale() / _dsoSig2->get_view_rect().height(); - - const double delta2 = _dsoSig2->get_hw_offset() * scale2; - _sample_num = data->get_sample_count(); assert(_sample_num <= _total_sample_num); const int index1 = _dsoSig1->get_index(); - const int index2 = _dsoSig2->get_index(); const uint8_t* value_buffer1 = data->get_samples(0, 0, index1); - const uint8_t* value_buffer2 = data->get_samples(0, 0, index2); - double value1, value2; - for (uint64_t sample = 0; sample < _sample_num; sample++) { - value1 = *(value_buffer1 + sample); - value2 = *(value_buffer2 + sample); + // Source #1 in volts for a given sample index. + auto v1 = [&](uint64_t s) -> double { + return delta1 - scale1 * (*(value_buffer1 + s)); + }; - switch(_type) - { - case MATH_ADD: - _math[sample] = ((delta1 - scale1 * value1) + (delta2 - scale2 * value2)) / mathFactor; - break; - case MATH_SUB: - _math[sample] = ((delta1 - scale1 * value1) - (delta2 - scale2 * value2)) / mathFactor; - break; - case MATH_MUL: - _math[sample] = (delta1 - scale1 * value1) * (delta2 - scale2 * value2) / mathFactor; - break; - case MATH_DIV: - _math[sample] = (delta1 - scale1 * value1) / (delta2 - scale2 * value2) / mathFactor; - break; + if (unary) { + const double dt = (samplerate() > 0) ? 1.0 / samplerate() : 1.0; + const uint64_t win = (uint64_t)max(1, _filter_width); + double integ = 0.0; + double acc = 0.0; // running window sum for the filters + + for (uint64_t sample = 0; sample < _sample_num; sample++) { + const double v = v1(sample); + double r = 0.0; + + switch(_type) + { + case MATH_INTEG: + integ += v * dt; + r = integ; + break; + case MATH_DIFF: + r = (v - v1(sample == 0 ? 0 : sample - 1)) / dt; + break; + case MATH_ABS: + r = fabs(v); + break; + case MATH_SQUARE: + r = v * v; + break; + case MATH_SQRT: + r = (v < 0) ? -sqrt(-v) : sqrt(v); + break; + case MATH_LOWPASS: + case MATH_HIGHPASS: { + acc += v; + if (sample >= win) + acc -= v1(sample - win); + const uint64_t n = min(sample + 1, win); + const double avg = acc / n; + r = (_type == MATH_LOWPASS) ? avg : (v - avg); + break; + } + default: + r = v; + break; + } + + _math[sample] = r / mathFactor; + } + } + else { + auto k2 = _dsoSig2->get_factor(); + + const double scale2 = _dsoSig2->get_vDialValue() / 1000.0 * k2 * DS_CONF_DSO_VDIVS * + _dsoSig2->get_scale() / _dsoSig2->get_view_rect().height(); + + const double delta2 = _dsoSig2->get_hw_offset() * scale2; + + const int index2 = _dsoSig2->get_index(); + const uint8_t* value_buffer2 = data->get_samples(0, 0, index2); + + for (uint64_t sample = 0; sample < _sample_num; sample++) { + const double value1 = v1(sample); + const double value2 = delta2 - scale2 * (*(value_buffer2 + sample)); + + switch(_type) + { + case MATH_ADD: + _math[sample] = (value1 + value2) / mathFactor; + break; + case MATH_SUB: + _math[sample] = (value1 - value2) / mathFactor; + break; + case MATH_MUL: + _math[sample] = value1 * value2 / mathFactor; + break; + case MATH_DIV: + _math[sample] = value1 / value2 / mathFactor; + break; + default: + break; + } } } diff --git a/DSView/pv/data/mathstack.h b/DSView/pv/data/mathstack.h index 216d4119b..cf627806a 100644 --- a/DSView/pv/data/mathstack.h +++ b/DSView/pv/data/mathstack.h @@ -61,8 +61,21 @@ class MathStack : public QObject, public SignalData MATH_SUB, MATH_MUL, MATH_DIV, + // Unary operators below only use the 1st source. + MATH_INTEG, // running integral (∫ x dt) + MATH_DIFF, // time derivative (d/dt) + MATH_ABS, // absolute value (|x|) + MATH_SQUARE, // square (x²) + MATH_SQRT, // signed sqrt (√x) + MATH_LOWPASS, // moving-average low-pass + MATH_HIGHPASS, // x - moving-average (high-pass) }; + // True for the single-source operators (everything from MATH_INTEG on). + static bool is_unary(MathType type) { + return type >= MATH_INTEG; + } + struct EnvelopeSample { double min; @@ -113,6 +126,10 @@ class MathStack : public QObject, public SignalData MathType get_type(); uint64_t get_sample_num(); + // Window length (in samples) for the moving-average LP/HP filters. + void set_filter_width(int width) { _filter_width = (width < 1) ? 1 : width; } + int get_filter_width() { return _filter_width; } + void enable_envelope(bool enable); uint64_t default_vDialValue(); @@ -138,6 +155,7 @@ class MathStack : public QObject, public SignalData view::DsoSignal *_dsoSig2; MathType _type; + int _filter_width; uint64_t _sample_num; uint64_t _total_sample_num; math_state _math_state; diff --git a/DSView/pv/dialogs/chanmeasure.cpp b/DSView/pv/dialogs/chanmeasure.cpp new file mode 100644 index 000000000..b54463b4a --- /dev/null +++ b/DSView/pv/dialogs/chanmeasure.cpp @@ -0,0 +1,341 @@ +/* + * This file is part of the DSView project. + * DSView is based on PulseView. + * + * Copyright (C) 2026 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "chanmeasure.h" + +#include +#include +#include + +#include +#include + +#include "../sigsession.h" +#include "../view/dsosignal.h" +#include "../data/dsosnapshot.h" +#include "../ui/langresource.h" + +using namespace std; + +namespace pv { +namespace dialogs { + +namespace { + +// Threshold-crossing edges of one DSO channel, in nanoseconds, plus the mean +// period. Uses a mid-level threshold with 5% hysteresis to reject noise. +struct EdgeInfo +{ + bool valid = false; + std::vector rising; // rising-edge times (ns) + std::vector falling; // falling-edge times (ns) + double period = 0.0; // ns (mean rising-to-rising) +}; + +EdgeInfo extract_edges(view::DsoSignal *sig, double dt_ns) +{ + EdgeInfo info; + + if (sig == NULL || !sig->enabled() || dt_ns <= 0) + return info; + + data::DsoSnapshot *data = sig->data(); + if (data == NULL || data->empty()) + return info; + + const uint64_t total = data->get_sample_count(); + const uint8_t *buf = data->get_samples(0, 0, sig->get_index()); + if (total < 2 || buf == NULL) + return info; + + const int hw_offset = sig->get_hw_offset(); + const double k = data->get_measure_voltage_factor(sig->get_index()); + const double data_scale = data->get_data_scale(sig->get_index()); + const double vfactor = sig->get_vDial()->get_factor(); + const int vrect_h = sig->get_view_rect().height(); + const double vscale = (vrect_h > 0) + ? data_scale * k * vfactor * DS_CONF_DSO_VDIVS / vrect_h : 0.0; + + const uint64_t MaxSamples = 8000000; + const uint64_t n = min(total, MaxSamples); + + auto volt = [&](uint64_t i) -> double { + return (hw_offset - (double)buf[i]) * vscale; + }; + + double vmin = 1e300, vmax = -1e300; + for (uint64_t i = 0; i < n; i++) { + const double v = volt(i); + vmin = min(vmin, v); + vmax = max(vmax, v); + } + const double span = (vmax > vmin) ? (vmax - vmin) : 1.0; + const double mid = (vmax + vmin) / 2.0; + const double hyst = span * 0.05; + const double hi = mid + hyst; + const double lo = mid - hyst; + + // Schmitt-trigger edge detection: flip the high/low state on the upper / + // lower thresholds. This tolerates finite rise time and noise, which a + // single-step "crossed mid this sample" test does not. + bool is_high = (volt(0) >= mid); + for (uint64_t i = 1; i < n; i++) { + const double v = volt(i); + if (!is_high && v >= hi) { + is_high = true; + info.rising.push_back(i * dt_ns); + } else if (is_high && v <= lo) { + is_high = false; + info.falling.push_back(i * dt_ns); + } + } + + if (info.rising.size() >= 2) { + double sum = 0; + for (size_t e = 1; e < info.rising.size(); e++) + sum += info.rising[e] - info.rising[e - 1]; + info.period = sum / (info.rising.size() - 1); + } + + info.valid = true; + return info; +} + +// Mean signed delay (ns) from A's edges to B's edges, wrapped into +// (-period/2, +period/2] so it reads as the fractional shift within a cycle. +// Returns false if it cannot be computed. +bool mean_delay(const std::vector &ea, const std::vector &eb, + double period, double &out_ns) +{ + if (ea.empty() || eb.empty() || period <= 0) + return false; + + double sum = 0; + int cnt = 0; + for (double ta : ea) { + // Nearest B edge to ta. + auto it = std::lower_bound(eb.begin(), eb.end(), ta); + double best = 0; + bool have = false; + if (it != eb.end()) { + best = *it - ta; + have = true; + } + if (it != eb.begin()) { + const double d = *(it - 1) - ta; + if (!have || fabs(d) < fabs(best)) { + best = d; + have = true; + } + } + if (!have) + continue; + + // Wrap into (-period/2, +period/2]. + while (best > period / 2) best -= period; + while (best <= -period / 2) best += period; + + sum += best; + cnt++; + } + + if (cnt == 0) + return false; + + out_ns = sum / cnt; + return true; +} + +QString fmt_time_ns(double t_ns) +{ + const double a = fabs(t_ns); + if (a >= 1e9) + return QString::number(t_ns / 1e9, 'f', 3) + " s"; + if (a >= 1e6) + return QString::number(t_ns / 1e6, 'f', 3) + " ms"; + if (a >= 1e3) + return QString::number(t_ns / 1e3, 'f', 3) + " us"; + return QString::number(t_ns, 'f', 2) + " ns"; +} + +QString fmt_freq_hz(double f_hz) +{ + const double a = fabs(f_hz); + if (a >= 1e6) + return QString::number(f_hz / 1e6, 'f', 3) + " MHz"; + if (a >= 1e3) + return QString::number(f_hz / 1e3, 'f', 3) + " kHz"; + return QString::number(f_hz, 'f', 2) + " Hz"; +} + +} // namespace + +DsoChannelMeasure::DsoChannelMeasure(SigSession *session, QWidget *parent) : + DSDialog(parent), + _session(session), + _button_box(QDialogButtonBox::Close, Qt::Horizontal, this) +{ + setMinimumSize(380, 260); + + _srcA_combobox = new DsComboBox(this); + _srcB_combobox = new DsComboBox(this); + for (auto s : _session->get_signals()) { + if (s->signal_type() == SR_CHANNEL_DSO) { + view::DsoSignal *dsoSig = (view::DsoSignal*)s; + _srcA_combobox->addItem(dsoSig->get_name(), + QVariant::fromValue(dsoSig->get_index())); + _srcB_combobox->addItem(dsoSig->get_name(), + QVariant::fromValue(dsoSig->get_index())); + } + } + // Default the 2nd source to a different channel when possible. + if (_srcB_combobox->count() > 1) + _srcB_combobox->setCurrentIndex(1); + + _result_label = new QLabel(this); + _result_label->setTextInteractionFlags(Qt::TextSelectableByMouse); + _result_label->setWordWrap(true); + _result_label->setAlignment(Qt::AlignTop | Qt::AlignLeft); + + QFormLayout *src_layout = new QFormLayout(); + src_layout->addRow( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_1ST_SOURCE), "1st Source"), _srcA_combobox); + src_layout->addRow( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_2ST_SOURCE), "2st Source"), _srcB_combobox); + + _layout = new QVBoxLayout(); + _layout->addLayout(src_layout); + _layout->addWidget(_result_label, 1); + _layout->addWidget(&_button_box); + + layout()->addLayout(_layout); + setTitle(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CH_MEASURE), + "Channel-to-channel measure")); + + connect(&_button_box, SIGNAL(rejected()), this, SLOT(reject())); + connect(&_button_box, SIGNAL(accepted()), this, SLOT(accept())); + connect(_srcA_combobox, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_source_changed(int))); + connect(_srcB_combobox, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_source_changed(int))); + + compute(); +} + +DsoChannelMeasure::~DsoChannelMeasure() +{ +} + +void DsoChannelMeasure::on_source_changed(int index) +{ + (void)index; + compute(); +} + +void DsoChannelMeasure::compute() +{ + const int idxA = (_srcA_combobox->count() > 0) + ? _srcA_combobox->itemData(_srcA_combobox->currentIndex()).toInt() : -1; + const int idxB = (_srcB_combobox->count() > 0) + ? _srcB_combobox->itemData(_srcB_combobox->currentIndex()).toInt() : -1; + + view::DsoSignal *sigA = NULL; + view::DsoSignal *sigB = NULL; + for (auto s : _session->get_signals()) { + if (s->signal_type() == SR_CHANNEL_DSO) { + view::DsoSignal *d = (view::DsoSignal*)s; + if (d->get_index() == idxA) sigA = d; + if (d->get_index() == idxB) sigB = d; + } + } + + if (sigA == NULL || sigB == NULL) { + _result_label->setText( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CH_NEED_TWO), + "Select two DSO channels.")); + return; + } + if (sigA == sigB) { + _result_label->setText( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CH_NEED_DIFF), + "Select two different channels.")); + return; + } + + // Prefer the analysed snapshot's own sample rate (matches the waveform + // painter); fall back to the session's capture-buffer rate. + double samplerate = (sigA->data() != NULL) ? sigA->data()->samplerate() : 0.0; + if (samplerate <= 0) + samplerate = _session->cur_snap_samplerate(); + const double dt_ns = (samplerate > 0) ? 1e9 / samplerate : 0.0; + + EdgeInfo ea = extract_edges(sigA, dt_ns); + EdgeInfo eb = extract_edges(sigB, dt_ns); + + if (!ea.valid || !eb.valid) { + _result_label->setText( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HIST_NO_DATA), + "No captured data. Run an acquisition first.")); + return; + } + + QString text; + const QString none = "--"; + + const QString freqA = (ea.period > 0) + ? fmt_freq_hz(1e9 / ea.period) : none; + const QString freqB = (eb.period > 0) + ? fmt_freq_hz(1e9 / eb.period) : none; + + text += QString("Freq %1: %2\n").arg(sigA->get_name()).arg(freqA); + text += QString("Freq %1: %2\n\n").arg(sigB->get_name()).arg(freqB); + + // Reference period for phase: the 1st source's period. + const double period = ea.period; + + double delay_ns = 0, skew_ns = 0; + const bool have_delay = mean_delay(ea.rising, eb.rising, period, delay_ns); + const bool have_skew = mean_delay(ea.falling, eb.falling, period, skew_ns); + + text += QString("%1: %2\n") + .arg(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CH_DELAY), "Delay (rise)")) + .arg(have_delay ? fmt_time_ns(delay_ns) : none); + text += QString("%1: %2\n") + .arg(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CH_SKEW), "Skew (fall)")) + .arg(have_skew ? fmt_time_ns(skew_ns) : none); + + QString phase = none; + if (have_delay && period > 0) { + double deg = delay_ns / period * 360.0; + // Present in (-180, 180]. + while (deg > 180) deg -= 360; + while (deg <= -180) deg += 360; + phase = QString::number(deg, 'f', 2) + " °"; + } + text += QString("%1: %2") + .arg(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CH_PHASE), "Phase")) + .arg(phase); + + _result_label->setText(text); +} + +} // namespace dialogs +} // namespace pv diff --git a/DSView/pv/dialogs/chanmeasure.h b/DSView/pv/dialogs/chanmeasure.h new file mode 100644 index 000000000..edf12484c --- /dev/null +++ b/DSView/pv/dialogs/chanmeasure.h @@ -0,0 +1,67 @@ +/* + * This file is part of the DSView project. + * DSView is based on PulseView. + * + * Copyright (C) 2026 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSVIEW_PV_CHANMEASURE_H +#define DSVIEW_PV_CHANMEASURE_H + +#include +#include +#include + +#include "dsdialog.h" +#include "../ui/dscombobox.h" + +namespace pv { + +class SigSession; + +namespace dialogs { + +// Channel-to-channel timing measurements (phase, delay, skew) between two DSO +// channels, computed from the current snapshot. +class DsoChannelMeasure : public DSDialog +{ + Q_OBJECT + +public: + DsoChannelMeasure(SigSession *session, QWidget *parent); + ~DsoChannelMeasure(); + +private slots: + void on_source_changed(int index); + +private: + void compute(); + +private: + SigSession *_session; + + DsComboBox *_srcA_combobox; + DsComboBox *_srcB_combobox; + QLabel *_result_label; + QVBoxLayout *_layout; + QDialogButtonBox _button_box; +}; + +} // namespace dialogs +} // namespace pv + +#endif // DSVIEW_PV_CHANMEASURE_H diff --git a/DSView/pv/dialogs/dsohistogram.cpp b/DSView/pv/dialogs/dsohistogram.cpp new file mode 100644 index 000000000..109730886 --- /dev/null +++ b/DSView/pv/dialogs/dsohistogram.cpp @@ -0,0 +1,442 @@ +/* + * This file is part of the DSView project. + * DSView is based on PulseView. + * + * Copyright (C) 2026 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "dsohistogram.h" + +#include +#include +#include + +#include +#include + +#include "../sigsession.h" +#include "../view/dsosignal.h" +#include "../data/dsosnapshot.h" +#include "../ui/langresource.h" + +using namespace std; + +namespace pv { +namespace dialogs { + +//------------------------------------------------------------------- HistogramPlot + +HistogramPlot::HistogramPlot(QWidget *parent) : + QWidget(parent), + _x_min(0), + _x_max(0) +{ + setMinimumHeight(150); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +void HistogramPlot::set_data(const QVector &bins, double x_min, + double x_max, const QString &x_unit, + const QString &title) +{ + _bins = bins; + _x_min = x_min; + _x_max = x_max; + _x_unit = x_unit; + _title = title; + update(); +} + +void HistogramPlot::clear_data() +{ + _bins.clear(); + update(); +} + +void HistogramPlot::paintEvent(QPaintEvent *event) +{ + (void)event; + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing, false); + + QColor fore(palette().color(foregroundRole())); + QColor bar = fore; + bar.setAlpha(160); + + // Size the title and axis-label bands to the actual font so they are never + // clipped (the UI font can be larger, e.g. in the German translation). + const int text_h = p.fontMetrics().height(); + + const int left = 4; + const int right = width() - 4; + const int top = text_h + 4; // room for the title + const int bottom = height() - text_h - 4; // room for the axis labels + + // Title. + p.setPen(fore); + p.drawText(QRect(left, 0, right - left, top), + Qt::AlignLeft | Qt::AlignVCenter, _title); + + if (bottom - top < 4) + return; + + // Plot frame. + p.setPen(QPen(fore, 1)); + p.drawRect(QRect(left, top, right - left, bottom - top)); + + if (_bins.isEmpty()) { + p.drawText(QRect(left, top, right - left, bottom - top), + Qt::AlignCenter, "--"); + return; + } + + double peak = 0; + for (double v : _bins) + peak = max(peak, v); + if (peak <= 0) + peak = 1; + + const int plot_w = right - left; + const int plot_h = bottom - top; + const int n = _bins.size(); + + p.setPen(Qt::NoPen); + p.setBrush(bar); + for (int i = 0; i < n; i++) { + const int x0 = left + (int)((double)i / n * plot_w); + const int x1 = left + (int)((double)(i + 1) / n * plot_w); + const int h = (int)(_bins[i] / peak * (plot_h - 1)); + if (h > 0) + p.fillRect(QRect(x0, bottom - h, max(1, x1 - x0 - 1), h), bar); + } + + // Axis min/max annotations, in the band below the frame. + p.setPen(fore); + p.drawText(QRect(left, bottom, plot_w, height() - bottom), + Qt::AlignLeft | Qt::AlignVCenter, + QString::number(_x_min, 'f', 2) + " " + _x_unit); + p.drawText(QRect(left, bottom, plot_w, height() - bottom), + Qt::AlignRight | Qt::AlignVCenter, + QString::number(_x_max, 'f', 2) + " " + _x_unit); +} + +//------------------------------------------------------------------- DsoHistogram + +DsoHistogram::DsoHistogram(SigSession *session, QWidget *parent) : + DSDialog(parent), + _session(session), + _button_box(QDialogButtonBox::Close, Qt::Horizontal, this) +{ + setMinimumSize(552, 460); + + _ch_combobox = new DsComboBox(this); + for (auto s : _session->get_signals()) { + if (s->signal_type() == SR_CHANNEL_DSO) { + view::DsoSignal *dsoSig = (view::DsoSignal*)s; + _ch_combobox->addItem(dsoSig->get_name(), + QVariant::fromValue(dsoSig->get_index())); + } + } + + _value_plot = new HistogramPlot(this); + _time_plot = new HistogramPlot(this); + + _stats_label = new QLabel(this); + _stats_label->setTextInteractionFlags(Qt::TextSelectableByMouse); + _stats_label->setWordWrap(true); + _stats_label->setTextFormat(Qt::RichText); + + // Voltage unit selector (values are computed in mV internally). + _unit_combobox = new DsComboBox(this); + _unit_combobox->addItem("mV", QVariant::fromValue(1.0)); + _unit_combobox->addItem("V", QVariant::fromValue(0.001)); + // DsComboBox forces AdjustToContents, which makes setMinimumWidth() a + // no-op; size it by a minimum contents length instead so the item text + // (plus the drop-down arrow) is not cropped. + _unit_combobox->setSizeAdjustPolicy( + QComboBox::AdjustToMinimumContentsLengthWithIcon); + _unit_combobox->setMinimumContentsLength(5); + + QLabel *ch_label = new QLabel( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CHANNEL), "Channel"), this); + QLabel *unit_label = new QLabel( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_UNIT), "Unit"), this); + QHBoxLayout *ch_layout = new QHBoxLayout(); + ch_layout->addWidget(ch_label); + ch_layout->addWidget(_ch_combobox); + ch_layout->addStretch(1); + ch_layout->addWidget(unit_label); + ch_layout->addWidget(_unit_combobox); + + _layout = new QVBoxLayout(); + _layout->addLayout(ch_layout); + _layout->addWidget(_value_plot, 1); + _layout->addWidget(_time_plot, 1); + _layout->addWidget(_stats_label); + _layout->addWidget(&_button_box); + + layout()->addLayout(_layout); + setTitle(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HISTOGRAM), "Histogram / Jitter")); + + connect(&_button_box, SIGNAL(rejected()), this, SLOT(reject())); + connect(&_button_box, SIGNAL(accepted()), this, SLOT(accept())); + connect(_ch_combobox, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_channel_changed(int))); + connect(_unit_combobox, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_channel_changed(int))); + + compute(); +} + +DsoHistogram::~DsoHistogram() +{ +} + +void DsoHistogram::on_channel_changed(int index) +{ + (void)index; + compute(); +} + +// Format a time interval given in nanoseconds. +static QString fmt_time_ns(double t_ns) +{ + const double a = fabs(t_ns); + if (a >= 1e9) + return QString::number(t_ns / 1e9, 'f', 3) + " s"; + if (a >= 1e6) + return QString::number(t_ns / 1e6, 'f', 3) + " ms"; + if (a >= 1e3) + return QString::number(t_ns / 1e3, 'f', 3) + " us"; + return QString::number(t_ns, 'f', 2) + " ns"; +} + +static QString fmt_freq_hz(double f_hz) +{ + const double a = fabs(f_hz); + if (a >= 1e6) + return QString::number(f_hz / 1e6, 'f', 3) + " MHz"; + if (a >= 1e3) + return QString::number(f_hz / 1e3, 'f', 3) + " kHz"; + return QString::number(f_hz, 'f', 2) + " Hz"; +} + +void DsoHistogram::compute() +{ + _value_plot->clear_data(); + _time_plot->clear_data(); + + int index = -1; + if (_ch_combobox->count() > 0) + index = _ch_combobox->itemData(_ch_combobox->currentIndex()).toInt(); + + view::DsoSignal *dsoSig = NULL; + for (auto s : _session->get_signals()) { + if (s->signal_type() == SR_CHANNEL_DSO) { + view::DsoSignal *d = (view::DsoSignal*)s; + if (d->get_index() == index) { + dsoSig = d; + break; + } + } + } + + if (dsoSig == NULL || !dsoSig->enabled()) { + _stats_label->setText( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HIST_NO_CHANNEL), + "No enabled DSO channel selected.")); + return; + } + + data::DsoSnapshot *data = dsoSig->data(); + if (data == NULL || data->empty()) { + _stats_label->setText( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HIST_NO_DATA), + "No captured data. Run an acquisition first.")); + return; + } + + const uint64_t total = data->get_sample_count(); + const uint8_t *buf = data->get_samples(0, 0, dsoSig->get_index()); + if (total < 2 || buf == NULL) { + _stats_label->setText( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HIST_NO_DATA), + "No captured data. Run an acquisition first.")); + return; + } + + // Raw-sample -> millivolt conversion, mirroring DsoSignal::get_voltage(). + const int hw_offset = dsoSig->get_hw_offset(); + const double k = data->get_measure_voltage_factor(dsoSig->get_index()); + const double data_scale = data->get_data_scale(dsoSig->get_index()); + const double vfactor = dsoSig->get_vDial()->get_factor(); + const int vrect_h = dsoSig->get_view_rect().height(); + const double vscale = (vrect_h > 0) + ? data_scale * k * vfactor * DS_CONF_DSO_VDIVS / vrect_h : 0.0; + + // Keep the dialog responsive on very deep captures. + const uint64_t MaxSamples = 8000000; + const uint64_t n = min(total, MaxSamples); + + auto volt = [&](uint64_t i) -> double { + return (hw_offset - (double)buf[i]) * vscale; + }; + + // --- value (amplitude) histogram + stats --- + double vmin = 1e300, vmax = -1e300, vsum = 0; + for (uint64_t i = 0; i < n; i++) { + const double v = volt(i); + vmin = min(vmin, v); + vmax = max(vmax, v); + vsum += v; + } + const double vmean = vsum / n; + const double vspan = (vmax > vmin) ? (vmax - vmin) : 1.0; + + // Selected display unit (values are computed in mV): factor + precision. + const double vfac = _unit_combobox->itemData( + _unit_combobox->currentIndex()).toDouble(); + const QString vunit = _unit_combobox->currentText(); + const int vprec = (vfac < 1.0) ? 4 : 2; // more decimals when showing V + + const int NBINS = 128; + QVector vbins(NBINS, 0.0); + for (uint64_t i = 0; i < n; i++) { + int b = (int)((volt(i) - vmin) / vspan * (NBINS - 1)); + b = max(0, min(NBINS - 1, b)); + vbins[b] += 1.0; + } + _value_plot->set_data(vbins, vmin * vfac, vmax * vfac, vunit, + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HIST_VALUE), "Value distribution")); + + // --- timing (jitter) histogram + stats --- + // Use the analysed snapshot's own sample rate (this is what the waveform + // painter uses); cur_snap_samplerate() reads the capture buffer, which can + // read back as 0 for the view buffer and silently disable jitter analysis. + double samplerate = data->samplerate(); + if (samplerate <= 0) + samplerate = _session->cur_snap_samplerate(); + const double dt_ns = (samplerate > 0) ? 1e9 / samplerate : 0.0; + + // Detect edges directly on the raw ADC samples (0..255). This keeps the + // jitter measurement independent of the voltage scaling, which can read + // back as zero in some states and would otherwise flatten the signal. + int raw_min = 255, raw_max = 0; + for (uint64_t i = 0; i < n; i++) { + const int r = buf[i]; + raw_min = min(raw_min, r); + raw_max = max(raw_max, r); + } + const double raw_mid = (raw_min + raw_max) / 2.0; + const double raw_hyst = (raw_max - raw_min) * 0.05; + + // Schmitt-trigger edge detection: track the high/low state and flip it on + // the upper/lower hysteresis thresholds. A rising edge is recorded each + // time the state goes low->high. This handles finite rise time and noise + // (unlike a single-step "crossed mid this sample" test). + std::vector edges; // rising-edge sample indices + if (raw_max > raw_min) { + const double hi = raw_mid + raw_hyst; + const double lo = raw_mid - raw_hyst; + bool is_high = (buf[0] >= raw_mid); + for (uint64_t i = 1; i < n; i++) { + const int r = buf[i]; + if (!is_high && r >= hi) { + is_high = true; + edges.push_back(i); + } else if (is_high && r <= lo) { + is_high = false; + } + } + } + + // Build the statistics as a two-column table (metric | value) so the + // amplitude and timing figures line up and read cleanly. + auto row = [](const QString &k, const QString &v) { + return QString("%1" + "%2").arg(k).arg(v); + }; + auto sep = []() { + return QString("
"); + }; + + auto vstr = [&](double mv_value) { + return QString::number(mv_value * vfac, 'f', vprec) + " " + vunit; + }; + + QString stats = ""; + stats += row(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CHANNEL), "Channel"), + "CH" + dsoSig->get_name()); + stats += row("Samples", QString::number(n)); + stats += sep(); + stats += row("Vmax", vstr(vmax)); + stats += row("Vmin", vstr(vmin)); + stats += row("Vpp", vstr(vmax - vmin)); + stats += row("Vmean", vstr(vmean)); + + if (edges.size() >= 3 && dt_ns > 0) { + std::vector periods; // ns + periods.reserve(edges.size() - 1); + for (size_t e = 1; e < edges.size(); e++) + periods.push_back((edges[e] - edges[e - 1]) * dt_ns); + + double pmin = 1e300, pmax = -1e300, psum = 0; + for (double p : periods) { + pmin = min(pmin, p); + pmax = max(pmax, p); + psum += p; + } + const double pmean = psum / periods.size(); + double var = 0; + for (double p : periods) + var += (p - pmean) * (p - pmean); + const double pstd = sqrt(var / periods.size()); // RMS jitter + const double ppk = pmax - pmin; // pk-pk jitter + + const int TBINS = 128; + QVector tbins(TBINS, 0.0); + const double pspan = (pmax > pmin) ? (pmax - pmin) : 1.0; + for (double p : periods) { + int b = (int)((p - pmin) / pspan * (TBINS - 1)); + b = max(0, min(TBINS - 1, b)); + tbins[b] += 1.0; + } + _time_plot->set_data(tbins, pmin, pmax, "ns", + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HIST_PERIOD), + "Period distribution (jitter)")); + + stats += sep(); + stats += row("Edges", QString::number((qulonglong)edges.size())); + stats += row("Mean period", fmt_time_ns(pmean)); + stats += row("Frequency", fmt_freq_hz(1e9 / pmean)); + stats += row("RMS jitter", fmt_time_ns(pstd)); + stats += row("Pk-pk jitter", fmt_time_ns(ppk)); + } else { + stats += sep(); + stats += QString("").arg( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HIST_NO_JITTER), + "Not enough edges for jitter analysis.")); + stats += row("Edges found", QString::number((qulonglong)edges.size())); + } + + stats += "
%1
"; + _stats_label->setText(stats); +} + +} // namespace dialogs +} // namespace pv diff --git a/DSView/pv/dialogs/dsohistogram.h b/DSView/pv/dialogs/dsohistogram.h new file mode 100644 index 000000000..e845a92a9 --- /dev/null +++ b/DSView/pv/dialogs/dsohistogram.h @@ -0,0 +1,95 @@ +/* + * This file is part of the DSView project. + * DSView is based on PulseView. + * + * Copyright (C) 2026 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSVIEW_PV_DSOHISTOGRAM_H +#define DSVIEW_PV_DSOHISTOGRAM_H + +#include +#include +#include +#include +#include + +#include "dsdialog.h" +#include "../ui/dscombobox.h" + +namespace pv { + +class SigSession; + +namespace dialogs { + +// A small bar-chart widget that draws a single histogram (bin counts) with an +// x-axis annotated by its min/max value and a title. +class HistogramPlot : public QWidget +{ + Q_OBJECT + +public: + HistogramPlot(QWidget *parent = NULL); + + void set_data(const QVector &bins, double x_min, double x_max, + const QString &x_unit, const QString &title); + void clear_data(); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QVector _bins; + double _x_min; + double _x_max; + QString _x_unit; + QString _title; +}; + +// Value + timing (jitter) histogram of a single DSO channel, with basic +// amplitude and jitter statistics. Computed once from the current snapshot. +class DsoHistogram : public DSDialog +{ + Q_OBJECT + +public: + DsoHistogram(SigSession *session, QWidget *parent); + ~DsoHistogram(); + +private slots: + void on_channel_changed(int index); + +private: + void compute(); + +private: + SigSession *_session; + + DsComboBox *_ch_combobox; + DsComboBox *_unit_combobox; + HistogramPlot *_value_plot; + HistogramPlot *_time_plot; + QLabel *_stats_label; + QVBoxLayout *_layout; + QDialogButtonBox _button_box; +}; + +} // namespace dialogs +} // namespace pv + +#endif // DSVIEW_PV_DSOHISTOGRAM_H diff --git a/DSView/pv/dialogs/mathoptions.cpp b/DSView/pv/dialogs/mathoptions.cpp index ac76ffda0..f295113e2 100644 --- a/DSView/pv/dialogs/mathoptions.cpp +++ b/DSView/pv/dialogs/mathoptions.cpp @@ -55,23 +55,38 @@ MathOptions::MathOptions(SigSession *session, QWidget *parent) : lisa_label->setPixmap(QPixmap(":/icons/math.svg")); _math_group = new QGroupBox(this); - QHBoxLayout *type_layout = new QHBoxLayout(); - QRadioButton *add_radio = new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_ADD), "Add"), _math_group); - add_radio->setProperty("type", data::MathStack::MATH_ADD); - type_layout->addWidget(add_radio); - QRadioButton *sub_radio = new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SUBSTRACT), "Substract"), _math_group); - sub_radio->setProperty("type", data::MathStack::MATH_SUB); - type_layout->addWidget(sub_radio); - QRadioButton *mul_radio = new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_MULTIPLY), "Multiply"), _math_group); - mul_radio->setProperty("type", data::MathStack::MATH_MUL); - type_layout->addWidget(mul_radio); - QRadioButton *div_radio = new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_DIVIDE), "Divide"), _math_group); - div_radio->setProperty("type", data::MathStack::MATH_DIV); - type_layout->addWidget(div_radio); - _math_radio.append(add_radio); - _math_radio.append(sub_radio); - _math_radio.append(mul_radio); - _math_radio.append(div_radio); + QGridLayout *type_layout = new QGridLayout(); + + // Place operator radios in a 4-column grid (11 operators no longer fit on + // a single row). + int rrow = 0, rcol = 0; + auto place = [&](QRadioButton *b, int t) { + b->setProperty("type", t); + _math_radio.append(b); + type_layout->addWidget(b, rrow, rcol); + if (++rcol >= 4) { rcol = 0; rrow++; } + }; + + place(new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_ADD), "Add"), _math_group), data::MathStack::MATH_ADD); + place(new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SUBSTRACT), "Substract"), _math_group), data::MathStack::MATH_SUB); + place(new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_MULTIPLY), "Multiply"), _math_group), data::MathStack::MATH_MUL); + place(new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_DIVIDE), "Divide"), _math_group), data::MathStack::MATH_DIV); + place(new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_INTEGRATE), "Integrate"), _math_group), data::MathStack::MATH_INTEG); + place(new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_DIFFERENTIATE), "Differentiate"), _math_group), data::MathStack::MATH_DIFF); + place(new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_ABSOLUTE), "Absolute"), _math_group), data::MathStack::MATH_ABS); + place(new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SQUARE), "Square"), _math_group), data::MathStack::MATH_SQUARE); + place(new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_SQRT), "Sqrt"), _math_group), data::MathStack::MATH_SQRT); + place(new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_LOWPASS), "Low pass"), _math_group), data::MathStack::MATH_LOWPASS); + place(new QRadioButton(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HIGHPASS), "High pass"), _math_group), data::MathStack::MATH_HIGHPASS); + + // Moving-average window (samples) used by the low/high-pass filters. + _filter_label = new QLabel(_math_group); + _filter_width = new QSpinBox(_math_group); + _filter_width->setRange(1, 100000); + _filter_width->setValue(10); + type_layout->addWidget(_filter_label, rrow + 1, 0, 1, 2); + type_layout->addWidget(_filter_width, rrow + 1, 2, 1, 2); + _math_group->setLayout(type_layout); _src1_group = new QGroupBox(this); @@ -104,6 +119,7 @@ MathOptions::MathOptions(SigSession *session, QWidget *parent) : auto math = _session->get_math_trace(); if (math) { _enable->setChecked(math->enabled()); + _filter_width->setValue(math->get_math_stack()->get_filter_width()); for (QVector::const_iterator i = _src1_radio.begin(); i != _src1_radio.end(); i++) { if ((*i)->property("index").toInt() == math->src1()) { @@ -171,6 +187,7 @@ void MathOptions::retranslateUi() { _enable->setText(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_ENABLE), "Enable")); _math_group->setTitle(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_MATH_TYPE), "Math Type")); + _filter_label->setText(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_FILTER_WIDTH), "Filter window (samples)")); _src1_group->setTitle(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_1ST_SOURCE), "1st Source")); _src2_group->setTitle(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_2ST_SOURCE), "2st Source")); setTitle(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_MATH_OPTIONS), "Math Options")); @@ -208,6 +225,11 @@ void MathOptions::Apply() break; } } + // Unary operators (integrate/differentiate/abs/square/sqrt/filters) only + // use the 1st source; fall back to it so a 2nd source need not be picked. + if (data::MathStack::is_unary(type) && src2 == -1) + src2 = src1; + bool enable = (src1 != -1 && src2 != -1 && _enable->isChecked()); view::DsoSignal *dsoSig1 = NULL; view::DsoSignal *dsoSig2 = NULL; @@ -223,8 +245,8 @@ void MathOptions::Apply() } if (dsoSig1 != NULL && dsoSig2 != NULL){ - _session->math_rebuild(enable, dsoSig1, dsoSig2, type); - } + _session->math_rebuild(enable, dsoSig1, dsoSig2, type, _filter_width->value()); + } } void MathOptions::reject() diff --git a/DSView/pv/dialogs/mathoptions.h b/DSView/pv/dialogs/mathoptions.h index 30b5c43bd..483cc4133 100644 --- a/DSView/pv/dialogs/mathoptions.h +++ b/DSView/pv/dialogs/mathoptions.h @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include "../view/dsosignal.h" #include "../toolbars/titlebar.h" #include "dsdialog.h" @@ -80,6 +82,8 @@ class MathOptions : public DSDialog, public IUiWindow QVector _src1_radio; QVector _src2_radio; QVector _math_radio; + QLabel *_filter_label; + QSpinBox *_filter_width; QDialogButtonBox _button_box; QGridLayout *_layout; }; diff --git a/DSView/pv/dialogs/refoptions.cpp b/DSView/pv/dialogs/refoptions.cpp new file mode 100644 index 000000000..08cb3d953 --- /dev/null +++ b/DSView/pv/dialogs/refoptions.cpp @@ -0,0 +1,123 @@ +/* + * This file is part of the DSView project. + * DSView is based on PulseView. + * + * Copyright (C) 2026 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "refoptions.h" + +#include +#include + +#include "../sigsession.h" +#include "../view/dsosignal.h" +#include "../ui/langresource.h" + +using namespace std; + +namespace pv { +namespace dialogs { + +RefOptions::RefOptions(SigSession *session, QWidget *parent) : + DSDialog(parent), + _session(session), + _button_box(QDialogButtonBox::Close, Qt::Horizontal, this) +{ + setMinimumSize(340, 180); + + _ch_combobox = new DsComboBox(this); + for (auto s : _session->get_signals()) { + if (s->signal_type() == SR_CHANNEL_DSO) { + view::DsoSignal *dsoSig = (view::DsoSignal*)s; + _ch_combobox->addItem(dsoSig->get_name(), + QVariant::fromValue(dsoSig->get_index())); + } + } + + _save_btn = new QPushButton( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_REF_SAVE), "Save reference"), this); + _clear_btn = new QPushButton( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_REF_CLEAR), "Clear all"), this); + _count_label = new QLabel(this); + + QFormLayout *ch_layout = new QFormLayout(); + ch_layout->addRow( + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CHANNEL), "Channel"), _ch_combobox); + + QHBoxLayout *btn_layout = new QHBoxLayout(); + btn_layout->addWidget(_save_btn); + btn_layout->addWidget(_clear_btn); + + _layout = new QVBoxLayout(); + _layout->addLayout(ch_layout); + _layout->addLayout(btn_layout); + _layout->addWidget(_count_label); + _layout->addWidget(&_button_box); + + layout()->addLayout(_layout); + setTitle(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_REF_OPTIONS), "Reference Waveforms")); + + connect(&_button_box, SIGNAL(rejected()), this, SLOT(reject())); + connect(&_button_box, SIGNAL(accepted()), this, SLOT(accept())); + connect(_save_btn, SIGNAL(clicked()), this, SLOT(on_save())); + connect(_clear_btn, SIGNAL(clicked()), this, SLOT(on_clear())); + + update_count(); +} + +RefOptions::~RefOptions() +{ +} + +void RefOptions::update_count() +{ + _count_label->setText( + QString("%1: %2") + .arg(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_REF_STORED), "Stored references")) + .arg((int)_session->get_ref_waves().size())); +} + +void RefOptions::on_save() +{ + if (_ch_combobox->count() == 0) + return; + + const int index = + _ch_combobox->itemData(_ch_combobox->currentIndex()).toInt(); + + for (auto s : _session->get_signals()) { + if (s->signal_type() == SR_CHANNEL_DSO) { + view::DsoSignal *d = (view::DsoSignal*)s; + if (d->get_index() == index) { + _session->add_ref_wave(d); + break; + } + } + } + + update_count(); +} + +void RefOptions::on_clear() +{ + _session->clear_ref_waves(); + update_count(); +} + +} // namespace dialogs +} // namespace pv diff --git a/DSView/pv/dialogs/refoptions.h b/DSView/pv/dialogs/refoptions.h new file mode 100644 index 000000000..1e6745b34 --- /dev/null +++ b/DSView/pv/dialogs/refoptions.h @@ -0,0 +1,70 @@ +/* + * This file is part of the DSView project. + * DSView is based on PulseView. + * + * Copyright (C) 2026 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSVIEW_PV_REFOPTIONS_H +#define DSVIEW_PV_REFOPTIONS_H + +#include +#include +#include +#include + +#include "dsdialog.h" +#include "../ui/dscombobox.h" + +namespace pv { + +class SigSession; + +namespace dialogs { + +// Save the current DSO channel as a frozen reference waveform, or clear the +// stored references. References are overlaid on the live view. +class RefOptions : public DSDialog +{ + Q_OBJECT + +public: + RefOptions(SigSession *session, QWidget *parent); + ~RefOptions(); + +private slots: + void on_save(); + void on_clear(); + +private: + void update_count(); + +private: + SigSession *_session; + + DsComboBox *_ch_combobox; + QPushButton *_save_btn; + QPushButton *_clear_btn; + QLabel *_count_label; + QVBoxLayout *_layout; + QDialogButtonBox _button_box; +}; + +} // namespace dialogs +} // namespace pv + +#endif // DSVIEW_PV_REFOPTIONS_H diff --git a/DSView/pv/sigsession.cpp b/DSView/pv/sigsession.cpp index 2546e6da4..751e6075d 100644 --- a/DSView/pv/sigsession.cpp +++ b/DSView/pv/sigsession.cpp @@ -1781,7 +1781,8 @@ namespace pv void SigSession::math_rebuild(bool enable, view::DsoSignal *dsoSig1, view::DsoSignal *dsoSig2, - data::MathStack::MathType type) + data::MathStack::MathType type, + int filter_width) { ds_lock_guard lock(_data_mutex); @@ -1790,7 +1791,8 @@ namespace pv DESTROY_OBJECT(_math_trace); - auto math_stack = new data::MathStack(this, dsoSig1, dsoSig2, type); + auto math_stack = new data::MathStack(this, dsoSig1, dsoSig2, type); + math_stack->set_filter_width(filter_width); _math_trace = new view::MathTrace(enable, math_stack, dsoSig1, dsoSig2); if (_math_trace && _math_trace->enabled()) @@ -1811,6 +1813,40 @@ namespace pv _math_trace->set_enable(false); } + void SigSession::add_ref_wave(view::DsoSignal *sig) + { + if (sig == NULL) + return; + + data::DsoSnapshot *data = sig->data(); + if (data == NULL || data->empty()) + return; + + const uint64_t n = data->get_sample_count(); + const uint8_t *buf = data->get_samples(0, 0, sig->get_index()); + if (n == 0 || buf == NULL) + return; + + RefWave rw; + rw.index = sig->get_index(); + rw.samples.assign(buf, buf + n); + rw.samplerate = data->samplerate(); + rw.colour = sig->get_colour(); + rw.name = QString("Ref %1").arg((int)_ref_waves.size() + 1); + _ref_waves.push_back(rw); + + signals_changed(); + } + + void SigSession::clear_ref_waves() + { + if (_ref_waves.empty()) + return; + + _ref_waves.clear(); + signals_changed(); + } + void SigSession::nodata_timeout() { int flag; @@ -2475,9 +2511,10 @@ namespace pv } void SigSession::clear_signals() - { + { DESTROY_OBJECT(_math_trace); - + _ref_waves.clear(); + for (int i=0; i< (int)_signals.size(); i++) { auto *p = _signals[i]; diff --git a/DSView/pv/sigsession.h b/DSView/pv/sigsession.h index e6830e097..7fc44b8a7 100644 --- a/DSView/pv/sigsession.h +++ b/DSView/pv/sigsession.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -239,7 +240,26 @@ class SigSession: inline view::MathTrace* get_math_trace(){ return _math_trace; } - + + // ---- Reference waveforms ------------------------------------------------ + // A frozen copy of a DSO channel's samples, overlaid on the live view. It + // is rendered with the source channel's current vertical scaling (looked + // up by index at paint time) so it stays aligned to the grid. + struct RefWave { + int index; // source DSO channel index + std::vector samples; + double samplerate; + QColor colour; + QString name; + }; + + // Snapshot the given DSO channel into a new reference waveform. + void add_ref_wave(view::DsoSignal *sig); + void clear_ref_waves(); + inline std::vector& get_ref_waves(){ + return _ref_waves; + } + uint16_t get_ch_num(int type); inline bool is_data_lock(){ @@ -255,7 +275,8 @@ class SigSession: void math_rebuild(bool enable,pv::view::DsoSignal *dsoSig1, pv::view::DsoSignal *dsoSig2, - data::MathStack::MathType type); + data::MathStack::MathType type, + int filter_width = 10); inline bool trigd(){ return _trigger_flag; @@ -574,6 +595,7 @@ class SigSession: std::vector _spectrum_traces; view::LissajousTrace *_lissajous_trace; view::MathTrace *_math_trace; + std::vector _ref_waves; DsTimer _feed_timer; DsTimer _out_timer; diff --git a/DSView/pv/toolbars/trigbar.cpp b/DSView/pv/toolbars/trigbar.cpp index 8f43a254d..543594f92 100644 --- a/DSView/pv/toolbars/trigbar.cpp +++ b/DSView/pv/toolbars/trigbar.cpp @@ -29,6 +29,9 @@ #include "../dialogs/fftoptions.h" #include "../dialogs/lissajousoptions.h" #include "../dialogs/mathoptions.h" +#include "../dialogs/dsohistogram.h" +#include "../dialogs/chanmeasure.h" +#include "../dialogs/refoptions.h" #include "../view/trace.h" #include "../dialogs/applicationpardlg.h" #include "../ui/langresource.h" @@ -60,11 +63,23 @@ TrigBar::TrigBar(SigSession *session, QWidget *parent) : _action_math = new QAction(this); _action_math->setObjectName(QString::fromUtf8("actionMath")); - + + _action_histogram = new QAction(this); + _action_histogram->setObjectName(QString::fromUtf8("actionHistogram")); + + _action_chanmeasure = new QAction(this); + _action_chanmeasure->setObjectName(QString::fromUtf8("actionChanMeasure")); + + _action_reference = new QAction(this); + _action_reference->setObjectName(QString::fromUtf8("actionReference")); + _function_menu = new QMenu(this); _function_menu->setContentsMargins(0,0,0,0); _function_menu->addAction(_action_fft); _function_menu->addAction(_action_math); + _function_menu->addAction(_action_histogram); + _function_menu->addAction(_action_chanmeasure); + _function_menu->addAction(_action_reference); _function_button.setPopupMode(QToolButton::InstantPopup); _function_button.setMenu(_function_menu); @@ -129,6 +144,9 @@ TrigBar::TrigBar(SigSession *session, QWidget *parent) : connect(_action_fft, SIGNAL(triggered()), this, SLOT(on_actionFft_triggered())); connect(_action_math, SIGNAL(triggered()), this, SLOT(on_actionMath_triggered())); + connect(_action_histogram, SIGNAL(triggered()), this, SLOT(on_actionHistogram_triggered())); + connect(_action_chanmeasure, SIGNAL(triggered()), this, SLOT(on_actionChanMeasure_triggered())); + connect(_action_reference, SIGNAL(triggered()), this, SLOT(on_actionReference_triggered())); connect(_action_lissajous, SIGNAL(triggered()), this, SLOT(on_actionLissajous_triggered())); connect(_dark_style, SIGNAL(triggered()), this, SLOT(on_actionDark_triggered())); connect(_light_style, SIGNAL(triggered()), this, SLOT(on_actionLight_triggered())); @@ -165,6 +183,9 @@ void TrigBar::retranslateUi() _action_fft->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_FUNCTION_FFT), "FFT")); _action_math->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_FUNCTION_MATH), "Math")); + _action_histogram->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_FUNCTION_HISTOGRAM), "Histogram")); + _action_chanmeasure->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_FUNCTION_CHMEASURE), "Ch-Ch Measure")); + _action_reference->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_FUNCTION_REFERENCE), "Reference")); _action_dispalyOptions->setText(L_S(STR_PAGE_TOOLBAR, S_ID(IDS_TOOLBAR_DISPLAY_OPTIONS), "Options")); } @@ -183,6 +204,9 @@ void TrigBar::reStyle() _action_fft->setIcon(QIcon(iconPath+"/fft.svg")); _action_math->setIcon(QIcon(iconPath+"/math.svg")); + _action_histogram->setIcon(QIcon(iconPath+"/measure.svg")); + _action_chanmeasure->setIcon(QIcon(iconPath+"/measure.svg")); + _action_reference->setIcon(QIcon(iconPath+"/math.svg")); _action_lissajous->setIcon(QIcon(iconPath+"/lissajous.svg")); _dark_style->setIcon(QIcon(iconPath+"/dark.svg")); _light_style->setIcon(QIcon(iconPath+"/light.svg")); @@ -329,6 +353,24 @@ void TrigBar::on_actionMath_triggered() } } +void TrigBar::on_actionHistogram_triggered() +{ + pv::dialogs::DsoHistogram hist_dlg(_session, this); + hist_dlg.exec(); +} + +void TrigBar::on_actionChanMeasure_triggered() +{ + pv::dialogs::DsoChannelMeasure ch_dlg(_session, this); + ch_dlg.exec(); +} + +void TrigBar::on_actionReference_triggered() +{ + pv::dialogs::RefOptions ref_dlg(_session, this); + ref_dlg.exec(); +} + void TrigBar::on_actionDark_triggered() { sig_setTheme(THEME_STYLE_DARK); diff --git a/DSView/pv/toolbars/trigbar.h b/DSView/pv/toolbars/trigbar.h index 25d843b0a..1d33c5185 100644 --- a/DSView/pv/toolbars/trigbar.h +++ b/DSView/pv/toolbars/trigbar.h @@ -78,6 +78,9 @@ private slots: void on_actionLissajous_triggered(); void on_actionFft_triggered(); void on_actionMath_triggered(); + void on_actionHistogram_triggered(); + void on_actionChanMeasure_triggered(); + void on_actionReference_triggered(); void on_display_setting(); public slots: @@ -108,6 +111,9 @@ public slots: QMenu *_function_menu; QAction *_action_fft; QAction *_action_math; + QAction *_action_histogram; + QAction *_action_chanmeasure; + QAction *_action_reference; QMenu *_display_menu; QMenu *_themes; diff --git a/DSView/pv/view/dsosignal.cpp b/DSView/pv/view/dsosignal.cpp index 0d0b2e26b..901bbf406 100644 --- a/DSView/pv/view/dsosignal.cpp +++ b/DSView/pv/view/dsosignal.cpp @@ -885,6 +885,12 @@ void DsoSignal::paint_mid(QPainter &p, int left, int right, QColor fore, QColor _mean = (index == 0) ? status.ch0_acc_mean : status.ch1_acc_mean; _mean = hw_offset - _mean / _data->get_sample_count(); } + + // The hardware frequently returns no cycle data for the 2nd + // channel even on a valid signal, so its period/frequency/duty/ + // count/width read "--". Fall back to a software measurement. + if (!_level_valid || _period == 0) + compute_soft_measure(hw_offset); } } } @@ -1035,6 +1041,108 @@ void DsoSignal::paint_trace(QPainter &p, } } +void DsoSignal::compute_soft_measure(int hw_offset) +{ + if (_data == NULL || _data->empty()) + return; + + const uint64_t total = _data->get_sample_count(); + if (total < 2) + return; + + const uint8_t *buf = _data->get_samples(0, 0, get_index()); + if (buf == NULL) + return; + + const double samplerate = _data->samplerate(); + if (samplerate <= 0) + return; + + uint16_t total_channels = g_slist_length(session->get_device()->get_channels()); + if (total_channels == 1 && _data->is_file()) + total_channels++; + const uint16_t enabled_channels = _data->get_channel_num(); + if (enabled_channels == 0) + return; + + // Nanoseconds per channel sample (matches the hardware measurement path). + const double tfactor = ((double)total_channels / enabled_channels) + * SR_GHZ(1) * 1.0 / samplerate; + + // Cap the scan so continuous repaints stay cheap; this still covers many + // cycles for a stable measurement. + const uint64_t MaxScan = 1000000; + const uint64_t n = min(total, MaxScan); + + // Work in voltage-proportional space (higher value = higher voltage) so + // "high time" matches the hardware's positive-duty convention. + auto val = [&](uint64_t i) -> double { return (double)hw_offset - buf[i]; }; + + double vmin = 1e300, vmax = -1e300; + int rmin = 255, rmax = 0; + for (uint64_t i = 0; i < n; i++) { + const double v = val(i); + vmin = min(vmin, v); + vmax = max(vmax, v); + rmin = min(rmin, (int)buf[i]); + rmax = max(rmax, (int)buf[i]); + } + if (vmax <= vmin) + return; // flat: nothing to measure + + const double mid = (vmin + vmax) / 2.0; + const double hyst = (vmax - vmin) * 0.05; + const double hi = mid + hyst; + const double lo = mid - hyst; + + // Schmitt-trigger edge detection (rising = low->high voltage). + std::vector rising, falling; + bool is_high = (val(0) >= mid); + for (uint64_t i = 1; i < n; i++) { + const double v = val(i); + if (!is_high && v >= hi) { + is_high = true; + rising.push_back(i); + } else if (is_high && v <= lo) { + is_high = false; + falling.push_back(i); + } + } + + if (rising.size() < 2) + return; // not enough cycles + + // Mean period (samples) from consecutive rising edges. + double psum = 0; + for (size_t k = 1; k < rising.size(); k++) + psum += rising[k] - rising[k - 1]; + const double period_samples = psum / (rising.size() - 1); + + // Mean high-level duration: rising edge to the next falling edge. + double hsum = 0; + int hn = 0; + size_t fi = 0; + for (size_t k = 0; k < rising.size(); k++) { + while (fi < falling.size() && falling[fi] <= rising[k]) + fi++; + if (fi < falling.size()) { + hsum += (double)(falling[fi] - rising[k]); + hn++; + } + } + const double high_samples = (hn > 0) ? hsum / hn : 0.0; + + _period = period_samples * tfactor; + _high_time = high_samples * tfactor; + _pcount = (uint32_t)rising.size(); + _min = (uint8_t)rmin; + _max = (uint8_t)rmax; + _low = (uint8_t)rmin; + _high = (uint8_t)rmax; + _level_valid = true; + _mValid = true; +} + void DsoSignal::paint_envelope(QPainter &p, const pv::data::DsoSnapshot *snapshot, int zeroY, int left, const int64_t start, const int64_t end, int hw_offset, @@ -1286,25 +1394,15 @@ QRectF DsoSignal::get_rect(DsoSetRegions type, int y, int right) void DsoSignal::paint_hover_measure(QPainter &p, QColor fore, QColor back) { const int hw_offset = get_hw_offset(); - // Hover measure - if (_hover_en && _hover_point != QPointF(-1, -1)) { - QString hover_str = get_voltage(hw_offset - _hover_value, 2); - const int hover_width = p.boundingRect(0, 0, INT_MAX, INT_MAX, - Qt::AlignLeft | Qt::AlignTop, hover_str).width() + 10; - const int hover_height = p.boundingRect(0, 0, INT_MAX, INT_MAX, - Qt::AlignLeft | Qt::AlignTop, hover_str).height(); - QRectF hover_rect(_hover_point.x(), _hover_point.y()-hover_height/2, hover_width, hover_height); - if (hover_rect.right() > get_view_rect().right()) - hover_rect.moveRight(_hover_point.x()); - if (hover_rect.top() < get_view_rect().top()) - hover_rect.moveTop(_hover_point.y()); - if (hover_rect.bottom() > get_view_rect().bottom()) - hover_rect.moveBottom(_hover_point.y()); + // Hover measure. Only the point marker is drawn on the trace here; the + // voltage value itself is shown in the consolidated floating panel drawn + // by Viewport::paintMeasure (see the DSO_VALUE branch), which is far + // easier to read than a number printed on top of the waveform. + if (_hover_en && _hover_point != QPointF(-1, -1)) { p.setPen(fore); p.setBrush(back); p.drawRect(_hover_point.x()-1, _hover_point.y()-1, HoverPointSize, HoverPointSize); - p.drawText(hover_rect, Qt::AlignCenter | Qt::AlignTop | Qt::TextDontClip, hover_str); } auto &cursor_list = _view->get_cursorList(); diff --git a/DSView/pv/view/dsosignal.h b/DSView/pv/view/dsosignal.h index 660c9bb7e..7c53904d2 100644 --- a/DSView/pv/view/dsosignal.h +++ b/DSView/pv/view/dsosignal.h @@ -260,6 +260,11 @@ class DsoSignal : public Signal uint64_t num_channels); void paint_hover_measure(QPainter &p, QColor fore, QColor back); + + // Compute the cycle measurements (period/frequency/duty/count/width/level) + // from the sample buffer. Used as a fallback for channels the hardware + // leaves unmeasured - e.g. the 2nd channel often returns no cycle data. + void compute_soft_measure(int hw_offset); void auto_set(); void call_auto_end(); diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index ca9915460..e688aa12c 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -141,7 +141,7 @@ View::View(SigSession *session, pv::toolbars::SamplingBar *sampling_bar, QWidget _header = new Header(*this); _devmode = new DevMode(this, session); - setViewportMargins(headerWidth(), RulerHeight, 0, 0); + setViewportMargins(headerWidth(), RulerHeight, 0, get_bottom_margin()); // windows splitter _time_viewport = new Viewport(*this, TIME_VIEW); @@ -1019,6 +1019,17 @@ bool View::viewportEvent(QEvent *e) } } +int View::get_bottom_margin() +{ + // The status/measurement bar floats over the bottom of the viewport. The + // scrollable time pane can scroll its content clear of it, but the fixed + // FFT splitter pane cannot, so reserve room for the bar only while the FFT + // pane is visible - otherwise it would be cropped underneath the bar. + if (_viewbottom && _fft_viewport && _fft_viewport->isVisible()) + return _viewbottom->height(); + return 0; +} + int View::headerWidth() { int headerWidth = _header->get_nameEditWidth(); @@ -1034,7 +1045,7 @@ int View::headerWidth() } } - setViewportMargins(headerWidth, RulerHeight, 0, 0); + setViewportMargins(headerWidth, RulerHeight, 0, get_bottom_margin()); return headerWidth; } @@ -1048,7 +1059,7 @@ void View::resizeEvent(QResizeEvent*) } reconstruct(); - setViewportMargins(headerWidth(), RulerHeight, 0, 0); + setViewportMargins(headerWidth(), RulerHeight, 0, get_bottom_margin()); update_margins(); update_scroll(); signals_changed(NULL); @@ -1121,7 +1132,7 @@ void View::v_scroll_value_changed(int value) void View::data_updated() { - setViewportMargins(headerWidth(), RulerHeight, 0, 0); + setViewportMargins(headerWidth(), RulerHeight, 0, get_bottom_margin()); update_margins(); // Update the scroll bars diff --git a/DSView/pv/view/view.h b/DSView/pv/view/view.h index 938917c3a..3a1d58707 100644 --- a/DSView/pv/view/view.h +++ b/DSView/pv/view/view.h @@ -244,6 +244,11 @@ class View : public QScrollArea, public IUiWindow int headerWidth(); + // Bottom viewport margin reserved for the status/measurement bar. Non-zero + // only when the FFT splitter pane is shown, so that fixed pane isn't + // cropped underneath the bar. + int get_bottom_margin(); + inline Ruler* get_ruler(){ return _ruler; } @@ -491,11 +496,13 @@ private slots: pv::toolbars::SamplingBar *_sampling_bar; QWidget *_viewcenter; - ViewStatus *_viewbottom; + // Zero-initialised so get_bottom_margin(), reached via headerWidth() + // during construction, sees null (not garbage) before these are assigned. + ViewStatus *_viewbottom = nullptr; QVBoxLayout *_statusLayout; QSplitter *_vsplitter; Viewport *_time_viewport; - Viewport *_fft_viewport; + Viewport *_fft_viewport = nullptr; Viewport *_active_viewport; LissajousFigure *_lissajous; std::list _viewport_list; diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp index 7743c9c34..e8e8dc1fb 100644 --- a/DSView/pv/view/viewport.cpp +++ b/DSView/pv/view/viewport.cpp @@ -35,7 +35,7 @@ #include #include -#include +#include #include #include #include @@ -43,7 +43,7 @@ #include "../config/appconfig.h" #include "../dsvdef.h" #include "../appcontrol.h" -#include "../log.h" +#include "../log.h" #include "../ui/langresource.h" #include "../ui/fn.h" #include "lissajoustrace.h" @@ -95,7 +95,7 @@ Viewport::Viewport(View &parent, View_type type) : _edge_hit = false; _transfer_started = false; _timer_cnt = 0; - + _sample_received = 0; _is_checked_trig = false; @@ -105,7 +105,7 @@ Viewport::Viewport(View &parent, View_type type) : // drag inertial _drag_strength = 0; _drag_timer.setSingleShot(true); - + _cmenu = new QMenu(this); QAction *yAction = _cmenu->addAction(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_ADD_Y_CURSOR), "Add Y-cursor")); QAction *xAction = _cmenu->addAction(L_S(STR_PAGE_DLG, S_ID(IDS_DLG_ADD_X_CURSOR), "Add X-cursor")); @@ -160,15 +160,15 @@ bool Viewport::event(QEvent *event) void Viewport::paintEvent(QPaintEvent *event) { - (void)event; + (void)event; doPaint(); } void Viewport::doPaint() -{ +{ using pv::view::Signal; - + QStyleOption o; o.initFrom(this); QPainter p(this); @@ -190,7 +190,7 @@ void Viewport::doPaint() QColor back(QWidget::palette().color(QWidget::backgroundRole())); fore.setAlpha(View::ForeAlpha); _view.set_back(false); - + std::vector traces; _view.get_traces(_type, traces); @@ -198,11 +198,11 @@ void Viewport::doPaint() t->paint_back(p, 0, _view.get_view_width(), fore, back); if (_view.back_ready()) break; - } + } int mode = _view.session().get_device()->get_work_mode(); - if (mode == LOGIC || _view.session().is_instant()) + if (mode == LOGIC || _view.session().is_instant()) { if (_view.session().is_init_status()) { @@ -213,7 +213,7 @@ void Viewport::doPaint() paintSignals(p, fore, back); } else if (_view.session().is_realtime_refresh()) - { + { _view.session().have_new_realtime_refresh(false); // Try to reset refresh timer. if (_view.session().have_view_data() || _view.session().is_instant()) @@ -229,7 +229,7 @@ void Viewport::doPaint() if (!_transfer_started){ bool triggered; int captured_progress; - + if (_view.session().get_capture_status(triggered, captured_progress)){ _view.show_captured_progress(triggered, captured_progress); } @@ -239,7 +239,7 @@ void Viewport::doPaint() _view.repeat_unshow(); paintProgress(p, fore, back); } - } + } } else { paintSignals(p, fore, back); @@ -259,13 +259,13 @@ void Viewport::doPaint() } void Viewport::paintCursors(QPainter &p) -{ +{ const QRect xrect = _view.get_view_rect(); auto &cursor_list = _view.get_cursorList(); if (_view.cursors_shown() && _type == TIME_VIEW) { - for (auto cursor : cursor_list) { + for (auto cursor : cursor_list) { const int64_t cursorX = _view.index2pixel(cursor->index()); if (xrect.contains(_view.hover_point().x(), _view.hover_point().y()) && qAbs(cursorX - _view.hover_point().x()) <= HitCursorMargin) @@ -349,7 +349,7 @@ void Viewport::paintSignals(QPainter &p, QColor fore, QColor back) std::vector traces; _view.get_traces(_type, traces); - if (_view.session().get_device()->get_work_mode() == LOGIC) + if (_view.session().get_device()->get_work_mode() == LOGIC) { bool bFirst = true; uint64_t end_align_sample; @@ -360,19 +360,19 @@ void Viewport::paintSignals(QPainter &p, QColor fore, QColor back) if (t->signal_type() == SR_CHANNEL_LOGIC) { LogicSignal *logic_signal = (LogicSignal*)t; - + if (bFirst) end_align_sample = logic_signal->data()->get_ring_sample_count(); - + logic_signal->paint_mid_align_sample(p, 0, t->get_view_rect().right(), fore, back, end_align_sample); bFirst = false; } else{ t->paint_mid(p, 0, t->get_view_rect().right(), fore, back); - } - } + } + } } - } + } else { if (_view.scale() != _curScale || _view.x_offset() != _curOffset || @@ -399,24 +399,27 @@ void Viewport::paintSignals(QPainter &p, QColor fore, QColor back) isLissa = true; } } - + for(auto t : traces) { if (t->enabled()) - { + { if (isLissa && t->signal_type() == SR_CHANNEL_DSO) continue; if (isLissa && t->signal_type() == SR_CHANNEL_MATH) continue; - + t->paint_mid(dbp, 0, t->get_view_rect().right(), fore, back); - } + } } _need_update = false; } p.drawPixmap(0, 0, _pixmap); } + // frozen reference waveforms, overlaid on top of the live traces + paint_ref_waves(p); + // plot cursors paintCursors(p); @@ -491,7 +494,7 @@ void Viewport::paintSignals(QPainter &p, QColor fore, QColor back) //plot trigger information if (_view.session().get_device()->get_work_mode() == DSO - && _view.session().is_running_status()) + && _view.session().is_running_status()) { int type; bool roll = false; @@ -506,13 +509,13 @@ void Viewport::paintSignals(QPainter &p, QColor fore, QColor back) if (type == DSO_TRIGGER_AUTO && roll) { type_str = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_AUTO_ROLL), "Auto(Roll)"); - + if (_view.session().is_instant()){ type_str += ", "; type_str += L_S(STR_PAGE_DLG, S_ID(IDS_DLG_VIEW_CAPTURE), "Capturing"); bDot = true; } - } + } else if (type == DSO_TRIGGER_AUTO && !_view.session().trigd()) { type_str = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_AUTO), "Auto"); @@ -521,11 +524,11 @@ void Viewport::paintSignals(QPainter &p, QColor fore, QColor back) type_str += L_S(STR_PAGE_DLG, S_ID(IDS_DLG_VIEW_CAPTURE), "Capturing"); bDot = true; } - } + } else if (_waiting_trig > 0) { - type_str = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_WAITING_TRIG), "Waiting Trig"); + type_str = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_WAITING_TRIG), "Waiting Trig"); bDot = true; - } + } else { type_str = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_TRIG_D), "Trig'd"); } @@ -560,14 +563,102 @@ void Viewport::paintSignals(QPainter &p, QColor fore, QColor back) p.setPen(QColor(255,0,0,200)); p.drawText(_view.get_view_rect(), Qt::AlignRight | Qt::AlignTop, data_status); p.setPen(fore); - } - } + } + } } } } +void Viewport::paint_ref_waves(QPainter &p) +{ + if (_type != TIME_VIEW) + return; + if (_view.session().get_device()->get_work_mode() != DSO) + return; + + auto &refs = _view.session().get_ref_waves(); + if (refs.empty()) + return; + + std::vector traces; + _view.get_traces(TIME_VIEW, traces); + + const double vscale = _view.scale(); + const int64_t x_offset = _view.x_offset(); + const double trig_hoff = _view.trig_hoff(); + const int left = 0; + const int width = _view.get_view_width(); + + if (vscale <= 0) + return; + + for (auto &rw : refs) { + // The reference is rendered with its source channel's current vertical + // scaling, so it tracks the channel's dial and stays on the grid. + DsoSignal *sig = NULL; + for (auto t : traces) { + if (t->signal_type() == SR_CHANNEL_DSO && t->get_index() == rw.index) { + sig = (DsoSignal*)t; + break; + } + } + if (sig == NULL || !sig->enabled()) + continue; + + const int64_t n = (int64_t)rw.samples.size(); + if (n < 2 || rw.samplerate <= 0) + continue; + + const float zeroY = sig->get_zero_vpos(); + const int hw_offset = sig->get_hw_offset(); + const float sscale = sig->get_scale(); + const QRect vrect = sig->get_view_rect(); + const float top = vrect.top(); + const float bottom = vrect.bottom(); + + const double samples_per_pixel = rw.samplerate * vscale; + if (samples_per_pixel <= 0) + continue; + const double pixels_per_sample = 1.0 / samples_per_pixel; + const int64_t last_sample = n - 1; + const double start = x_offset * samples_per_pixel - trig_hoff; + const double end = start + samples_per_pixel * width; + const int64_t start_sample = + min(max((int64_t)floor(start), (int64_t)0), last_sample); + const int64_t end_sample = + min(max((int64_t)ceil(end) + 1, (int64_t)0), last_sample); + if (end_sample <= start_sample) + continue; + + const int64_t count = end_sample - start_sample + 1; + QPointF *points = new QPointF[count]; + QPointF *point = points; + float x = (start_sample / samples_per_pixel - x_offset) + left + + trig_hoff * pixels_per_sample; + + for (int64_t s = start_sample; s <= end_sample; s++) { + const uint8_t value = rw.samples[s]; + const float y = min(max(top, zeroY + (value - hw_offset) * sscale), bottom); + *point++ = QPointF(x, y); + x += pixels_per_sample; + } + + QColor c = rw.colour; + c.setAlpha(180); + QPen pen(c); + pen.setStyle(Qt::DashLine); + p.setPen(pen); + p.drawPolyline(points, point - points); + + // Label the reference near its left end. + p.drawText(QPointF(left + 4, top + 12), rw.name); + + delete[] points; + } +} + void Viewport::get_captured_progress(double &progress, int &progress100) -{ +{ const uint64_t sample_limits = _view.session().cur_samplelimits(); progress = -(_sample_received * 1.0 / sample_limits * 360 * 16); progress100 = ceil(progress / -3.6 / 16); @@ -589,7 +680,7 @@ void Viewport::paintProgress(QPainter &p, QColor fore, QColor back) int captured_progress = 0; get_captured_progress(progress, progress100); - + p.setRenderHint(QPainter::Antialiasing, true); p.setPen(Qt::gray); p.setBrush(Qt::NoBrush); @@ -673,9 +764,9 @@ void Viewport::paintProgress(QPainter &p, QColor fore, QColor back) p.drawEllipse(cenRightPos, trigger_radius, trigger_radius); bool triggered; - + if (_view.session().get_capture_status(triggered, captured_progress)){ - p.setPen(View::Blue); + p.setPen(View::Blue); QFont font = p.font(); float fSize = AppConfig::Instance().appOptions.fontSize; @@ -685,17 +776,17 @@ void Viewport::paintProgress(QPainter &p, QColor fore, QColor back) p.setFont(font); QRect status_rect = QRect(cenPos.x() - radius, cenPos.y() + radius * 0.4, radius * 2, radius * 0.5); - + 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) + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_TRIGGERED), "Triggered! ") + QString::number(captured_progress) + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CAPTURED), "% Captured")); } else { p.drawText(status_rect, Qt::AlignCenter | Qt::AlignVCenter, - L_S(STR_PAGE_DLG, S_ID(IDS_DLG_WAITING_FOR_TRIGGER), "Waiting for Trigger! ") + QString::number(captured_progress) + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_WAITING_FOR_TRIGGER), "Waiting for Trigger! ") + QString::number(captured_progress) + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_CAPTURED), "% Captured")); } @@ -703,13 +794,13 @@ void Viewport::paintProgress(QPainter &p, QColor fore, QColor back) } } - else { + else { p.setPen(View::Green); QFont font=p.font(); font.setPointSize(50); font.setBold(true); p.setFont(font); - + p.drawText(_view.get_view_rect(), Qt::AlignCenter | Qt::AlignVCenter, QString::number(progress100)+"%"); prgRate(progress100); } @@ -726,7 +817,7 @@ void Viewport::paintProgress(QPainter &p, QColor fore, QColor back) void Viewport::mousePressEvent(QMouseEvent *event) { assert(event); - + _mouse_down_point = event->pos(); _drag_last_mouse_pos = _mouse_down_point; _mouse_down_offset = QPoint(_view.x_offset(), _view.y_offset()); @@ -772,8 +863,8 @@ void Viewport::mousePressEvent(QMouseEvent *event) event->button() == Qt::LeftButton && _view.session().get_device()->get_work_mode() == DSO) { - for(auto s : _view.session().get_signals()) - { + for(auto s : _view.session().get_signals()) + { if (s->signal_type() == SR_CHANNEL_DSO && s->enabled()) { DsoSignal *dsoSig = (DsoSignal*)s; if (dsoSig->get_trig_rect(0, _view.get_view_width()).contains(_mouse_point)) { @@ -797,9 +888,9 @@ void Viewport::mousePressEvent(QMouseEvent *event) else if (qAbs(searchX - event->pos().x()) <= HitCursorMargin) { _view.get_ruler()->set_grabbed_cursor(_view.get_search_cursor()); set_action(CURS_MOVE); - } + } } - + if (_action_type == NO_ACTION && _view.cursors_shown()) { auto &cursor_list = _view.get_cursorList(); auto i = cursor_list.begin(); @@ -827,7 +918,7 @@ void Viewport::mousePressEvent(QMouseEvent *event) const double cursorX = xrect.left() + (*i)->value(XCursor::XCur_Y)*xrect.width(); const double cursorY0 = xrect.top() + (*i)->value(XCursor::XCur_X0)*xrect.height(); const double cursorY1 = xrect.top() + (*i)->value(XCursor::XCur_X1)*xrect.height(); - + if ((*i)->get_close_rect(xrect).contains(_view.hover_point())) { _view.del_xcursor(*i); if (xcursor_list.empty()) @@ -840,7 +931,7 @@ void Viewport::mousePressEvent(QMouseEvent *event) bool sig_looped = ((*i)->channel() == NULL); bool no_dsoSig = true; - while (true) { + while (true) { if ((*s)->signal_type() == SR_CHANNEL_DSO && (*s)->enabled()) { view::DsoSignal *dsoSig = (view::DsoSignal*)(*s); no_dsoSig = false; @@ -941,13 +1032,13 @@ void Viewport:: mouseMoveEvent(QMouseEvent *event) if ((event->buttons() & Qt::LeftButton) || !(event->buttons() | Qt::NoButton)) { if (_action_type == DSO_TRIG_MOVE) { - if (_drag_sig && _drag_sig->signal_type() == SR_CHANNEL_DSO) { + if (_drag_sig && _drag_sig->signal_type() == SR_CHANNEL_DSO) { view::DsoSignal *dsoSig = (view::DsoSignal*)_drag_sig; dsoSig->set_trig_vpos(event->pos().y()); _dso_trig_moved = true; } } - + if (_action_type == CURS_MOVE) { TimeMarker* grabbed_marker = _view.get_ruler()->get_grabbed_cursor(); if (grabbed_marker) { @@ -955,7 +1046,7 @@ void Viewport:: mouseMoveEvent(QMouseEvent *event) uint64_t index0 = 0, index1 = 0, index2 = 0; bool logic = false; - for(auto s : _view.session().get_signals()) { + for(auto s : _view.session().get_signals()) { if (mode == LOGIC && s->signal_type() == SR_CHANNEL_LOGIC) { view::LogicSignal *logicSig = (view::LogicSignal*)s; if (logicSig->measure(event->pos(), index0, index1, index2)) { @@ -1016,7 +1107,7 @@ void Viewport:: mouseMoveEvent(QMouseEvent *event) hover_x = xrect.right(); } - double rate = (hover_x - xrect.left()) * 1.0 / xrect.width(); + double rate = (hover_x - xrect.left()) * 1.0 / xrect.width(); xc->set_value(xc->grabbed(), min(rate, 1.0)); } else { @@ -1024,7 +1115,7 @@ void Viewport:: mouseMoveEvent(QMouseEvent *event) int body_y = _view.get_body_height(); if (msy > body_y) msy = body_y; - + double rate = (msy - xrect.top()) * 1.0 / xrect.height(); xc->set_value(xc->grabbed(), max(rate, 0.0)); } @@ -1054,7 +1145,7 @@ void Viewport:: mouseMoveEvent(QMouseEvent *event) _mouse_point = event->pos(); measure(); - + update(UpdateEventType::UPDATE_EV_MS_MOVE); } @@ -1101,11 +1192,11 @@ void Viewport::onLogicMouseRelease(QMouseEvent *event) if (_mouse_down_point.x() == event->pos().x()) { const auto &sigs = _view.session().get_signals(); - for(auto s : sigs) { + for(auto s : sigs) { if (s->signal_type() == SR_CHANNEL_LOGIC) { view::LogicSignal *logicSig = (view::LogicSignal*)s; if (logicSig->is_by_edge(event->pos(), _edge_start, 10)) { - set_action(LOGIC_JUMP); + set_action(LOGIC_JUMP); _cur_preX = _view.index2pixel(_edge_start); _cur_preY = logicSig->get_y(); _cur_preY_top = logicSig->get_y() - logicSig->get_totalHeight()/2 - 12; @@ -1138,7 +1229,7 @@ void Viewport::onLogicMouseRelease(QMouseEvent *event) } } } - } + } break; } case LOGIC_EDGE: @@ -1172,7 +1263,7 @@ void Viewport::onLogicMouseRelease(QMouseEvent *event) case LOGIC_MOVE: default: break; - } + } } void Viewport::onDsoMouseRelease(QMouseEvent *event) @@ -1216,7 +1307,7 @@ void Viewport::onDsoMouseRelease(QMouseEvent *event) for(auto t : traces){ t->select(false); - } + } } break; } @@ -1313,7 +1404,7 @@ void Viewport::mouseReleaseEvent(QMouseEvent *event) set_action(NO_ACTION); auto &xcursor_list = _view.get_xcursorList(); auto i = xcursor_list.begin(); - + while (i != xcursor_list.end()) { (*i)->rel_grabbed(); i++; @@ -1322,7 +1413,7 @@ void Viewport::mouseReleaseEvent(QMouseEvent *event) _xcurs_moved = false; } } - + /* // This code block prevents the cursor from moving. if (mode == LOGIC && event->button() == Qt::LeftButton){ @@ -1434,7 +1525,7 @@ void Viewport::wheelEvent(QWheelEvent *event) bool isVertical = true; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - x = (int)event->position().x(); + x = (int)event->position().x(); int anglex = event->angleDelta().x(); int angley = event->angleDelta().y(); @@ -1470,7 +1561,7 @@ void Viewport::wheelEvent(QWheelEvent *event) if (_type == FFT_VIEW) { for (auto t : _view.session().get_spectrum_traces()) - { + { if (t->enabled()) { t->zoom(zoom_scale, x); @@ -1501,7 +1592,7 @@ void Viewport::wheelEvent(QWheelEvent *event) } } else - { + { bLstTime = false; (void)bLstTime; @@ -1514,7 +1605,7 @@ void Viewport::wheelEvent(QWheelEvent *event) const auto &sigs = _view.session().get_signals(); for (auto s : sigs) { - if (s->signal_type() == SR_CHANNEL_DSO){ + if (s->signal_type() == SR_CHANNEL_DSO){ view::DsoSignal *dsoSig = (view::DsoSignal*)s; dsoSig->auto_end(); } @@ -1602,7 +1693,7 @@ void Viewport::set_receive_len(quint64 length) int mode = _view.session().get_device()->get_work_mode(); if (mode == LOGIC) - { + { if (_view.session().get_device()->is_file() == false) { if (!_is_checked_trig && _view.session().is_triged()){ @@ -1633,14 +1724,14 @@ void Viewport::set_receive_len(quint64 length) if (_view.session().have_new_realtime_refresh(true) == false){ return; } - } + } } if (mode == LOGIC && AppConfig::Instance().appOptions.autoScrollLatestData && _view.session().is_realtime_refresh()) { _view.scroll_to_logic_last_data_time(); - } + } // Received new data, and refresh the view. update(UpdateEventType::UPDATE_EV_GENERIC); @@ -1674,10 +1765,10 @@ void Viewport::clear_dso_xm() void Viewport::measure() { if (_view.session().is_data_lock()) - return; + return; if (_view.session().is_loop_mode() && _view.session().is_working()) return; - + _measure_type = NO_MEASURE; if (_type == TIME_VIEW) { @@ -1752,7 +1843,7 @@ void Viewport::measure() _edge_hit = false; } } - } + } else if (s->signal_type() == SR_CHANNEL_DSO) { view::DsoSignal *dsoSig = ( view::DsoSignal*)s; if (s->enabled()) { @@ -1969,11 +2060,14 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) p.drawText(measure6_rect, Qt::AlignLeft | Qt::AlignVCenter, label_duty_str); p.drawText(measure6_rect, Qt::AlignRight | Qt::AlignVCenter, _mm_duty); } - } + } if (_action_type == NO_ACTION && _measure_type == DSO_VALUE) { + struct MRow { QString name; QString val; QColor colour; }; + std::vector rows; + for(auto s : _view.session().get_signals()) { if (s->signal_type() == SR_CHANNEL_DSO) { uint64_t index; @@ -1985,8 +2079,14 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) p.setBrush(Qt::NoBrush); p.drawLine(hpoint.x(), dsoSig->get_view_rect().top(), hpoint.x(), dsoSig->get_view_rect().bottom()); + + MRow r; + r.name = "CH" + dsoSig->get_name(); + r.val = dsoSig->get_voltage(dsoSig->get_hw_offset() - value, 3); + r.colour = dsoSig->get_colour(); + rows.push_back(r); } - } + } else if (s->signal_type() == SR_CHANNEL_ANALOG) { uint64_t index; double value; @@ -2000,10 +2100,64 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) } } } + + // Draw the hovered voltages in a single floating panel (like the + // logic-mode measurement popup) instead of printing each number on + // top of its trace, where it is hard to read. + if (!rows.empty()) { + // The floating panel is drawn 50% larger than the trace font and + // in bold to keep the hovered values easy to read. + const double sc = _view.get_trace_font_scale() * 1.2; + QFont measure_font = p.font(); + measure_font.setStretch(QFont::Unstretched); + measure_font.setBold(true); + measure_font.setPointSizeF(AppConfig::Instance().GetTraceFontSize() * sc); + p.setFont(measure_font); + + int name_w = 0, val_w = 0; + for (auto &r : rows) { + name_w = max(name_w, p.boundingRect(0, 0, INT_MAX, INT_MAX, + Qt::AlignLeft | Qt::AlignVCenter, r.name).width()); + val_w = max(val_w, p.boundingRect(0, 0, INT_MAX, INT_MAX, + Qt::AlignLeft | Qt::AlignVCenter, r.val).width()); + } + + const double pad = 6.0 * sc; + const double gap = 16.0 * sc; + const double row_h = 20.0 * sc; + const double box_w = pad * 2 + name_w + gap + val_w; + const double box_h = pad * 2 + row_h * rows.size(); + + const double width = _view.get_view_width() + - _view.verticalScrollBar()->geometry().width(); + const double vheight = _view.get_view_height() + - _view.horizontalScrollBar()->geometry().height() - View::StatusHeight; + + // Offset from the cursor, flipping to stay inside the view. + double bx = hoverpoint_x + MousePointerClearance; + double by = hoverpoint_y + MousePointerClearance; + if (bx + box_w > width) bx = hoverpoint_x - box_w - MouseEdgeClearance; + if (by + box_h > vheight) by = hoverpoint_y - box_h - MousePointerClearance; + if (bx < 0) bx = 0; + if (by < 0) by = 0; + + p.setPen(Qt::NoPen); + p.setBrush(back.black() > 0x80 ? View::TransparentLightBlue + : View::TransparentLightYellow); + p.drawRect(QRectF(bx, by, box_w, box_h)); + + for (size_t i = 0; i < rows.size(); i++) { + QRectF rr(bx + pad, by + pad + i * row_h, box_w - 2 * pad, row_h); + p.setPen(rows[i].colour); + p.drawText(rr, Qt::AlignLeft | Qt::AlignVCenter, rows[i].name); + p.setPen(fore); + p.drawText(rr, Qt::AlignRight | Qt::AlignVCenter, rows[i].val); + } + } } if (_dso_ym_valid) { - for(auto s : _view.session().get_signals()) { + for(auto s : _view.session().get_signals()) { if (s->signal_type() == SR_CHANNEL_DSO) { view::DsoSignal *dsoSig = (view::DsoSignal*)s; if (dsoSig->get_index() == _dso_ym_sig_index) { @@ -2132,7 +2286,7 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) measure_updated(); } - if (_action_type == LOGIC_EDGE + if (_action_type == LOGIC_EDGE && _view.session().have_view_data()){ p.setPen(active_color); p.drawLine(QLineF(_cur_preX, _cur_midY-5, _cur_preX, _cur_midY+5)); @@ -2206,10 +2360,10 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) QString delta_text = _view.get_index_delta(_edge_start, _edge_end) + "/" + QString::number(delta); QFontMetrics fm = this->fontMetrics(); - + const int rectW = fm.boundingRect(delta_text).width() + 60; const int rectH = fm.height() + 10; - + const int rectY = (height() - hoverpoint_x < rectH + 20) ? hoverpoint_y - 10 - rectH : hoverpoint_y + 20; const int rectX = (width() - hoverpoint_x < rectW) ? hoverpoint_x - rectW : hoverpoint_x; QRectF jump_rect = QRectF(rectX, rectY, rectW, rectH); @@ -2296,14 +2450,14 @@ void Viewport::on_trigger_timer() } void Viewport::on_drag_timer() -{ +{ const int64_t offset = _view.x_offset(); const double scale = _view.scale(); if (_view.session().is_stopped_status() && _drag_strength != 0 && offset < _view.get_max_offset() - && offset > _view.get_min_offset()) + && offset > _view.get_min_offset()) { _view.set_scale_offset(scale, offset + _drag_strength); _drag_strength /= DragDamping; @@ -2339,7 +2493,7 @@ void Viewport::show_wait_trigger() } void Viewport::unshow_wait_trigger() -{ +{ _waiting_trig = 0; update(UpdateEventType::UPDATE_EV_GENERIC); } @@ -2397,14 +2551,14 @@ void Viewport::flash_yscale_badge() void Viewport::add_cursor_y() { uint64_t index; - index = _view.pixel2index(_cur_preX); + index = _view.pixel2index(_cur_preX); _view.add_cursor(index); _view.show_cursors(true); } void Viewport::add_cursor_x() { - double ypos = (_cur_preY - _view.get_view_rect().top()) * 1.0 / _view.get_view_height(); + double ypos = (_cur_preY - _view.get_view_rect().top()) * 1.0 / _view.get_view_height(); _view.add_xcursor(ypos, ypos); _view.show_xcursors(true); } @@ -2420,7 +2574,7 @@ void Viewport::UpdateTheme() } void Viewport::UpdateFont() -{ +{ QFont font = this->font(); font.setPointSizeF(AppConfig::Instance().appOptions.fontSize); _yAction->setFont(font); diff --git a/DSView/pv/view/viewport.h b/DSView/pv/view/viewport.h index ffd013085..a9e46f322 100644 --- a/DSView/pv/view/viewport.h +++ b/DSView/pv/view/viewport.h @@ -152,6 +152,7 @@ class Viewport : public QWidget, public IUiWindow void paintProgress(QPainter& p, QColor fore, QColor back); void paintMeasure(QPainter &p, QColor fore, QColor back); void paintCursors(QPainter &p); + void paint_ref_waves(QPainter &p); void paintYScaleBadge(QPainter &p, QColor fore, QColor back); // Briefly show the y-scale badge (logic mode) after a vertical zoom. From a601d63e53386b5c5a88ccd8fe0b4b2edc06c8a6 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Sat, 18 Jul 2026 16:58:58 +0200 Subject: [PATCH 24/34] Updated headers --- DSView/pv/data/mathstack.cpp | 1 + DSView/pv/data/mathstack.h | 1 + DSView/pv/dialogs/chanmeasure.cpp | 2 +- DSView/pv/dialogs/chanmeasure.h | 2 +- DSView/pv/dialogs/dsohistogram.cpp | 2 +- DSView/pv/dialogs/dsohistogram.h | 2 +- DSView/pv/dialogs/mathoptions.cpp | 1 + DSView/pv/dialogs/mathoptions.h | 1 + DSView/pv/dialogs/refoptions.cpp | 2 +- DSView/pv/dialogs/refoptions.h | 2 +- DSView/pv/sigsession.cpp | 1 + DSView/pv/sigsession.h | 1 + DSView/pv/toolbars/trigbar.cpp | 1 + DSView/pv/toolbars/trigbar.h | 1 + DSView/pv/view/dsosignal.cpp | 1 + DSView/pv/view/dsosignal.h | 1 + DSView/pv/view/view.cpp | 1 + DSView/pv/view/view.h | 1 + DSView/pv/view/viewport.cpp | 1 + DSView/pv/view/viewport.h | 1 + 20 files changed, 20 insertions(+), 6 deletions(-) diff --git a/DSView/pv/data/mathstack.cpp b/DSView/pv/data/mathstack.cpp index 10cf7fa5b..0f506d6bb 100644 --- a/DSView/pv/data/mathstack.cpp +++ b/DSView/pv/data/mathstack.cpp @@ -3,6 +3,7 @@ * DSView is based on PulseView. * * Copyright (C) 2016 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/data/mathstack.h b/DSView/pv/data/mathstack.h index cf627806a..dfbf5ba78 100644 --- a/DSView/pv/data/mathstack.h +++ b/DSView/pv/data/mathstack.h @@ -3,6 +3,7 @@ * DSView is based on PulseView. * * Copyright (C) 2016 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/dialogs/chanmeasure.cpp b/DSView/pv/dialogs/chanmeasure.cpp index b54463b4a..58744ff2c 100644 --- a/DSView/pv/dialogs/chanmeasure.cpp +++ b/DSView/pv/dialogs/chanmeasure.cpp @@ -2,7 +2,7 @@ * This file is part of the DSView project. * DSView is based on PulseView. * - * Copyright (C) 2026 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/dialogs/chanmeasure.h b/DSView/pv/dialogs/chanmeasure.h index edf12484c..a9b9af950 100644 --- a/DSView/pv/dialogs/chanmeasure.h +++ b/DSView/pv/dialogs/chanmeasure.h @@ -2,7 +2,7 @@ * This file is part of the DSView project. * DSView is based on PulseView. * - * Copyright (C) 2026 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/dialogs/dsohistogram.cpp b/DSView/pv/dialogs/dsohistogram.cpp index 109730886..ca8ab3012 100644 --- a/DSView/pv/dialogs/dsohistogram.cpp +++ b/DSView/pv/dialogs/dsohistogram.cpp @@ -2,7 +2,7 @@ * This file is part of the DSView project. * DSView is based on PulseView. * - * Copyright (C) 2026 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/dialogs/dsohistogram.h b/DSView/pv/dialogs/dsohistogram.h index e845a92a9..399c4ad52 100644 --- a/DSView/pv/dialogs/dsohistogram.h +++ b/DSView/pv/dialogs/dsohistogram.h @@ -2,7 +2,7 @@ * This file is part of the DSView project. * DSView is based on PulseView. * - * Copyright (C) 2026 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/dialogs/mathoptions.cpp b/DSView/pv/dialogs/mathoptions.cpp index f295113e2..74529f942 100644 --- a/DSView/pv/dialogs/mathoptions.cpp +++ b/DSView/pv/dialogs/mathoptions.cpp @@ -3,6 +3,7 @@ * DSView is based on PulseView. * * Copyright (C) 2015 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/dialogs/mathoptions.h b/DSView/pv/dialogs/mathoptions.h index 483cc4133..3a87691b5 100644 --- a/DSView/pv/dialogs/mathoptions.h +++ b/DSView/pv/dialogs/mathoptions.h @@ -3,6 +3,7 @@ * DSView is based on PulseView. * * Copyright (C) 2015 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/dialogs/refoptions.cpp b/DSView/pv/dialogs/refoptions.cpp index 08cb3d953..b09fde09a 100644 --- a/DSView/pv/dialogs/refoptions.cpp +++ b/DSView/pv/dialogs/refoptions.cpp @@ -2,7 +2,7 @@ * This file is part of the DSView project. * DSView is based on PulseView. * - * Copyright (C) 2026 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/dialogs/refoptions.h b/DSView/pv/dialogs/refoptions.h index 1e6745b34..efc44b0ef 100644 --- a/DSView/pv/dialogs/refoptions.h +++ b/DSView/pv/dialogs/refoptions.h @@ -2,7 +2,7 @@ * This file is part of the DSView project. * DSView is based on PulseView. * - * Copyright (C) 2026 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/sigsession.cpp b/DSView/pv/sigsession.cpp index 751e6075d..f48b2b240 100644 --- a/DSView/pv/sigsession.cpp +++ b/DSView/pv/sigsession.cpp @@ -4,6 +4,7 @@ * * Copyright (C) 2012 Joel Holdsworth * Copyright (C) 2013 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/sigsession.h b/DSView/pv/sigsession.h index 7fc44b8a7..d3799d540 100644 --- a/DSView/pv/sigsession.h +++ b/DSView/pv/sigsession.h @@ -4,6 +4,7 @@ * * Copyright (C) 2012 Joel Holdsworth * Copyright (C) 2013 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/toolbars/trigbar.cpp b/DSView/pv/toolbars/trigbar.cpp index 543594f92..288d129ba 100644 --- a/DSView/pv/toolbars/trigbar.cpp +++ b/DSView/pv/toolbars/trigbar.cpp @@ -3,6 +3,7 @@ * DSView is based on PulseView. * * Copyright (C) 2013 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/toolbars/trigbar.h b/DSView/pv/toolbars/trigbar.h index 1d33c5185..024f432d7 100644 --- a/DSView/pv/toolbars/trigbar.h +++ b/DSView/pv/toolbars/trigbar.h @@ -3,6 +3,7 @@ * DSView is based on PulseView. * * Copyright (C) 2013 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/view/dsosignal.cpp b/DSView/pv/view/dsosignal.cpp index 901bbf406..1249b7c47 100644 --- a/DSView/pv/view/dsosignal.cpp +++ b/DSView/pv/view/dsosignal.cpp @@ -3,6 +3,7 @@ * DSView is based on PulseView. * * Copyright (C) 2013 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/view/dsosignal.h b/DSView/pv/view/dsosignal.h index 7c53904d2..52732dba6 100644 --- a/DSView/pv/view/dsosignal.h +++ b/DSView/pv/view/dsosignal.h @@ -3,6 +3,7 @@ * DSView is based on PulseView. * * Copyright (C) 2013 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index e688aa12c..2921d1407 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -4,6 +4,7 @@ * * Copyright (C) 2012 Joel Holdsworth * Copyright (C) 2013 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/view/view.h b/DSView/pv/view/view.h index 3a1d58707..dce9f78f4 100644 --- a/DSView/pv/view/view.h +++ b/DSView/pv/view/view.h @@ -4,6 +4,7 @@ * * Copyright (C) 2012 Joel Holdsworth * Copyright (C) 2013 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp index e8e8dc1fb..4ac6f2841 100644 --- a/DSView/pv/view/viewport.cpp +++ b/DSView/pv/view/viewport.cpp @@ -4,6 +4,7 @@ * * Copyright (C) 2012 Joel Holdsworth * Copyright (C) 2013 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/DSView/pv/view/viewport.h b/DSView/pv/view/viewport.h index a9e46f322..c5a2d663b 100644 --- a/DSView/pv/view/viewport.h +++ b/DSView/pv/view/viewport.h @@ -4,6 +4,7 @@ * * Copyright (C) 2012 Joel Holdsworth * Copyright (C) 2013 DreamSourceLab + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From f6b12937932525035eaae699bdaae98c0d2bf97d Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Sat, 18 Jul 2026 20:25:15 +0200 Subject: [PATCH 25/34] Improved titlebar --- DSView/pv/mainframe.cpp | 33 +++++++++-- DSView/pv/toolbars/titlebar.cpp | 100 +++++++++++++++++++++----------- DSView/pv/view/dsosignal.cpp | 41 +++++++++++-- DSView/pv/view/dsosignal.h | 4 ++ 4 files changed, 137 insertions(+), 41 deletions(-) diff --git a/DSView/pv/mainframe.cpp b/DSView/pv/mainframe.cpp index 477720699..094cd6848 100644 --- a/DSView/pv/mainframe.cpp +++ b/DSView/pv/mainframe.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include @@ -578,10 +579,34 @@ bool MainFrame::eventFilter(QObject *object, QEvent *event) } } else if (type == QEvent::MouseButtonPress) { - if (mouse_event->button() == Qt::LeftButton) - if (_hit_border != None) + if (mouse_event->button() == Qt::LeftButton && _hit_border != None) { + + // Wayland forbids clients from setting their own geometry, so the + // manual per-pixel resize below (computed from raw global mouse + // deltas) is a no-op/unreliable there, exactly like the manual + // window move was. Ask the compositor to perform the resize + // instead, via the same protocol native window-edge resizing uses. + if (QGuiApplication::platformName().startsWith("wayland", Qt::CaseInsensitive)) { + QWindow *win = windowHandle(); + if (win != NULL) { + static const QHash edgeMap = { + { TopLeft, Qt::TopEdge | Qt::LeftEdge }, + { Top, Qt::TopEdge }, + { TopRight, Qt::TopEdge | Qt::RightEdge }, + { Right, Qt::RightEdge }, + { BottomRight, Qt::BottomEdge | Qt::RightEdge }, + { Bottom, Qt::BottomEdge }, + { BottomLeft, Qt::BottomEdge | Qt::LeftEdge }, + { Left, Qt::LeftEdge }, + }; + win->startSystemResize(edgeMap.value(_hit_border)); + return true; + } + } + _bDraging = true; - _timer.start(50); + } + _timer.start(50); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) _clickPos = mouse_event->globalPosition().toPoint(); @@ -589,7 +614,7 @@ bool MainFrame::eventFilter(QObject *object, QEvent *event) _clickPos = mouse_event->globalPos(); #endif _dragStartRegion = GetFormRegion(); - } + } else if (type == QEvent::MouseButtonRelease) { if (mouse_event->button() == Qt::LeftButton) { _bDraging = false; diff --git a/DSView/pv/toolbars/titlebar.cpp b/DSView/pv/toolbars/titlebar.cpp index 3743bb5d3..4954af9b6 100644 --- a/DSView/pv/toolbars/titlebar.cpp +++ b/DSView/pv/toolbars/titlebar.cpp @@ -21,17 +21,18 @@ #include "titlebar.h" #include -#include +#include #include #include #include -#include +#include #include #include #include #include #include #include +#include #include "../config/appconfig.h" #include "../appcontrol.h" @@ -44,7 +45,7 @@ namespace toolbars { TitleBar::TitleBar(bool top, QWidget *parent, ITitleParent *titleParent, bool hasClose) : QWidget(parent) -{ +{ _minimizeButton = NULL; _maximizeButton = NULL; _closeButton = NULL; @@ -56,14 +57,14 @@ TitleBar::TitleBar(bool top, QWidget *parent, ITitleParent *titleParent, bool ha _title = NULL; _is_native = false; _titleParent = titleParent; - _is_done_moved = false; + _is_done_moved = false; _is_able_drag = true; assert(parent); setObjectName("TitleBar"); setContentsMargins(0,0,0,0); - setFixedHeight(32); + setFixedHeight(32); QHBoxLayout *lay1 = new QHBoxLayout(this); @@ -77,7 +78,9 @@ TitleBar::TitleBar(bool top, QWidget *parent, ITitleParent *titleParent, bool ha _maximizeButton->setObjectName("MaximizeButton"); lay1->addWidget(_minimizeButton); + lay1->addSpacing(6); lay1->addWidget(_maximizeButton); + lay1->addSpacing(6); connect(this, SIGNAL(normalShow()), parent, SLOT(showNormal())); connect(this, SIGNAL( maximizedShow()), parent, SLOT(showMaximized())); @@ -97,12 +100,12 @@ TitleBar::TitleBar(bool top, QWidget *parent, ITitleParent *titleParent, bool ha lay1->setContentsMargins(0,0,0,0); lay1->setSpacing(0); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + ADD_UI(this); } -TitleBar::~TitleBar(){ +TitleBar::~TitleBar(){ DESTROY_QT_OBJECT(_minimizeButton); DESTROY_QT_OBJECT(_maximizeButton); DESTROY_QT_OBJECT(_closeButton); @@ -129,14 +132,14 @@ bool TitleBar::ParentIsMaxsized() { if (_titleParent != NULL){ return _titleParent->ParentIsMaxsized(); - } + } else{ return parentWidget()->isMaximized(); } } void TitleBar::paintEvent(QPaintEvent *event) -{ +{ //draw logo icon QStyleOption o; o.initFrom(this); @@ -177,9 +180,9 @@ void TitleBar::setTitle(QString title) } else if (_parent != NULL){ _parent->setWindowTitle(title); - } + } } - + QString TitleBar::title() { if (!_is_native){ @@ -200,7 +203,7 @@ void TitleBar::showMaxRestore() } else { _maximizeButton->setIcon(QIcon(iconPath+"/restore.svg")); maximizedShow(); - } + } } void TitleBar::setRestoreButton(bool max) @@ -212,19 +215,29 @@ void TitleBar::setRestoreButton(bool max) _maximizeButton->setIcon(QIcon(iconPath+"/restore.svg")); } } - + void TitleBar::mousePressEvent(QMouseEvent* event) -{ +{ + // Middle-click-to-minimize is a common native title bar convention on + // Linux desktops (GNOME/KDE/etc.); only the top-level window's bar has a + // minimize button/action. showMinimized() remembers the maximized/normal + // state to restore to, so it needs no extra bookkeeping here. + if (_isTop && event->button() == Qt::MiddleButton) { + _parent->showMinimized(); + event->accept(); + return; + } + bool ableMove = !ParentIsMaxsized(); - if(event->button() == Qt::LeftButton && ableMove && _is_able_drag) + if(event->button() == Qt::LeftButton && ableMove && _is_able_drag) { int x = event->pos().x(); - int y = event->pos().y(); - + int y = event->pos().y(); + bool bTopWidow = AppControl::Instance()->GetTopWindow() == _parent; bool bClick = (x >= 6 && y >= 5 && x <= width() - 6); //top window need resize hit check - + if (!bTopWidow || bClick ){ // Wayland forbids clients from positioning themselves; manual @@ -252,21 +265,21 @@ void TitleBar::mousePressEvent(QMouseEvent* event) _oldPos = _titleParent->GetParentPos(); } else{ - _oldPos = _parent->pos(); + _oldPos = _parent->pos(); } _is_done_moved = false; - + event->accept(); return; - } - } + } + } QWidget::mousePressEvent(event); } void TitleBar::mouseMoveEvent(QMouseEvent *event) -{ - if(_is_draging){ +{ + if(_is_draging){ int datX = 0; int datY = 0; @@ -322,10 +335,10 @@ void TitleBar::mouseMoveEvent(QMouseEvent *event) _parent->move(x, y); } - + event->accept(); return; - } + } QWidget::mouseMoveEvent(event); } @@ -340,10 +353,10 @@ void TitleBar::mouseReleaseEvent(QMouseEvent* event) } void TitleBar::mouseDoubleClickEvent(QMouseEvent *event) -{ - QWidget::mouseDoubleClickEvent(event); +{ + QWidget::mouseDoubleClickEvent(event); - if (_isTop){ + if (_isTop){ QTimer::singleShot(200, this, [this](){ showMaxRestore(); @@ -353,7 +366,7 @@ void TitleBar::mouseDoubleClickEvent(QMouseEvent *event) void TitleBar::UpdateLanguage() { - + } void TitleBar::UpdateTheme() @@ -362,16 +375,37 @@ void TitleBar::UpdateTheme() } void TitleBar::UpdateFont() -{ +{ QFont font = this->font(); font.setPointSizeF(AppConfig::Instance().appOptions.fontSize+1); _title->setFont(font); + + // Scale the bar height with the configured font instead of a bare fixed + // pixel value, so it doesn't look undersized next to a native title bar + // when the font size (or a HiDPI/Wayland text scale) is larger than the + // default. The +14 padding leaves comfortable room for the min/max/close + // button icons; 32 is kept as the floor so the default look is unchanged. + const int textHeight = QFontMetrics(font).height(); + const int barHeight = qMax(32, textHeight + 14); + setFixedHeight(barHeight); + + // Scale the min/max/close button icons (and with them, the buttons' + // clickable area) with the bar height too - the previous default icon + // size (~16px, whatever QToolButton falls back to unset) left them + // looking small and cramped next to a taller, native-sized bar. + const QSize iconSize(barHeight * 0.55, barHeight * 0.55); + if (_minimizeButton != NULL) + _minimizeButton->setIconSize(iconSize); + if (_maximizeButton != NULL) + _maximizeButton->setIconSize(iconSize); + if (_closeButton != NULL) + _closeButton->setIconSize(iconSize); } void TitleBar::EnableAbleDrag(bool bEnabled) { _is_able_drag = bEnabled; } - + } // namespace toolbars } // namespace pv diff --git a/DSView/pv/view/dsosignal.cpp b/DSView/pv/view/dsosignal.cpp index 1249b7c47..876b3494a 100644 --- a/DSView/pv/view/dsosignal.cpp +++ b/DSView/pv/view/dsosignal.cpp @@ -76,6 +76,7 @@ DsoSignal::DsoSignal(data::DsoSnapshot *data, _vDialActive = false; _mValid = false; _level_valid = false; + _soft_measure_logged = false; _autoV = false; _autoH = false; _autoV_over = false; @@ -887,11 +888,43 @@ void DsoSignal::paint_mid(QPainter &p, int left, int right, QColor fore, QColor _mean = hw_offset - _mean / _data->get_sample_count(); } - // The hardware frequently returns no cycle data for the 2nd - // channel even on a valid signal, so its period/frequency/duty/ - // count/width read "--". Fall back to a software measurement. - if (!_level_valid || _period == 0) + // The hardware's own cycle/level measurement engine can fail to + // evaluate a channel correctly - most commonly because that + // channel's trigger is misconfigured (the trigger comparator and + // the auto-measurement level detection share the same hardware + // path), while the displayed waveform is unaffected since it is + // just the raw streamed samples. Fall back to a software + // measurement derived from those samples in that case. + if (!_level_valid || _period == 0) { + if (!_soft_measure_logged) { + // Log the raw hardware fields behind the fallback decision + // (not just the fact of it), so it is possible to tell + // whether the device genuinely found no valid cycle + // (count/levels read 0) or reports invalid despite having + // usable-looking counters. + const uint32_t count = (index == 0) ? status.ch0_cyc_cnt : status.ch1_cyc_cnt; + const uint32_t tlen = (index == 0) ? status.ch0_cyc_tlen : status.ch1_cyc_tlen; + const uint8_t lvl_high = (index == 0) ? status.ch0_high_level : status.ch1_high_level; + const uint8_t lvl_low = (index == 0) ? status.ch0_low_level : status.ch1_low_level; + dsv_info("DsoSignal: channel %d reports no valid hardware " + "cycle measurement (level_valid=%d, cyc_cnt=%u, " + "cyc_tlen=%u, high_level=%u, low_level=%u, " + "max=%u, min=%u), using software fallback. " + "This channel's trigger is probably set wrong " + "(the hardware measurement engine shares the " + "trigger comparator, so a misconfigured trigger " + "can break measurement without affecting the " + "displayed waveform).", + index, _level_valid, count, tlen, + lvl_high, lvl_low, _max, _min); + _soft_measure_logged = true; + } compute_soft_measure(hw_offset); + } + else { + // Hardware recovered: log again if it drops out later. + _soft_measure_logged = false; + } } } } diff --git a/DSView/pv/view/dsosignal.h b/DSView/pv/view/dsosignal.h index 52732dba6..236b4c95d 100644 --- a/DSView/pv/view/dsosignal.h +++ b/DSView/pv/view/dsosignal.h @@ -293,6 +293,10 @@ class DsoSignal : public Signal uint8_t _min; double _period; bool _level_valid; + // Set once compute_soft_measure() has logged that it is substituting for + // missing hardware cycle data, so repeated repaints don't spam the log. + // Cleared again once the hardware reports valid cycle data. + bool _soft_measure_logged; uint8_t _high; uint8_t _low; double _rms; From 571073ec41cbf7b9faba6ab53d64b62b95378762 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Sat, 18 Jul 2026 23:45:24 +0200 Subject: [PATCH 26/34] Minor improvements --- DSView/pv/data/dsoedgedetect.h | 91 ++++++++++++++++++ DSView/pv/dialogs/chanmeasure.cpp | 38 +++----- DSView/pv/dialogs/dsohistogram.cpp | 109 +++++++++++++++------- DSView/pv/dialogs/dsohistogram.h | 11 ++- DSView/pv/prop/binding/decoderoptions.cpp | 5 +- DSView/pv/prop/binding/deviceoptions.cpp | 5 +- DSView/pv/prop/binding/probeoptions.cpp | 3 +- DSView/pv/toolbars/filebar.cpp | 1 - DSView/pv/toolbars/logobar.cpp | 3 - DSView/pv/view/dsosignal.cpp | 61 ++++++------ DSView/pv/view/dsosignal.h | 13 +++ DSView/pv/view/viewport.cpp | 9 +- DSView/pv/view/viewport.h | 8 ++ 13 files changed, 255 insertions(+), 102 deletions(-) create mode 100644 DSView/pv/data/dsoedgedetect.h diff --git a/DSView/pv/data/dsoedgedetect.h b/DSView/pv/data/dsoedgedetect.h new file mode 100644 index 000000000..f18c9b080 --- /dev/null +++ b/DSView/pv/data/dsoedgedetect.h @@ -0,0 +1,91 @@ +/* + * This file is part of the DSView project. + * DSView is based on PulseView. + * + * Copyright (C) 2026 Schildkroet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DSVIEW_PV_DATA_DSOEDGEDETECT_H +#define DSVIEW_PV_DATA_DSOEDGEDETECT_H + +#include +#include +#include + +namespace pv { +namespace data { + +// Sample indices of the rising/falling edges found by dso_detect_edges(). +struct DsoEdgeSet +{ + std::vector rising; + std::vector falling; +}; + +// Schmitt-trigger edge detection over a DSO sample range: tracks a high/low +// state and flips it once the signal clears the upper/lower threshold (5% of +// hysteresis around the value's midpoint), recording the sample index of each +// transition. This tolerates finite rise time and noise, unlike a single-step +// "crossed mid this sample" test. +// +// value_at(i) must return the signal's value for sample index i, in whatever +// unit is convenient at the call site (raw ADC counts, volts, hw_offset- +// relative counts, ...) - only relative comparisons are made, so the unit +// does not matter as long as it is used consistently across the whole range. +// Returns an empty set if there are fewer than 2 samples or the signal is +// flat (max <= min), since no threshold could then be established. +template +DsoEdgeSet dso_detect_edges(uint64_t sample_count, ValueFn &&value_at) +{ + DsoEdgeSet edges; + + if (sample_count < 2) + return edges; + + double vmin = 1e300, vmax = -1e300; + for (uint64_t i = 0; i < sample_count; i++) { + const double v = value_at(i); + vmin = std::min(vmin, v); + vmax = std::max(vmax, v); + } + if (vmax <= vmin) + return edges; // flat signal: no threshold to detect edges against + + const double mid = (vmin + vmax) / 2.0; + const double hyst = (vmax - vmin) * 0.05; + const double hi = mid + hyst; + const double lo = mid - hyst; + + bool is_high = (value_at(0) >= mid); + for (uint64_t i = 1; i < sample_count; i++) { + const double v = value_at(i); + if (!is_high && v >= hi) { + is_high = true; + edges.rising.push_back(i); + } else if (is_high && v <= lo) { + is_high = false; + edges.falling.push_back(i); + } + } + + return edges; +} + +} // namespace data +} // namespace pv + +#endif // DSVIEW_PV_DATA_DSOEDGEDETECT_H diff --git a/DSView/pv/dialogs/chanmeasure.cpp b/DSView/pv/dialogs/chanmeasure.cpp index 58744ff2c..90ddc5aa3 100644 --- a/DSView/pv/dialogs/chanmeasure.cpp +++ b/DSView/pv/dialogs/chanmeasure.cpp @@ -31,6 +31,7 @@ #include "../sigsession.h" #include "../view/dsosignal.h" #include "../data/dsosnapshot.h" +#include "../data/dsoedgedetect.h" #include "../ui/langresource.h" using namespace std; @@ -81,32 +82,17 @@ EdgeInfo extract_edges(view::DsoSignal *sig, double dt_ns) return (hw_offset - (double)buf[i]) * vscale; }; - double vmin = 1e300, vmax = -1e300; - for (uint64_t i = 0; i < n; i++) { - const double v = volt(i); - vmin = min(vmin, v); - vmax = max(vmax, v); - } - const double span = (vmax > vmin) ? (vmax - vmin) : 1.0; - const double mid = (vmax + vmin) / 2.0; - const double hyst = span * 0.05; - const double hi = mid + hyst; - const double lo = mid - hyst; - - // Schmitt-trigger edge detection: flip the high/low state on the upper / - // lower thresholds. This tolerates finite rise time and noise, which a - // single-step "crossed mid this sample" test does not. - bool is_high = (volt(0) >= mid); - for (uint64_t i = 1; i < n; i++) { - const double v = volt(i); - if (!is_high && v >= hi) { - is_high = true; - info.rising.push_back(i * dt_ns); - } else if (is_high && v <= lo) { - is_high = false; - info.falling.push_back(i * dt_ns); - } - } + // Fully qualified: the local variable "data" above shadows the "data" + // namespace within this function. + const pv::data::DsoEdgeSet edge_set = pv::data::dso_detect_edges(n, volt); + + // Convert sample indices to times (ns) for this dialog's phase/delay math. + info.rising.reserve(edge_set.rising.size()); + for (uint64_t idx : edge_set.rising) + info.rising.push_back(idx * dt_ns); + info.falling.reserve(edge_set.falling.size()); + for (uint64_t idx : edge_set.falling) + info.falling.push_back(idx * dt_ns); if (info.rising.size() >= 2) { double sum = 0; diff --git a/DSView/pv/dialogs/dsohistogram.cpp b/DSView/pv/dialogs/dsohistogram.cpp index ca8ab3012..451ccdfa8 100644 --- a/DSView/pv/dialogs/dsohistogram.cpp +++ b/DSView/pv/dialogs/dsohistogram.cpp @@ -31,6 +31,7 @@ #include "../sigsession.h" #include "../view/dsosignal.h" #include "../data/dsosnapshot.h" +#include "../data/dsoedgedetect.h" #include "../ui/langresource.h" using namespace std; @@ -43,7 +44,9 @@ namespace dialogs { HistogramPlot::HistogramPlot(QWidget *parent) : QWidget(parent), _x_min(0), - _x_max(0) + _x_max(0), + _has_mean(false), + _mean_value(0.0) { setMinimumHeight(150); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -51,13 +54,16 @@ HistogramPlot::HistogramPlot(QWidget *parent) : void HistogramPlot::set_data(const QVector &bins, double x_min, double x_max, const QString &x_unit, - const QString &title) + const QString &title, bool has_mean, + double mean_value) { _bins = bins; _x_min = x_min; _x_max = x_max; _x_unit = x_unit; _title = title; + _has_mean = has_mean; + _mean_value = mean_value; update(); } @@ -67,6 +73,11 @@ void HistogramPlot::clear_data() update(); } +QString HistogramPlot::fmt_value(double v) const +{ + return QString::number(v, 'f', 2) + " " + _x_unit; +} + void HistogramPlot::paintEvent(QPaintEvent *event) { (void)event; @@ -115,6 +126,20 @@ void HistogramPlot::paintEvent(QPaintEvent *event) const int plot_h = bottom - top; const int n = _bins.size(); + // Gridlines (drawn under the bars): 3 horizontal divisions (25/50/75% of + // the peak count) and vertical divisions at the same 25/50/75% x + // fractions, so a bar's height and position can be read off against a + // scale instead of guessed against bare min/max endpoints. + QColor grid = fore; + grid.setAlpha(45); + p.setPen(QPen(grid, 1, Qt::DotLine)); + for (int k = 1; k <= 3; k++) { + const int gy = bottom - (int)(plot_h * (k / 4.0)); + p.drawLine(left + 1, gy, right - 1, gy); + const int gx = left + (int)(plot_w * (k / 4.0)); + p.drawLine(gx, top + 1, gx, bottom - 1); + } + p.setPen(Qt::NoPen); p.setBrush(bar); for (int i = 0; i < n; i++) { @@ -125,14 +150,47 @@ void HistogramPlot::paintEvent(QPaintEvent *event) p.fillRect(QRect(x0, bottom - h, max(1, x1 - x0 - 1), h), bar); } - // Axis min/max annotations, in the band below the frame. + // Mean marker: a dashed vertical line plus a small label, so it is easy + // to see at a glance how the distribution sits relative to its average. + if (_has_mean && _x_max > _x_min) { + const double rate = (_mean_value - _x_min) / (_x_max - _x_min); + if (rate >= 0.0 && rate <= 1.0) { + QColor accent = palette().color(QPalette::Highlight); + const int mx = left + (int)(rate * plot_w); + p.setPen(QPen(accent, 1, Qt::DashLine)); + p.drawLine(mx, top + 1, mx, bottom - 1); + + p.setPen(accent); + const QString mean_str = "Mean " + fmt_value(_mean_value); + const int label_w = p.fontMetrics().horizontalAdvance(mean_str); + // Keep the label inside the frame regardless of which side the + // marker falls on. + int label_x = mx + 3; + if (label_x + label_w > right) + label_x = mx - 3 - label_w; + p.drawText(QRect(label_x, top + 2, label_w, text_h), mean_str); + } + } + + // Axis annotations, in the band below the frame: min/mid/max values + // matching the vertical gridlines above, plus the peak bin count inside + // the frame's top-right corner so bar heights read against a real scale + // rather than only relative to each other. p.setPen(fore); p.drawText(QRect(left, bottom, plot_w, height() - bottom), - Qt::AlignLeft | Qt::AlignVCenter, - QString::number(_x_min, 'f', 2) + " " + _x_unit); + Qt::AlignLeft | Qt::AlignVCenter, fmt_value(_x_min)); p.drawText(QRect(left, bottom, plot_w, height() - bottom), + Qt::AlignHCenter | Qt::AlignVCenter, + fmt_value((_x_min + _x_max) / 2.0)); + p.drawText(QRect(left, bottom, plot_w, height() - bottom), + Qt::AlignRight | Qt::AlignVCenter, fmt_value(_x_max)); + + QColor peak_fore = fore; + peak_fore.setAlpha(180); + p.setPen(peak_fore); + p.drawText(QRect(left, top + 2, plot_w - 4, text_h), Qt::AlignRight | Qt::AlignVCenter, - QString::number(_x_max, 'f', 2) + " " + _x_unit); + "n=" + QString::number((qulonglong)peak)); } //------------------------------------------------------------------- DsoHistogram @@ -322,7 +380,8 @@ void DsoHistogram::compute() vbins[b] += 1.0; } _value_plot->set_data(vbins, vmin * vfac, vmax * vfac, vunit, - L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HIST_VALUE), "Value distribution")); + L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HIST_VALUE), "Value distribution"), + true, vmean * vfac); // --- timing (jitter) histogram + stats --- // Use the analysed snapshot's own sample rate (this is what the waveform @@ -336,34 +395,11 @@ void DsoHistogram::compute() // Detect edges directly on the raw ADC samples (0..255). This keeps the // jitter measurement independent of the voltage scaling, which can read // back as zero in some states and would otherwise flatten the signal. - int raw_min = 255, raw_max = 0; - for (uint64_t i = 0; i < n; i++) { - const int r = buf[i]; - raw_min = min(raw_min, r); - raw_max = max(raw_max, r); - } - const double raw_mid = (raw_min + raw_max) / 2.0; - const double raw_hyst = (raw_max - raw_min) * 0.05; - - // Schmitt-trigger edge detection: track the high/low state and flip it on - // the upper/lower hysteresis thresholds. A rising edge is recorded each - // time the state goes low->high. This handles finite rise time and noise - // (unlike a single-step "crossed mid this sample" test). - std::vector edges; // rising-edge sample indices - if (raw_max > raw_min) { - const double hi = raw_mid + raw_hyst; - const double lo = raw_mid - raw_hyst; - bool is_high = (buf[0] >= raw_mid); - for (uint64_t i = 1; i < n; i++) { - const int r = buf[i]; - if (!is_high && r >= hi) { - is_high = true; - edges.push_back(i); - } else if (is_high && r <= lo) { - is_high = false; - } - } - } + // (Fully qualified: the local variable "data" above shadows the "data" + // namespace within this function.) + const pv::data::DsoEdgeSet edge_set = pv::data::dso_detect_edges( + n, [&](uint64_t i) -> double { return (double)buf[i]; }); + const std::vector &edges = edge_set.rising; // Build the statistics as a two-column table (metric | value) so the // amplitude and timing figures line up and read cleanly. @@ -418,7 +454,8 @@ void DsoHistogram::compute() } _time_plot->set_data(tbins, pmin, pmax, "ns", L_S(STR_PAGE_DLG, S_ID(IDS_DLG_HIST_PERIOD), - "Period distribution (jitter)")); + "Period distribution (jitter)"), + true, pmean); stats += sep(); stats += row("Edges", QString::number((qulonglong)edges.size())); diff --git a/DSView/pv/dialogs/dsohistogram.h b/DSView/pv/dialogs/dsohistogram.h index 399c4ad52..c945a7f48 100644 --- a/DSView/pv/dialogs/dsohistogram.h +++ b/DSView/pv/dialogs/dsohistogram.h @@ -46,19 +46,28 @@ class HistogramPlot : public QWidget public: HistogramPlot(QWidget *parent = NULL); + // has_mean/mean_value optionally draw a dashed marker line at that x + // position (e.g. the dataset's mean), which helps gauge how a distorted + // distribution sits relative to its average at a glance. void set_data(const QVector &bins, double x_min, double x_max, - const QString &x_unit, const QString &title); + const QString &x_unit, const QString &title, + bool has_mean = false, double mean_value = 0.0); void clear_data(); protected: void paintEvent(QPaintEvent *event) override; +private: + QString fmt_value(double v) const; + private: QVector _bins; double _x_min; double _x_max; QString _x_unit; QString _title; + bool _has_mean; + double _mean_value; }; // Value + timing (jitter) histogram of a single DSO channel, with basic diff --git a/DSView/pv/prop/binding/decoderoptions.cpp b/DSView/pv/prop/binding/decoderoptions.cpp index f5da31378..6d85c99f4 100644 --- a/DSView/pv/prop/binding/decoderoptions.cpp +++ b/DSView/pv/prop/binding/decoderoptions.cpp @@ -22,7 +22,7 @@ #include #include "decoderoptions.h" -#include +#include #include #include "../../data/decoderstack.h" @@ -35,8 +35,9 @@ #include "../../config/appconfig.h" using namespace boost; +using namespace boost::placeholders; using namespace std; - + namespace pv { namespace prop { namespace binding { diff --git a/DSView/pv/prop/binding/deviceoptions.cpp b/DSView/pv/prop/binding/deviceoptions.cpp index 3ad6812d4..602a60a23 100644 --- a/DSView/pv/prop/binding/deviceoptions.cpp +++ b/DSView/pv/prop/binding/deviceoptions.cpp @@ -22,7 +22,7 @@ #include "deviceoptions.h" -#include +#include #include #include #include "../bool.h" @@ -35,8 +35,9 @@ #include "../../sigsession.h" #include "../../deviceagent.h" #include "../../ui/langresource.h" - + using namespace std; +using namespace boost::placeholders; namespace pv { namespace prop { diff --git a/DSView/pv/prop/binding/probeoptions.cpp b/DSView/pv/prop/binding/probeoptions.cpp index 3d9ac3ef4..804ae45b1 100644 --- a/DSView/pv/prop/binding/probeoptions.cpp +++ b/DSView/pv/prop/binding/probeoptions.cpp @@ -20,7 +20,7 @@ */ #include "probeoptions.h" -#include +#include #include #include #include "../bool.h" @@ -34,6 +34,7 @@ #include "../../ui/langresource.h" using namespace std; +using namespace boost::placeholders; namespace pv { namespace prop { diff --git a/DSView/pv/toolbars/filebar.cpp b/DSView/pv/toolbars/filebar.cpp index f562431ce..a1ca34c55 100644 --- a/DSView/pv/toolbars/filebar.cpp +++ b/DSView/pv/toolbars/filebar.cpp @@ -19,7 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include #include #include #include diff --git a/DSView/pv/toolbars/logobar.cpp b/DSView/pv/toolbars/logobar.cpp index 411a8faea..9a2093138 100644 --- a/DSView/pv/toolbars/logobar.cpp +++ b/DSView/pv/toolbars/logobar.cpp @@ -19,9 +19,6 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include - - #include #include #include diff --git a/DSView/pv/view/dsosignal.cpp b/DSView/pv/view/dsosignal.cpp index 876b3494a..b1fbc9a2e 100644 --- a/DSView/pv/view/dsosignal.cpp +++ b/DSView/pv/view/dsosignal.cpp @@ -29,7 +29,8 @@ #include "view.h" #include "../dsvdef.h" #include "../data/dsosnapshot.h" -#include "../sigsession.h" +#include "../data/dsoedgedetect.h" +#include "../sigsession.h" #include "../log.h" #include "../appcontrol.h" #include "../ui/langresource.h" @@ -77,6 +78,9 @@ DsoSignal::DsoSignal(data::DsoSnapshot *data, _mValid = false; _level_valid = false; _soft_measure_logged = false; + _soft_measure_cache_valid = false; + _soft_measure_cache_data = NULL; + _soft_measure_cache_sample_count = 0; _autoV = false; _autoH = false; _autoV_over = false; @@ -1084,6 +1088,22 @@ void DsoSignal::compute_soft_measure(int hw_offset) if (total < 2) return; + // paint_mid() calls this on every repaint for as long as the hardware + // measurement stays invalid, which - while an acquisition is actively + // running - can be many times per second. Skip the O(n) rescan below + // unless the underlying dataset actually changed. get_trig_time() is + // used rather than just the sample count because the configured capture + // depth (and so get_sample_count()) is normally the same on every run, + // which would otherwise make this cache never invalidate across repeat + // captures. + const QDateTime trig_time = session->get_trig_time(); + if (_soft_measure_cache_valid && + _soft_measure_cache_data == _data && + _soft_measure_cache_sample_count == total && + _soft_measure_cache_trig_time == trig_time) { + return; // _period/_high_time/etc already hold the current result + } + const uint8_t *buf = _data->get_samples(0, 0, get_index()); if (buf == NULL) return; @@ -1108,43 +1128,30 @@ void DsoSignal::compute_soft_measure(int hw_offset) const uint64_t MaxScan = 1000000; const uint64_t n = min(total, MaxScan); + // Once we reach here, we are about to (re)compute the result for this + // dataset - remember its identity so the next call can skip straight to + // the early-return above until the data changes again. + _soft_measure_cache_valid = true; + _soft_measure_cache_data = _data; + _soft_measure_cache_sample_count = total; + _soft_measure_cache_trig_time = trig_time; + // Work in voltage-proportional space (higher value = higher voltage) so // "high time" matches the hardware's positive-duty convention. auto val = [&](uint64_t i) -> double { return (double)hw_offset - buf[i]; }; - double vmin = 1e300, vmax = -1e300; int rmin = 255, rmax = 0; for (uint64_t i = 0; i < n; i++) { - const double v = val(i); - vmin = min(vmin, v); - vmax = max(vmax, v); rmin = min(rmin, (int)buf[i]); rmax = max(rmax, (int)buf[i]); } - if (vmax <= vmin) - return; // flat: nothing to measure - - const double mid = (vmin + vmax) / 2.0; - const double hyst = (vmax - vmin) * 0.05; - const double hi = mid + hyst; - const double lo = mid - hyst; - - // Schmitt-trigger edge detection (rising = low->high voltage). - std::vector rising, falling; - bool is_high = (val(0) >= mid); - for (uint64_t i = 1; i < n; i++) { - const double v = val(i); - if (!is_high && v >= hi) { - is_high = true; - rising.push_back(i); - } else if (is_high && v <= lo) { - is_high = false; - falling.push_back(i); - } - } + + const data::DsoEdgeSet edge_set = data::dso_detect_edges(n, val); + const std::vector &rising = edge_set.rising; + const std::vector &falling = edge_set.falling; if (rising.size() < 2) - return; // not enough cycles + return; // flat, or not enough cycles // Mean period (samples) from consecutive rising edges. double psum = 0; diff --git a/DSView/pv/view/dsosignal.h b/DSView/pv/view/dsosignal.h index 236b4c95d..b109d2cea 100644 --- a/DSView/pv/view/dsosignal.h +++ b/DSView/pv/view/dsosignal.h @@ -26,6 +26,7 @@ #include "signal.h" #include "../dstimer.h" +#include namespace pv { namespace data { @@ -297,6 +298,18 @@ class DsoSignal : public Signal // missing hardware cycle data, so repeated repaints don't spam the log. // Cleared again once the hardware reports valid cycle data. bool _soft_measure_logged; + + // Identifies the dataset compute_soft_measure() last computed its result + // for, so it can skip re-scanning the sample buffer on repaints that + // don't follow a new acquisition (e.g. hover/cursor redraws while the + // hardware measurement stays invalid for many consecutive frames). + // get_trig_time() changes on every new capture even when the configured + // sample depth (and so get_sample_count()) stays the same between runs, + // which a count-only check would miss. + bool _soft_measure_cache_valid; + const pv::data::DsoSnapshot *_soft_measure_cache_data; + uint64_t _soft_measure_cache_sample_count; + QDateTime _soft_measure_cache_trig_time; uint8_t _high; uint8_t _low; double _rms; diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp index 4ac6f2841..74f23da0f 100644 --- a/DSView/pv/view/viewport.cpp +++ b/DSView/pv/view/viewport.cpp @@ -632,7 +632,12 @@ void Viewport::paint_ref_waves(QPainter &p) continue; const int64_t count = end_sample - start_sample + 1; - QPointF *points = new QPointF[count]; + // Reuse the scratch buffer across calls/waves instead of a fresh + // heap array every repaint - resize() only reallocates when growing + // past the buffer's current capacity. + if (_ref_wave_points.size() < count) + _ref_wave_points.resize(count); + QPointF *points = _ref_wave_points.data(); QPointF *point = points; float x = (start_sample / samples_per_pixel - x_offset) + left + trig_hoff * pixels_per_sample; @@ -653,8 +658,6 @@ void Viewport::paint_ref_waves(QPainter &p) // Label the reference near its left end. p.drawText(QPointF(left + 4, top + 12), rw.name); - - delete[] points; } } diff --git a/DSView/pv/view/viewport.h b/DSView/pv/view/viewport.h index c5a2d663b..9c612f099 100644 --- a/DSView/pv/view/viewport.h +++ b/DSView/pv/view/viewport.h @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include "../view/view.h" @@ -190,6 +192,12 @@ private slots: QPixmap _pixmap; QMenu *_cmenu; + // Reusable scratch buffer for paint_ref_waves(), so it does not have to + // heap-allocate/free a QPointF array on every repaint (including ones + // triggered by mere mouse movement) - QVector::resize() only reallocates + // when growing past the buffer's current capacity. + QVector _ref_wave_points; + uint64_t _sample_received; QPoint _mouse_point; QPoint _mouse_down_point; From a3b4ead9c395a6daf73e2932b8a580d7e26e6c6f Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Mon, 20 Jul 2026 09:44:26 +0200 Subject: [PATCH 27/34] Update win workflow --- .github/workflows/build.yml | 28 +++++++++++++++++++++++++++- DSView/pv/appcontrol.cpp | 28 ++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3839229b4..26c311c27 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -181,6 +181,7 @@ jobs: install: >- git zip + rsync mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja @@ -197,9 +198,21 @@ jobs: - name: Configure run: | + # Force CMake to find MSYS2's own mingw64 Python (whose runtime DLL + # we actually bundle below) instead of the native Windows Python + # that's preinstalled on the windows-latest runner (e.g. python314.dll + # under the hosted tool cache). If CMake picks that one up instead, + # DSView.exe ends up linked against a DLL that only exists on the CI + # runner, isn't found by the /mingw64/bin-only dependency bundling + # loop, and the packaged app fails to start with "python314.dll was + # not found". cmake -S . -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=dsview-dist + -DCMAKE_INSTALL_PREFIX=dsview-dist \ + -DPython3_ROOT_DIR=/mingw64 \ + -DPython3_FIND_STRATEGY=LOCATION \ + -DPython3_FIND_REGISTRY=NEVER \ + -DPython3_FIND_VIRTUALENV=STANDARD - name: Build run: cmake --build build -j "$(nproc)" @@ -221,6 +234,19 @@ jobs: cp -v /mingw64/share/qt6/plugins/${group}/*.dll "dsview-dist/${group}/" done + # DSView embeds Python (for its protocol decoders) via + # Py_InitializeEx(); without its own copy of the standard library + # (encodings, os.py, etc.) next to the exe, the interpreter can't + # bootstrap once libpython3.14.dll is copied out of MSYS2's + # /mingw64 prefix layout, and Py_FatalError() aborts the process + # silently before any window is shown. Bundle it under "pylib", + # which appcontrol.cpp points PYTHONHOME at via applicationDirPath(). + PYVER="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')" + mkdir -p "dsview-dist/pylib" + rsync -a \ + --exclude 'test' --exclude 'idlelib' --exclude 'tkinter' --exclude '__pycache__' \ + "/mingw64/lib/python${PYVER}/" "dsview-dist/pylib/" + # The install rules only place DSView.exe + data files; the MSYS2 # toolchain/Qt6 runtime DLLs it's dynamically linked against still # need to be copied in manually. Resolve the dependency closure with diff --git a/DSView/pv/appcontrol.cpp b/DSView/pv/appcontrol.cpp index d5cb559b5..df29b1eea 100644 --- a/DSView/pv/appcontrol.cpp +++ b/DSView/pv/appcontrol.cpp @@ -94,15 +94,35 @@ bool AppControl::Init() srd_log_set_context(dsv_log_context()); -#if defined(_WIN32) && defined(DEBUG_INFO) - //able run debug with qtcreator - QString pythonHome = "c:/python"; +#if defined(_WIN32) + // The packaged app carries its own copy of the Python standard library + // next to DSView.exe (see the "pylib" folder produced by the Windows CI + // build), since CPython's own relative-path auto-detection isn't + // reliable once the interpreter DLL is copied out of its original + // MSYS2/mingw64 prefix layout into a flat distribution folder. Without + // PYTHONHOME pointing at it, Py_InitializeEx() fails to find the stdlib + // and calls Py_FatalError(), which aborts the process before any window + // is shown and before any of our own logging can run. + QString pythonHome = QCoreApplication::applicationDirPath() + "/pylib"; QDir pydir; if (pydir.exists(pythonHome)){ const wchar_t *pyhome = reinterpret_cast(pythonHome.utf16()); srd_set_python_home(pyhome); + + // PYTHONHOME alone isn't enough: MSYS2's Python is built with a + // Unix-style prefix layout, so Py_SetPythonHome() makes CPython look + // for the stdlib under "/lib/pythonX.Y/...", not directly + // inside "/". Our bundled copy sits flat in "pylib/" (its + // encodings/, os.py etc. are direct children), so point PYTHONPATH + // straight at it too - this is read during interpreter bootstrap, + // before Py_SetPythonHome's own (mismatched) landmark search would + // otherwise fail to find "encodings" and abort the process. + QString libDynload = pythonHome + "/lib-dynload"; + QString pythonPath = pythonHome; + if (pydir.exists(libDynload)) + pythonPath += ";" + libDynload; + qputenv("PYTHONPATH", pythonPath.toUtf8()); } - #endif //the python script path of decoder From e61c50de17d09570953ef316e1cb74dd78eb52ec Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Mon, 20 Jul 2026 10:47:42 +0200 Subject: [PATCH 28/34] Fix floating view in DSO mode --- DSView/pv/view/viewport.cpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp index 74f23da0f..6eab54276 100644 --- a/DSView/pv/view/viewport.cpp +++ b/DSView/pv/view/viewport.cpp @@ -1850,23 +1850,28 @@ void Viewport::measure() } else if (s->signal_type() == SR_CHANNEL_DSO) { view::DsoSignal *dsoSig = ( view::DsoSignal*)s; - if (s->enabled()) { - if (_measure_en && dsoSig->measure(_view.hover_point())) { - _measure_type = DSO_VALUE; - } - else { - _measure_type = NO_MEASURE; - } + // measure() must run even while the channel is disabled: it + // resets its own hover state (_hover_en = false) and returns + // false in that case. Gating the call itself on s->enabled() + // (as before) skipped that reset entirely, so a channel + // disabled after being hovered kept showing a stale row in + // the floating measurement panel forever. + // + // Only ever promote _measure_type to DSO_VALUE here, never + // reset it back to NO_MEASURE - it already starts each call + // at NO_MEASURE (set once above, before this loop), and with + // several DSO/analog channels in play, one channel failing + // to hover-match (e.g. because it's disabled, or the mouse + // is outside its row in split mode) must not clobber another + // channel's successful match from earlier in this same loop. + if (_measure_en && dsoSig->measure(_view.hover_point())) { + _measure_type = DSO_VALUE; } } else if (s->signal_type() == SR_CHANNEL_ANALOG) { view::AnalogSignal *analogSig = (view::AnalogSignal*)s; - if (s->enabled()) { - if (_measure_en && analogSig->measure(_view.hover_point())) { - _measure_type = DSO_VALUE; - } else { - _measure_type = NO_MEASURE; - } + if (_measure_en && analogSig->measure(_view.hover_point())) { + _measure_type = DSO_VALUE; } } } From d631a51393e84adf908432d1100b86f633a70506 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Mon, 20 Jul 2026 11:37:52 +0200 Subject: [PATCH 29/34] Improved driver handling --- .github/workflows/build.yml | 26 ++++++ .gitignore | 3 + CMakeLists.txt | 19 ++++ DSView/packaging/debian/postinst | 15 ++++ DSView/packaging/debian/postrm | 13 +++ DSView/pv/config/appconfig.cpp | 2 + DSView/pv/config/appconfig.h | 1 + DSView/pv/mainframe.cpp | 89 +++++++++++++++++++ DSView/pv/mainframe.h | 1 + installer/windows/dsl_usb_instruments.inf | 91 ++++++++++++++++++++ installer/windows/dsview.iss | 62 +++++++++++++ installer/windows/installed_via_setup.marker | 4 + 12 files changed, 326 insertions(+) create mode 100755 DSView/packaging/debian/postinst create mode 100755 DSView/packaging/debian/postrm create mode 100644 installer/windows/dsl_usb_instruments.inf create mode 100644 installer/windows/dsview.iss create mode 100644 installer/windows/installed_via_setup.marker diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 26c311c27..81f56a4f2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -277,6 +277,32 @@ jobs: name: DSView-windows path: DSView-windows-x86_64.zip + # Native Windows shell rather than the job-default MSYS2 bash for the + # Inno Setup steps below: iscc.exe/pnputil.exe are plain Windows tools, + # and MSYS2 bash's automatic POSIX<->Windows path conversion is an + # unnecessary complication for invoking them. + - name: Install Inno Setup + shell: pwsh + run: choco install innosetup -y --no-progress + + - name: Build Windows installer + shell: pwsh + run: | + # Read the app version from CMakeLists.txt instead of hand-duplicating + # it here, so the installer's version can't silently drift from the + # one DSView itself reports. + $major = (Select-String -Path CMakeLists.txt -Pattern 'DS_VERSION_MAJOR (\d+)').Matches[0].Groups[1].Value + $minor = (Select-String -Path CMakeLists.txt -Pattern 'DS_VERSION_MINOR (\d+)').Matches[0].Groups[1].Value + $micro = (Select-String -Path CMakeLists.txt -Pattern 'DS_VERSION_MICRO (\d+)').Matches[0].Groups[1].Value + $version = "$major.$minor.$micro" + & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" "installer\windows\dsview.iss" "/DMyAppVersion=$version" + + - name: Upload installer artifact + uses: actions/upload-artifact@v6 + with: + name: DSView-windows-installer + path: installer/windows/Output/DSView-windows-x86_64-setup.exe + release: name: Publish latest pre-release needs: [linux, windows] diff --git a/.gitignore b/.gitignore index 6bc68cf18..07d032f6a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ autom4te.cache !cmake_modules CPackConfig.cmake CPackSourceConfig.cmake +_CPack_Packages +*.deb +*.AppImage cmake_install.cmake Makefile *.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a9cf55ff..11462b8a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -709,6 +709,15 @@ else() install(FILES DSView/DreamSourceLab.rules DESTINATION /etc/udev/rules.d RENAME 60-dreamsourcelab.rules) endif() + # Also ship a copy alongside the app itself (share/DSView, same as the + # other resources installed below), so it ends up inside the AppImage + # too - unlike the install() rules above, AppImage packaging has no + # way to place files outside its own AppDir, so this is the only copy + # an AppImage user has access to. MainFrame::show_driver_hint_once() + # looks for it here to give AppImage users a one-time copy-paste fix + # when the system-wide rule (installed above) isn't present. + install(FILES DSView/DreamSourceLab.rules DESTINATION share/DSView) + endif() install(FILES NEWS25 DESTINATION share/DSView RENAME NEWS25) @@ -734,6 +743,16 @@ set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${DS_VERSION_MAJOR}.${DS_VERSION_MINOR}.${DS_VERSION_MICRO}") set(CPACK_SOURCE_GENERATOR "TGZ") +# Reload udev immediately after installing/removing the .deb, so the +# 60-dreamsourcelab.rules permission fix (see the udev install() rules above) +# takes effect without requiring a reboot or manual `udevadm` invocation. +# CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA is ignored by every generator except DEB, +# so this is safe to set unconditionally. +set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA + "${CMAKE_CURRENT_SOURCE_DIR}/DSView/packaging/debian/postinst" + "${CMAKE_CURRENT_SOURCE_DIR}/DSView/packaging/debian/postrm" +) + include(CPack) #=============================================================================== diff --git a/DSView/packaging/debian/postinst b/DSView/packaging/debian/postinst new file mode 100755 index 000000000..32d5434e0 --- /dev/null +++ b/DSView/packaging/debian/postinst @@ -0,0 +1,15 @@ +#!/bin/sh +# Reload udev after installing 60-dreamsourcelab.rules (see CMakeLists.txt), +# so the device permission fix takes effect immediately - without this, a +# freshly plugged-in DreamSourceLab device would still be denied access by +# libusb until the next reboot or a manual `udevadm control --reload-rules`. +set -e + +if [ "$1" = "configure" ]; then + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules || true + udevadm trigger --subsystem-match=usb || true + fi +fi + +exit 0 diff --git a/DSView/packaging/debian/postrm b/DSView/packaging/debian/postrm new file mode 100755 index 000000000..a5439ef3b --- /dev/null +++ b/DSView/packaging/debian/postrm @@ -0,0 +1,13 @@ +#!/bin/sh +# Reload udev after removing 60-dreamsourcelab.rules, so a DreamSourceLab +# device's permissions revert immediately rather than only on next reboot. +set -e + +if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules || true + udevadm trigger --subsystem-match=usb || true + fi +fi + +exit 0 diff --git a/DSView/pv/config/appconfig.cpp b/DSView/pv/config/appconfig.cpp index e6b0f35ff..3a51521ea 100644 --- a/DSView/pv/config/appconfig.cpp +++ b/DSView/pv/config/appconfig.cpp @@ -294,6 +294,7 @@ static void _loadHistory(UserHistory &o, QSettings &st) getFiled("openDir", st, o.openDir, ""); getFiled("protocolExportPath", st, o.protocolExportPath, ""); getFiled("exportFormat", st, o.exportFormat, ""); + getFiled("showDriverHint", st, o.showDriverHint, true); st.endGroup(); } @@ -308,6 +309,7 @@ static void _saveHistory(UserHistory &o, QSettings &st) setFiled("openDir", st, o.openDir); setFiled("protocolExportPath", st, o.protocolExportPath); setFiled("exportFormat", st, o.exportFormat); + setFiled("showDriverHint", st, o.showDriverHint); st.endGroup(); } diff --git a/DSView/pv/config/appconfig.h b/DSView/pv/config/appconfig.h index d972cc786..faf3bec7f 100644 --- a/DSView/pv/config/appconfig.h +++ b/DSView/pv/config/appconfig.h @@ -132,6 +132,7 @@ struct UserHistory QString openDir; QString protocolExportPath; QString exportFormat; + bool showDriverHint; }; struct FontParam diff --git a/DSView/pv/mainframe.cpp b/DSView/pv/mainframe.cpp index 094cd6848..86b3ea16f 100644 --- a/DSView/pv/mainframe.cpp +++ b/DSView/pv/mainframe.cpp @@ -737,6 +737,12 @@ void MainFrame::ShowFormInit() mainWindow->restore_dock(); }); + // Delayed past ShowHelpDocAsync()'s 300ms so the two one-time startup + // notices (help doc, driver hint) don't pop up on top of each other. + QTimer::singleShot(800, this, [this](){ + show_driver_hint_once(); + }); + if (!_is_win32_parent_window){ QFrame::show(); return; @@ -1154,6 +1160,89 @@ void MainFrame::show_doc() } } +void MainFrame::show_driver_hint_once() +{ + AppConfig &app = AppConfig::Instance(); + if (!app.userHistory.showDriverHint) + return; + + QString text; + +#ifdef _WIN32 + // Skip entirely if this is an installed copy from the Inno Setup + // installer (installer/windows/dsview.iss) - it already stages the + // WinUSB driver via pnputil during setup, so there's nothing to warn + // about. Only the portable zip (which can't run pnputil unelevated) + // needs the hint below. + if (QFile::exists(QCoreApplication::applicationDirPath() + + "/installed_via_setup.marker")) + return; + + // Unlike Linux (a permission bit), Windows needs an actual WinUSB driver + // bound to the device before libusb can open it at all - there is no + // generic "any USB device just works" path. The installer sets this up + // automatically; the portable zip build does not. + text = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_DRIVER_HINT_WIN), + "DreamSourceLab hardware needs a WinUSB driver to be recognized by " + "Windows. If your device does not appear in the device list, install " + "a WinUSB driver for it (for example with the free 'Zadig' tool), " + "then reconnect the device."); +#else + // Skip entirely if the udev rule is already active system-wide (a + // .deb/source "make install" already places it there) - nothing to warn + // the user about in that case. + static const char *rule_paths[] = { + "/usr/lib/udev/rules.d/60-dreamsourcelab.rules", + "/lib/udev/rules.d/60-dreamsourcelab.rules", + "/etc/udev/rules.d/60-dreamsourcelab.rules", + }; + for (const char *p : rule_paths) { + if (QFile::exists(p)) + return; + } + + // A copy of the rule ships in share/DSView next to the executable in + // every Linux packaging (system install, .deb, AppImage - see the + // install() rule in CMakeLists.txt); a plain uninstalled dev build won't + // have it, so fall back to printing the one-line rule to create by hand. + const QString bundled = QCoreApplication::applicationDirPath() + + "/../share/DSView/DreamSourceLab.rules"; + + if (QFile::exists(bundled)) { + text = QString(L_S(STR_PAGE_MSG, S_ID(IDS_MSG_DRIVER_HINT_LINUX_BUNDLED), + "DreamSourceLab hardware needs a udev rule granting USB access, " + "which was not found on this system (this is normal when running " + "the AppImage). To install it, run:\n\n" + "sudo cp \"%1\" /etc/udev/rules.d/ && " + "sudo udevadm control --reload-rules")) + .arg(bundled); + } else { + text = L_S(STR_PAGE_MSG, S_ID(IDS_MSG_DRIVER_HINT_LINUX), + "DreamSourceLab hardware needs a udev rule granting USB access, " + "which was not found on this system. Create " + "/etc/udev/rules.d/60-dreamsourcelab.rules with the following " + "line, then run 'sudo udevadm control --reload-rules':\n\n" + "SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"2a0e\", MODE=\"0666\""); + } +#endif + + QMessageBox msg(this); + msg.setWindowTitle(L_S(STR_PAGE_MSG, S_ID(IDS_MSG_DRIVER_HINT_TITLE), + "Hardware driver note")); + msg.setText(text); + QPushButton *noMoreButton = msg.addButton( + L_S(STR_PAGE_MSG, S_ID(IDS_MSG_NOT_SHOW_AGAIN), "Not Show Again"), + QMessageBox::ActionRole); + msg.addButton(L_S(STR_PAGE_MSG, S_ID(IDS_MSG_IGNORE), "Ignore"), + QMessageBox::ActionRole); + msg.exec(); + + if (msg.clickedButton() == noMoreButton) { + app.userHistory.showDriverHint = false; + app.SaveHistory(); + } +} + QWidget* MainFrame::GetMainWindow() { return _mainWindow; diff --git a/DSView/pv/mainframe.h b/DSView/pv/mainframe.h index 18960deca..1df3c7b6f 100644 --- a/DSView/pv/mainframe.h +++ b/DSView/pv/mainframe.h @@ -144,6 +144,7 @@ public slots: void writeSettings(); void ReadSettings(); void AttachNativeWindow(); + void show_driver_hint_once(); //ITitleParent void MoveWindow(int x, int y) override; diff --git a/installer/windows/dsl_usb_instruments.inf b/installer/windows/dsl_usb_instruments.inf new file mode 100644 index 000000000..9ae71a0e8 --- /dev/null +++ b/installer/windows/dsl_usb_instruments.inf @@ -0,0 +1,91 @@ +; WinUSB driver binding for DreamSourceLab USB instruments (DSLogic/DSCope +; family), for use with `pnputil /add-driver dsl_usb_instruments.inf /install`. +; +; Trimmed from DreamSourceLab's own official installer +; (DSView_v1.3.2_x64_setup.exe, Drivers\dsl_usb_instruments.inf): the +; [Manufacturer]/model list and the WinUSB-binding sections below are copied +; verbatim from that proven-working INF. The WDF co-installer sections +; (CoInstallers/CopyFiles/SourceDisks*) were dropped - those exist only to +; support Windows versions older than 10, where KMDF/WinUSB were not yet +; in-box; on Windows 10/11 they are unnecessary, so no co-installer DLLs need +; to be bundled or redistributed here. +; +; If DreamSourceLab ships a new product with a new USB PID, both this file +; and libsigrok4DSL/hardware/DSL/dsl.h's device tables need updating - there +; is no automatic sync between the two. + +[Version] +Signature = "$Windows NT$" +Class = USB +ClassGUID={36FC9E60-C465-11CF-8056-444553540000} +Provider = %Provider% +DriverVer=11/02/2006,6.0.6000.16388 + + +; ========== Manufacturer/Models sections =========== + +[Manufacturer] +%Provider% = DreamSourceLabMfg,NTamd64 + +[DreamSourceLabMfg.NTamd64] +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0001 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0002 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0003 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0004 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0005 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0006 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0007 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0008 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0009 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_000A +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_000B +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_000C +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_000D +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_000E +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_000F +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0010 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0011 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0012 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0013 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0014 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0015 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0016 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0017 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0018 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_0019 +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_001A +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_001B +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_001C +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_001D +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_001E +%USB\DreamSourceLab.DeviceDesc% = DSL_Install, USB\VID_2A0E&PID_001F + +; =================== Installation =================== + +[DSL_Install] +Include=winusb.inf +Needs=WINUSB.NT + +[DSL_Install.Services] +Include=winusb.inf +AddService=WinUSB,0x00000002,WinUSB_ServiceInstall + +[WinUSB_ServiceInstall] +DisplayName = %WinUSB_SvcDesc% +ServiceType = 1 +StartType = 3 +ErrorControl = 1 +ServiceBinary = %12%\WinUSB.sys + +[DSL_Install.Wdf] +KmdfService=WINUSB, WinUsb_Install + +[WinUSB_Install] +KmdfLibraryVersion=1.7 + +; =================== Strings =================== + +[Strings] +Provider="DreamSourceLab" +USB\DreamSourceLab.DeviceDesc="DreamSourceLab USB-based Instrument" +WinUSB_SvcDesc="DreamSourceLab USB-based Instrument" diff --git a/installer/windows/dsview.iss b/installer/windows/dsview.iss new file mode 100644 index 000000000..47ac354e9 --- /dev/null +++ b/installer/windows/dsview.iss @@ -0,0 +1,62 @@ +; Inno Setup script for the Windows DSView installer. +; +; Unlike the plain portable zip (the other Windows CI artifact), this +; installer also stages the WinUSB driver binding for DreamSourceLab hardware +; (dsl_usb_instruments.inf, in this same directory) via pnputil, so a fresh +; Windows install detects the hardware without any extra manual driver setup. +; +; Expects to be compiled with the working directory at the repo root, e.g.: +; iscc installer\windows\dsview.iss /DMyAppVersion=1.5.0 +; MyAppVersion defaults below if not passed on the command line. +; +; Source paths below are relative to this script's own directory (Inno +; Setup's default SourceDir), so they reach up to the repo-root-relative +; dsview-dist\ folder that the CI workflow's earlier steps already assemble +; (see .github/workflows/build.yml, the "Install" and "Bundle runtime DLLs" +; steps of the windows job). + +#ifndef MyAppVersion + #define MyAppVersion "1.5.0" +#endif +#define MyAppName "DSView" +#define MyAppPublisher "DreamSourceLab" +#define DistDir "..\..\dsview-dist" + +[Setup] +AppId={{7E5B6A4E-5B7B-4C7A-9C7B-6A6E3C1B6A4E} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +DefaultDirName={autopf}\{#MyAppName} +DefaultGroupName={#MyAppName} +DisableProgramGroupPage=yes +; The whole install (including the pnputil driver-staging step below) needs +; to run elevated; this avoids a second, separate UAC prompt mid-install. +PrivilegesRequired=admin +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +Compression=lzma2 +SolidCompression=yes +OutputDir=Output +OutputBaseFilename=DSView-windows-x86_64-setup +UninstallDisplayIcon={app}\DSView.exe + +[Files] +Source: "{#DistDir}\*"; DestDir: "{app}"; Flags: recursesubdirs ignoreversion +Source: "dsl_usb_instruments.inf"; DestDir: "{app}\drivers"; Flags: ignoreversion +; Lets MainFrame::show_driver_hint_once() (mainframe.cpp) tell apart an +; installed copy (driver already staged below) from the portable zip (where +; it still points the user at Zadig, since pnputil can't run unelevated). +Source: "installed_via_setup.marker"; DestDir: "{app}"; Flags: ignoreversion + +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "{app}\DSView.exe" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\DSView.exe"; Tasks: desktopicon + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Run] +Filename: "{sys}\pnputil.exe"; Parameters: "/add-driver ""{app}\drivers\dsl_usb_instruments.inf"" /install"; \ + StatusMsg: "Installing DreamSourceLab USB driver..."; Flags: runhidden waituntilterminated +Filename: "{app}\DSView.exe"; Description: "{cm:LaunchProgram,{#MyAppName}}"; Flags: nowait postinstall skipifsilent diff --git a/installer/windows/installed_via_setup.marker b/installer/windows/installed_via_setup.marker new file mode 100644 index 000000000..5608d4511 --- /dev/null +++ b/installer/windows/installed_via_setup.marker @@ -0,0 +1,4 @@ +This file's presence tells DSView (MainFrame::show_driver_hint_once() in +mainframe.cpp) that it was installed via the Inno Setup installer, which +already staged the WinUSB driver (dsl_usb_instruments.inf) via pnputil during +setup - so the first-start driver hint dialog should stay silent. From 626e5ea08fc0995b41ec4dbd167b45b446a8c22c Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Tue, 21 Jul 2026 02:44:44 +0200 Subject: [PATCH 30/34] Improved appimage build --- .github/workflows/build.yml | 68 +++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81f56a4f2..95450f954 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -104,30 +104,52 @@ jobs: rm -rf AppDir DESTDIR="${GITHUB_WORKSPACE}/AppDir" cmake --install build - - name: Download linuxdeployqt + - name: Download linuxdeploy run: | - wget -q -O linuxdeployqt \ - "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" - chmod +x linuxdeployqt + # linuxdeployqt (previously used here) has a regression in its + # current "continuous" build: run against DSView's .desktop file, it + # silently omits libQt6*.so from the bundle entirely (confirmed by + # rebuilding this exact sequence locally and inspecting the result - + # it parses Qt6Widgets/Qt6Gui/Qt6Core as dependencies via its own + # logging, but never copies them). The resulting "AppImage" only + # happened to run on machines that already had system Qt6 installed, + # which is indistinguishable from a working build unless tested on a + # clean system - reported as "AppImage doesn't run without Qt6 + # pre-installed". linuxdeploy + linuxdeploy-plugin-qt (actively + # maintained, purpose-built for Qt) does not share this bug - this + # was verified locally: a completely clean environment (env -i, no + # inherited LD_LIBRARY_PATH/QT_PLUGIN_PATH) successfully launched the + # resulting AppImage. + wget -q -O linuxdeploy \ + "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" + wget -q -O linuxdeploy-plugin-qt \ + "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" + chmod +x linuxdeploy linuxdeploy-plugin-qt - name: Build AppImage run: | export QMAKE="$(which qmake6)" + # linuxdeploy finds the "qt" plugin (linuxdeploy-plugin-qt) by + # searching PATH for a linuxdeploy-plugin-qt executable. + export PATH="${GITHUB_WORKSPACE}:${PATH}" + # Deploy Qt/shared-lib dependencies into AppDir, but don't build the # final .AppImage yet - we still need to bundle the Python stdlib - # (DSView embeds Python for its protocol decoders, and linuxdeployqt - # only picks up libpython*.so via ldd, not the stdlib data files it - # loads at runtime, e.g. the `encodings` module). - ./linuxdeployqt --appimage-extract-and-run \ - "${GITHUB_WORKSPACE}/AppDir/usr/share/applications/dsview.desktop" \ - -qmake="${QMAKE}" - - # linuxdeployqt doesn't reliably pick up libpython*.so (it isn't a - # direct Qt dependency), so make sure it's actually in AppDir/usr/lib - # by copying whatever the just-installed binary is linked against. - ldd "${GITHUB_WORKSPACE}/AppDir/usr/bin/DSView" \ - | awk '/libpython/ {print $3}' \ - | xargs -r -I{} cp -Lv {} "${GITHUB_WORKSPACE}/AppDir/usr/lib/" + # (DSView embeds Python for its protocol decoders; the interpreter + # needs the stdlib data files it loads at runtime, e.g. the + # `encodings` module, which no Qt-focused deploy tool knows about). + ./linuxdeploy --appdir "${GITHUB_WORKSPACE}/AppDir" --plugin qt + + # Belt-and-suspenders: linuxdeploy-plugin-qt's own dependency scan + # already bundles libpython*.so (verified locally), but guard with + # an existence check rather than assume it always will - a plain + # `cp` would fail the step (source/dest resolving to the same file + # exits non-zero, and this workflow's steps run with `bash -e`) if + # it's already there. + PYSO="$(ldd "${GITHUB_WORKSPACE}/AppDir/usr/bin/DSView" | awk '/libpython/ {print $3}')" + if [ -n "${PYSO}" ] && [ ! -e "${GITHUB_WORKSPACE}/AppDir/usr/lib/$(basename "${PYSO}")" ]; then + cp -Lv "${PYSO}" "${GITHUB_WORKSPACE}/AppDir/usr/lib/" + fi PYVER="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')" PYLIBDIR="/usr/lib/python${PYVER}" @@ -137,10 +159,14 @@ jobs: --exclude 'test' --exclude 'idlelib' --exclude 'tkinter' --exclude '__pycache__' \ "${PYLIBDIR}/" "${DEST}/" - # linuxdeployqt's AppRun only sets up the Qt/library environment. - # Wrap it with our own AppRun that also points the bundled Python - # interpreter at the bundled stdlib, instead of falling back to - # whatever python3 (and version) happens to be on the host. + # linuxdeploy's AppRun (a plain symlink to the binary for Qt6 apps - + # it relies on the $ORIGIN rpaths the qt plugin already set on every + # bundled library, not an LD_LIBRARY_PATH-setting script) only + # covers Qt/library resolution. Wrap it with our own AppRun that + # also points the bundled Python interpreter at the bundled stdlib, + # instead of falling back to whatever python3 (and version) happens + # to be on the host. exec transparently follows the AppRun.qt + # symlink, so this works the same regardless of what form it takes. mv "${GITHUB_WORKSPACE}/AppDir/AppRun" "${GITHUB_WORKSPACE}/AppDir/AppRun.qt" cat > "${GITHUB_WORKSPACE}/AppDir/AppRun" < Date: Fri, 21 Aug 2026 13:27:11 +0200 Subject: [PATCH 31/34] Improved some UI elements; Increased calibration range --- DSView/pv/dialogs/calibration.cpp | 102 +++++++++++++++++++----------- DSView/pv/dialogs/calibration.h | 1 + DSView/pv/view/dsosignal.cpp | 11 +++- DSView/pv/view/viewport.cpp | 25 +++++++- libsigrok4DSL/hardware/DSL/dsl.h | 2 +- 5 files changed, 98 insertions(+), 43 deletions(-) diff --git a/DSView/pv/dialogs/calibration.cpp b/DSView/pv/dialogs/calibration.cpp index be5e60be9..10fd0f770 100644 --- a/DSView/pv/dialogs/calibration.cpp +++ b/DSView/pv/dialogs/calibration.cpp @@ -22,6 +22,8 @@ #include "calibration.h" #include +#include +#include #include #include #include @@ -65,7 +67,8 @@ Calibration::Calibration(QWidget *parent) : Qt::WindowFlags flags = windowFlags(); this->setWindowFlags(flags | Qt::Tool); #endif - this->setFixedSize(450, 300); + this->setMinimumSize(650, 400); + this->resize(750, 560); this->setWindowOpacity(0.7); this->setModal(false); @@ -81,10 +84,21 @@ Calibration::Calibration(QWidget *parent) : _flayout->setFormAlignment(Qt::AlignLeft); _flayout->setLabelAlignment(Qt::AlignLeft); _flayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + + QWidget *scrollContent = new QWidget(this); + scrollContent->setLayout(_flayout); + + QScrollArea *scrollArea = new QScrollArea(this); + scrollArea->setWidget(scrollContent); + scrollArea->setWidgetResizable(true); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + QGridLayout *glayout = new QGridLayout(); glayout->setVerticalSpacing(5); - glayout->addLayout(_flayout, 1, 0, 1, 7); + glayout->addWidget(scrollArea, 1, 0, 1, 7); glayout->addWidget(_save_btn, 2, 0); glayout->addWidget(new QWidget(this), 2, 1); glayout->setColumnStretch(1, 1); @@ -142,45 +156,51 @@ void Calibration::BuildUI() sr_channel *const probe = (sr_channel*)l->data; assert(probe); - QSlider *gain_slider = new QSlider(Qt::Horizontal, this); - QLabel *gain_label = new QLabel("gain", this); - gain_label->setAlignment(Qt::AlignVCenter); - _flayout->addRow(gain_label, gain_slider); - - QSlider *off_slider = new QSlider(Qt::Horizontal, this); - QLabel *off_label = new QLabel("off", this); - off_label->setAlignment(Qt::AlignVCenter); - _flayout->addRow(off_label, off_slider); - - QSlider *comp_slider = NULL; - QLabel *comp_label = NULL; - - bool comb_comp_en = false; + auto addSliderRow = [this](const char *name) -> ui_param_info { + ui_param_info info; + info.slider = new QSlider(Qt::Horizontal, this); + info.slider->setMinimumWidth(450); + info.lable = new QLabel(name, this); + info.lable->setAlignment(Qt::AlignVCenter); + info.lable->setMinimumWidth(40); + info.value_label = new QLabel("0", this); + info.value_label->setAlignment(Qt::AlignVCenter | Qt::AlignRight); + info.value_label->setMinimumWidth(60); + + QWidget *row = new QWidget(this); + QHBoxLayout *hlayout = new QHBoxLayout(row); + hlayout->setContentsMargins(0, 0, 0, 0); + hlayout->addWidget(info.slider, 1); + hlayout->addWidget(info.value_label, 0); + + _flayout->addRow(info.lable, row); + return info; + }; + + ui_param_info gain_info = addSliderRow("gain"); + ui_param_info off_info = addSliderRow("off"); + + ui_param_info comp_info = {NULL, NULL, NULL}; + bool comb_comp_en = false; _device_agent->get_config_bool(SR_CONF_PROBE_COMB_COMP_EN, comb_comp_en); if (comb_comp_en){ - comp_slider = new QSlider(Qt::Horizontal, this); - comp_label = new QLabel("comp", this); - comp_label->setAlignment(Qt::AlignVCenter); - _flayout->addRow(comp_label, comp_slider); - } + comp_info = addSliderRow("comp"); + } channel_param_widget form; - form.gain.lable = gain_label; - form.gain.slider = gain_slider; - form.off.lable = off_label; - form.off.slider = off_slider; - form.comp.lable = comp_label; - form.comp.slider = comp_slider; + form.gain = gain_info; + form.off = off_info; + form.comp = comp_info; form.probe = probe; _params.push_back(form); - - connect(gain_slider, SIGNAL(valueChanged(int)), this, SLOT(set_value(int))); - connect(off_slider, SIGNAL(valueChanged(int)), this, SLOT(set_value(int))); - if (comp_slider != NULL){ - connect(comp_slider, SIGNAL(valueChanged(int)), this, SLOT(set_value(int))); + connect(gain_info.slider, SIGNAL(valueChanged(int)), this, SLOT(set_value(int))); + connect(off_info.slider, SIGNAL(valueChanged(int)), this, SLOT(set_value(int))); + + if (comp_info.slider != NULL){ + connect(comp_info.slider, SIGNAL(valueChanged(int)), this, SLOT(set_value(int))); } } @@ -215,7 +235,8 @@ void Calibration::update_device_info() form->gain.slider->setRange(-vgain_range/2, vgain_range/2); form->gain.slider->setValue(vgain - vgain_default); - + form->gain.value_label->setText(QString::number(form->gain.slider->value())); + uint64_t voff = 0; uint16_t voff_range = 0; int v; @@ -229,14 +250,16 @@ void Calibration::update_device_info() form->off.slider->setRange(0, voff_range); form->off.slider->setValue(voff); - + form->off.value_label->setText(QString::number(form->off.slider->value())); + if (form->comp.slider != NULL) { int comb_comp = 0; _device_agent->get_config_int16(SR_CONF_PROBE_COMB_COMP, comb_comp, probe, NULL); form->comp.slider->setRange(-127, 127); form->comp.slider->setValue(comb_comp); - } + form->comp.value_label->setText(QString::number(form->comp.slider->value())); + } } update(); @@ -274,15 +297,18 @@ void Calibration::set_value(int value) if (form.gain.slider == sc){ uint64_t vgain_default; if (_device_agent->get_config_uint64(SR_CONF_PROBE_VGAIN_DEFAULT, vgain_default, form.probe)){ - _device_agent->set_config_uint64(SR_CONF_PROBE_VGAIN, value+vgain_default, form.probe); + _device_agent->set_config_uint64(SR_CONF_PROBE_VGAIN, value+vgain_default, form.probe); } + form.gain.value_label->setText(QString::number(value)); } else if (form.off.slider == sc){ - _device_agent->set_config_uint16(SR_CONF_PROBE_PREOFF, value, form.probe); + _device_agent->set_config_uint16(SR_CONF_PROBE_PREOFF, value, form.probe); + form.off.value_label->setText(QString::number(value)); } else if (form.comp.slider == sc){ _device_agent->set_config_int16(SR_CONF_PROBE_COMB_COMP, value, form.probe); - } + form.comp.value_label->setText(QString::number(value)); + } } } diff --git a/DSView/pv/dialogs/calibration.h b/DSView/pv/dialogs/calibration.h index d831ba40e..bff4a882e 100644 --- a/DSView/pv/dialogs/calibration.h +++ b/DSView/pv/dialogs/calibration.h @@ -49,6 +49,7 @@ class Calibration : public DSDialog, public IUiWindow { QLabel *lable; QSlider *slider; + QLabel *value_label; }; struct channel_param_widget diff --git a/DSView/pv/view/dsosignal.cpp b/DSView/pv/view/dsosignal.cpp index b1fbc9a2e..fc8a71af7 100644 --- a/DSView/pv/view/dsosignal.cpp +++ b/DSView/pv/view/dsosignal.cpp @@ -203,6 +203,7 @@ bool DsoSignal::go_vDialPre(bool manul) } session->get_device()->set_config_uint16(SR_CONF_PROBE_OFFSET, _zero_offset, _probe, NULL); + _data->set_measure_voltage_factor(_vDial->get_value(), get_index()); _view->vDial_updated(); _view->set_update(_viewport, true); @@ -219,7 +220,7 @@ bool DsoSignal::go_vDialPre(bool manul) bool DsoSignal::go_vDialNext(bool manul) { if (_autoV && manul) - autoV_end(); + autoV_end(); if (enabled() && !_vDial->isMax()) { @@ -238,6 +239,7 @@ bool DsoSignal::go_vDialNext(bool manul) } session->get_device()->set_config_uint16(SR_CONF_PROBE_OFFSET, _zero_offset, _probe, NULL); + _data->set_measure_voltage_factor(_vDial->get_value(), get_index()); _view->vDial_updated(); _view->set_update(_viewport, true); @@ -940,8 +942,11 @@ void DsoSignal::paint_fore(QPainter &p, int left, int right, QColor fore, QColor assert(_view); - fore.setAlpha(View::BackAlpha); - QPen pen(fore); + // Use the channel's own colour (not the grid's fore colour) so the zero + // line stands out against the grid instead of blending into it. + QColor zero_colour = _colour; + zero_colour.setAlpha(View::ForeAlpha); + QPen pen(zero_colour); pen.setStyle(Qt::DotLine); p.setPen(pen); p.drawLine(left, get_zero_vpos(), right, get_zero_vpos()); diff --git a/DSView/pv/view/viewport.cpp b/DSView/pv/view/viewport.cpp index 6eab54276..25d468ab1 100644 --- a/DSView/pv/view/viewport.cpp +++ b/DSView/pv/view/viewport.cpp @@ -2076,6 +2076,8 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) struct MRow { QString name; QString val; QColor colour; }; std::vector rows; + bool have_hover_index = false; + uint64_t hover_sample_index = 0; for(auto s : _view.session().get_signals()) { if (s->signal_type() == SR_CHANNEL_DSO) { @@ -2094,6 +2096,11 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) r.val = dsoSig->get_voltage(dsoSig->get_hw_offset() - value, 3); r.colour = dsoSig->get_colour(); rows.push_back(r); + + if (!have_hover_index) { + hover_sample_index = index; + have_hover_index = true; + } } } else if (s->signal_type() == SR_CHANNEL_ANALOG) { @@ -2114,6 +2121,16 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) // logic-mode measurement popup) instead of printing each number on // top of its trace, where it is hard to read. if (!rows.empty()) { + if (have_hover_index) { + MRow r; + r.name = L_S(STR_PAGE_DLG, S_ID(IDS_DLG_TIME), "Time"); + r.val = _view.get_ruler()->format_real_time( + hover_sample_index - _view.session().get_trigger_pos(), + _view.session().cur_snap_samplerate()); + r.colour = fore; + rows.insert(rows.begin(), r); + } + // The floating panel is drawn 50% larger than the trace font and // in bold to keep the hovered values easy to read. const double sc = _view.get_trace_font_scale() * 1.2; @@ -2123,7 +2140,13 @@ void Viewport::paintMeasure(QPainter &p, QColor fore, QColor back) measure_font.setPointSizeF(AppConfig::Instance().GetTraceFontSize() * sc); p.setFont(measure_font); - int name_w = 0, val_w = 0; + // Size the value column from a worst-case template rather than the + // actual strings, so the panel doesn't resize when a value's unit + // switches (e.g. V <-> mV) as the signal changes. + const QString val_w_template = "-999.000mV"; + + int name_w = 0, val_w = p.boundingRect(0, 0, INT_MAX, INT_MAX, + Qt::AlignLeft | Qt::AlignVCenter, val_w_template).width(); for (auto &r : rows) { name_w = max(name_w, p.boundingRect(0, 0, INT_MAX, INT_MAX, Qt::AlignLeft | Qt::AlignVCenter, r.name).width()); diff --git a/libsigrok4DSL/hardware/DSL/dsl.h b/libsigrok4DSL/hardware/DSL/dsl.h index db8569aa4..4786f3899 100644 --- a/libsigrok4DSL/hardware/DSL/dsl.h +++ b/libsigrok4DSL/hardware/DSL/dsl.h @@ -144,7 +144,7 @@ * trans: the whole windows offset map to the offset pwm(1024 total) * voff: offset pwm constant bias to balance circuit offset */ -#define CALI_VGAIN_RANGE 200 +#define CALI_VGAIN_RANGE 600 struct DSL_caps { uint64_t mode_caps; From 3d0fd5eefa768585715cb8f74f5210c911eaf20a Mon Sep 17 00:00:00 2001 From: Schildkroet Date: Thu, 10 Sep 2026 09:36:02 +0200 Subject: [PATCH 32/34] Fix #1 high-res mouse scrolling --- DSView/pv/dock/keywordlineedit.cpp | 9 +-- DSView/pv/dock/keywordlineedit.h | 4 ++ DSView/pv/view/header.cpp | 20 +++++-- DSView/pv/view/header.h | 4 ++ DSView/pv/view/view.cpp | 11 ++-- DSView/pv/view/view.h | 5 ++ DSView/pv/view/wheelaccumulator.h | 90 ++++++++++++++++++++++++++++++ 7 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 DSView/pv/view/wheelaccumulator.h diff --git a/DSView/pv/dock/keywordlineedit.cpp b/DSView/pv/dock/keywordlineedit.cpp index d2fc7eccd..1d42ee1d6 100644 --- a/DSView/pv/dock/keywordlineedit.cpp +++ b/DSView/pv/dock/keywordlineedit.cpp @@ -135,12 +135,9 @@ void KeyLineEdit::wheelEvent(QWheelEvent *event) delta = event->delta(); #endif - if (delta > 0){ - v++; - } - else{ - v--; - } + // High-resolution wheels send a fraction of a detent per event, so + // only step once a whole detent has been scrolled. + v += _wheel_accum.take(delta); if (_min != 0 || _max != 0) { diff --git a/DSView/pv/dock/keywordlineedit.h b/DSView/pv/dock/keywordlineedit.h index 414073ccf..8ad74970d 100644 --- a/DSView/pv/dock/keywordlineedit.h +++ b/DSView/pv/dock/keywordlineedit.h @@ -28,6 +28,7 @@ #include #include #include +#include "../view/wheelaccumulator.h" class IKeywordActive{ public: @@ -91,6 +92,9 @@ public slots: int _max; bool _is_number_mode; bool _is_spin_mode; + + // Turns the sub-detent deltas of high-resolution wheels into whole steps. + pv::view::WheelAccumulator _wheel_accum; }; //---------PopupLineEditInput diff --git a/DSView/pv/view/header.cpp b/DSView/pv/view/header.cpp index 4d91ac45e..7bf68f237 100644 --- a/DSView/pv/view/header.cpp +++ b/DSView/pv/view/header.cpp @@ -347,6 +347,10 @@ void Header::wheelEvent(QWheelEvent *event) // Vertical scrolling double shift = 0; + // The dials the traces expose react in whole steps only, so the raw + // delta has to be turned into whole wheel detents first - high + // resolution wheels report a fraction of a detent per event and would + // otherwise always be rounded down to zero. #ifdef Q_OS_DARWIN static bool active = true; static int64_t last_time; @@ -362,19 +366,25 @@ void Header::wheelEvent(QWheelEvent *event) active = true; else active = false; + + if (shift == 0) + _wheel_accum.reset(); } else { - shift = -delta / 80.0; + shift = -_wheel_accum.take(delta); } #else - shift = delta / 80.0; + shift = _wheel_accum.take(delta); #endif - for (auto t : traces) + if (shift != 0) { - if (t->mouse_wheel(width(), pos, shift)) - break; + for (auto t : traces) + { + if (t->mouse_wheel(width(), pos, shift)) + break; + } } _view.verticalScrollBar()->setValue(_view.verticalScrollBar()->value() - delta); update(); diff --git a/DSView/pv/view/header.h b/DSView/pv/view/header.h index 51f6164c4..29a6cca1e 100644 --- a/DSView/pv/view/header.h +++ b/DSView/pv/view/header.h @@ -29,6 +29,7 @@ #include #include "../ui/uimanager.h" #include "../dock/keywordlineedit.h" +#include "wheelaccumulator.h" namespace pv { namespace view { @@ -101,6 +102,9 @@ private slots: std::list > _drag_traces; Trace *_context_trace; bool _mouse_is_down; + + // Turns the sub-detent deltas of high-resolution wheels into whole steps. + WheelAccumulator _wheel_accum; }; } // namespace view diff --git a/DSView/pv/view/view.cpp b/DSView/pv/view/view.cpp index 2921d1407..f500bedb6 100644 --- a/DSView/pv/view/view.cpp +++ b/DSView/pv/view/view.cpp @@ -321,11 +321,14 @@ bool View::zoom(double steps, int offset) return ret; } + // The horizontal knob only moves in whole positions, so sub-step wheel + // events (high-resolution wheels) have to be summed up first, otherwise + // they would each be discarded and zooming would never happen. + const int knob_steps = _dso_zoom_accum.take(steps); + double hori_res = -1; - if(steps > 0.5) - hori_res = _sampling_bar->hori_knob(-1); - else if (steps < -0.5) - hori_res = _sampling_bar->hori_knob(1); + for (int i = 0; i < ABS_VAL(knob_steps); i++) + hori_res = _sampling_bar->hori_knob(knob_steps > 0 ? -1 : 1); if (hori_res > 0) { const double scale = _session->cur_view_time() / width; diff --git a/DSView/pv/view/view.h b/DSView/pv/view/view.h index dce9f78f4..69978c0fe 100644 --- a/DSView/pv/view/view.h +++ b/DSView/pv/view/view.h @@ -46,6 +46,7 @@ #include "../dsvdef.h" #include "../interface/icallbacks.h" #include "../ui/uimanager.h" +#include "wheelaccumulator.h" class DeviceAgent; @@ -526,6 +527,10 @@ private slots: int _spanY; int _signalHeight; double _trace_height_factor; + + // Collects sub-detent wheel steps so high-resolution wheels can still + // advance the (discrete) DSO horizontal knob. + WheelAccumulator _dso_zoom_accum{1.0}; bool _dso_split_channels; bool _updating_scroll; diff --git a/DSView/pv/view/wheelaccumulator.h b/DSView/pv/view/wheelaccumulator.h new file mode 100644 index 000000000..1028e95d8 --- /dev/null +++ b/DSView/pv/view/wheelaccumulator.h @@ -0,0 +1,90 @@ +/* + * This file is part of the DSView project. + * Copyright (C) 2024 DreamSourceLab + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. + */ + +#ifndef DSVIEW_PV_VIEW_WHEELACCUMULATOR_H +#define DSVIEW_PV_VIEW_WHEELACCUMULATOR_H + +#include +#include + +namespace pv { +namespace view { + +// A classic notched mouse wheel reports one detent as an angleDelta() of 120. +// High-resolution wheels (libinput/Wayland, free-spinning wheels, trackpads) +// instead send a burst of much smaller deltas that add up to those 120 units. +// Anything that reacts in discrete steps (the DSO horizontal knob, the vertical +// dials in the header, spin boxes) would round every one of those fractions +// down to "no step at all", which is why zooming looks completely dead on such +// devices. This helper sums the fractions up and hands out whole steps only, +// carrying the remainder over to the next event, so one physical detent always +// produces exactly one step no matter how finely the device reports it. +class WheelAccumulator +{ +public: + // unit: how much input makes up one step (120 for raw angleDelta values, + // 1.0 when the caller already works in detents). + explicit WheelAccumulator(double unit = 120.0) : + _unit(unit), + _accum(0), + _last_time(0) + { + } + + // Returns the number of whole steps contained in amount, keeping the + // leftover for the following event. Sign follows amount. + int take(double amount) + { + if (amount == 0) + return 0; + + const int64_t now = QDateTime::currentMSecsSinceEpoch(); + + // Drop a stale remainder, and never let opposite directions cancel out + // a scroll the user just started. + if (now - _last_time > IdleResetMs || (_accum != 0 && (_accum > 0) != (amount > 0))) + _accum = 0; + + _last_time = now; + _accum += amount / _unit; + + const int steps = (int)(_accum > 0 ? std::floor(_accum) : std::ceil(_accum)); + _accum -= steps; + + return steps; + } + + void reset() + { + _accum = 0; + _last_time = 0; + } + +private: + static const int64_t IdleResetMs = 500; + + double _unit; + double _accum; + int64_t _last_time; +}; + +} // namespace view +} // namespace pv + +#endif From 0ac97db62ad5bffb58bf0ed1c9a68afab553462f Mon Sep 17 00:00:00 2001 From: Schildkroet Date: Thu, 10 Sep 2026 10:55:59 +0200 Subject: [PATCH 33/34] Updated dependencies --- CMakeLists.txt | 30 ++++++++++++++++++++++++++++-- DSView/main.cpp | 10 ++++++++++ DSView/pv/view/wheelaccumulator.h | 6 ++++-- INSTALL | 16 ++++++++++------ 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 11462b8a2..151c0778a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -177,6 +177,24 @@ message(STATUS " includes:" ${ZLIB_INCLUDE_DIRS}) message(STATUS " libraries:" ${ZLIB_LIBRARIES}) include_directories(${ZLIB_INCLUDE_DIRS}) +#=============================================================================== +#= Qt Svg +# +# DSView never calls a Qt Svg function - it only needs the qsvg image and icon +# engine plugins that ship with the module. A plain link would therefore be +# thrown away again by --as-needed (the default on most Linux distros), and +# with no DT_NEEDED entry, dpkg-shlibdeps and the AppImage/DLL bundlers cannot +# tell that libqt6svg6 has to come along. Keep the link explicitly on ELF +# targets so the runtime dependency is recorded. +#------------------------------------------------------------------------------- +macro(ds_link_qt_svg svg_target) + if(UNIX AND NOT APPLE) + list(APPEND QT_LIBRARIES -Wl,--no-as-needed ${svg_target} -Wl,--as-needed) + else() + list(APPEND QT_LIBRARIES ${svg_target}) + endif() +endmacro() + #=============================================================================== #= Qt6 or Qt5 # @@ -190,9 +208,15 @@ if(Qt6Core_FOUND) message(STATUS " includes:" ${Qt6Core_INCLUDE_DIRS}) find_package(Qt6Widgets REQUIRED) find_package(Qt6Gui REQUIRED) + # The themes and icons are SVG, which Qt can only decode through the qsvg + # image/icon engine plugins shipping with the Qt Svg module. Requiring it + # here turns a missing module into a configure error instead of a UI whose + # checkboxes, combobox arrows and window buttons are silently not painted. + find_package(Qt6Svg REQUIRED) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt6Widgets_EXECUTABLE_COMPILE_FLAGS}") - set(QT_INCLUDE_DIRS ${Qt6Gui_INCLUDE_DIRS} ${Qt6Widgets_INCLUDE_DIRS}) + set(QT_INCLUDE_DIRS ${Qt6Gui_INCLUDE_DIRS} ${Qt6Widgets_INCLUDE_DIRS} ${Qt6Svg_INCLUDE_DIRS}) set(QT_LIBRARIES Qt6::Gui Qt6::Widgets) + ds_link_qt_svg(Qt6::Svg) if(WIN32) find_package(Qt6Core5Compat REQUIRED) add_definitions(${Qt6Core5Compat_DEFINITIONS}) @@ -209,9 +233,11 @@ if(Qt5Core_FOUND) message(STATUS " includes:" ${Qt5Core_INCLUDE_DIRS}) find_package(Qt5Widgets REQUIRED) find_package(Qt5Gui REQUIRED) + find_package(Qt5Svg REQUIRED) # see the Qt6 branch above set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") - set(QT_INCLUDE_DIRS ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) + set(QT_INCLUDE_DIRS ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Svg_INCLUDE_DIRS}) set(QT_LIBRARIES Qt5::Gui Qt5::Widgets) + ds_link_qt_svg(Qt5::Svg) add_definitions(${Qt5Gui_DEFINITIONS} ${Qt5Widgets_DEFINITIONS}) endif() diff --git a/DSView/main.cpp b/DSView/main.cpp index ee60bc705..27f7da843 100644 --- a/DSView/main.cpp +++ b/DSView/main.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "dsapplication.h" #include "mystyle.h" #include "pv/mainframe.h" @@ -217,6 +218,15 @@ bool bHighScale = true; dsv_info("----------------- version: %s-----------------", DS_VERSION_STRING); dsv_info("Qt:%s", QT_VERSION_STR); + // The themes draw every checkbox, radio button, combobox arrow and window + // button from SVG resources. Decoding those needs Qt's qsvg image plugin + // (Debian: libqt6svg6, Fedora: qt6-qtsvg). Qt reports nothing when it is + // absent - it just hands out null pixmaps and the controls come up blank, + // so say it here rather than leave the user with an unexplained UI. + if (!QImageReader::supportedImageFormats().contains("svg")){ + dsv_warn("Qt svg image plugin missing, icons and checkboxes will not be drawn. Please install it (e.g. libqt6svg6)."); + } + QDateTime dateTime = QDateTime::currentDateTime(); std::string strTime = dateTime .toString("yyyy-MM-dd hh:mm:ss").toStdString(); dsv_info("%s", strTime.c_str()); diff --git a/DSView/pv/view/wheelaccumulator.h b/DSView/pv/view/wheelaccumulator.h index 1028e95d8..321f1574c 100644 --- a/DSView/pv/view/wheelaccumulator.h +++ b/DSView/pv/view/wheelaccumulator.h @@ -1,6 +1,8 @@ /* * This file is part of the DSView project. - * Copyright (C) 2024 DreamSourceLab + * DSView is based on PulseView. + * + * Copyright (C) 2026 Schildkroet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,7 +16,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef DSVIEW_PV_VIEW_WHEELACCUMULATOR_H diff --git a/INSTALL b/INSTALL index e64038e0d..2e5576bfe 100644 --- a/INSTALL +++ b/INSTALL @@ -9,7 +9,7 @@ Requirements - g++ - make - cmake >= 2.6 - - Qt >= 5.0 + - Qt >= 5.0 (Core, Gui, Widgets and Svg) - libglib >= 2.32.0 - zlib - libusb-1.0 >= 1.0.16 @@ -29,21 +29,25 @@ Step1: Installing the requirements: please check your respective distro's package manager tool if you use other distros Debian/Ubuntu: - $ sudo apt install git gcc g++ make cmake qt5-default libglib2.0-dev zlib1g-dev \ + $ sudo apt install git gcc g++ make cmake qt6-base-dev libqt6svg6-dev libglib2.0-dev zlib1g-dev \ libusb-1.0-0-dev libboost-dev libfftw3-dev python3-dev libudev-dev pkg-config How to install qt on ubuntu? - qt5: qt5-default,qtbase5-dev - qt6: qt6-base-dev,libQt6Svg*,libgl1-mesa-dev*,libxkbcommon-dev,libvulkan-dev + qt5: qtbase5-dev,libqt5svg5-dev + qt6: qt6-base-dev,libqt6svg6-dev,libgl1-mesa-dev*,libxkbcommon-dev,libvulkan-dev + + The Qt Svg module is not optional: the themes draw all checkboxes, combobox + arrows and window buttons from SVG files, and without its qsvg plugins those + controls are simply not painted (the build and the startup log stay clean). Fedora (18, 19): $ sudo yum install git gcc g++ make cmake libtool pkgconfig glib2-devel \ zlib-devel libudev-devel libusb1-devel \ - python3-devel qt-devel boost-devel libfftw3-devel + python3-devel qt6-qtbase-devel qt6-qtsvg-devel boost-devel libfftw3-devel Arch: $ pacman -S base-devel git cmake glib2 zlib libusb - python boost qt5 fftw + python boost qt6-base qt6-svg fftw Mac: install git From 54e7a898b36caf6ac7a53f75d8c6baff2093dd94 Mon Sep 17 00:00:00 2001 From: Schildkroet Date: Thu, 10 Sep 2026 11:00:28 +0200 Subject: [PATCH 34/34] Fixed decoder escape sequence --- libsigrokdecode4DSL/decoders/arm_etmv3/pd.py | 8 ++++---- libsigrokdecode4DSL/decoders/arm_itm/pd.py | 6 +++--- libsigrokdecode4DSL/decoders/ds1307/pd.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libsigrokdecode4DSL/decoders/arm_etmv3/pd.py b/libsigrokdecode4DSL/decoders/arm_etmv3/pd.py index 175b202bd..f46ee58d2 100644 --- a/libsigrokdecode4DSL/decoders/arm_etmv3/pd.py +++ b/libsigrokdecode4DSL/decoders/arm_etmv3/pd.py @@ -212,10 +212,10 @@ def load_objdump(self): disasm = disasm.decode('utf-8', 'replace') - instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*') - branchpat = re.compile('(b|bl|b..|bl..|cbnz|cbz)(?:\.[wn])?\s+(?:r[0-9]+,\s*)?([0-9a-fA-F]+)') - filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?') - funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*') + instpat = re.compile(r'\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*') + branchpat = re.compile(r'(b|bl|b..|bl..|cbnz|cbz)(?:\.[wn])?\s+(?:r[0-9]+,\s*)?([0-9a-fA-F]+)') + filepat = re.compile(r'[^\s]+[/\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?') + funcpat = re.compile(r'[0-9a-fA-F]+\s*<([^>]+)>:.*') prev_src = '' prev_file = '' diff --git a/libsigrokdecode4DSL/decoders/arm_itm/pd.py b/libsigrokdecode4DSL/decoders/arm_itm/pd.py index 42a798e42..6e98ea89b 100644 --- a/libsigrokdecode4DSL/decoders/arm_itm/pd.py +++ b/libsigrokdecode4DSL/decoders/arm_itm/pd.py @@ -113,9 +113,9 @@ def load_objdump(self): disasm = disasm.decode('utf-8', 'replace') - instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*') - filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?') - funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*') + instpat = re.compile(r'\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*') + filepat = re.compile(r'[^\s]+[/\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?') + funcpat = re.compile(r'[0-9a-fA-F]+\s*<([^>]+)>:.*') prev_file = '' prev_func = '' diff --git a/libsigrokdecode4DSL/decoders/ds1307/pd.py b/libsigrokdecode4DSL/decoders/ds1307/pd.py index 51d673d35..f93ae63ed 100644 --- a/libsigrokdecode4DSL/decoders/ds1307/pd.py +++ b/libsigrokdecode4DSL/decoders/ds1307/pd.py @@ -48,11 +48,11 @@ def regs_and_bits(): l = [('reg_' + r.lower(), r + ' register') for r in regs] - l += [('bit_' + re.sub('\/| ', '_', b).lower(), b + ' bit') for b in bits] + l += [('bit_' + re.sub(r'\/| ', '_', b).lower(), b + ' bit') for b in bits] return tuple(l) a = ['REG_' + r.upper() for r in regs] + \ - ['BIT_' + re.sub('\/| ', '_', b).upper() for b in bits] + \ + ['BIT_' + re.sub(r'\/| ', '_', b).upper() for b in bits] + \ ['READ_DATE_TIME', 'WRITE_DATE_TIME', 'READ_REG', 'WRITE_REG', 'WARNING'] Ann = SrdIntEnum.from_list('Ann', a)