// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include "LiveComboBox.h" #include #include #include #include #include #include LiveComboBox::LiveComboBox(QWidget *parent) : QComboBox(parent) { // The popup is a separate window holding the view. Not every way of closing it goes through // hidePopup() (a click outside just hides the window), so watch the window itself. view()->window()->installEventFilter(this); } bool LiveComboBox::SetItems(const QVector &items) { if (view()->isVisible()) { pending_ = items; return false; } pending_.reset(); if (items == items_) return false; Replace(items); return true; } void LiveComboBox::Replace(const QVector &items) { QSignalBlocker blocker(this); const QVariant selected = currentData(); clear(); for (const auto &item : items) { if (item.separator) { insertSeparator(count()); continue; } if (item.swatch.isValid()) { QPixmap pixmap(14, 14); pixmap.fill(item.swatch); addItem(QIcon(pixmap), item.text, item.data); } else addItem(item.text, item.data); if (!item.tooltip.isEmpty()) setItemData(count() - 1, item.tooltip, Qt::ToolTipRole); } items_ = items; const int index = selected.isValid() ? findData(selected) : -1; setCurrentIndex(index >= 0 ? index : (count() > 0 ? 0 : -1)); } void LiveComboBox::ApplyPending() { if (!pending_ || view()->isVisible()) return; const QVector items = std::move(*pending_); pending_.reset(); if (items == items_) return; Replace(items); emit itemsReplaced(); } bool LiveComboBox::eventFilter(QObject *watched, QEvent *event) { // Queued: a click on an item hides the popup first and selects the item afterwards, so the // held-back items go in once the pick has landed, and keep it. if (event->type() == QEvent::Hide && pending_) QTimer::singleShot(0, this, &LiveComboBox::ApplyPending); return QComboBox::eventFilter(watched, event); }