#include "filter-combo-box.hpp" #include "ui-helpers.hpp" #include #include #include namespace advss { bool FilterComboBox::_filteringEnabled = false; FilterComboBox::FilterComboBox(QWidget *parent, const QString &placehodler) : QComboBox(parent) { // If the filtering behaviour of the FilterComboBox is disabled it is // just a regular QComboBox with the option to set a placeholder so exit // the constructor early. if (!_filteringEnabled) { if (!placehodler.isEmpty()) { setPlaceholderText(placehodler); } return; } // Allow edit for completer but don't add new entries on pressing enter setEditable(true); setInsertPolicy(InsertPolicy::NoInsert); if (!placehodler.isEmpty()) { lineEdit()->setPlaceholderText(placehodler); // Make sure that the placeholder text is visible QFontMetrics fontMetrics(font()); int textWidth = fontMetrics.boundingRect(placehodler).width(); QStyleOptionComboBox comboBoxOption; comboBoxOption.initFrom(this); int buttonWidth = style()->subControlRect(QStyle::CC_ComboBox, &comboBoxOption, QStyle::SC_ComboBoxArrow, this) .width(); setMinimumWidth(buttonWidth + textWidth); } setMaxVisibleItems(30); auto c = completer(); c->setCaseSensitivity(Qt::CaseInsensitive); c->setFilterMode(Qt::MatchContains); c->setCompletionMode(QCompleter::PopupCompletion); connect(c, QOverload::of(&QCompleter::highlighted), this, &FilterComboBox::CompleterHighlightChanged); connect(lineEdit(), &QLineEdit::textChanged, this, &FilterComboBox::TextChanged); } void FilterComboBox::SetFilterBehaviourEnabled(bool value) { FilterComboBox::_filteringEnabled = value; } void FilterComboBox::SetAllowUnmatchedSelection(bool allow) { _allowUnmatchedSelection = allow; } void FilterComboBox::setCurrentText(const QString &text) { if (_filteringEnabled) { lineEdit()->setText(text); } QComboBox::setCurrentText(text); } void FilterComboBox::setItemText(int index, const QString &text) { QComboBox::setItemText(index, text); if (_filteringEnabled && index == currentIndex()) { const QSignalBlocker b(this); lineEdit()->setText(text); } } QSize FilterComboBox::sizeHint() const { QSize size = QComboBox::sizeHint(); if (!_filteringEnabled) { return QComboBox::sizeHint(); } QFontMetrics fm(font()); int extra = fm.horizontalAdvance("X") * 2; // Add padding size.setWidth(size.width() + extra); return size; } void FilterComboBox::focusOutEvent(QFocusEvent *event) { // Reset on invalid selection if (!_allowUnmatchedSelection && findText(currentText()) == -1) { setCurrentIndex(-1); Emit(-1, ""); } QComboBox::focusOutEvent(event); _lastCompleterHighlightRow = -1; } static int findXthOccurrence(QComboBox *list, int count, const QString &value) { if (value.isEmpty() || count < 1) { return -1; } const auto size = list->count(); int idx = FindIdxInRagne(list, 0, size, value.toStdString(), Qt::MatchContains | Qt::MatchFixedString); if (count == 1) { return idx; } for (int i = 1; i < count; i++) { idx = FindIdxInRagne(list, idx, size, value.toStdString(), Qt::MatchContains | Qt::MatchFixedString); } return idx; } void FilterComboBox::CompleterHighlightChanged(const QModelIndex &index) { _lastCompleterHighlightRow = index.row(); const auto text = currentText(); int idx = findXthOccurrence(this, _lastCompleterHighlightRow, text); if (idx == -1) { return; } Emit(idx, text); } void FilterComboBox::TextChanged(const QString &text) { auto c = completer(); const bool completerActive = c->completionCount() > 0; int count = completerActive ? _lastCompleterHighlightRow + 1 : 1; int idx = findXthOccurrence(this, count, text); if (idx == -1) { return; } } void FilterComboBox::Emit(int index, const QString &text) { if (_lastEmittedIndex != index) { _lastEmittedIndex = index; emit currentIndexChanged(index); } if (_lastEmittedText != text) { _lastEmittedText = text; emit currentTextChanged(text); } } } // namespace advss