summaryrefslogtreecommitdiffstats
path: root/desktop-widgets/command_edit.cpp
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2019-02-07 19:59:34 +0100
committerGravatar Dirk Hohndel <dirk@hohndel.org>2019-04-12 18:19:07 +0300
commit8c89f6fe1520d9d88fb81acab995ba803f5c4ac1 (patch)
treefb081e05e0832e91591696187c8fa148464da4c0 /desktop-widgets/command_edit.cpp
parent1c854d580ae21d6647bcdd5d6db3e57cda9b5f3c (diff)
downloadsubsurface-8c89f6fe1520d9d88fb81acab995ba803f5c4ac1.tar.gz
Undo: implement undo of tag editing
The code follows the other edit-commands, but uses its own base class, because it is distinctly different. Editing the tag field does not simply mean setting the tag for all dives, but rather adding and removing individual tags. This class will be reused for editing of dive buddies and masters. Modify the tag widget thus that it sends an editingFinished() signal when it goes out of focus. The editingFinished() signal was prevented by hooking into the return, enter and tab key-events. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Diffstat (limited to 'desktop-widgets/command_edit.cpp')
-rw-r--r--desktop-widgets/command_edit.cpp125
1 files changed, 125 insertions, 0 deletions
diff --git a/desktop-widgets/command_edit.cpp b/desktop-widgets/command_edit.cpp
index c1e00bdf1..c3a47016e 100644
--- a/desktop-widgets/command_edit.cpp
+++ b/desktop-widgets/command_edit.cpp
@@ -325,4 +325,129 @@ DiveField EditMode::fieldId() const
return DiveField::MODE;
}
+// ***** Tag based commands *****
+EditTagsBase::EditTagsBase(const QVector<dive *> &divesIn, const QStringList &newListIn, struct dive *d):
+ dives(divesIn.toStdVector()),
+ newList(newListIn),
+ oldDive(d)
+{
+}
+
+// Two helper functions: returns true if first list contains any tag or
+// misses any tag of second list.
+static bool containsAny(const QStringList &tags1, const QStringList &tags2)
+{
+ return std::any_of(tags2.begin(), tags2.end(), [&tags1](const QString &tag)
+ { return tags1.contains(tag); });
+}
+
+static bool missesAny(const QStringList &tags1, const QStringList &tags2)
+{
+ return std::any_of(tags2.begin(), tags2.end(), [&tags1](const QString &tag)
+ { return !tags1.contains(tag); });
+}
+
+// This is quite hackish: we can't use virtual functions in the constructor and
+// therefore can't initialize the list of dives [the values of the dives are
+// accessed by virtual functions]. Therefore, we (mis)use the fact that workToBeDone()
+// is called exactly once before adding the Command to the system and perform this here.
+// To be more explicit about this, we might think about renaming workToBeDone() to init().
+bool EditTagsBase::workToBeDone()
+{
+ // changing the tags on multiple dives is semantically strange - what's the right thing to do?
+ // here's what I think... add the tags that were added to the displayed dive and remove the tags
+ // that were removed from it
+
+ // Calculate tags to add and tags to remove
+ QStringList oldList = data(oldDive);
+ for (const QString &s: newList) {
+ if (!oldList.contains(s))
+ tagsToAdd.push_back(s);
+ }
+ for (const QString &s: oldList) {
+ if (!newList.contains(s))
+ tagsToRemove.push_back(s);
+ }
+
+ // Now search for all dives that either
+ // - miss a tag to be added
+ // - have a tag to be removed
+ std::vector<dive *> divesNew;
+ divesNew.reserve(dives.size());
+ for (dive *d: dives) {
+ QStringList tags = data(d);
+ if (missesAny(tags, tagsToAdd) || containsAny(tags, tagsToRemove))
+ divesNew.push_back(d);
+ }
+ dives = std::move(divesNew);
+
+ // Create a text for the menu entry. In the case of multiple dives add the number
+ size_t num_dives = dives.size();
+ if (num_dives > 0)
+ //: remove the part in parantheses for %n = 1
+ setText(tr("Edit %1 (%n dive(s))", "", num_dives).arg(fieldName()));
+
+ return num_dives;
+}
+
+void EditTagsBase::undo()
+{
+ if (dives.empty()) {
+ qWarning("Edit command called with empty dives list (shouldn't happen)");
+ return;
+ }
+
+ for (dive *d: dives) {
+ QStringList tags = data(d);
+ for (const QString &tag: tagsToRemove)
+ tags.removeAll(tag);
+ for (const QString &tag: tagsToAdd) {
+ if (!tags.contains(tag))
+ tags.push_back(tag);
+ }
+ invalidate_dive_cache(d); // Ensure that dive is written in git_save()
+ set(d, tags);
+ }
+
+ std::swap(tagsToAdd, tagsToRemove);
+
+ emit diveListNotifier.divesEdited(QVector<dive *>::fromStdVector(dives), fieldId());
+
+ mark_divelist_changed(true);
+}
+
+// Undo and redo do the same as just the stored value is exchanged
+void EditTagsBase::redo()
+{
+ undo();
+}
+
+// ***** Tags *****
+QStringList EditTags::data(struct dive *d) const
+{
+ QStringList res;
+ for (const struct tag_entry *tag = d->tag_list; tag; tag = tag->next)
+ res.push_back(tag->tag->name);
+ return res;
+}
+
+void EditTags::set(struct dive *d, const QStringList &v) const
+{
+ taglist_free(d->tag_list);
+ d->tag_list = NULL;
+ for (const QString &tag: v)
+ taglist_add_tag(&d->tag_list, qPrintable(tag));
+ taglist_cleanup(&d->tag_list);
+}
+
+QString EditTags::fieldName() const
+{
+ return tr("tags");
+}
+
+DiveField EditTags::fieldId() const
+{
+ return DiveField::TAGS;
+}
+
} // namespace Command