#include "typeselectorpopup.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "themes/thememanager.h" namespace rcx { // ── parseTypeSpec ── TypeSpec parseTypeSpec(const QString& text) { TypeSpec spec; QString s = text.trimmed(); if (s.isEmpty()) return spec; // Check for pointer suffix: "Ball*" or "Ball**" if (s.endsWith('*')) { spec.isPointer = true; s.chop(1); spec.ptrDepth = 1; if (s.endsWith('*')) { s.chop(1); spec.ptrDepth = 2; } spec.baseName = s.trimmed(); return spec; } // Check for array suffix: "int32_t[10]" int bracket = s.indexOf('['); if (bracket > 0 && s.endsWith(']')) { spec.baseName = s.left(bracket).trimmed(); QString countStr = s.mid(bracket + 1, s.size() - bracket - 2); bool ok; int count = countStr.toInt(&ok); if (ok && count > 0) spec.arrayCount = count; return spec; } spec.baseName = s; return spec; } // ── Custom delegate: gutter checkmark + icon + text + sections ── class TypeSelectorDelegate : public QStyledItemDelegate { public: explicit TypeSelectorDelegate(TypeSelectorPopup* popup, QObject* parent = nullptr) : QStyledItemDelegate(parent), m_popup(popup) {} void setFont(const QFont& f) { m_font = f; } void setFilteredTypes(const QVector* filtered, const TypeEntry* current, bool hasCurrent) { m_filtered = filtered; m_current = current; m_hasCurrent = hasCurrent; } void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { painter->save(); const auto& t = ThemeManager::instance().current(); int row = index.row(); bool isSection = (m_filtered && row >= 0 && row < m_filtered->size() && (*m_filtered)[row].entryKind == TypeEntry::Section); bool isDisabled = (m_filtered && row >= 0 && row < m_filtered->size() && !(*m_filtered)[row].enabled); // Background if (isSection) { // No background highlight for sections } else if (isDisabled) { // Subtle background on hover only if (option.state & QStyle::State_MouseOver) painter->fillRect(option.rect, t.surface); } else { if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, t.selected); else if (option.state & QStyle::State_MouseOver) painter->fillRect(option.rect, t.hover); } int x = option.rect.x(); int y = option.rect.y(); int h = option.rect.height(); int w = option.rect.width(); // Scale metrics from font height QFontMetrics fmMain(m_font); int iconSz = fmMain.height(); // icon matches text height int gutterW = fmMain.horizontalAdvance(QChar(0x25B8)) + 4; int iconColW = iconSz + 4; // Section: centered dim text with horizontal rules if (isSection) { painter->setPen(t.textDim); QFont dimFont = m_font; dimFont.setPointSize(qMax(7, m_font.pointSize() - 1)); painter->setFont(dimFont); QFontMetrics fm(dimFont); QString text = index.data().toString(); int textW = fm.horizontalAdvance(text); int textX = x + (w - textW) / 2; int lineY = y + h / 2; // Left rule if (textX > x + 8) painter->drawLine(x + 8, lineY, textX - 6, lineY); // Text painter->drawText(QRect(textX, y, textW, h), Qt::AlignVCenter, text); // Right rule if (textX + textW + 6 < x + w - 8) painter->drawLine(textX + textW + 6, lineY, x + w - 8, lineY); painter->restore(); return; } // Gutter: side triangle if current if (m_hasCurrent && m_filtered && row >= 0 && row < m_filtered->size()) { const TypeEntry& entry = (*m_filtered)[row]; bool isCurrent = false; if (m_current->entryKind == TypeEntry::Primitive && entry.entryKind == TypeEntry::Primitive) isCurrent = (entry.primitiveKind == m_current->primitiveKind); else if (m_current->entryKind == TypeEntry::Composite && entry.entryKind == TypeEntry::Composite) isCurrent = (entry.structId == m_current->structId); if (isCurrent) { painter->setPen(t.text); painter->setFont(m_font); painter->drawText(QRect(x, y, gutterW, h), Qt::AlignCenter, QString(QChar(0x25B8))); } } x += gutterW; // Icon (scaled to font height) — only for composite entries bool hasIcon = (m_filtered && row >= 0 && row < m_filtered->size() && (*m_filtered)[row].entryKind == TypeEntry::Composite); if (hasIcon) { static QIcon structIcon(QStringLiteral(":/vsicons/symbol-structure.svg")); QPixmap pm = structIcon.pixmap(iconSz, iconSz); if (isDisabled) { // Paint dimmed QPixmap dimmed(pm.size()); dimmed.fill(Qt::transparent); QPainter p(&dimmed); p.setOpacity(0.35); p.drawPixmap(0, 0, pm); p.end(); painter->drawPixmap(x, y + (h - iconSz) / 2, dimmed); } else { structIcon.paint(painter, x, y + (h - iconSz) / 2, iconSz, iconSz); } } x += iconColW; // Text QColor textColor; if (isDisabled) textColor = t.textDim; else if (option.state & QStyle::State_Selected) textColor = option.palette.color(QPalette::HighlightedText); else textColor = option.palette.color(QPalette::Text); painter->setPen(textColor); painter->setFont(m_font); painter->drawText(QRect(x, y, option.rect.right() - x, h), Qt::AlignVCenter | Qt::AlignLeft, index.data().toString()); painter->restore(); } QSize sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& index) const override { QFontMetrics fm(m_font); int row = index.row(); bool isSection = (m_filtered && row >= 0 && row < m_filtered->size() && (*m_filtered)[row].entryKind == TypeEntry::Section); int h = isSection ? fm.height() + 2 : fm.height() + 8; return QSize(200, h); } private: TypeSelectorPopup* m_popup = nullptr; QFont m_font; const QVector* m_filtered = nullptr; const TypeEntry* m_current = nullptr; bool m_hasCurrent = false; }; // ── TypeSelectorPopup ── TypeSelectorPopup::TypeSelectorPopup(QWidget* parent) : QFrame(parent, Qt::Popup | Qt::FramelessWindowHint) { setAttribute(Qt::WA_DeleteOnClose, false); const auto& theme = ThemeManager::instance().current(); QPalette pal; pal.setColor(QPalette::Window, theme.backgroundAlt); pal.setColor(QPalette::WindowText, theme.text); pal.setColor(QPalette::Base, theme.background); pal.setColor(QPalette::AlternateBase, theme.surface); pal.setColor(QPalette::Text, theme.text); pal.setColor(QPalette::Button, theme.button); pal.setColor(QPalette::ButtonText, theme.text); pal.setColor(QPalette::Highlight, theme.hover); pal.setColor(QPalette::HighlightedText, theme.text); setPalette(pal); setAutoFillBackground(true); setFrameShape(QFrame::NoFrame); setLineWidth(0); auto* layout = new QVBoxLayout(this); layout->setContentsMargins(6, 6, 6, 6); layout->setSpacing(4); // Row 1: title + Esc hint { auto* row = new QHBoxLayout; row->setContentsMargins(0, 0, 0, 0); m_titleLabel = new QLabel(QStringLiteral("Change type")); m_titleLabel->setPalette(pal); QFont bold = m_titleLabel->font(); bold.setBold(true); m_titleLabel->setFont(bold); row->addWidget(m_titleLabel); row->addStretch(); m_escLabel = new QToolButton; m_escLabel->setText(QStringLiteral("\u2715 Esc")); m_escLabel->setAutoRaise(true); m_escLabel->setCursor(Qt::PointingHandCursor); m_escLabel->setStyleSheet(QStringLiteral( "QToolButton { color: %1; border: none; padding: 2px 6px; }" "QToolButton:hover { color: %2; }") .arg(theme.textDim.name(), theme.indHoverSpan.name())); connect(m_escLabel, &QToolButton::clicked, this, [this]() { hide(); }); row->addWidget(m_escLabel); layout->addLayout(row); } // Row 2: + Create new type button (flat, no gradient) { m_createBtn = new QToolButton; m_createBtn->setText(QStringLiteral("+ Create new type\u2026")); m_createBtn->setToolButtonStyle(Qt::ToolButtonTextOnly); m_createBtn->setAutoRaise(true); m_createBtn->setCursor(Qt::PointingHandCursor); m_createBtn->setStyleSheet(QStringLiteral( "QToolButton { color: %1; border: none; padding: 3px 6px; }" "QToolButton:hover { color: %2; background: %3; }") .arg(theme.textMuted.name(), theme.text.name(), theme.hover.name())); connect(m_createBtn, &QToolButton::clicked, this, [this]() { emit createNewTypeRequested(); hide(); }); layout->addWidget(m_createBtn); } // Separator { m_separator = new QFrame; m_separator->setFrameShape(QFrame::HLine); m_separator->setFrameShadow(QFrame::Plain); QPalette sepPal = pal; sepPal.setColor(QPalette::WindowText, theme.border); m_separator->setPalette(sepPal); m_separator->setFixedHeight(1); layout->addWidget(m_separator); } // Row 3: Modifier toggles [ plain ] [ * ] [ ** ] [ [n] ] { m_modRow = new QWidget; auto* modLayout = new QHBoxLayout(m_modRow); modLayout->setContentsMargins(0, 0, 0, 0); modLayout->setSpacing(3); m_modGroup = new QButtonGroup(this); m_modGroup->setExclusive(true); QString btnStyle = QStringLiteral( "QToolButton { color: %1; background: %2; border: 1px solid %3;" " padding: 2px 8px; border-radius: 3px; }" "QToolButton:checked { color: %4; background: %5; border-color: %5; }" "QToolButton:hover:!checked { background: %6; }") .arg(theme.textDim.name(), theme.background.name(), theme.border.name(), theme.text.name(), theme.selected.name(), theme.hover.name()); auto makeToggle = [&](const QString& label, int id) -> QToolButton* { auto* btn = new QToolButton; btn->setText(label); btn->setCheckable(true); btn->setCursor(Qt::PointingHandCursor); btn->setStyleSheet(btnStyle); m_modGroup->addButton(btn, id); modLayout->addWidget(btn); return btn; }; m_btnPlain = makeToggle(QStringLiteral("plain"), 0); m_btnPtr = makeToggle(QStringLiteral("*"), 1); m_btnDblPtr = makeToggle(QStringLiteral("**"), 2); m_btnArray = makeToggle(QStringLiteral("[n]"), 3); m_btnPlain->setChecked(true); // Array count input (shown only when [n] is active) m_arrayCountEdit = new QLineEdit; m_arrayCountEdit->setPlaceholderText(QStringLiteral("n")); m_arrayCountEdit->setValidator(new QIntValidator(1, 99999, m_arrayCountEdit)); m_arrayCountEdit->setFixedWidth(50); m_arrayCountEdit->setPalette(pal); m_arrayCountEdit->hide(); modLayout->addWidget(m_arrayCountEdit); modLayout->addStretch(); layout->addWidget(m_modRow); connect(m_modGroup, &QButtonGroup::idToggled, this, [this](int id, bool checked) { if (!checked) return; m_arrayCountEdit->setVisible(id == 3); if (id == 3) { if (m_arrayCountEdit->text().trimmed().isEmpty()) m_arrayCountEdit->setText(QStringLiteral("1")); m_arrayCountEdit->setFocus(); m_arrayCountEdit->selectAll(); } updateModifierPreview(); }); connect(m_arrayCountEdit, &QLineEdit::textChanged, this, [this]() { updateModifierPreview(); }); } // Row 4: Filter + preview { m_filterEdit = new QLineEdit; m_filterEdit->setPlaceholderText(QStringLiteral("Filter types\u2026")); m_filterEdit->setClearButtonEnabled(true); m_filterEdit->setPalette(pal); m_filterEdit->installEventFilter(this); connect(m_filterEdit, &QLineEdit::textChanged, this, &TypeSelectorPopup::applyFilter); layout->addWidget(m_filterEdit); m_previewLabel = new QLabel; m_previewLabel->setPalette(pal); m_previewLabel->setStyleSheet(QStringLiteral( "QLabel { color: %1; padding: 1px 6px; }").arg(theme.syntaxType.name())); m_previewLabel->hide(); layout->addWidget(m_previewLabel); } // Row 4: List { m_model = new QStringListModel(this); m_listView = new QListView; m_listView->setModel(m_model); m_listView->setPalette(pal); m_listView->setFrameShape(QFrame::NoFrame); m_listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_listView->setMouseTracking(true); m_listView->setEditTriggers(QAbstractItemView::NoEditTriggers); m_listView->viewport()->setAttribute(Qt::WA_Hover, true); m_listView->installEventFilter(this); auto* delegate = new TypeSelectorDelegate(this, m_listView); m_listView->setItemDelegate(delegate); layout->addWidget(m_listView, 1); connect(m_listView, &QListView::clicked, this, [this](const QModelIndex& index) { acceptIndex(index.row()); }); } } void TypeSelectorPopup::warmUp() { // One-time per-process cost (~170ms): Qt lazily initializes the style/font/DLL // subsystem the first time a popup with complex children is shown. Pre-pay it // by briefly showing a throwaway dummy popup with a QListView, then show+hide // ourselves. { auto* primer = new QFrame(nullptr, Qt::Popup | Qt::FramelessWindowHint); primer->resize(300, 400); auto* lay = new QVBoxLayout(primer); lay->addWidget(new QLabel(QStringLiteral("x"))); lay->addWidget(new QLineEdit); auto* model = new QStringListModel(primer); QStringList items; for (int i = 0; i < 10; i++) items << QStringLiteral("x"); model->setStringList(items); auto* lv = new QListView; lv->setModel(model); lay->addWidget(lv); primer->show(); QApplication::processEvents(); primer->hide(); QApplication::processEvents(); delete primer; } TypeEntry dummy; dummy.entryKind = TypeEntry::Primitive; dummy.primitiveKind = NodeKind::Hex8; dummy.displayName = QStringLiteral("warmup"); setTypes({dummy}); popup(QPoint(-9999, -9999)); hide(); QApplication::processEvents(); } void TypeSelectorPopup::setFont(const QFont& font) { m_font = font; m_titleLabel->setFont([&]() { QFont f = font; f.setBold(true); return f; }()); m_escLabel->setFont(font); m_createBtn->setFont(font); m_filterEdit->setFont(font); m_listView->setFont(font); m_previewLabel->setFont(font); QFont smallFont = font; smallFont.setPointSize(qMax(7, font.pointSize() - 1)); m_btnPlain->setFont(smallFont); m_btnPtr->setFont(smallFont); m_btnDblPtr->setFont(smallFont); m_btnArray->setFont(smallFont); m_arrayCountEdit->setFont(smallFont); auto* delegate = static_cast(m_listView->itemDelegate()); if (delegate) delegate->setFont(font); } void TypeSelectorPopup::applyTheme(const Theme& theme) { QPalette pal; pal.setColor(QPalette::Window, theme.backgroundAlt); pal.setColor(QPalette::WindowText, theme.text); pal.setColor(QPalette::Base, theme.background); pal.setColor(QPalette::AlternateBase, theme.surface); pal.setColor(QPalette::Text, theme.text); pal.setColor(QPalette::Button, theme.button); pal.setColor(QPalette::ButtonText, theme.text); pal.setColor(QPalette::Highlight, theme.hover); pal.setColor(QPalette::HighlightedText, theme.text); setPalette(pal); m_titleLabel->setPalette(pal); m_filterEdit->setPalette(pal); m_listView->setPalette(pal); m_previewLabel->setPalette(pal); m_arrayCountEdit->setPalette(pal); // Separator QPalette sepPal = pal; sepPal.setColor(QPalette::WindowText, theme.border); m_separator->setPalette(sepPal); // Esc button m_escLabel->setStyleSheet(QStringLiteral( "QToolButton { color: %1; border: none; padding: 2px 6px; }" "QToolButton:hover { color: %2; }") .arg(theme.textDim.name(), theme.indHoverSpan.name())); // Create button m_createBtn->setStyleSheet(QStringLiteral( "QToolButton { color: %1; border: none; padding: 3px 6px; }" "QToolButton:hover { color: %2; background: %3; }") .arg(theme.textMuted.name(), theme.text.name(), theme.hover.name())); // Modifier toggle buttons QString btnStyle = QStringLiteral( "QToolButton { color: %1; background: %2; border: 1px solid %3;" " padding: 2px 8px; border-radius: 3px; }" "QToolButton:checked { color: %4; background: %5; border-color: %5; }" "QToolButton:hover:!checked { background: %6; }") .arg(theme.textDim.name(), theme.background.name(), theme.border.name(), theme.text.name(), theme.selected.name(), theme.hover.name()); m_btnPlain->setStyleSheet(btnStyle); m_btnPtr->setStyleSheet(btnStyle); m_btnDblPtr->setStyleSheet(btnStyle); m_btnArray->setStyleSheet(btnStyle); // Preview label m_previewLabel->setStyleSheet(QStringLiteral( "QLabel { color: %1; padding: 1px 6px; }").arg(theme.syntaxType.name())); } void TypeSelectorPopup::setTitle(const QString& title) { m_titleLabel->setText(title); } void TypeSelectorPopup::setMode(TypePopupMode mode) { m_mode = mode; bool showMods = (mode == TypePopupMode::FieldType || mode == TypePopupMode::ArrayElement); m_modRow->setVisible(showMods); // Always reset to plain — prevents stale state from leaking across modes // (PointerTarget hides buttons but applyFilter still reads their state) m_btnPlain->setChecked(true); m_arrayCountEdit->clear(); m_arrayCountEdit->hide(); } void TypeSelectorPopup::setCurrentNodeSize(int bytes) { m_currentNodeSize = bytes; } void TypeSelectorPopup::setModifier(int modId, int arrayCount) { if (modId == 1) m_btnPtr->setChecked(true); else if (modId == 2) m_btnDblPtr->setChecked(true); else if (modId == 3) { m_btnArray->setChecked(true); m_arrayCountEdit->setText(QString::number(arrayCount)); m_arrayCountEdit->show(); } else { m_btnPlain->setChecked(true); } } void TypeSelectorPopup::setTypes(const QVector& types, const TypeEntry* current) { m_allTypes = types; if (current) { m_currentEntry = *current; m_hasCurrent = true; } else { m_currentEntry = TypeEntry{}; m_hasCurrent = false; } // Don't reset modifier buttons here — setMode() already resets to plain, // and setModifier() may have preselected a button between setMode/setTypes. m_previewLabel->hide(); m_filterEdit->clear(); applyFilter(QString()); } void TypeSelectorPopup::popup(const QPoint& globalPos) { QFontMetrics fm(m_font); int maxTextW = fm.horizontalAdvance(QStringLiteral("Choose element type Esc")); for (const auto& t : m_allTypes) { QString text = t.classKeyword.isEmpty() ? t.displayName : (t.classKeyword + QStringLiteral(" ") + t.displayName); int gutterW = fm.horizontalAdvance(QChar(0x25B8)) + 4; int iconColW = fm.height() + 4; int w = gutterW + iconColW + fm.horizontalAdvance(text) + 16; if (w > maxTextW) maxTextW = w; } int popupW = qBound(280, maxTextW + 24, 500); int rowH = fm.height() + 8; int headerH = rowH * 3 + 30; if (m_modRow->isVisible()) headerH += rowH + 4; // extra row for modifier toggles int listH = qBound(rowH * 3, rowH * (int)m_filteredTypes.size(), rowH * 14); int popupH = headerH + listH; QScreen* screen = QApplication::screenAt(globalPos); if (screen) { QRect avail = screen->availableGeometry(); if (globalPos.y() + popupH > avail.bottom()) popupH = avail.bottom() - globalPos.y(); if (globalPos.x() + popupW > avail.right()) popupW = avail.right() - globalPos.x(); } setFixedSize(popupW, popupH); move(globalPos); show(); raise(); activateWindow(); m_filterEdit->setFocus(); // Pre-select current type in list if (m_hasCurrent) { for (int i = 0; i < m_filteredTypes.size(); i++) { const auto& entry = m_filteredTypes[i]; if (entry.entryKind == TypeEntry::Section) continue; bool match = false; if (m_currentEntry.entryKind == TypeEntry::Primitive && entry.entryKind == TypeEntry::Primitive) match = (entry.primitiveKind == m_currentEntry.primitiveKind); else if (m_currentEntry.entryKind == TypeEntry::Composite && entry.entryKind == TypeEntry::Composite) match = (entry.structId == m_currentEntry.structId); if (match) { m_listView->setCurrentIndex(m_model->index(i)); break; } } } } void TypeSelectorPopup::updateModifierPreview() { int modId = m_modGroup->checkedId(); if (modId <= 0) { m_previewLabel->hide(); return; } QString suffix; if (modId == 1) suffix = QStringLiteral("*"); else if (modId == 2) suffix = QStringLiteral("**"); else if (modId == 3) { QString countText = m_arrayCountEdit->text().trimmed(); suffix = countText.isEmpty() ? QStringLiteral("[n]") : QStringLiteral("[%1]").arg(countText); } m_previewLabel->setText(QStringLiteral("\u2192 %1").arg(suffix)); m_previewLabel->show(); } void TypeSelectorPopup::applyFilter(const QString& text) { m_filteredTypes.clear(); QStringList displayStrings; QString filterBase = text.trimmed(); // Separate primitives and composites (all types shown regardless of modifier) QVector primitives, composites; for (const auto& t : m_allTypes) { if (t.entryKind == TypeEntry::Section) continue; bool matchesFilter = filterBase.isEmpty() || t.displayName.contains(filterBase, Qt::CaseInsensitive) || t.classKeyword.contains(filterBase, Qt::CaseInsensitive); if (!matchesFilter) continue; if (t.entryKind == TypeEntry::Primitive) primitives.append(t); else if (t.entryKind == TypeEntry::Composite) composites.append(t); } auto alphabetical = [](const TypeEntry& a, const TypeEntry& b) { return a.displayName.compare(b.displayName, Qt::CaseInsensitive) < 0; }; // For non-Root modes, sort primitives: same-size first, then rest — alphabetical within each group if (m_mode != TypePopupMode::Root && m_currentNodeSize > 0 && !primitives.isEmpty()) { QVector sameSize, other; for (const auto& p : primitives) { if (sizeForKind(p.primitiveKind) == m_currentNodeSize) sameSize.append(p); else other.append(p); } std::sort(sameSize.begin(), sameSize.end(), alphabetical); std::sort(other.begin(), other.end(), alphabetical); primitives = sameSize + other; } else { std::sort(primitives.begin(), primitives.end(), alphabetical); } // Helper lambdas for appending sections auto appendPrimitives = [&]() { if (primitives.isEmpty()) return; TypeEntry sec; sec.entryKind = TypeEntry::Section; sec.displayName = QStringLiteral("primitives"); sec.enabled = false; m_filteredTypes.append(sec); displayStrings << sec.displayName; for (const auto& p : primitives) { m_filteredTypes.append(p); displayStrings << p.displayName; } }; auto appendComposites = [&]() { if (composites.isEmpty()) return; TypeEntry sec; sec.entryKind = TypeEntry::Section; sec.displayName = QStringLiteral("project types"); sec.enabled = false; m_filteredTypes.append(sec); displayStrings << sec.displayName; for (const auto& c : composites) { m_filteredTypes.append(c); QString label = c.classKeyword.isEmpty() ? c.displayName : (c.classKeyword + QStringLiteral(" ") + c.displayName); displayStrings << label; } }; // Root mode: project types first (composites are the primary selection) if (m_mode == TypePopupMode::Root) { appendComposites(); appendPrimitives(); } else { appendPrimitives(); appendComposites(); } m_model->setStringList(displayStrings); auto* delegate = static_cast(m_listView->itemDelegate()); if (delegate) delegate->setFilteredTypes(&m_filteredTypes, &m_currentEntry, m_hasCurrent); // Select first selectable item int first = nextSelectableRow(0, 1); if (first >= 0) m_listView->setCurrentIndex(m_model->index(first)); } void TypeSelectorPopup::acceptCurrent() { QModelIndex idx = m_listView->currentIndex(); if (idx.isValid()) acceptIndex(idx.row()); } void TypeSelectorPopup::acceptIndex(int row) { if (row < 0 || row >= m_filteredTypes.size()) return; const TypeEntry& entry = m_filteredTypes[row]; if (entry.entryKind == TypeEntry::Section) return; if (!entry.enabled) return; // Build full text with modifier from toggle buttons int modId = m_modGroup->checkedId(); QString fullText = entry.displayName; if (modId == 1) fullText += QStringLiteral("*"); else if (modId == 2) fullText += QStringLiteral("**"); else if (modId == 3) { QString countText = m_arrayCountEdit->text().trimmed(); if (!countText.isEmpty()) fullText += QStringLiteral("[%1]").arg(countText); } emit typeSelected(entry, fullText); hide(); } int TypeSelectorPopup::nextSelectableRow(int from, int direction) const { int i = from; while (i >= 0 && i < m_filteredTypes.size()) { const auto& e = m_filteredTypes[i]; if (e.entryKind != TypeEntry::Section && e.enabled) return i; i += direction; } return -1; } bool TypeSelectorPopup::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::KeyPress) { auto* ke = static_cast(event); if (ke->key() == Qt::Key_Escape) { hide(); return true; } if (obj == m_filterEdit) { if (ke->key() == Qt::Key_Down) { m_listView->setFocus(); QModelIndex cur = m_listView->currentIndex(); int startRow = cur.isValid() ? cur.row() : 0; int next = nextSelectableRow(startRow, 1); if (next >= 0) m_listView->setCurrentIndex(m_model->index(next)); return true; } if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) { acceptCurrent(); return true; } } if (obj == m_listView) { if (ke->key() == Qt::Key_Up) { QModelIndex cur = m_listView->currentIndex(); if (!cur.isValid() || cur.row() == 0) { m_filterEdit->setFocus(); return true; } // Skip sections and disabled entries int prev = nextSelectableRow(cur.row() - 1, -1); if (prev < 0) { m_filterEdit->setFocus(); return true; } m_listView->setCurrentIndex(m_model->index(prev)); return true; } if (ke->key() == Qt::Key_Down) { QModelIndex cur = m_listView->currentIndex(); int startRow = cur.isValid() ? cur.row() + 1 : 0; int next = nextSelectableRow(startRow, 1); if (next >= 0) m_listView->setCurrentIndex(m_model->index(next)); return true; } if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) { acceptCurrent(); return true; } // Forward printable keys to filter edit for type-to-filter if (!ke->text().isEmpty() && ke->text()[0].isPrint()) { m_filterEdit->setFocus(); m_filterEdit->setText(m_filterEdit->text() + ke->text()); return true; } } } return QFrame::eventFilter(obj, event); } void TypeSelectorPopup::hideEvent(QHideEvent* event) { QFrame::hideEvent(event); emit dismissed(); } } // namespace rcx