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