[VDS] Allow tags to toggle to a NOT state to hide non-matching decks (#5920)

* Allow excluding tags.

* Lint.

* My linter is broken, don't ask.

* Zzz.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2025-05-07 03:23:49 +02:00 committed by GitHub
parent f16ba6861b
commit 46e146b34a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 82 additions and 41 deletions

View file

@ -6,7 +6,7 @@
#include <QPainter> #include <QPainter>
DeckPreviewTagDisplayWidget::DeckPreviewTagDisplayWidget(QWidget *parent, const QString &_tagName) DeckPreviewTagDisplayWidget::DeckPreviewTagDisplayWidget(QWidget *parent, const QString &_tagName)
: QWidget(parent), tagName(_tagName), isSelected(false) : QWidget(parent), tagName(_tagName), state(TagState::NotSelected)
{ {
// Create layout // Create layout
auto *layout = new QHBoxLayout(this); auto *layout = new QHBoxLayout(this);
@ -48,36 +48,58 @@ QSize DeckPreviewTagDisplayWidget::sizeHint() const
void DeckPreviewTagDisplayWidget::mousePressEvent(QMouseEvent *event) void DeckPreviewTagDisplayWidget::mousePressEvent(QMouseEvent *event)
{ {
if (event->button() == Qt::LeftButton) { switch (event->button()) {
setSelected(!isSelected); case Qt::LeftButton:
emit tagClicked(); setState(TagState::Selected);
break;
case Qt::RightButton:
setState(TagState::Excluded);
break;
case Qt::MiddleButton:
setState(TagState::NotSelected);
break;
default:
break;
} }
QWidget::mousePressEvent(event);
}
void DeckPreviewTagDisplayWidget::setSelected(bool selected) emit tagClicked();
{ QWidget::mousePressEvent(event);
isSelected = selected;
update(); // Trigger a repaint
} }
void DeckPreviewTagDisplayWidget::paintEvent(QPaintEvent *event) void DeckPreviewTagDisplayWidget::paintEvent(QPaintEvent *event)
{ {
QPainter painter(this); QPainter painter(this);
// Set background color QColor backgroundColor;
QColor backgroundColor = isSelected ? QColor(173, 216, 230) : Qt::white; QColor borderColor;
int borderWidth;
switch (state) {
case TagState::Selected:
backgroundColor = QColor(173, 216, 230); // Light blue
borderColor = Qt::blue;
borderWidth = 2;
break;
case TagState::Excluded:
backgroundColor = QColor(255, 182, 193); // Light red/pink
borderColor = Qt::red;
borderWidth = 2;
break;
case TagState::NotSelected:
default:
backgroundColor = Qt::white;
borderColor = Qt::gray;
borderWidth = 1;
break;
}
painter.setBrush(backgroundColor); painter.setBrush(backgroundColor);
painter.setPen(Qt::NoPen); painter.setPen(Qt::NoPen);
// Draw background
painter.drawRect(rect()); painter.drawRect(rect());
// Draw border QPen borderPen(borderColor, borderWidth);
QColor borderColor = isSelected ? Qt::blue : Qt::gray;
QPen borderPen(borderColor, isSelected ? 2 : 1);
painter.setPen(borderPen); painter.setPen(borderPen);
painter.drawRect(rect().adjusted(0, 0, -1, -1)); // Adjust for pen width painter.drawRect(rect().adjusted(0, 0, -1, -1));
// Calculate font size based on widget height // Calculate font size based on widget height
QFont font = painter.font(); QFont font = painter.font();

View file

@ -6,6 +6,13 @@
#include <QString> #include <QString>
#include <QWidget> #include <QWidget>
enum class TagState
{
NotSelected,
Selected,
Excluded
};
class DeckPreviewTagDisplayWidget : public QWidget class DeckPreviewTagDisplayWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -22,16 +29,16 @@ public:
{ {
return tagName; return tagName;
} }
bool getSelected() const TagState getState() const
{ {
return isSelected; return state;
} }
/** void setState(const TagState newState)
* @brief Sets the selected state of the tag. {
* @param selected True if the tag is selected, false otherwise. state = newState;
*/ update();
void setSelected(bool selected); };
signals: signals:
/** /**
@ -61,7 +68,7 @@ private:
QLabel *tagLabel; ///< Label for displaying the tag name. QLabel *tagLabel; ///< Label for displaying the tag name.
QPushButton *closeButton; ///< Button to close/remove the tag. QPushButton *closeButton; ///< Button to close/remove the tag.
QString tagName; ///< The name of the tag. QString tagName; ///< The name of the tag.
bool isSelected; ///< Indicates whether the tag is selected. TagState state; ///< Indicates whether the tag is unselected, selected, or excluded.
}; };
#endif // DECK_PREVIEW_TAG_DISPLAY_WIDGET_H #endif // DECK_PREVIEW_TAG_DISPLAY_WIDGET_H

View file

@ -33,32 +33,42 @@ void VisualDeckStorageTagFilterWidget::showEvent(QShowEvent *event)
void VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QList<DeckPreviewWidget *> &deckPreviews) const void VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QList<DeckPreviewWidget *> &deckPreviews) const
{ {
// Collect selected tags from DeckPreviewTagDisplayWidget
QStringList selectedTags; QStringList selectedTags;
QStringList excludedTags;
// Collect selected and excluded tags
for (DeckPreviewTagDisplayWidget *tagWidget : findChildren<DeckPreviewTagDisplayWidget *>()) { for (DeckPreviewTagDisplayWidget *tagWidget : findChildren<DeckPreviewTagDisplayWidget *>()) {
if (tagWidget->getSelected()) { switch (tagWidget->getState()) {
selectedTags.append(tagWidget->getTagName()); case TagState::Selected:
selectedTags.append(tagWidget->getTagName());
break;
case TagState::Excluded:
excludedTags.append(tagWidget->getTagName());
break;
default:
break;
} }
} }
// If no tags are selected, set all decks as visible // If no tags are selected or excluded, show all
if (selectedTags.isEmpty()) { if (selectedTags.isEmpty() && excludedTags.isEmpty()) {
for (DeckPreviewWidget *deckPreview : deckPreviews) { for (DeckPreviewWidget *deckPreview : deckPreviews) {
deckPreview->filteredByTags = false; deckPreview->filteredByTags = false;
} }
return; return;
} }
// Filter DeckPreviewWidgets that contain all of the selected tags
QList<DeckPreviewWidget *> filteredDecks;
for (DeckPreviewWidget *deckPreview : deckPreviews) { for (DeckPreviewWidget *deckPreview : deckPreviews) {
QStringList deckTags = deckPreview->deckLoader->getTags(); QStringList deckTags = deckPreview->deckLoader->getTags();
// Check if all selectedTags are in deckTags bool hasAllSelected = std::all_of(selectedTags.begin(), selectedTags.end(),
bool allTagsPresent = std::all_of(selectedTags.begin(), selectedTags.end(),
[&deckTags](const QString &tag) { return deckTags.contains(tag); }); [&deckTags](const QString &tag) { return deckTags.contains(tag); });
deckPreview->filteredByTags = !allTagsPresent; bool hasAnyExcluded = std::any_of(excludedTags.begin(), excludedTags.end(),
[&deckTags](const QString &tag) { return deckTags.contains(tag); });
// Filter out if any excluded tag is present or if any selected tag is missing
deckPreview->filteredByTags = !(hasAllSelected && !hasAnyExcluded);
} }
} }
@ -72,13 +82,15 @@ void VisualDeckStorageTagFilterWidget::refreshTags()
void VisualDeckStorageTagFilterWidget::removeTagsNotInList(const QSet<QString> &tags) void VisualDeckStorageTagFilterWidget::removeTagsNotInList(const QSet<QString> &tags)
{ {
// Iterate through all DeckPreviewTagDisplayWidgets auto *flowWidget = findChild<FlowWidget *>();
for (DeckPreviewTagDisplayWidget *tagWidget : findChildren<DeckPreviewTagDisplayWidget *>()) { for (DeckPreviewTagDisplayWidget *tagWidget : findChildren<DeckPreviewTagDisplayWidget *>()) {
// If the tag is not in the provided tags list, remove the widget const QString &tagName = tagWidget->getTagName();
if (!tags.contains(tagWidget->getTagName())) {
auto *flowWidget = findChild<FlowWidget *>(); // Keep the tag widget if it is either selected or excluded
if (!tags.contains(tagName) && tagWidget->getState() == TagState::NotSelected) {
flowWidget->removeWidget(tagWidget); flowWidget->removeWidget(tagWidget);
tagWidget->deleteLater(); // Safely delete the widget tagWidget->deleteLater();
} }
} }
} }