Filter: replace checked-state by struct

In the future, we might be smarter about the dive-counts and calculate
them only once and incrementally (if e.g. new dives are added).
Prepare for more complex caching by turning the checked boolean into
a struct, which can then be extended by a count and other things
(e.g. the name).

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2018-08-27 11:49:16 +02:00 committed by Dirk Hohndel
parent 80fe8fb331
commit ec586d0e0a
2 changed files with 51 additions and 34 deletions

View file

@ -32,30 +32,41 @@ FilterModelBase::FilterModelBase(QObject *parent) : QStringListModel(parent),
{ {
} }
// Update the stringList and the checkState array. // Update the stringList and the items array.
// The last item is supposed to be the "Show Empty Tags" entry. // The last item is supposed to be the "Show Empty Tags" entry.
void FilterModelBase::updateList(const QStringList &newList) void FilterModelBase::updateList(const QStringList &newList)
{ {
// Keep copy of old checkState array to reimport them later. // Keep copy of the old items array to reimport the checked state later.
std::vector<char> oldCheckState = checkState; // Note that by using std::move(), this is an essentially free operation:
checkState.resize(newList.count()); // The data is moved from the old array to the new one and the old array
std::fill(checkState.begin(), checkState.end(), false); // is reset to zero size.
anyChecked = false; std::vector<Item> oldItems = std::move(items);
// Ignore last item, since this is the "Show Empty Tags" entry. // Resize the cleared array to the new size. This leaves the checked
for (int i = 0; i < rowCount() - 1; i++) { // flag in an undefined state (since we didn't define a default constructor).
if (oldCheckState[i]) { items.resize(newList.count());
// First, reset all checked states to false
anyChecked = false;
for (Item &item: items)
item.checked = false;
// Then, restore the checked state. Ignore the last item, since
// this is the "Show Empty Tags" entry.
for (int i = 0; i < (int)oldItems.size() - 1; i++) {
if (oldItems[i].checked) {
int ind = newList.indexOf(stringList()[i]); int ind = newList.indexOf(stringList()[i]);
if (ind >= 0 && ind < newList.count() - 1) { if (ind >= 0 && ind < newList.count() - 1) {
checkState[ind] = true; items[ind].checked = true;
anyChecked = true; anyChecked = true;
} }
} }
} }
// On program startup, the old list is empty. // Reset the state of the "Show Empty Tags" entry. But be careful:
if (rowCount() > 0 && oldCheckState.back()) { // on program startup, the old list is empty.
checkState.back() = true; if (!oldItems.empty() && !items.empty() && oldItems.back().checked) {
items.back().checked = true;
anyChecked = true; anyChecked = true;
} }
setStringList(newList); setStringList(newList);
@ -69,10 +80,10 @@ Qt::ItemFlags FilterModelBase::flags(const QModelIndex &index) const
bool FilterModelBase::setData(const QModelIndex &index, const QVariant &value, int role) bool FilterModelBase::setData(const QModelIndex &index, const QVariant &value, int role)
{ {
if (role == Qt::CheckStateRole) { if (role == Qt::CheckStateRole) {
checkState[index.row()] = value.toBool(); items[index.row()].checked = value.toBool();
anyChecked = false; anyChecked = false;
for (int i = 0; i < rowCount(); i++) { for (const Item &item: items) {
if (checkState[i] == true) { if (item.checked) {
anyChecked = true; anyChecked = true;
break; break;
} }
@ -86,7 +97,7 @@ bool FilterModelBase::setData(const QModelIndex &index, const QVariant &value, i
QVariant FilterModelBase::data(const QModelIndex &index, int role) const QVariant FilterModelBase::data(const QModelIndex &index, int role) const
{ {
if (role == Qt::CheckStateRole) { if (role == Qt::CheckStateRole) {
return checkState[index.row()] ? Qt::Checked : Qt::Unchecked; return items[index.row()].checked ? Qt::Checked : Qt::Unchecked;
} else if (role == Qt::DisplayRole) { } else if (role == Qt::DisplayRole) {
QString value = stringList()[index.row()]; QString value = stringList()[index.row()];
int count = countDives((index.row() == rowCount() - 1) ? "" : qPrintable(value)); int count = countDives((index.row() == rowCount() - 1) ? "" : qPrintable(value));
@ -97,23 +108,25 @@ QVariant FilterModelBase::data(const QModelIndex &index, int role) const
void FilterModelBase::clearFilter() void FilterModelBase::clearFilter()
{ {
std::fill(checkState.begin(), checkState.end(), false); for (Item &item: items)
item.checked = false;
anyChecked = false; anyChecked = false;
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0)); emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0));
} }
void FilterModelBase::selectAll() void FilterModelBase::selectAll()
{ {
std::fill(checkState.begin(), checkState.end(), true); for (Item &item: items)
item.checked = true;
anyChecked = true; anyChecked = true;
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0)); emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0));
} }
void FilterModelBase::invertSelection() void FilterModelBase::invertSelection()
{ {
for (char &b : checkState) for (Item &item : items)
b = !b; item.checked = !item.checked;
anyChecked = std::any_of(checkState.begin(), checkState.end(), [](char b) { return !!b; }); anyChecked = std::any_of(items.begin(), items.end(), [](Item &item) { return !!item.checked; });
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0)); emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0));
} }
@ -143,13 +156,13 @@ bool SuitsFilterModel::doFilter(const dive *d) const
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())
return checkState[rowCount() - 1] != negate; return items[rowCount() - 1].checked != negate;
// 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 (items[i].checked && suit == suitList[i])
return !negate; return !negate;
} }
return negate; return negate;
@ -208,7 +221,7 @@ bool TagFilterModel::doFilter(const dive *d) const
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";
return checkState[rowCount() - 1] != negate; return items[rowCount() - 1].checked != negate;
// have at least one tag. // have at least one tag.
QStringList tagList = stringList(); QStringList tagList = stringList();
@ -217,7 +230,7 @@ bool TagFilterModel::doFilter(const dive *d) const
while (head) { while (head) {
QString tagName(head->tag->name); QString tagName(head->tag->name);
int index = tagList.indexOf(tagName); int index = tagList.indexOf(tagName);
if (index >= 0 && checkState[index]) if (index >= 0 && items[index].checked)
return !negate; return !negate;
head = head->next; head = head->next;
} }
@ -249,13 +262,13 @@ bool BuddyFilterModel::doFilter(const dive *d) const
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())
return checkState[rowCount() - 1] != negate; return items[rowCount() - 1].checked != negate;
// 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 (items[i].checked && personsList.contains(buddyList[i], Qt::CaseInsensitive))
return !negate; return !negate;
} }
return negate; return negate;
@ -300,13 +313,13 @@ bool LocationFilterModel::doFilter(const dive *d) const
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())
return checkState[rowCount() - 1] != negate; return items[rowCount() - 1].checked != negate;
// 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 (items[i].checked && location == locationList[i])
return !negate; return !negate;
} }
return negate; return negate;
@ -341,8 +354,8 @@ void LocationFilterModel::changeName(const QString &oldName, const QString &newN
// If there was already an entry with the new name, we are merging entries. // If there was already an entry with the new name, we are merging entries.
// Thus, if the old entry was selected, also select the new entry. // Thus, if the old entry was selected, also select the new entry.
if (newIndex >= 0 && checkState[oldIndex]) if (newIndex >= 0 && items[oldIndex].checked)
checkState[newIndex] = true; items[newIndex].checked = true;
setStringList(list); setStringList(list);
} }
@ -356,7 +369,7 @@ void LocationFilterModel::addName(const QString &newName)
if (!anyChecked || newName.isEmpty() || list.indexOf(newName) >= 0) if (!anyChecked || newName.isEmpty() || list.indexOf(newName) >= 0)
return; return;
list.prepend(newName); list.prepend(newName);
checkState.insert(checkState.begin(), true); items.insert(items.begin(), { true });
setStringList(list); setStringList(list);
} }

View file

@ -11,12 +11,16 @@ struct dive;
class FilterModelBase : public QStringListModel { class FilterModelBase : public QStringListModel {
Q_OBJECT Q_OBJECT
protected:
struct Item {
bool checked;
};
std::vector<Item> items;
public: public:
virtual bool doFilter(const dive *d) const = 0; virtual bool doFilter(const dive *d) const = 0;
void clearFilter(); void clearFilter();
void selectAll(); void selectAll();
void invertSelection(); void invertSelection();
std::vector<char> checkState;
bool anyChecked; bool anyChecked;
bool negate; bool negate;
public public