diff --git a/README.md b/README.md index 3ee6417..e99ca41 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,16 @@ The AntScope2 software is designed to support various models of RigExpert analyzers under various operating systems. -Windows: QT5, QT Creator 2 and higher +Windows: Qt6, Qt Creator 2 and higher -Linux: to do +Linux: Qt6, qmake. Build with: + +``` +qmake6 CONFIG+=release AntScope.pro +make +``` + +Depends on `qt6-base`, `qt6-serialport`, `qt6-connectivity`, and `libusb` (used via the vendored HIDAPI backend for HID-connected analyzers). Packaged for Arch Linux/AUR as +[`antscope2`](https://aur.archlinux.org/packages/antscope2) (pinned to a known-good commit, patched) +and [`antscope2-git`](https://aur.archlinux.org/packages/antscope2-git) (tracks a patched branch's tip - see either package's `PKGBUILD` for the udev rule, desktop file, and data-path packaging details this repo's own build doesn't set up on its own). Mac OS: to do diff --git a/analyzer/ble_analyzer.h b/analyzer/ble_analyzer.h index 6fc0f9d..88f98fe 100644 --- a/analyzer/ble_analyzer.h +++ b/analyzer/ble_analyzer.h @@ -11,7 +11,6 @@ #include #include #include "baseanalyzer.h" -#include "screeninfo.h" enum { BLE_VER_CMD = (quint8)0xE6, @@ -194,7 +193,6 @@ private slots: void measuringChanged(); void aliveChanged(); void statsChanged(); - void setScreenInfo(ScreenInfo& screen); private: QString m_error; diff --git a/mainwindow.cpp b/mainwindow.cpp index 680f693..0f9f767 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -536,7 +536,11 @@ MainWindow::MainWindow(QWidget *parent) : m_1secTimer = new QTimer(this); connect(m_1secTimer, SIGNAL(timeout()), this, SLOT(on_1secTimerTick())); - m_1secTimer->start(100); + m_1secTimer->start(1000); + + m_focusDebounceTimer = new QTimer(this); + m_focusDebounceTimer->setSingleShot(true); + connect(m_focusDebounceTimer, &QTimer::timeout, this, &MainWindow::onFocusDebounceTimeout); loadLanguage(languages_small[m_languageNumber]); ui->tableWidget_presets->horizontalHeader()->show(); @@ -761,12 +765,15 @@ void MainWindow::closeEvent(QCloseEvent *event) bool MainWindow::event(QEvent * e) { - if(e->type() == QEvent::WindowActivate) - { - emit focus(true); - }else if (e->type() == QEvent::WindowDeactivate) + if(e->type() == QEvent::WindowActivate || e->type() == QEvent::WindowDeactivate) { - emit focus(false); + // Some window managers/compositors send rapid, sometimes + // continuous, Activate/Deactivate churn (e.g. triggered by an + // always-on-top Qt::Tool child being mapped/unmapped, which is + // itself a reaction to a previous Activate here - a feedback + // loop). Do not react to any single event; only act once the + // window's activation state has held steady for a while. + m_focusDebounceTimer->start(300); }else if (e->type() == QEvent::WindowStateChange) { updateGraph(); @@ -774,6 +781,15 @@ bool MainWindow::event(QEvent * e) return QMainWindow::event(e) ; } +void MainWindow::onFocusDebounceTimeout() +{ + bool active = isActiveWindow(); + if (active != m_lastEmittedFocus) { + m_lastEmittedFocus = active; + emit focus(active); + } +} + void MainWindow::setWidgetsSettings() { QPen pen; @@ -6806,6 +6822,15 @@ void MainWindow::on_selectDeviceDialog() return; } + // dlg.exec() below runs a nested event loop, during which any other + // queued call to this same slot (e.g. a pending QTimer::singleShot + // from on_refreshConnection()) would still fire and stack a second + // SelectDeviceDialog on top of the first. Guard against that. + if (m_selectDeviceDialogOpen) { + return; + } + m_selectDeviceDialogOpen = true; + SelectDeviceDialog dlg(false, this); if (dlg.exec() == QDialog::Accepted) { SelectionParameters sel_par = SelectionParameters::selected; @@ -6815,6 +6840,7 @@ void MainWindow::on_selectDeviceDialog() emit m_analyzer->analyzerFound(selected->index()); } } + m_selectDeviceDialogOpen = false; closeSettingsDialog(); ui->settingsBtn->setEnabled(true); } diff --git a/mainwindow.h b/mainwindow.h index 5728d36..0354177 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -160,6 +160,8 @@ class MainWindow : public QMainWindow // QTimer *m_redrawTimer; QTimer *m_1secTimer; + QTimer *m_focusDebounceTimer; + bool m_lastEmittedFocus = true; double m_cableVelFactor; double m_cableResistance; @@ -199,6 +201,7 @@ class MainWindow : public QMainWindow bool m_addingMarker; bool m_isMouseClick; bool m_bInterrupted; + bool m_selectDeviceDialogOpen = false; QMap m_BandsMap; bool m_darkColorTheme = true; QPalette m_lightPalette; @@ -338,6 +341,7 @@ private slots: void on_firmwareAutoUpdateStateChanged( bool state); void on_antScopeAutoUpdateStateChanged( bool state); void on_1secTimerTick(); + void onFocusDebounceTimeout(); void on_calibrationChanged(); void on_SaveFile(int row, QString path); void on_mouseDoubleClick(QMouseEvent* e); diff --git a/markers.cpp b/markers.cpp index 663011f..55f9960 100644 --- a/markers.cpp +++ b/markers.cpp @@ -24,7 +24,7 @@ Markers::Markers(QObject *parent) : QObject(parent), if(m_markersHint == NULL) { - m_markersHint = new MarkersPopUp(); + m_markersHint = new MarkersPopUp(MainWindow::m_mainWindow, true); m_markersHint->setHiding(false); if(m_markersHintEnabled && !m_markersList.isEmpty()) m_markersHint->focusShow(); diff --git a/markers.h b/markers.h index 663d78f..06942bb 100644 --- a/markers.h +++ b/markers.h @@ -103,7 +103,7 @@ class Markers : public QObject Measurements *m_measurements; - bool m_focus; + bool m_focus = true; double interpolate(double fq1, double fq2, double fq3, double param1, double param2); diff --git a/markerspopup.cpp b/markerspopup.cpp index 9274a04..d4d9138 100644 --- a/markerspopup.cpp +++ b/markerspopup.cpp @@ -5,7 +5,7 @@ QMap MarkersHeaderColumn::m_mapHeader; -MarkersPopUp::MarkersPopUp(QWidget *parent) : QWidget(parent), +MarkersPopUp::MarkersPopUp(QWidget *parent, bool embedded) : QWidget(parent), m_durability(2000), m_hiding(true), m_x(0), @@ -16,15 +16,23 @@ MarkersPopUp::MarkersPopUp(QWidget *parent) : QWidget(parent), m_mainY(0), m_mainBiasX(0), m_mainBiasY(0), + m_embedded(embedded), m_bgColor(0,0,0,180), m_penColor(255,255,255,180), m_textColor("white") { - setWindowFlags(Qt::FramelessWindowHint | // Отключаем оформление окна - Qt::Tool | // Отменяем показ в качестве отдельного окна - Qt::WindowStaysOnTopHint); // Устанавливаем поверх всех окон + if (m_embedded) { + // A plain child widget of its parent, not a separate top-level + // window - see PopUp::init() in popup.cpp for the full + // rationale (same fix, same class of bug). + } else { + setWindowFlags(Qt::FramelessWindowHint | // Отключаем оформление окна + Qt::Tool | // Отменяем показ в качестве отдельного окна + Qt::WindowStaysOnTopHint | // Устанавливаем поверх всех окон + Qt::WindowDoesNotAcceptFocus); // Никогда не становится активным окном + setAttribute(Qt::WA_ShowWithoutActivating); // При показе, виджет не получается фокуса автоматически + } setAttribute(Qt::WA_TranslucentBackground); // Указываем, что фон будет прозрачным - setAttribute(Qt::WA_ShowWithoutActivating); // При показе, виджет не получается фокуса автоматически animation.setTargetObject(this); // Устанавливаем целевой объект анимации animation.setPropertyName("popupOpacity"); // Устанавливаем анимируемое свойство @@ -69,7 +77,20 @@ void MarkersPopUp::setName(QString name) m_settings->endGroup(); - setGeometry(m_x,m_y,width(),height()); + applyGeometry(); +} + +void MarkersPopUp::applyGeometry() +{ + // m_x/m_y are tracked as global screen coordinates throughout this + // class (mirrors PopUp::applyGeometry() in popup.cpp - see there for + // the full rationale). + if (m_embedded && parentWidget()) { + QPoint local = parentWidget()->mapFromGlobal(QPoint(m_x, m_y)); + setGeometry(local.x(), local.y(), width(), height()); + } else { + setGeometry(m_x, m_y, width(), height()); + } } MarkersPopUp::~MarkersPopUp() @@ -177,18 +198,31 @@ void MarkersPopUp::show() void MarkersPopUp::focusShow() { - //qDebug() << "MarkersPopUp::focusShow()" << m_menuVisible; - QWidget::show(); + applyGeometry(); + if (!isVisible()) { + QWidget::show(); + } + if (m_embedded) { + raise(); + } } void MarkersPopUp::focusHide() { - //qDebug() << "MarkersPopUp::focusHide()" << m_menuVisible; if (m_menuVisible) { setVisible(true); return; } - QWidget::hide(); + if (m_embedded) { + // A plain child widget: hiding it does not touch any top-level + // window, so there is no compositor feedback-loop risk here. + QWidget::hide(); + return; + } + // Park off-screen instead of QWidget::hide(): see PopUp::focusHide() + // for why unmapping this Qt::Tool surface causes a feedback loop on + // some Wayland compositors. + move(-32000, -32000); } void MarkersPopUp::hideAnimation() @@ -230,10 +264,7 @@ void MarkersPopUp::mouseMoveEvent(QMouseEvent * ) { m_x = QCursor::pos().x() - m_biasX; m_y = QCursor::pos().y() - m_biasY; - setGeometry(m_x, - m_y, - width(), - height()); + applyGeometry(); m_mainBiasX = m_x - m_mainX; m_mainBiasY = m_y - m_mainY; } @@ -245,20 +276,14 @@ void MarkersPopUp::MainWindowPos(int x, int y) m_x = x + m_mainBiasX; m_y = y + m_mainBiasY; - setGeometry(m_x, - m_y, - width(), - height()); + applyGeometry(); } void MarkersPopUp::setPosition(int x, int y) { m_x = x; m_y = y; - setGeometry(m_x, - m_y, - width(), - height()); + applyGeometry(); } void MarkersPopUp::setTextColor(QString color) diff --git a/markerspopup.h b/markerspopup.h index 1e01476..fffcece 100644 --- a/markerspopup.h +++ b/markerspopup.h @@ -43,7 +43,7 @@ class MarkersPopUp : public QWidget float getPopupOpacity() const; public: - explicit MarkersPopUp(QWidget *parent = 0); + explicit MarkersPopUp(QWidget *parent = 0, bool embedded = false); ~MarkersPopUp(); void setName(QString name); int getDurability (void) const {return m_durability;} @@ -125,6 +125,12 @@ private slots: int m_parentX; int m_parentY; + // When true, this MarkersPopUp is a plain child widget of its parent + // rather than a separate Qt::Tool top-level window - see + // markerspopup.cpp for why. + bool m_embedded; + void applyGeometry(); + QColor m_bgColor; QColor m_penColor; QString m_textColor; diff --git a/measurements.cpp b/measurements.cpp index 1cccef7..fe2d204 100644 --- a/measurements.cpp +++ b/measurements.cpp @@ -89,7 +89,7 @@ Measurements::Measurements(QObject *parent) : QObject(parent), if(m_graphHint == NULL) { - m_graphHint = new PopUp(); + m_graphHint = new PopUp(MainWindow::m_mainWindow, true); m_graphHint->setHiding(false); m_settings->beginGroup("Settings"); bool darkTheme = m_settings->value("darkColorTheme", true).toBool(); @@ -116,7 +116,7 @@ Measurements::Measurements(QObject *parent) : QObject(parent), if(m_graphBriefHint == NULL) { - m_graphBriefHint = new PopUp(); + m_graphBriefHint = new PopUp(MainWindow::m_mainWindow, true); m_graphBriefHint->setHiding(false); //m_graphBriefHint->setPopupText("0\n0"); m_graphBriefHint->setName(tr("BriefHint")); diff --git a/measurements.h b/measurements.h index 41be83b..318ba37 100644 --- a/measurements.h +++ b/measurements.h @@ -216,7 +216,7 @@ class Measurements : public QObject qint32 m_farEndMeasurement; QCPItemEllipse * m_smithTracer; - bool m_focus; + bool m_focus = true; bool m_oneFqMode = false; qint64 m_oneFqStartTime; diff --git a/onefqwidget.cpp b/onefqwidget.cpp index b83bf1d..e93011d 100644 --- a/onefqwidget.cpp +++ b/onefqwidget.cpp @@ -28,7 +28,7 @@ OneFqWidget::OneFqWidget(int _points, QWidget *parent) : setAttribute(Qt::WA_ShowWithoutActivating); m_label.setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - m_label.setStyleSheet("QLabel { color : " + m_textColor.name() + + m_label.setStyleSheet("QLabel { color : " + m_textColor.name() + ";" "margin-top: 6px;" "margin-bottom: 6px;" "margin-left: 10px;" diff --git a/popup.cpp b/popup.cpp index 0eff1ac..a8c772e 100644 --- a/popup.cpp +++ b/popup.cpp @@ -5,7 +5,7 @@ #include #include -PopUp::PopUp(QWidget *parent) : QWidget(parent), +PopUp::PopUp(QWidget *parent, bool embedded) : QWidget(parent), m_bgColor(0,0,0,180), m_penColor(255,155,255,180), m_textColor("white"), @@ -19,12 +19,13 @@ PopUp::PopUp(QWidget *parent) : QWidget(parent), m_mainX(177), m_mainY(131), m_mainBiasX(0), - m_mainBiasY(0) + m_mainBiasY(0), + m_embedded(embedded) { init(); } -PopUp::PopUp(QString buttonName, QWidget *parent) : QWidget(parent), +PopUp::PopUp(QString buttonName, QWidget *parent, bool embedded) : QWidget(parent), m_bgColor(0,0,0,180), m_penColor(255,155,255,180), m_textColor("white"), @@ -40,25 +41,35 @@ PopUp::PopUp(QString buttonName, QWidget *parent) : QWidget(parent), m_mainBiasX(0), m_mainBiasY(0), m_buttonName(buttonName), - m_showButton(true) + m_showButton(true), + m_embedded(embedded) { init(); } void PopUp::init() { - setWindowFlags(Qt::FramelessWindowHint | - Qt::Tool | - Qt::WindowStaysOnTopHint); + if (m_embedded) { + // A plain child widget of its parent, not a separate top-level + // window: there is no window for a window manager/compositor to + // ever see, so this cannot perturb the parent's activation state + // (see focusShow()/focusHide()) and it cannot show up in any + // window/task list either. + } else { + setWindowFlags(Qt::FramelessWindowHint | + Qt::Tool | + Qt::WindowStaysOnTopHint | + Qt::WindowDoesNotAcceptFocus); + setAttribute(Qt::WA_ShowWithoutActivating); + } setAttribute(Qt::WA_TranslucentBackground); - setAttribute(Qt::WA_ShowWithoutActivating); animation.setTargetObject(this); animation.setPropertyName("popupOpacity"); connect(&animation, &QAbstractAnimation::finished, this, &PopUp::hide); label.setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - label.setStyleSheet("QLabel { color : " + m_textColor + + label.setStyleSheet("QLabel { color : " + m_textColor + ";" "margin-top: 6px;" "margin-bottom: 6px;" "margin-left: 10px;" @@ -119,7 +130,23 @@ void PopUp::setName(QString name) m_settings->endGroup(); - setGeometry(m_x,m_y,width(),height()); + applyGeometry(); +} + +void PopUp::applyGeometry() +{ + // m_x/m_y are tracked as global screen coordinates throughout this + // class (MainWindowPos(), mouseMoveEvent() and every caller of + // setPosition() all compute them that way, matching the original + // Qt::Tool top-level design). When embedded as a child widget, + // translate to parent-relative coordinates right here instead of + // touching that positioning math anywhere else. + if (m_embedded && parentWidget()) { + QPoint local = parentWidget()->mapFromGlobal(QPoint(m_x, m_y)); + setGeometry(local.x(), local.y(), width(), height()); + } else { + setGeometry(m_x, m_y, width(), height()); + } } PopUp::~PopUp() @@ -185,12 +212,33 @@ void PopUp::show() void PopUp::focusShow() { - QWidget::show(); + applyGeometry(); + if (!isVisible()) { + QWidget::show(); + } + if (m_embedded) { + raise(); + } } void PopUp::focusHide() { - QWidget::hide(); + if (m_embedded) { + // A plain child widget: hiding it does not touch any top-level + // window, so there is no compositor feedback-loop risk here - + // see the Qt::Tool branch below for what that risk was. + QWidget::hide(); + return; + } + // Park off-screen instead of QWidget::hide(): unmapping this + // Qt::Tool top-level surface causes some Wayland compositors + // (observed on COSMIC/cosmic-comp) to send a spurious + // WindowActivate/WindowDeactivate event to the parent window. Since + // that event drives this same show/hide logic (via MainWindow::event + // -> focus() -> showHideHints()), unmapping creates a self-sustaining + // feedback loop ("flickering popups"). Moving off-screen is visually + // equivalent while keeping the surface continuously mapped. + move(-32000, -32000); } void PopUp::hideAnimation() diff --git a/popup.h b/popup.h index b8a48e3..dcda853 100644 --- a/popup.h +++ b/popup.h @@ -21,8 +21,8 @@ class PopUp : public QWidget float getPopupOpacity() const; public: - explicit PopUp(QWidget *parent = 0); - explicit PopUp(QString button, QWidget *parent = 0); + explicit PopUp(QWidget *parent = 0, bool embedded = false); + explicit PopUp(QString button, QWidget *parent = 0, bool embedded = false); ~PopUp(); void init(); void setName(QString name); @@ -104,6 +104,10 @@ protected slots: int m_parentX; int m_parentY; + // When true, this PopUp is a plain child widget of its parent rather + // than a separate Qt::Tool top-level window - see popup.cpp for why. + bool m_embedded; + void applyGeometry(); QString m_name;