Introduce negate-toggle buttons to filter lists

Introduce toggle buttons which mean "filter all dives except
those fulfilling the selected criteria".

The old code used to check for rowCount() == 0. This should never happen,
because there is always a row "empty field". This check was moved into
the preamble of the functions to seperate it from the actual logic.

Fixes #435

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2017-12-24 14:35:59 +01:00 committed by Dirk Hohndel
parent b86c70ab2c
commit b6bf57a13b
4 changed files with 55 additions and 41 deletions

View file

@ -51,6 +51,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QToolButton" name="notButton">
<property name="text">
<string>¬</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>

View file

@ -516,6 +516,7 @@ FilterBase::FilterBase(FilterModelBase *model_, QWidget *parent) : QWidget(paren
filter->setSourceModel(model); filter->setSourceModel(model);
filter->setFilterCaseSensitivity(Qt::CaseInsensitive); filter->setFilterCaseSensitivity(Qt::CaseInsensitive);
connect(ui.filterInternalList, SIGNAL(textChanged(QString)), filter, SLOT(setFilterFixedString(QString))); connect(ui.filterInternalList, SIGNAL(textChanged(QString)), filter, SLOT(setFilterFixedString(QString)));
connect(ui.notButton, &QToolButton::toggled, model, &FilterModelBase::setNegate);
ui.filterList->setModel(filter); ui.filterList->setModel(filter);
addContextMenuEntry(tr("Select All"), &FilterModelBase::selectAll); addContextMenuEntry(tr("Select All"), &FilterModelBase::selectAll);

View file

@ -26,7 +26,8 @@ CREATE_INSTANCE_METHOD(SuitsFilterModel)
CREATE_INSTANCE_METHOD(MultiFilterSortModel) CREATE_INSTANCE_METHOD(MultiFilterSortModel)
FilterModelBase::FilterModelBase(QObject *parent) : QStringListModel(parent), FilterModelBase::FilterModelBase(QObject *parent) : QStringListModel(parent),
anyChecked(false) anyChecked(false),
negate(false)
{ {
} }
@ -115,6 +116,12 @@ void FilterModelBase::invertSelection()
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0)); emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0));
} }
void FilterModelBase::setNegate(bool negateParam)
{
negate = negateParam;
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0));
}
SuitsFilterModel::SuitsFilterModel(QObject *parent) : FilterModelBase(parent) SuitsFilterModel::SuitsFilterModel(QObject *parent) : FilterModelBase(parent)
{ {
} }
@ -129,28 +136,25 @@ bool SuitsFilterModel::doFilter(dive *d, QModelIndex &index0, QAbstractItemModel
Q_UNUSED(index0); Q_UNUSED(index0);
Q_UNUSED(sourceModel); Q_UNUSED(sourceModel);
if (!anyChecked) { // rowCount() == 0 should never happen, because we have the "no suits" row
// let's handle it gracefully anyway.
if (!anyChecked || rowCount() == 0)
return true; return true;
}
// Checked means 'Show', Unchecked means 'Hide'. // Checked means 'Show', Unchecked means 'Hide'.
QString suit(d->suit); QString suit(d->suit);
// only show empty suit dives if the user checked that. // only show empty suit dives if the user checked that.
if (suit.isEmpty()) { if (suit.isEmpty())
if (rowCount() > 0) return checkState[rowCount() - 1] != negate;
return checkState[rowCount() - 1];
else
return true;
}
// there is a suit selected // there is a suit selected
QStringList suitList = stringList(); QStringList suitList = stringList();
// Ignore last item, since this is the "Show Empty Tags" entry // Ignore last item, since this is the "Show Empty Tags" entry
for (int i = 0; i < rowCount() - 1; i++) { for (int i = 0; i < rowCount() - 1; i++) {
if (checkState[i] && suit == suitList[i]) if (checkState[i] && suit == suitList[i])
return true; return !negate;
} }
return false; return negate;
} }
void SuitsFilterModel::repopulate() void SuitsFilterModel::repopulate()
@ -200,18 +204,16 @@ bool TagFilterModel::doFilter(dive *d, QModelIndex &index0, QAbstractItemModel *
Q_UNUSED(sourceModel); Q_UNUSED(sourceModel);
// If there's nothing checked, this should show everything // If there's nothing checked, this should show everything
if (!anyChecked) { // rowCount() == 0 should never happen, because we have the "no tags" row
// let's handle it gracefully anyway.
if (!anyChecked || rowCount() == 0)
return true; return true;
}
// Checked means 'Show', Unchecked means 'Hide'. // Checked means 'Show', Unchecked means 'Hide'.
struct tag_entry *head = d->tag_list; struct tag_entry *head = d->tag_list;
if (!head) { // last tag means "Show empty tags"; if (!head) // last tag means "Show empty tags";
if (rowCount() > 0) return checkState[rowCount() - 1] != negate;
return checkState[rowCount() - 1];
else
return true;
}
// have at least one tag. // have at least one tag.
QStringList tagList = stringList(); QStringList tagList = stringList();
@ -221,11 +223,11 @@ bool TagFilterModel::doFilter(dive *d, QModelIndex &index0, QAbstractItemModel *
QString tagName(head->tag->name); QString tagName(head->tag->name);
int index = tagList.indexOf(tagName); int index = tagList.indexOf(tagName);
if (checkState[index]) if (checkState[index])
return true; return !negate;
head = head->next; head = head->next;
} }
} }
return false; return negate;
} }
BuddyFilterModel::BuddyFilterModel(QObject *parent) : FilterModelBase(parent) BuddyFilterModel::BuddyFilterModel(QObject *parent) : FilterModelBase(parent)
@ -243,30 +245,28 @@ bool BuddyFilterModel::doFilter(dive *d, QModelIndex &index0, QAbstractItemModel
Q_UNUSED(sourceModel); Q_UNUSED(sourceModel);
// If there's nothing checked, this should show everything // If there's nothing checked, this should show everything
if (!anyChecked) { // rowCount() == 0 should never happen, because we have the "no tags" row
// let's handle it gracefully anyway.
if (!anyChecked || rowCount() == 0)
return true; return true;
}
// Checked means 'Show', Unchecked means 'Hide'. // Checked means 'Show', Unchecked means 'Hide'.
QString persons = QString(d->buddy) + "," + QString(d->divemaster); QString persons = QString(d->buddy) + "," + QString(d->divemaster);
QStringList personsList = persons.split(',', QString::SkipEmptyParts); QStringList personsList = persons.split(',', QString::SkipEmptyParts);
for (QString &s : personsList) for (QString &s : personsList)
s = s.trimmed(); s = s.trimmed();
// only show empty buddie dives if the user checked that. // only show empty buddie dives if the user checked that.
if (personsList.isEmpty()) { if (personsList.isEmpty())
if (rowCount() > 0) return checkState[rowCount() - 1] != negate;
return checkState[rowCount() - 1];
else
return true;
}
// have at least one buddy // have at least one buddy
QStringList buddyList = stringList(); QStringList buddyList = stringList();
// Ignore last item, since this is the "Show Empty Tags" entry // Ignore last item, since this is the "Show Empty Tags" entry
for (int i = 0; i < rowCount() - 1; i++) { for (int i = 0; i < rowCount() - 1; i++) {
if (checkState[i] && personsList.contains(buddyList[i], Qt::CaseInsensitive)) if (checkState[i] && personsList.contains(buddyList[i], Qt::CaseInsensitive))
return true; return !negate;
} }
return false; return negate;
} }
void BuddyFilterModel::repopulate() void BuddyFilterModel::repopulate()
@ -302,27 +302,25 @@ bool LocationFilterModel::doFilter(struct dive *d, QModelIndex &index0, QAbstrac
Q_UNUSED(index0); Q_UNUSED(index0);
Q_UNUSED(sourceModel); Q_UNUSED(sourceModel);
if (!anyChecked) { // rowCount() == 0 should never happen, because we have the "no location" row
// let's handle it gracefully anyway.
if (!anyChecked || rowCount() == 0)
return true; return true;
}
// Checked means 'Show', Unchecked means 'Hide'. // Checked means 'Show', Unchecked means 'Hide'.
QString location(get_dive_location(d)); QString location(get_dive_location(d));
// only show empty location dives if the user checked that. // only show empty location dives if the user checked that.
if (location.isEmpty()) { if (location.isEmpty())
if (rowCount() > 0) return checkState[rowCount() - 1] != negate;
return checkState[rowCount() - 1];
else
return true;
}
// There is a location selected // There is a location selected
QStringList locationList = stringList(); QStringList locationList = stringList();
// Ignore last item, since this is the "Show Empty Tags" entry // Ignore last item, since this is the "Show Empty Tags" entry
for (int i = 0; i < rowCount() - 1; i++) { for (int i = 0; i < rowCount() - 1; i++) {
if (checkState[i] && location == locationList[i]) if (checkState[i] && location == locationList[i])
return true; return !negate;
} }
return false; return negate;
} }
void LocationFilterModel::repopulate() void LocationFilterModel::repopulate()

View file

@ -8,6 +8,7 @@
#include <vector> #include <vector>
class FilterModelBase : public QStringListModel { class FilterModelBase : public QStringListModel {
Q_OBJECT
public: public:
virtual bool doFilter(struct dive *d, QModelIndex &index0, QAbstractItemModel *sourceModel) const = 0; virtual bool doFilter(struct dive *d, QModelIndex &index0, QAbstractItemModel *sourceModel) const = 0;
void clearFilter(); void clearFilter();
@ -15,6 +16,10 @@ public:
void invertSelection(); void invertSelection();
std::vector<char> checkState; std::vector<char> checkState;
bool anyChecked; bool anyChecked;
bool negate;
public
slots:
void setNegate(bool negate);
protected: protected:
explicit FilterModelBase(QObject *parent = 0); explicit FilterModelBase(QObject *parent = 0);
void updateList(const QStringList &new_list); void updateList(const QStringList &new_list);