From 4374605c1295ede804d1ae1355094f8a3e0429e9 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 19 Apr 2020 18:48:23 +0200 Subject: undo: make adding of pictures undoable This one is a bit hairy, because two things might happen if the picture has a geo location: - A dive gets a newly generated dive site set. - The dive site of a dive is edited. Therefore the undo command has to store keep track of that. Oh my. Signed-off-by: Berthold Stoeger --- commands/command.cpp | 5 +++ commands/command.h | 1 + commands/command_pictures.cpp | 84 ++++++++++++++++++++++++++++++++++++++++ commands/command_pictures.h | 26 +++++++++++++ core/dive.c | 14 ------- core/dive.h | 2 - desktop-widgets/divelistview.cpp | 19 ++++++--- 7 files changed, 129 insertions(+), 22 deletions(-) diff --git a/commands/command.cpp b/commands/command.cpp index d67009978..7c154ee24 100644 --- a/commands/command.cpp +++ b/commands/command.cpp @@ -372,4 +372,9 @@ void removePictures(const std::vector &pictures) execute(new RemovePictures(pictures)); } +void addPictures(const std::vector &pictures) +{ + execute(new AddPictures(pictures)); +} + } // namespace Command diff --git a/commands/command.h b/commands/command.h index e4c3b05d2..6072ca9c3 100644 --- a/commands/command.h +++ b/commands/command.h @@ -131,6 +131,7 @@ struct PictureListForAddition { }; void setPictureOffset(dive *d, const QString &filename, offset_t offset); void removePictures(const std::vector &pictures); +void addPictures(const std::vector &pictures); } // namespace Command diff --git a/commands/command_pictures.cpp b/commands/command_pictures.cpp index 2e40c0362..66284fc86 100644 --- a/commands/command_pictures.cpp +++ b/commands/command_pictures.cpp @@ -2,6 +2,7 @@ #include "command_pictures.h" #include "core/subsurface-qt/divelistnotifier.h" +#include "qt-models/divelocationmodel.h" namespace Command { @@ -153,4 +154,87 @@ void RemovePictures::redo() picturesToAdd = removePictures(picturesToRemove); } +AddPictures::AddPictures(const std::vector &pictures) : picturesToAdd(pictures) +{ + // Sort the pictures according to the backend-rules. Moreover see if we have to set / create divesites. + size_t count = 0; + for (PictureListForAddition &p: picturesToAdd) { + count += p.pics.size(); + std::sort(p.pics.begin(), p.pics.end()); + + // Find a picture with a location + auto it = std::find_if(p.pics.begin(), p.pics.end(), [](const PictureObj &p) { return has_location(&p.location); }); + if (it != p.pics.end()) { + // There is a dive with a location, we might want to modify the dive accordingly. + struct dive_site *ds = p.d->dive_site; + if (!ds) { + // This dive doesn't yet have a dive site -> add a new dive site. + dive_site *ds = alloc_dive_site_with_gps("", &it->location); + sitesToAdd.emplace_back(ds); + sitesToSet.push_back({ p.d, ds }); + } else if (!dive_site_has_gps_location(ds)) { + // This dive has a dive site, but without coordinates. Let's add them. + sitesToEdit.push_back({ ds, it->location }); + } + } + } + + if (count == 0) { + picturesToAdd.clear(); // This signals that nothing is to be done + return; + } + setText(Command::Base::tr("add %n pictures(s)", "", count)); +} + +bool AddPictures::workToBeDone() +{ + return !picturesToAdd.empty(); +} + +void AddPictures::swapDiveSites() +{ + for (DiveSiteEntry &entry: sitesToSet) { + dive_site *ds = entry.d->dive_site; + if (ds) + unregister_dive_from_dive_site(entry.d); // the dive-site pointer in the dive is now NULL + std::swap(ds, entry.ds); + if (ds) + add_dive_to_dive_site(entry.d, ds); + emit diveListNotifier.divesChanged(QVector{ entry.d }, DiveField::DIVESITE); + } + + for (DiveSiteEditEntry &entry: sitesToEdit) { + std::swap(entry.ds->location, entry.location); + emit diveListNotifier.diveSiteChanged(entry.ds, LocationInformationModel::LOCATION); // Inform frontend of changed dive site. + } +} + +void AddPictures::undo() +{ + swapDiveSites(); + picturesToAdd = removePictures(picturesToRemove); + + // Remove dive sites + for (dive_site *siteToRemove: sitesToRemove) { + int idx = unregister_dive_site(siteToRemove); + sitesToAdd.emplace_back(siteToRemove); + emit diveListNotifier.diveSiteDeleted(siteToRemove, idx); // Inform frontend of removed dive site. + } + sitesToRemove.clear(); +} + +void AddPictures::redo() +{ + // Add dive sites + for (OwningDiveSitePtr &siteToAdd: sitesToAdd) { + sitesToRemove.push_back(siteToAdd.get()); + int idx = register_dive_site(siteToAdd.release()); // Return ownership to backend. + emit diveListNotifier.diveSiteAdded(sitesToRemove.back(), idx); // Inform frontend of new dive site. + } + sitesToAdd.clear(); + + swapDiveSites(); + picturesToRemove = addPictures(picturesToAdd); +} + } // namespace Command diff --git a/commands/command_pictures.h b/commands/command_pictures.h index 802c63c6c..472a92565 100644 --- a/commands/command_pictures.h +++ b/commands/command_pictures.h @@ -35,5 +35,31 @@ private: bool workToBeDone() override; }; +class AddPictures final : public Base { +public: + AddPictures(const std::vector &pictures); +private: + struct DiveSiteEntry { + dive *d; + dive_site *ds; + }; + struct DiveSiteEditEntry { + dive_site *ds; + location_t location; + }; + std::vector picturesToAdd; // for redo + std::vector sitesToAdd; //for redo + std::vector picturesToRemove; // for undo + std::vector sitesToRemove; // for undo + std::vector sitesToSet; // for redo and undo + std::vector sitesToEdit; // for redo and undo + + void swapDiveSites(); + + void undo() override; + void redo() override; + bool workToBeDone() override; +}; + } // namespace Command #endif diff --git a/core/dive.c b/core/dive.c index d5ffc162b..b64b3ff01 100644 --- a/core/dive.c +++ b/core/dive.c @@ -3497,20 +3497,6 @@ void set_git_prefs(const char *prefs) git_prefs.pp_graphs.po2 = 1; } -void dive_set_geodata_from_picture(struct dive *dive, struct picture *picture, struct dive_site_table *table) -{ - struct dive_site *ds = dive->dive_site; - if (!dive_site_has_gps_location(ds) && has_location(&picture->location)) { - if (ds) { - ds->location = picture->location; - } else { - ds = create_dive_site_with_gps("", &picture->location, table); - add_dive_to_dive_site(dive, ds); - invalidate_dive_cache(dive); - } - } -} - /* clones a dive and moves given dive computer to front */ struct dive *make_first_dc(const struct dive *d, int dc_number) { diff --git a/core/dive.h b/core/dive.h index 2113c2a9c..a7bd4c447 100644 --- a/core/dive.h +++ b/core/dive.h @@ -204,8 +204,6 @@ extern enum divemode_t get_current_divemode(const struct divecomputer *dc, int t extern struct event *get_next_divemodechange(const struct event **evd, bool update_pointer); extern enum divemode_t get_divemode_at_time(const struct divecomputer *dc, int dtime, const struct event **ev_dmc); -extern void dive_set_geodata_from_picture(struct dive *dive, struct picture *picture, struct dive_site_table *table); - extern bool has_gaschange_event(const struct dive *dive, const struct divecomputer *dc, int idx); extern int explicit_first_cylinder(const struct dive *dive, const struct divecomputer *dc); extern int get_depth_at_time(const struct divecomputer *dc, unsigned int time); diff --git a/desktop-widgets/divelistview.cpp b/desktop-widgets/divelistview.cpp index fc7d34047..a47c05b67 100644 --- a/desktop-widgets/divelistview.cpp +++ b/desktop-widgets/divelistview.cpp @@ -891,20 +891,27 @@ void DiveListView::matchImagesToDives(QStringList fileNames) return; updateLastImageTimeOffset(shiftDialog.amount()); + // Create the data structure of pictures to be added: a list of pictures per dive. + std::vector pics; for (const QString &fileName: fileNames) { struct dive *d; picture *pic = create_picture(qPrintable(fileName), shiftDialog.amount(), shiftDialog.matchAll(), &d); if (!pic) continue; - add_picture(&d->pictures, *pic); - dive_set_geodata_from_picture(d, pic, &dive_site_table); - invalidate_dive_cache(d); + PictureObj pObj(*pic); free(pic); + + auto it = std::find_if(pics.begin(), pics.end(), [d](const Command::PictureListForAddition &l) { return l.d == d; }); + if (it == pics.end()) + pics.push_back(Command::PictureListForAddition { d, { pObj } }); + else + it->pics.push_back(pObj); } - mark_divelist_changed(true); - copy_dive(current_dive, &displayed_dive); - DivePictureModel::instance()->updateDivePictures(); + if (pics.empty()) + return; + + Command::addPictures(pics); } void DiveListView::loadWebImages() -- cgit v1.2.3-70-g09d2