File indexing completed on 2025-01-19 03:53:35

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2010-10-23
0007  * Description : Graph data class for image history
0008  *
0009  * SPDX-FileCopyrightText: 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "itemhistorygraph.h"
0016 
0017 // Local includes
0018 
0019 #include "digikam_debug.h"
0020 #include "dimagehistory.h"
0021 #include "itemscanner.h"
0022 #include "itemhistorygraphdata.h"
0023 
0024 namespace Digikam
0025 {
0026 
0027 class Q_DECL_HIDDEN ItemHistoryGraphDataSharedNull : public QSharedDataPointer<ItemHistoryGraphData>
0028 {
0029 public:
0030 
0031     ItemHistoryGraphDataSharedNull()
0032         : QSharedDataPointer<ItemHistoryGraphData>(new ItemHistoryGraphData)
0033     {
0034     }
0035 };
0036 
0037 Q_GLOBAL_STATIC(ItemHistoryGraphDataSharedNull, imageHistoryGraphDataSharedNull)
0038 
0039 // -----------------------------------------------------------------------------------------------
0040 
0041 ItemInfo HistoryVertexProperties::firstItemInfo() const
0042 {
0043     if (infos.isEmpty())
0044     {
0045         return ItemInfo();
0046     }
0047 
0048     return infos.first();
0049 }
0050 
0051 bool HistoryVertexProperties::markedAs(HistoryImageId::Type type) const
0052 {
0053     if (referredImages.isEmpty())
0054     {
0055         return false;
0056     }
0057 
0058     Q_FOREACH (const HistoryImageId& ref, referredImages)
0059     {
0060         if (ref.m_type == type)
0061         {   // cppcheck-suppress useStlAlgorithm
0062             return true;
0063         }
0064     }
0065 
0066     return false;
0067 }
0068 
0069 bool HistoryVertexProperties::alwaysMarkedAs(HistoryImageId::Type type) const
0070 {
0071     if (referredImages.isEmpty())
0072     {
0073         return false;
0074     }
0075 
0076     Q_FOREACH (const HistoryImageId& ref, referredImages)
0077     {
0078         if (ref.m_type != type)
0079         {   // cppcheck-suppress useStlAlgorithm
0080             return false;
0081         }
0082     }
0083 
0084     return true;
0085 }
0086 
0087 bool HistoryVertexProperties::operator==(const QString& id) const
0088 {
0089     return uuid == id;
0090 }
0091 
0092 bool HistoryVertexProperties::operator==(const ItemInfo& info) const
0093 {
0094     return infos.contains(info);
0095 }
0096 
0097 bool HistoryVertexProperties::operator==(qlonglong id) const
0098 {
0099     Q_FOREACH (const ItemInfo& info, infos)
0100     {
0101         if (info.id() == id)
0102         {   // cppcheck-suppress useStlAlgorithm
0103             return true;
0104         }
0105     }
0106 
0107     return false;
0108 }
0109 
0110 bool HistoryVertexProperties::operator==(const HistoryImageId& other) const
0111 {
0112     if (!uuid.isEmpty() && !other.m_uuid.isEmpty())
0113     {
0114         return (uuid == other.m_uuid);
0115     }
0116 
0117     Q_FOREACH (const HistoryImageId& id, referredImages)
0118     {
0119         if (ItemScanner::sameReferredImage(id, other))
0120         {   // cppcheck-suppress useStlAlgorithm
0121 /*
0122             qCDebug(DIGIKAM_DATABASE_LOG) << id << "is the same as" << other;
0123 */
0124             return true;
0125         }
0126     }
0127 
0128     return false;
0129 }
0130 
0131 HistoryVertexProperties& HistoryVertexProperties::operator+=(const QString& id)
0132 {
0133     if (!uuid.isNull() && id.isNull())
0134     {
0135         uuid = id;
0136     }
0137 
0138     return *this;
0139 }
0140 
0141 HistoryVertexProperties& HistoryVertexProperties::operator+=(const ItemInfo& info)
0142 {
0143     if (!info.isNull() && !infos.contains(info))
0144     {
0145         infos << info;
0146 
0147         if (uuid.isNull())
0148         {
0149             uuid = info.uuid();
0150         }
0151     }
0152 
0153     return *this;
0154 }
0155 
0156 HistoryVertexProperties& HistoryVertexProperties::operator+=(const HistoryImageId& id)
0157 {
0158     if (id.isValid() && !referredImages.contains(id))
0159     {
0160         referredImages << id;
0161 
0162         if (uuid.isNull() && !id.m_uuid.isEmpty())
0163         {
0164             uuid = id.m_uuid;
0165         }
0166     }
0167 
0168     return *this;
0169 }
0170 
0171 QDebug operator<<(QDebug dbg, const HistoryVertexProperties& props)
0172 {
0173     Q_FOREACH (const ItemInfo& info, props.infos)
0174     {
0175         dbg.space() << info.id();
0176     }
0177 
0178     return dbg;
0179 }
0180 
0181 QDebug operator<<(QDebug dbg, const HistoryImageId& id)
0182 {
0183     dbg.nospace() << " { ";
0184     dbg.nospace() << id.m_uuid;
0185     dbg.space()   << id.m_type;
0186     dbg.space()   << id.m_fileName;
0187     dbg.space()   << id.m_filePath;
0188     dbg.space()   << id.m_creationDate;
0189     dbg.space()   << id.m_uniqueHash;
0190     dbg.space()   << id.m_fileSize;
0191     dbg.space()   << id.m_originalUUID;
0192     dbg.nospace() << " } ";
0193 
0194     return dbg;
0195 }
0196 
0197 // -----------------------------------------------------------------------------------------------
0198 
0199 HistoryEdgeProperties& HistoryEdgeProperties::operator+=(const FilterAction& action)
0200 {
0201     actions << action;
0202 
0203     return *this;
0204 }
0205 
0206 // -----------------------------------------------------------------------------------------------
0207 
0208 HistoryGraph::Vertex ItemHistoryGraphData::addVertex(const QList<HistoryImageId>& imageIds)
0209 {
0210     if (imageIds.isEmpty())
0211     {
0212         return Vertex();
0213     }
0214 
0215     Vertex v = addVertex(imageIds.first());
0216 
0217     if (imageIds.size() > 1)
0218     {
0219         applyProperties(v, QList<ItemInfo>(), imageIds);
0220     }
0221 
0222     return v;
0223 }
0224 
0225 HistoryGraph::Vertex ItemHistoryGraphData::addVertex(const HistoryImageId& imageId)
0226 {
0227     if (!imageId.isValid())
0228     {
0229         return Vertex();
0230     }
0231 
0232     Vertex v;
0233     QList<ItemInfo> infos;
0234 /*
0235     qCDebug(DIGIKAM_DATABASE_LOG) << "Adding vertex" << imageId.m_uuid.left(6) << imageId.fileName();
0236 */
0237     // find by HistoryImageId (most notably, by UUID)
0238 
0239     v = findVertexByProperties(imageId);
0240 /*
0241     qCDebug(DIGIKAM_DATABASE_LOG) << "Found by properties:" << (v.isNull() ? -1 : int(v));
0242 */
0243     if (v.isNull())
0244     {
0245         // Resolve HistoryImageId, find by ItemInfo
0246 
0247         Q_FOREACH (const qlonglong& id, ItemScanner::resolveHistoryImageId(imageId))
0248         {
0249             ItemInfo info(id);
0250 /*
0251             qCDebug(DIGIKAM_DATABASE_LOG) << "Found info id:" << info.id();
0252 */
0253             infos << info;
0254 
0255             v = findVertexByProperties(info);
0256         }
0257     }
0258 
0259     applyProperties(v, infos, QList<HistoryImageId>() << imageId);
0260 /*
0261     qCDebug(DIGIKAM_DATABASE_LOG) << "Returning vertex" << v;
0262 */
0263     return v;
0264 }
0265 
0266 HistoryGraph::Vertex ItemHistoryGraphData::addVertex(qlonglong id)
0267 {
0268     return addVertex(ItemInfo(id));
0269 }
0270 
0271 HistoryGraph::Vertex ItemHistoryGraphData::addVertex(const ItemInfo& info)
0272 {
0273     Vertex         v;
0274     QString        uuid;
0275     HistoryImageId id;
0276 
0277     // Simply find by image id
0278 
0279     v = findVertexByProperties(info);
0280 /*
0281     qCDebug(DIGIKAM_DATABASE_LOG) << "Find by id" << info.id() << ": found" << v.isNull();
0282 */
0283     if (v.isNull())
0284     {
0285         // Find by contents
0286 
0287         uuid = info.uuid();
0288 
0289         if (!uuid.isNull())
0290         {
0291             v = findVertexByProperties(uuid);
0292         }
0293 /*
0294         qCDebug(DIGIKAM_DATABASE_LOG) << "Find by uuid" << uuid << ": found" << v.isNull();
0295 */
0296         if (v.isNull())
0297         {
0298             id = info.historyImageId();
0299             v  = findVertexByProperties(id);
0300 /*
0301             qCDebug(DIGIKAM_DATABASE_LOG) << "Find by h-i-m" << ": found" << v.isNull();
0302 */
0303         }
0304 
0305         // Need to add new vertex. Do this through the method which will resolve the history id
0306 
0307         if (v.isNull())
0308         {
0309             v = addVertex(id);
0310         }
0311     }
0312 
0313     applyProperties(v, QList<ItemInfo>() << info, QList<HistoryImageId>() << id);
0314 /*
0315     qCDebug(DIGIKAM_DATABASE_LOG) << "Returning vertex" << v << properties(v).infos.size();
0316 */
0317     return v;
0318 }
0319 
0320 HistoryGraph::Vertex ItemHistoryGraphData::addVertexScanned(qlonglong id)
0321 {
0322     // short version where we do not read information about id from an ItemInfo
0323 
0324     Vertex v = findVertexByProperties(id);
0325 
0326     applyProperties(v, QList<ItemInfo>() << ItemInfo(id), QList<HistoryImageId>());
0327 
0328     return v;
0329 }
0330 
0331 void ItemHistoryGraphData::applyProperties(Vertex& v,
0332                                            const QList<ItemInfo>& infos,
0333                                            const QList<HistoryImageId>& ids)
0334 {
0335     // if needed, add a new vertex; or retrieve properties to add possibly new entries
0336 
0337     if (v.isNull())
0338     {
0339         v = HistoryGraph::addVertex();
0340     }
0341 
0342     HistoryVertexProperties& props = properties(v);
0343 
0344     // adjust properties
0345 
0346     Q_FOREACH (const ItemInfo& info, infos)
0347     {
0348         // cppcheck-suppress useStlAlgorithm
0349         props += info;
0350     }
0351 
0352     Q_FOREACH (const HistoryImageId& id, ids)
0353     {
0354         // cppcheck-suppress useStlAlgorithm
0355         props += id;
0356     }
0357 }
0358 
0359 int ItemHistoryGraphData::removeNextUnresolvedVertex(int index)
0360 {
0361     QList<Vertex> vs = vertices();       // clazy:exclude=missing-typeinfo
0362 
0363     for ( ; index < vs.size() ; ++index)
0364     {
0365         const Vertex& v                      = vs[index];
0366         const HistoryVertexProperties& props = properties(v);
0367 
0368         if (props.infos.isEmpty())
0369         {
0370             Q_FOREACH (const HistoryGraph::Edge& upperEdge, edges(v, HistoryGraph::EdgesToRoot))
0371             {
0372                 Q_FOREACH (const HistoryGraph::Edge& lowerEdge, edges(v, HistoryGraph::EdgesToLeaf))
0373                 {
0374                     HistoryEdgeProperties combinedProps;
0375                     combinedProps.actions        += properties(upperEdge).actions;
0376                     combinedProps.actions        += properties(lowerEdge).actions;
0377                     HistoryGraph::Edge connection = addEdge(source(lowerEdge), target(upperEdge));
0378                     setProperties(connection, combinedProps);
0379                 }
0380             }
0381 
0382             remove(v);
0383 
0384             return index;
0385         }
0386     }
0387 
0388     return index;
0389 }
0390 
0391 QHash<HistoryGraph::Vertex, HistoryImageId::Types> ItemHistoryGraphData::categorize() const
0392 {
0393     QHash<Vertex, HistoryImageId::Types> types;
0394 
0395     Q_FOREACH (const Vertex& v, vertices())
0396     {
0397         const HistoryVertexProperties& props = properties(v);
0398 
0399         HistoryImageId::Types type;
0400 
0401         if      (props.alwaysMarkedAs(HistoryImageId::Source))
0402         {
0403             type |= HistoryImageId::Source;
0404         }
0405         else if (isLeaf(v))
0406         {
0407             // Leaf: Assume current version
0408 
0409             type |= HistoryImageId::Current;
0410         }
0411         else if (isRoot(v))
0412         {
0413             // Root: Assume original if at least once marked as such
0414 
0415             if (props.markedAs(HistoryImageId::Original))
0416             {
0417                 type |= HistoryImageId::Original;
0418             }
0419         }
0420         else
0421         {
0422             type = HistoryImageId::Intermediate;
0423         }
0424 
0425         /*
0426          * There is one situation which cannot be deduced from the graph structure:
0427          * When there is a derived image, but the parent image shall be kept as visible "Current".
0428          * In this case, the ExplicitBranch flag can be set on the next action.
0429          */
0430         if (!(type & HistoryImageId::Current) && hasEdges(v, EdgesToLeaf))
0431         {
0432             // We check if all immediate actions set the ExplicitBranch flag
0433 
0434             bool allBranches = true;
0435 
0436             Q_FOREACH (const Edge& e, edges(v, EdgesToLeaf))
0437             {
0438                 const HistoryEdgeProperties& props2 = properties(e);
0439 
0440                 if (props2.actions.isEmpty())
0441                 {
0442                     continue; // unclear situation, ignore
0443                 }
0444 
0445                 if (!(props2.actions.first().flags() & FilterAction::ExplicitBranch))
0446                 {
0447                     allBranches = false;
0448                     break;
0449                 }
0450             }
0451 
0452             if (allBranches)
0453             {
0454                 type |= HistoryImageId::Current;
0455             }
0456         }
0457 
0458         types[v] = type;
0459     }
0460 
0461     return types;
0462 }
0463 
0464 // -----------------------------------------------------------------------------------------------
0465 
0466 ItemHistoryGraph::ItemHistoryGraph()
0467     : d(*imageHistoryGraphDataSharedNull)
0468 {
0469 }
0470 
0471 ItemHistoryGraph::ItemHistoryGraph(const ItemHistoryGraph& other)
0472     : d(other.d)
0473 {
0474 }
0475 
0476 ItemHistoryGraph::~ItemHistoryGraph()
0477 {
0478 }
0479 
0480 ItemHistoryGraph& ItemHistoryGraph::operator=(const ItemHistoryGraph& other)
0481 {
0482     d = other.d;
0483 
0484     return *this;
0485 }
0486 
0487 bool ItemHistoryGraph::isNull() const
0488 {
0489     return (d == *imageHistoryGraphDataSharedNull);
0490 }
0491 
0492 bool ItemHistoryGraph::isEmpty() const
0493 {
0494     return d->isEmpty();
0495 }
0496 
0497 bool ItemHistoryGraph::isSingleVertex() const
0498 {
0499     return (d->vertexCount() == 1);
0500 }
0501 
0502 bool ItemHistoryGraph::hasEdges() const
0503 {
0504     return d->hasEdges();
0505 }
0506 
0507 ItemHistoryGraphData& ItemHistoryGraph::data()
0508 {
0509     return *d;
0510 }
0511 
0512 const ItemHistoryGraphData& ItemHistoryGraph::data() const
0513 {
0514     return *d;
0515 }
0516 
0517 void ItemHistoryGraph::clear()
0518 {
0519     *d = HistoryGraph();
0520 }
0521 
0522 ItemHistoryGraph ItemHistoryGraph::fromInfo(const ItemInfo& info,
0523                                             HistoryLoadingMode loadingMode,
0524                                             ProcessingMode processingMode)
0525 {
0526     ItemHistoryGraph graph;
0527 
0528     if (loadingMode & LoadRelationCloud)
0529     {
0530         graph.addRelations(info.relationCloud());
0531     }
0532 
0533     if (loadingMode & LoadSubjectHistory)
0534     {
0535         graph.addHistory(info.imageHistory(), info);
0536     }
0537 
0538     if (loadingMode & LoadLeavesHistory)
0539     {
0540         Q_FOREACH (const ItemInfo& leaf, graph.leafImages())
0541         {
0542             if (leaf != info)
0543             {
0544                 graph.addHistory(leaf.imageHistory(), leaf);
0545             }
0546         }
0547     }
0548 
0549     if (processingMode == PrepareForDisplay)
0550     {
0551         graph.prepareForDisplay(info);
0552     }
0553 
0554     return graph;
0555 }
0556 
0557 void ItemHistoryGraph::addHistory(const DImageHistory& givenHistory, const ItemInfo& historySubject)
0558 {
0559     addHistory(givenHistory, historySubject.historyImageId());
0560 }
0561 
0562 void ItemHistoryGraph::addHistory(const DImageHistory& givenHistory, const HistoryImageId& subjectId)
0563 {
0564     // append the subject to its history
0565 
0566     DImageHistory history = givenHistory;
0567 
0568     if (subjectId.isValid())
0569     {
0570         history << subjectId;
0571     }
0572 
0573     d->addHistory(history);
0574 }
0575 
0576 void ItemHistoryGraph::addScannedHistory(const DImageHistory& history, qlonglong historySubjectId)
0577 {
0578     d->addHistory(history, historySubjectId);
0579 }
0580 
0581 void ItemHistoryGraphData::addHistory(const DImageHistory& history, qlonglong extraCurrent)
0582 {
0583     if (history.isEmpty())
0584     {
0585         return;
0586     }
0587 
0588     HistoryGraph::Vertex  last;
0589     HistoryEdgeProperties edgeProps;
0590 
0591     Q_FOREACH (const DImageHistory::Entry& entry, history.entries())
0592     {
0593         if (!last.isNull())
0594         {
0595             edgeProps += entry.action;
0596         }
0597 
0598         HistoryGraph::Vertex v;
0599 
0600         if (!entry.referredImages.isEmpty())
0601         {
0602             v = addVertex(entry.referredImages);
0603         }
0604 
0605         if (!v.isNull())
0606         {
0607             if (!last.isNull())
0608             {
0609                 if (v != last)
0610                 {
0611                     HistoryGraph::Edge e = addEdge(v, last);
0612                     setProperties(e, edgeProps);
0613                     edgeProps            = HistoryEdgeProperties();
0614                 }
0615                 else
0616                 {
0617                     qCWarning(DIGIKAM_DATABASE_LOG) << "Broken history: Same file referred by different entries. Refusing to add a loop.";
0618                 }
0619             }
0620 
0621             last = v;
0622         }
0623     }
0624 
0625     if (extraCurrent)
0626     {
0627         HistoryGraph::Vertex v = addVertexScanned(extraCurrent);
0628 
0629         if (!v.isNull() && !last.isNull() && (v != last))
0630         {
0631             HistoryGraph::Edge e = addEdge(v, last);
0632             setProperties(e, edgeProps);
0633         }
0634     }
0635 }
0636 
0637 void ItemHistoryGraph::addRelations(const QList<QPair<qlonglong, qlonglong> >& pairs)
0638 {
0639     HistoryGraph::Vertex v1, v2;
0640     typedef QPair<qlonglong, qlonglong> IdPair;
0641 
0642     Q_FOREACH (const IdPair& pair, pairs)
0643     {
0644         if ((pair.first < 1) || (pair.second < 1))
0645         {
0646             continue;
0647         }
0648 
0649         if (pair.first == pair.second)
0650         {
0651             qCWarning(DIGIKAM_DATABASE_LOG) << "Broken relations cloud: Refusing to add a loop.";
0652         }
0653 
0654         v1 = d->addVertex(pair.first);
0655         v2 = d->addVertex(pair.second);
0656 /*
0657         qCDebug(DIGIKAM_DATABASE_LOG) << "Adding" << v1 << "->" << v2;
0658 */
0659         d->addEdge(v1, v2);
0660     }
0661 }
0662 
0663 void ItemHistoryGraph::reduceEdges()
0664 {
0665     if (d->vertexCount() <= 1)
0666     {
0667         return;
0668     }
0669 
0670     QList<HistoryGraph::Edge> removedEgdes;
0671     HistoryGraph reduction = d->transitiveReduction(&removedEgdes);
0672 
0673     if (reduction.isEmpty())
0674     {
0675         return;    // reduction failed, not a DAG
0676     }
0677 
0678     Q_FOREACH (const HistoryGraph::Edge& e, removedEgdes)
0679     {
0680         if (!d->properties(e).actions.isEmpty())
0681         {
0682             // TODO: conflict resolution
0683 
0684             qCDebug(DIGIKAM_DATABASE_LOG) << "Conflicting history information: Edge removed by transitiveReduction is not empty.";
0685         }
0686     }
0687 
0688     *d = reduction;
0689 }
0690 
0691 bool ItemHistoryGraph::hasUnresolvedEntries() const
0692 {
0693     Q_FOREACH (const HistoryGraph::Vertex& v, d->vertices())
0694     {
0695         if (d->properties(v).infos.isEmpty())
0696         {
0697             return true;
0698         }
0699     }
0700 
0701     return false;
0702 }
0703 
0704 void ItemHistoryGraph::dropUnresolvedEntries()
0705 {
0706     // Remove nodes which could not be resolved into image infos
0707 
0708     // The problem is that with each removable, the vertex list is invalidated,
0709     // so we cannot do one loop over d->vertices
0710 
0711     for (int i = 0 ; i < d->vertexCount() ; )
0712     {
0713         i = d->removeNextUnresolvedVertex(i);
0714     }
0715 }
0716 
0717 void ItemHistoryGraph::sortForInfo(const ItemInfo& subject)
0718 {
0719     // Remove nodes which could not be resolved into image infos
0720 
0721     QList<HistoryGraph::Vertex> toRemove;   // clazy:exclude=missing-typeinfo
0722 
0723     Q_FOREACH (const HistoryGraph::Vertex& v, d->vertices())
0724     {
0725         HistoryVertexProperties& props = d->properties(v);
0726         ItemScanner::sortByProximity(props.infos, subject);
0727     }
0728 }
0729 
0730 void ItemHistoryGraph::prepareForDisplay(const ItemInfo& subject)
0731 {
0732     reduceEdges();
0733     dropUnresolvedEntries();
0734     sortForInfo(subject);
0735 }
0736 
0737 QList<QPair<qlonglong, qlonglong> > ItemHistoryGraph::relationCloud() const
0738 {
0739     QList<QPair<qlonglong, qlonglong> > pairs;
0740     ItemHistoryGraphData closure          = ItemHistoryGraphData(d->transitiveClosure());
0741     QList<HistoryGraph::VertexPair> edges = closure.edgePairs();
0742 
0743     Q_FOREACH (const HistoryGraph::VertexPair& edge, edges)
0744     {
0745         Q_FOREACH (const ItemInfo& source, closure.properties(edge.first).infos)
0746         {
0747             Q_FOREACH (const ItemInfo& target, closure.properties(edge.second).infos)
0748             {
0749                 pairs << QPair<qlonglong, qlonglong>(source.id(), target.id());
0750             }
0751         }
0752     }
0753 
0754     return pairs;
0755 }
0756 
0757 QPair<QList<qlonglong>, QList<qlonglong> > ItemHistoryGraph::relationCloudParallel() const
0758 {
0759     QList<qlonglong> subjects, objects;
0760     ItemHistoryGraphData closure          = ItemHistoryGraphData(d->transitiveClosure());
0761     QList<HistoryGraph::VertexPair> edges = closure.edgePairs();
0762 
0763     Q_FOREACH (const HistoryGraph::VertexPair& edge, edges)
0764     {
0765         Q_FOREACH (const ItemInfo& source, closure.properties(edge.first).infos)
0766         {
0767             Q_FOREACH (const ItemInfo& target, closure.properties(edge.second).infos)
0768             {
0769                 subjects << source.id();
0770                 objects  << target.id();
0771             }
0772         }
0773     }
0774 
0775     return qMakePair(subjects, objects);
0776 }
0777 
0778 QList<ItemInfo> ItemHistoryGraph::allImages() const
0779 {
0780     return d->toInfoList(d->vertices());
0781 }
0782 
0783 QList<qlonglong> ItemHistoryGraph::allImageIds() const
0784 {
0785     QList<qlonglong> ids;
0786 
0787     Q_FOREACH (const HistoryGraph::Vertex& v, d->vertices())
0788     {
0789         Q_FOREACH (const ItemInfo& info, d->properties(v).infos)
0790         {
0791             ids << info.id();
0792         }
0793     }
0794 
0795     return ids;
0796 }
0797 
0798 QList<ItemInfo> ItemHistoryGraph::rootImages() const
0799 {
0800     return d->toInfoList(d->roots());
0801 }
0802 
0803 QList<ItemInfo> ItemHistoryGraph::leafImages() const
0804 {
0805     return d->toInfoList(d->leaves());
0806 }
0807 
0808 QHash<ItemInfo, HistoryImageId::Types> ItemHistoryGraph::categorize() const
0809 {
0810     QHash<HistoryGraph::Vertex, HistoryImageId::Types> vertexType = d->categorize();
0811 
0812     QHash<ItemInfo, HistoryImageId::Types> types;
0813 
0814     Q_FOREACH (const HistoryGraph::Vertex& v, d->vertices())
0815     {
0816         const HistoryVertexProperties& props = d->properties(v);
0817 
0818         if (props.infos.isEmpty())
0819         {
0820             continue;
0821         }
0822 
0823         HistoryImageId::Types type = vertexType.value(v);
0824 
0825         Q_FOREACH (const ItemInfo& info, props.infos)
0826         {
0827             types[info] = type;
0828         }
0829     }
0830 
0831     return types;
0832 }
0833 
0834 static QString toString(const HistoryVertexProperties& props)
0835 {
0836     QString s = QLatin1String("Ids: ");
0837     QStringList ids;
0838 
0839     Q_FOREACH (const ItemInfo& info, props.infos)
0840     {
0841         ids << QString::number(info.id());
0842     }
0843 
0844     if (props.uuid.isEmpty())
0845     {
0846         if (ids.size() == 1)
0847         {
0848             return (QLatin1String("Id: ") + ids.first());
0849         }
0850         else
0851         {
0852             return (QLatin1String("Ids: (") + ids.join(QLatin1String(",")) + QLatin1Char(')'));
0853         }
0854     }
0855     else
0856     {
0857         if (ids.size() == 1)
0858         {
0859             return (QLatin1String("Id: ") + ids.first() + QLatin1String(" UUID: ") + props.uuid.left(6) + QLatin1String("..."));
0860         }
0861         else
0862         {
0863             return (QLatin1String("Ids: (") + ids.join(QLatin1String(",")) + QLatin1String(") UUID: ") + props.uuid.left(6) + QLatin1String("..."));
0864         }
0865     }
0866 }
0867 
0868 QDebug operator<<(QDebug dbg, const ItemHistoryGraph& g)
0869 {
0870     if (g.data().isEmpty())
0871     {
0872         dbg << "(Empty graph)";
0873 
0874         return dbg;
0875     }
0876 
0877     QList<HistoryGraph::Vertex> vertices = g.data().topologicalSort();  // clazy:exclude=missing-typeinfo
0878 
0879     if (vertices.isEmpty())
0880     {
0881         vertices = g.data().vertices();
0882         dbg << "Not-a-DAG-Graph with" << vertices.size() << "vertices:" << QT_ENDL;;
0883     }
0884     else
0885     {
0886         dbg << "Graph with" << vertices.size() << "vertices:" << QT_ENDL;;
0887     }
0888 
0889     Q_FOREACH (const HistoryGraph::Vertex& target, vertices)
0890     {
0891         QString targetString = toString(g.data().properties(target));
0892 
0893         QStringList sourceVertexTexts;
0894 
0895         Q_FOREACH (const HistoryGraph::Vertex& source, g.data().adjacentVertices(target, HistoryGraph::InboundEdges))
0896         {
0897             sourceVertexTexts << toString(g.data().properties(source));
0898         }
0899 
0900         if      (!sourceVertexTexts.isEmpty())
0901         {
0902             dbg.nospace() << QLatin1String("{ ")    + targetString + QLatin1String(" } ") +
0903                              QLatin1String("-> { ") + sourceVertexTexts.join(QLatin1String(" }, { ")) +
0904                              QLatin1String(" }") << QT_ENDL;;
0905         }
0906         else if (g.data().outDegree(target) == 0)
0907         {
0908             dbg.nospace() << QLatin1String("Unconnected: { ") + targetString + QLatin1String(" }") << QT_ENDL;;
0909         }
0910     }
0911 
0912     return dbg;
0913 }
0914 
0915 } // namespace Digikam