diff --git a/core/libs/database/history/itemhistorygraph.cpp b/core/libs/database/history/itemhistorygraph.cpp index 686de7e153..a2f0b8fb7e 100644 --- a/core/libs/database/history/itemhistorygraph.cpp +++ b/core/libs/database/history/itemhistorygraph.cpp @@ -1,899 +1,901 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-10-23 * Description : Graph data class for image history * * Copyright (C) 2010-2011 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "itemhistorygraph.h" // Local includes #include "digikam_debug.h" #include "dimagehistory.h" #include "itemscanner.h" #include "itemhistorygraphdata.h" namespace Digikam { class Q_DECL_HIDDEN ItemHistoryGraphDataSharedNull : public QSharedDataPointer { public: ItemHistoryGraphDataSharedNull() : QSharedDataPointer(new ItemHistoryGraphData) { } }; Q_GLOBAL_STATIC(ItemHistoryGraphDataSharedNull, imageHistoryGraphDataSharedNull) // ----------------------------------------------------------------------------------------------- ItemInfo HistoryVertexProperties::firstItemInfo() const { if (infos.isEmpty()) { return ItemInfo(); } return infos.first(); } bool HistoryVertexProperties::markedAs(HistoryImageId::Type type) const { if (referredImages.isEmpty()) { return false; } foreach (const HistoryImageId& ref, referredImages) { if (ref.m_type == type) { return true; } } return false; } bool HistoryVertexProperties::alwaysMarkedAs(HistoryImageId::Type type) const { if (referredImages.isEmpty()) { return false; } foreach (const HistoryImageId& ref, referredImages) { if (ref.m_type != type) { return false; } } return true; } bool HistoryVertexProperties::operator==(const QString& id) const { return uuid == id; } bool HistoryVertexProperties::operator==(const ItemInfo& info) const { return infos.contains(info); } bool HistoryVertexProperties::operator==(qlonglong id) const { foreach (const ItemInfo& info, infos) { if (info.id() == id) { return true; } } return false; } bool HistoryVertexProperties::operator==(const HistoryImageId& other) const { if (!uuid.isEmpty() && !other.m_uuid.isEmpty()) { return (uuid == other.m_uuid); } foreach (const HistoryImageId& id, referredImages) { if (ItemScanner::sameReferredImage(id, other)) { //qCDebug(DIGIKAM_DATABASE_LOG) << id << "is the same as" << other; return true; } } return false; } HistoryVertexProperties& HistoryVertexProperties::operator+=(const QString& id) { if (!uuid.isNull() && id.isNull()) { uuid = id; } return *this; } HistoryVertexProperties& HistoryVertexProperties::operator+=(const ItemInfo& info) { if (!info.isNull() && !infos.contains(info)) { infos << info; if (uuid.isNull()) { uuid = info.uuid(); } } return *this; } HistoryVertexProperties& HistoryVertexProperties::operator+=(const HistoryImageId& id) { if (id.isValid() && !referredImages.contains(id)) { referredImages << id; if (uuid.isNull() && !id.m_uuid.isEmpty()) { uuid = id.m_uuid; } } return *this; } QDebug operator<<(QDebug dbg, const HistoryVertexProperties& props) { foreach (const ItemInfo& info, props.infos) { dbg.space() << info.id(); } return dbg; } QDebug operator<<(QDebug dbg, const HistoryImageId& id) { dbg.nospace() << " { "; dbg.nospace() << id.m_uuid; dbg.space() << id.m_type; dbg.space() << id.m_fileName; dbg.space() << id.m_filePath; dbg.space() << id.m_creationDate; dbg.space() << id.m_uniqueHash; dbg.space() << id.m_fileSize; dbg.space() << id.m_originalUUID; dbg.nospace() << " } "; return dbg; } // ----------------------------------------------------------------------------------------------- HistoryEdgeProperties& HistoryEdgeProperties::operator+=(const FilterAction& action) { actions << action; + return *this; } // ----------------------------------------------------------------------------------------------- HistoryGraph::Vertex ItemHistoryGraphData::addVertex(const QList& imageIds) { if (imageIds.isEmpty()) { return Vertex(); } Vertex v = addVertex(imageIds.first()); if (imageIds.size() > 1) { applyProperties(v, QList(), imageIds); } return v; } HistoryGraph::Vertex ItemHistoryGraphData::addVertex(const HistoryImageId& imageId) { if (!imageId.isValid()) { return Vertex(); } Vertex v; QList infos; //qCDebug(DIGIKAM_DATABASE_LOG) << "Adding vertex" << imageId.m_uuid.left(6) << imageId.fileName(); // find by HistoryImageId (most notably, by UUID) v = findVertexByProperties(imageId); //qCDebug(DIGIKAM_DATABASE_LOG) << "Found by properties:" << (v.isNull() ? -1 : int(v)); if (v.isNull()) { // Resolve HistoryImageId, find by ItemInfo foreach (const qlonglong& id, ItemScanner::resolveHistoryImageId(imageId)) { ItemInfo info(id); //qCDebug(DIGIKAM_DATABASE_LOG) << "Found info id:" << info.id(); infos << info; v = findVertexByProperties(info); } } applyProperties(v, infos, QList() << imageId); //qCDebug(DIGIKAM_DATABASE_LOG) << "Returning vertex" << v; return v; } HistoryGraph::Vertex ItemHistoryGraphData::addVertex(qlonglong id) { return addVertex(ItemInfo(id)); } HistoryGraph::Vertex ItemHistoryGraphData::addVertex(const ItemInfo& info) { Vertex v; QString uuid; HistoryImageId id; // Simply find by image id v = findVertexByProperties(info); //qCDebug(DIGIKAM_DATABASE_LOG) << "Find by id" << info.id() << ": found" << v.isNull(); if (v.isNull()) { // Find by contents uuid = info.uuid(); if (!uuid.isNull()) { v = findVertexByProperties(uuid); } //qCDebug(DIGIKAM_DATABASE_LOG) << "Find by uuid" << uuid << ": found" << v.isNull(); if (v.isNull()) { id = info.historyImageId(); v = findVertexByProperties(id); //qCDebug(DIGIKAM_DATABASE_LOG) << "Find by h-i-m" << ": found" << v.isNull(); } // Need to add new vertex. Do this through the method which will resolve the history id if (v.isNull()) { v = addVertex(id); } } applyProperties(v, QList() << info, QList() << id); //qCDebug(DIGIKAM_DATABASE_LOG) << "Returning vertex" << v << properties(v).infos.size(); return v; } HistoryGraph::Vertex ItemHistoryGraphData::addVertexScanned(qlonglong id) { // short version where we do not read information about id from an ItemInfo Vertex v = findVertexByProperties(id); applyProperties(v, QList() << ItemInfo(id), QList()); return v; } void ItemHistoryGraphData::applyProperties(Vertex& v, - const QList& infos, - const QList& ids) + const QList& infos, + const QList& ids) { // if needed, add a new vertex; or retrieve properties to add possibly new entries if (v.isNull()) { v = HistoryGraph::addVertex(); } HistoryVertexProperties& props = properties(v); // adjust properties foreach (const ItemInfo& info, infos) { props += info; } foreach (const HistoryImageId& id, ids) { props += id; } } int ItemHistoryGraphData::removeNextUnresolvedVertex(int index) { QList vs = vertices(); for ( ; index < vs.size() ; ++index) { Vertex& v = vs[index]; const HistoryVertexProperties& props = properties(v); if (props.infos.isEmpty()) { foreach (const HistoryGraph::Edge& upperEdge, edges(v, HistoryGraph::EdgesToRoot)) { foreach (const HistoryGraph::Edge& lowerEdge, edges(v, HistoryGraph::EdgesToLeaf)) { HistoryEdgeProperties combinedProps; combinedProps.actions += properties(upperEdge).actions; combinedProps.actions += properties(lowerEdge).actions; HistoryGraph::Edge connection = addEdge(source(lowerEdge), target(upperEdge)); setProperties(connection, combinedProps); } } remove(v); return index; } } return index; } QHash ItemHistoryGraphData::categorize() const { QHash types; foreach (const Vertex& v, vertices()) { const HistoryVertexProperties& props = properties(v); HistoryImageId::Types type; if (props.alwaysMarkedAs(HistoryImageId::Source)) { type |= HistoryImageId::Source; } else if (isLeaf(v)) { // Leaf: Assume current version type |= HistoryImageId::Current; } else if (isRoot(v)) { // Root: Assume original if at least once marked as such if (props.markedAs(HistoryImageId::Original)) { type |= HistoryImageId::Original; } } else { type = HistoryImageId::Intermediate; } /* * There is one situation which cannot be deduced from the graph structure: * When there is a derived image, but the parent image shall be kept as visible "Current". * In this case, the ExplicitBranch flag can be set on the next action. */ if (!(type & HistoryImageId::Current) && hasEdges(v, EdgesToLeaf)) { // We check if all immediate actions set the ExplicitBranch flag bool allBranches = true; foreach (const Edge& e, edges(v, EdgesToLeaf)) { const HistoryEdgeProperties& props2 = properties(e); if (props2.actions.isEmpty()) { continue; // unclear situation, ignore } if (!(props2.actions.first().flags() & FilterAction::ExplicitBranch)) { allBranches = false; break; } } if (allBranches) { type |= HistoryImageId::Current; } } types[v] = type; } return types; } // ----------------------------------------------------------------------------------------------- ItemHistoryGraph::ItemHistoryGraph() : d(*imageHistoryGraphDataSharedNull) { } ItemHistoryGraph::ItemHistoryGraph(const ItemHistoryGraph& other) : d(other.d) { } ItemHistoryGraph::~ItemHistoryGraph() { } ItemHistoryGraph& ItemHistoryGraph::operator=(const ItemHistoryGraph& other) { d = other.d; + return *this; } bool ItemHistoryGraph::isNull() const { return (d == *imageHistoryGraphDataSharedNull); } bool ItemHistoryGraph::isEmpty() const { return d->isEmpty(); } bool ItemHistoryGraph::isSingleVertex() const { return (d->vertexCount() == 1); } bool ItemHistoryGraph::hasEdges() const { return d->hasEdges(); } ItemHistoryGraphData& ItemHistoryGraph::data() { return *d; } const ItemHistoryGraphData& ItemHistoryGraph::data() const { return *d; } void ItemHistoryGraph::clear() { *d = HistoryGraph(); } ItemHistoryGraph ItemHistoryGraph::fromInfo(const ItemInfo& info, HistoryLoadingMode loadingMode, ProcessingMode processingMode) { ItemHistoryGraph graph; if (loadingMode & LoadRelationCloud) { graph.addRelations(info.relationCloud()); } if (loadingMode & LoadSubjectHistory) { graph.addHistory(info.imageHistory(), info); } if (loadingMode & LoadLeavesHistory) { foreach (const ItemInfo& leaf, graph.leafImages()) { if (leaf != info) { graph.addHistory(leaf.imageHistory(), leaf); } } } if (processingMode == PrepareForDisplay) { graph.prepareForDisplay(info); } return graph; } void ItemHistoryGraph::addHistory(const DImageHistory& givenHistory, const ItemInfo& historySubject) { addHistory(givenHistory, historySubject.historyImageId()); } void ItemHistoryGraph::addHistory(const DImageHistory& givenHistory, const HistoryImageId& subjectId) { // append the subject to its history DImageHistory history = givenHistory; if (subjectId.isValid()) { history << subjectId; } d->addHistory(history); } void ItemHistoryGraph::addScannedHistory(const DImageHistory& history, qlonglong historySubjectId) { d->addHistory(history, historySubjectId); } void ItemHistoryGraphData::addHistory(const DImageHistory& history, qlonglong extraCurrent) { if (history.isEmpty()) { return; } HistoryGraph::Vertex last; HistoryEdgeProperties edgeProps; foreach (const DImageHistory::Entry& entry, history.entries()) { if (!last.isNull()) { edgeProps += entry.action; } HistoryGraph::Vertex v; if (!entry.referredImages.isEmpty()) { v = addVertex(entry.referredImages); } if (!v.isNull()) { if (!last.isNull()) { if (v != last) { HistoryGraph::Edge e = addEdge(v, last); setProperties(e, edgeProps); edgeProps = HistoryEdgeProperties(); } else { qCWarning(DIGIKAM_DATABASE_LOG) << "Broken history: Same file referred by different entries. Refusing to add a loop."; } } last = v; } } if (extraCurrent) { HistoryGraph::Vertex v = addVertexScanned(extraCurrent); if (!v.isNull() && !last.isNull() && (v != last)) { HistoryGraph::Edge e = addEdge(v, last); setProperties(e, edgeProps); } } } void ItemHistoryGraph::addRelations(const QList >& pairs) { HistoryGraph::Vertex v1, v2; typedef QPair IdPair; foreach (const IdPair& pair, pairs) { if ((pair.first < 1) || (pair.second < 1)) { continue; } if (pair.first == pair.second) { qCWarning(DIGIKAM_DATABASE_LOG) << "Broken relations cloud: Refusing to add a loop."; } v1 = d->addVertex(pair.first); v2 = d->addVertex(pair.second); //qCDebug(DIGIKAM_DATABASE_LOG) << "Adding" << v1 << "->" << v2; d->addEdge(v1, v2); } } void ItemHistoryGraph::reduceEdges() { if (d->vertexCount() <= 1) { return; } QList removedEgdes; HistoryGraph reduction = d->transitiveReduction(&removedEgdes); if (reduction.isEmpty()) { return; // reduction failed, not a DAG } foreach (const HistoryGraph::Edge& e, removedEgdes) { if (!d->properties(e).actions.isEmpty()) { // TODO: conflict resolution qCDebug(DIGIKAM_DATABASE_LOG) << "Conflicting history information: Edge removed by transitiveReduction is not empty."; } } *d = reduction; } bool ItemHistoryGraph::hasUnresolvedEntries() const { foreach (const HistoryGraph::Vertex& v, d->vertices()) { if (d->properties(v).infos.isEmpty()) { return true; } } return false; } void ItemHistoryGraph::dropUnresolvedEntries() { // Remove nodes which could not be resolved into image infos // The problem is that with each removable, the vertex list is invalidated, // so we cannot do one loop over d->vertices for (int i = 0 ; i < d->vertexCount() ; ) { i = d->removeNextUnresolvedVertex(i); } } void ItemHistoryGraph::sortForInfo(const ItemInfo& subject) { // Remove nodes which could not be resolved into image infos QList toRemove; foreach (const HistoryGraph::Vertex& v, d->vertices()) { HistoryVertexProperties& props = d->properties(v); ItemScanner::sortByProximity(props.infos, subject); } } void ItemHistoryGraph::prepareForDisplay(const ItemInfo& subject) { reduceEdges(); dropUnresolvedEntries(); sortForInfo(subject); } QList > ItemHistoryGraph::relationCloud() const { QList > pairs; ItemHistoryGraphData closure = ItemHistoryGraphData(d->transitiveClosure()); QList edges = closure.edgePairs(); foreach (const HistoryGraph::VertexPair& edge, edges) { foreach (const ItemInfo& source, closure.properties(edge.first).infos) { foreach (const ItemInfo& target, closure.properties(edge.second).infos) { pairs << QPair(source.id(), target.id()); } } } return pairs; } QPair, QList > ItemHistoryGraph::relationCloudParallel() const { QList subjects, objects; ItemHistoryGraphData closure = ItemHistoryGraphData(d->transitiveClosure()); QList edges = closure.edgePairs(); foreach (const HistoryGraph::VertexPair& edge, edges) { foreach (const ItemInfo& source, closure.properties(edge.first).infos) { foreach (const ItemInfo& target, closure.properties(edge.second).infos) { subjects << source.id(); objects << target.id(); } } } return qMakePair(subjects, objects); } QList ItemHistoryGraph::allImages() const { return d->toInfoList(d->vertices()); } QList ItemHistoryGraph::allImageIds() const { QList ids; foreach (const HistoryGraph::Vertex& v, d->vertices()) { foreach (const ItemInfo& info, d->properties(v).infos) { ids << info.id(); } } return ids; } QList ItemHistoryGraph::rootImages() const { return d->toInfoList(d->roots()); } QList ItemHistoryGraph::leafImages() const { return d->toInfoList(d->leaves()); } QHash ItemHistoryGraph::categorize() const { QHash vertexType = d->categorize(); QHash types; foreach (const HistoryGraph::Vertex& v, d->vertices()) { const HistoryVertexProperties& props = d->properties(v); if (props.infos.isEmpty()) { continue; } HistoryImageId::Types type = vertexType.value(v); foreach (const ItemInfo& info, props.infos) { types[info] = type; } } return types; } static QString toString(const HistoryVertexProperties& props) { QString s = QLatin1String("Ids: "); QStringList ids; foreach (const ItemInfo& info, props.infos) { ids << QString::number(info.id()); } if (props.uuid.isEmpty()) { if (ids.size() == 1) { return QLatin1String("Id: ") + ids.first(); } else { return QLatin1String("Ids: (") + ids.join(QLatin1String(",")) + QLatin1Char(')'); } } else { if (ids.size() == 1) { return QLatin1String("Id: ") + ids.first() + QLatin1String(" UUID: ") + props.uuid.left(6) + QLatin1String("..."); } else { return QLatin1String("Ids: (") + ids.join(QLatin1String(",")) + QLatin1String(") UUID: ") + props.uuid.left(6) + QLatin1String("..."); } } } QDebug operator<<(QDebug dbg, const ItemHistoryGraph& g) { if (g.data().isEmpty()) { dbg << "(Empty graph)"; return dbg; } QList vertices = g.data().topologicalSort(); if (vertices.isEmpty()) { vertices = g.data().vertices(); dbg << "Not-a-DAG-Graph with" << vertices.size() << "vertices:" << endl; } else { dbg << "Graph with" << vertices.size() << "vertices:" << endl; } foreach (const HistoryGraph::Vertex& target, vertices) { QString targetString = toString(g.data().properties(target)); QStringList sourceVertexTexts; foreach (const HistoryGraph::Vertex& source, g.data().adjacentVertices(target, HistoryGraph::InboundEdges)) { sourceVertexTexts << toString(g.data().properties(source)); } if (!sourceVertexTexts.isEmpty()) { dbg.nospace() << QLatin1String("{ ") + targetString + QLatin1String(" } ") + - QLatin1String("-> { ") + sourceVertexTexts.join(QLatin1String(" }, { ")) + + QLatin1String("-> { ") + sourceVertexTexts.join(QLatin1String(" }, { ")) + QLatin1String(" }") << endl; } else if (g.data().outDegree(target) == 0) { dbg.nospace() << QLatin1String("Unconnected: { ") + targetString + QLatin1String(" }") << endl; } } return dbg; } } // namespace Digikam diff --git a/core/libs/database/history/itemhistorygraph.h b/core/libs/database/history/itemhistorygraph.h index 0ccd7902c3..bf0aa8a6fe 100644 --- a/core/libs/database/history/itemhistorygraph.h +++ b/core/libs/database/history/itemhistorygraph.h @@ -1,196 +1,198 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-10-23 * Description : Graph data class for item history * * Copyright (C) 2010-2011 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_ITEM_HISTORY_GRAPH_H #define DIGIKAM_ITEM_HISTORY_GRAPH_H // Qt includes #include #include #include // Local includes #include "iteminfo.h" #include "historyimageid.h" #include "digikam_export.h" namespace Digikam { class ItemHistoryGraphData; class DImageHistory; class DIGIKAM_DATABASE_EXPORT ItemHistoryGraph { public: - ItemHistoryGraph(); - ItemHistoryGraph(const ItemHistoryGraph& other); - ~ItemHistoryGraph(); - - ItemHistoryGraph& operator=(const ItemHistoryGraph& other); - - bool isNull() const; - bool isEmpty() const; - bool isSingleVertex() const; - - /** - * Returns if the graph contains any edges. Because loops are not allowed, - * this also means (!isEmpty() && !isSingleVertex()). - */ - bool hasEdges() const; - - ItemHistoryGraphData& data(); - const ItemHistoryGraphData& data() const; - enum HistoryLoadingFlag { /// Load the relation cloud to the graph. Will give all edges, but no further info LoadRelationCloud = 1 << 0, /// Will load the DImageHistory of the given subject LoadSubjectHistory = 1 << 1, /// Will load the DImageHistory of all leave vertices of the graph LoadLeavesHistory = 1 << 2, LoadAll = LoadRelationCloud | LoadSubjectHistory | LoadLeavesHistory }; Q_DECLARE_FLAGS(HistoryLoadingMode, HistoryLoadingFlag) enum ProcessingMode { NoProcessing, PrepareForDisplay }; +public: + + ItemHistoryGraph(); + ItemHistoryGraph(const ItemHistoryGraph& other); + ~ItemHistoryGraph(); + + ItemHistoryGraph& operator=(const ItemHistoryGraph& other); + + bool isNull() const; + bool isEmpty() const; + bool isSingleVertex() const; + + /** + * Returns if the graph contains any edges. Because loops are not allowed, + * this also means (!isEmpty() && !isSingleVertex()). + */ + bool hasEdges() const; + + ItemHistoryGraphData& data(); + const ItemHistoryGraphData& data() const; + /** * Convenience: Reads all available history for the given info from the database * and returns the created graph. * Depending on mode, the graph will be preparedForDisplay(). * If no history is recorded and no relations found, a single-vertex graph is returned. */ static ItemHistoryGraph fromInfo(const ItemInfo& info, HistoryLoadingMode loadingMode = LoadAll, ProcessingMode processingMode = PrepareForDisplay); /** * Add the given history. * The optionally given info or id is used as the "current" image of the history. * If you read a history from a file's metadata or the database, you shall give the * relevant subject. */ void addHistory(const DImageHistory& history, const ItemInfo& historySubject = ItemInfo()); void addHistory(const DImageHistory& history, const HistoryImageId& historySubject = HistoryImageId()); /** * This is very similar to addHistory. The only difference is that * no attempt is made to retrieve an ItemInfo for the historySubjectId. * Can be useful in the context of scanning */ void addScannedHistory(const DImageHistory& history, qlonglong historySubjectId); /** * Add images and their relations from the given pairs. * Each pair (a,b) means "a is derived from b". */ void addRelations(const QList >& pairs); /** * Clears this graph. */ void clear(); /** * Remove edges which provide only duplicate information * (performs a transitive reduction). * Especially call this when addRelations() was used. */ void reduceEdges(); /** * Returns true if for any entry no ItemInfo could be located. */ - bool hasUnresolvedEntries() const; + bool hasUnresolvedEntries() const; /** * Remove all vertices from the graph for which no existing ItemInfo * could be found in the database */ void dropUnresolvedEntries(); /** * Sort vertex information prioritizing for the given vertex */ void sortForInfo(const ItemInfo& subject); /** * Combines reduceEdges(), dropOrphans() and sortForInfo() */ void prepareForDisplay(const ItemInfo& subject); /** * Returns all possible relations between images in this graph, * the edges of the transitive closure. * The first variant returns (1,2),(3,4),(6,8), the second (1,3,6)(2,4,8). */ - QList > relationCloud() const; - QPair, QList > relationCloudParallel() const; + QList > relationCloud() const; + QPair, QList > relationCloudParallel() const; /** * Returns image infos / ids from all vertices in this graph */ - QList allImages() const; - QList allImageIds() const; + QList allImages() const; + QList allImageIds() const; /** * Returns image infos / ids from all root vertices in this graph, * i.e. vertices with no precedent history. */ - QList rootImages() const; + QList rootImages() const; /** * Returns image infos / ids from all leaf vertices in this graph, * i.e. vertices with no subsequent history. */ - QList leafImages() const; + QList leafImages() const; /** * Attempts at a categorization of all images in the graph * into the types defined by HistoryImageId. * The type will be invalid if no decision can be made due to conflicting data. */ - QHash categorize() const; + QHash categorize() const; private: QSharedDataPointer d; }; QDebug DIGIKAM_DATABASE_EXPORT operator<<(QDebug dbg, const ItemHistoryGraph& g); } // namespace Digikam Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::ItemHistoryGraph::HistoryLoadingMode) #endif // DIGIKAM_ITEM_HISTORY_GRAPH_H diff --git a/core/libs/database/history/itemhistorygraphdata.h b/core/libs/database/history/itemhistorygraphdata.h index 51152f5253..ee00fd613d 100644 --- a/core/libs/database/history/itemhistorygraphdata.h +++ b/core/libs/database/history/itemhistorygraphdata.h @@ -1,156 +1,157 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-10-23 * Description : Graph data class for item history * * Copyright (C) 2010-2011 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_ITEM_HISTORY_GRAPH_DATA_H #define DIGIKAM_ITEM_HISTORY_GRAPH_DATA_H // Qt includes #include // Local includes #include "filteraction.h" #include "historyimageid.h" #include "itemhistorygraph_boost.h" namespace Digikam { class ItemInfo; /** * Every vertex has one associated object of this class * * All entries in a vertex refer to _identical_ images. * There can be multiple referred images in a history entry. * Each single HistoryImageId can resolve into none, one, * or multiple ItemInfos. * So there is no mapping between the two fields here. * * If an image is created from multiple source images (panorama etc.), * there will be one vertex per source image! */ class HistoryVertexProperties { public: ItemInfo firstItemInfo() const; bool markedAs(HistoryImageId::Type) const; bool alwaysMarkedAs(HistoryImageId::Type) const; bool operator==(const QString& uuid) const; bool operator==(const ItemInfo& info) const; bool operator==(qlonglong id) const; bool operator==(const HistoryImageId& info) const; HistoryVertexProperties& operator+=(const QString& uuid); HistoryVertexProperties& operator+=(const ItemInfo& info); HistoryVertexProperties& operator+=(const HistoryImageId& info); public: QString uuid; QList referredImages; QList infos; }; QDebug operator<<(QDebug dbg, const HistoryVertexProperties& props); QDebug operator<<(QDebug dbg, const HistoryImageId& id); // ------------------------------------------------------------------------------ /** * Every edge has one associated object of this class. * * For two vertices v1, v2 with and edge e, v1 -> v2, * describes the actions necessary to create v2 from v2: * v1 -> actions[0] -> ... -> actions[n] = v2. */ class HistoryEdgeProperties { public: QList actions; HistoryEdgeProperties& operator+=(const FilterAction& action); }; typedef Graph HistoryGraph; // ------------------------------------------------------------------------------ class ItemHistoryGraphData : public HistoryGraph, public QSharedData { public: ItemHistoryGraphData() : HistoryGraph(ChildToParent) { } explicit ItemHistoryGraphData(const HistoryGraph& g) : HistoryGraph(g) { } ItemHistoryGraphData& operator=(const HistoryGraph& g) { HistoryGraph::operator=(g); + return *this; } Vertex addVertex(const HistoryImageId& id); Vertex addVertex(const QList& imageIds); Vertex addVertex(qlonglong id); Vertex addVertexScanned(qlonglong id); Vertex addVertex(const ItemInfo& info); void addHistory(const DImageHistory& givenHistory, qlonglong extraCurrent = 0); int removeNextUnresolvedVertex(int begin); inline QList toInfoList(const QList& vertices) const { QList infos; foreach (const HistoryGraph::Vertex& v, vertices) { infos << properties(v).infos; } return infos; } QHash categorize() const; protected: void applyProperties(Vertex& v, const QList& infos, const QList& ids); }; } // namespace Digikam #endif // DIGIKAM_ITEM_HISTORY_GRAPH_DATA_H diff --git a/core/libs/database/history/itemhistorygraphmodel.cpp b/core/libs/database/history/itemhistorygraphmodel.cpp index 29477a0702..02ef8a85ae 100644 --- a/core/libs/database/history/itemhistorygraphmodel.cpp +++ b/core/libs/database/history/itemhistorygraphmodel.cpp @@ -1,1004 +1,1027 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-10-27 * Description : Model to an ItemHistoryGraph * * Copyright (C) 2010-2011 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "itemhistorygraphmodel.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "dcategorizedsortfilterproxymodel.h" #include "dimgfiltermanager.h" #include "itemlistmodel.h" #include "itemhistorygraphdata.h" namespace Digikam { class Q_DECL_HIDDEN HistoryTreeItem { public: enum HistoryTreeItemType { UnspecifiedType, VertexItemType, FilterActionItemType, HeaderItemType, CategoryItemType, SeparatorItemType }; public: explicit HistoryTreeItem(); virtual ~HistoryTreeItem(); virtual HistoryTreeItemType type() const { return UnspecifiedType; } bool isType(HistoryTreeItemType t) const { return (type() == t); } void addItem(HistoryTreeItem* child); int childCount() const { return children.size(); } HistoryTreeItem* child(int index) const { return children.at(index); } public: HistoryTreeItem* parent; QList children; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN VertexItem : public HistoryTreeItem { public: VertexItem() { } explicit VertexItem(const HistoryGraph::Vertex& v) : vertex(v), category(HistoryImageId::InvalidType) { } HistoryTreeItemType type() const override { return VertexItemType; } public: HistoryGraph::Vertex vertex; QModelIndex index; HistoryImageId::Types category; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN FilterActionItem : public HistoryTreeItem { public: FilterActionItem() { } explicit FilterActionItem(const FilterAction& action) : action(action) { } HistoryTreeItemType type() const override { return FilterActionItemType; } public: FilterAction action; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN HeaderItem : public HistoryTreeItem { public: explicit HeaderItem(const QString& title) : title(title) { } HistoryTreeItemType type() const override { return HeaderItemType; } public: QString title; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN CategoryItem : public HistoryTreeItem { public: explicit CategoryItem(const QString& title) : title(title) { } HistoryTreeItemType type() const override { return CategoryItemType; } public: QString title; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN SeparatorItem : public HistoryTreeItem { public: HistoryTreeItemType type() const override { return SeparatorItemType; } }; // ------------------------------------------------------------------------ #define if_isItem(class, name, pointer) \ if (pointer && static_cast(pointer)->type() == HistoryTreeItem:: class##Type) \ for (class* name = static_cast(pointer) ; name ; name=0) // ------------------------------------------------------------------------ HistoryTreeItem::HistoryTreeItem() : parent(nullptr) { } HistoryTreeItem::~HistoryTreeItem() { qDeleteAll(children); } void HistoryTreeItem::addItem(HistoryTreeItem* child) { children << child; child->parent = this; } // ------------------------------------------------------------------------ static bool oldestInfoFirst(const ItemInfo&a, const ItemInfo& b) { return a.modDateTime() < b.modDateTime(); } static bool newestInfoFirst(const ItemInfo&a, const ItemInfo& b) { return a.modDateTime() > b.modDateTime(); } template class Q_DECL_HIDDEN LessThanOnVertexItemInfo { public: LessThanOnVertexItemInfo(const HistoryGraph& graph, ItemInfoLessThan imageInfoLessThan) : graph(graph), imageInfoLessThan(imageInfoLessThan) { } bool operator()(const HistoryGraph::Vertex& a, const HistoryGraph::Vertex& b) const { const HistoryVertexProperties& propsA = graph.properties(a); const HistoryVertexProperties& propsB = graph.properties(b); if (propsA.infos.isEmpty()) + { return false; + } else if (propsB.infos.isEmpty()) + { return true; + } return imageInfoLessThan(propsA.infos.first(), propsB.infos.first()); } public: const HistoryGraph& graph; ItemInfoLessThan imageInfoLessThan; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN ItemHistoryGraphModel::Private { public: explicit Private() : mode(ItemHistoryGraphModel::CombinedTreeMode), rootItem(nullptr) { } ItemHistoryGraphModel::Mode mode; ItemHistoryGraph historyGraph; ItemInfo info; HistoryTreeItem* rootItem; QList vertexItems; ItemListModel imageModel; QList path; QHash categories; public: inline const ItemHistoryGraphData& graph() const { return historyGraph.data(); } inline HistoryTreeItem* historyItem(const QModelIndex& index) const { return index.isValid() ? static_cast(index.internalPointer()) : rootItem; } void build(); void buildImagesList(); void buildImagesTree(); void buildCombinedTree(const HistoryGraph::Vertex& ref); void addCombinedItemCategory(HistoryTreeItem* parentItem, QList& vertices, const QString& title, const HistoryGraph::Vertex& showActionsFrom, QList& added); void addItemSubgroup(VertexItem* parent, const QList& vertices, const QString& title, bool flat = false); void addIdenticalItems(HistoryTreeItem* parentItem, const HistoryGraph::Vertex& vertex, const QList& infos, const QString& title); VertexItem* createVertexItem(const HistoryGraph::Vertex& v, const ItemInfo& info = ItemInfo()); FilterActionItem* createFilterActionItem(const FilterAction& action); template LessThanOnVertexItemInfo sortBy(ItemInfoLessThan imageInfoLessThan) { return LessThanOnVertexItemInfo(graph(), imageInfoLessThan); } }; // ------------------------------------------------------------------------ VertexItem* ItemHistoryGraphModel::Private::createVertexItem(const HistoryGraph::Vertex& v, const ItemInfo& givenInfo) { const HistoryVertexProperties& props = graph().properties(v); ItemInfo info = givenInfo.isNull() ? props.firstItemInfo() : givenInfo; QModelIndex index = imageModel.indexForItemInfo(info); //qCDebug(DIGIKAM_DATABASE_LOG) << "Added" << info.id() << index; VertexItem* item = new VertexItem(v); item->index = index; item->category = categories.value(v); vertexItems << item; //qCDebug(DIGIKAM_DATABASE_LOG) << "Adding vertex item" << graph().properties(v).firstItemInfo().id() << index; + return item; } FilterActionItem* ItemHistoryGraphModel::Private::createFilterActionItem(const FilterAction& action) { //qCDebug(DIGIKAM_DATABASE_LOG) << "Adding vertex item for" << action.displayableName(); return new FilterActionItem(action); } void ItemHistoryGraphModel::Private::build() { delete rootItem; vertexItems.clear(); rootItem = new HistoryTreeItem; //qCDebug(DIGIKAM_DATABASE_LOG) << historyGraph; HistoryGraph::Vertex ref = graph().findVertexByProperties(info); path = graph().longestPathTouching(ref, sortBy(newestInfoFirst)); categories = graph().categorize(); if (path.isEmpty()) + { return; + } - if (mode == ItemHistoryGraphModel::ImagesListMode) + if (mode == ItemHistoryGraphModel::ImagesListMode) { buildImagesList(); } else if (mode == ItemHistoryGraphModel::ImagesTreeMode) { buildImagesTree(); } else if (mode == CombinedTreeMode) { buildCombinedTree(ref); } } void ItemHistoryGraphModel::Private::buildImagesList() { QList verticesOrdered = graph().verticesDepthFirstSorted(path.first(), sortBy(oldestInfoFirst)); foreach (const HistoryGraph::Vertex& v, verticesOrdered) { rootItem->addItem(createVertexItem(v)); } } void ItemHistoryGraphModel::Private::buildImagesTree() { QList verticesOrdered = graph().verticesDepthFirstSorted(path.first(), sortBy(oldestInfoFirst)); QMap distances = graph().shortestDistancesFrom(path.first()); QList sources; - int previousLevel = 0; - HistoryTreeItem* parent = rootItem; - VertexItem* item = nullptr; - VertexItem* previousItem = nullptr; + int previousLevel = 0; + HistoryTreeItem* parent = rootItem; + VertexItem* item = nullptr; + VertexItem* previousItem = nullptr; foreach (const HistoryGraph::Vertex& v, verticesOrdered) { int currentLevel = distances.value(v); if (currentLevel == -1) { // unreachable from first root if (graph().isRoot(v) && parent == rootItem) { // other first-level root? parent->addItem(createVertexItem(v)); } else { // add later as sources sources << v; } continue; } item = createVertexItem(v); if (!sources.isEmpty()) { addItemSubgroup(item, sources, i18nc("@title", "Source Images")); } if (currentLevel == previousLevel) { parent->addItem(item); } else if (currentLevel > previousLevel && previousItem) // check pointer, prevent crash is distances are faulty { previousItem->addItem(item); parent = previousItem; } else if (currentLevel < previousLevel) { - for (int level = currentLevel; level < previousLevel; ++level) + for (int level = currentLevel ; level < previousLevel ; ++level) { parent = parent->parent; } parent->addItem(item); } previousItem = item; previousLevel = currentLevel; } } void ItemHistoryGraphModel::Private::buildCombinedTree(const HistoryGraph::Vertex& ref) { - VertexItem* item = nullptr; - CategoryItem *categoryItem = new CategoryItem(i18nc("@title", "Image History")); + VertexItem* item = nullptr; + CategoryItem *categoryItem = new CategoryItem(i18nc("@title", "Image History")); rootItem->addItem(categoryItem); QList added; QList currentVersions = categories.keys(HistoryImageId::Current); QList leavesFromRef = graph().leavesFrom(ref); bool onePath = (leavesFromRef.size() <= 1); for (int i = 0 ; i < path.size() ; ++i) { const HistoryGraph::Vertex& v = path.at(i); HistoryGraph::Vertex previous = i ? path.at(i-1) : HistoryGraph::Vertex(); // HistoryGraph::Vertex next = i < path.size() - 1 ? path[i+1] : HistoryGraph::Vertex(); // qCDebug(DIGIKAM_DATABASE_LOG) << "Vertex on path" << path[i]; // create new item item = createVertexItem(v); QList vertices; // any extra sources? QList sources = graph().adjacentVertices(item->vertex, HistoryGraph::EdgesToRoot); foreach (const HistoryGraph::Vertex& source, sources) { if (source != previous) { rootItem->addItem(createVertexItem(source)); } } /* // Any other egdes off the main path? QList branches = graph().adjacentVertices(v, HistoryGraph::EdgesToLeaf); QList subgraph; foreach (const HistoryGraph::Vertex& branch, branches) { if (branch != next) { subgraph << graph().verticesDominatedByDepthFirstSorted(branch, v, sortBy(oldestInfoFirst)); } } addItemSubgroup(item, subgraph, i18nc("@title", "More Derived Images")); */ // Add filter actions above item HistoryEdgeProperties props = graph().properties(v, previous); foreach (const FilterAction& action, props.actions) { rootItem->addItem(createFilterActionItem(action)); } // now, add item rootItem->addItem(item); added << v; // If there are multiple derived images, we display them in the next section - if (v == ref && !onePath) + if ((v == ref) && !onePath) + { break; + } } foreach (const HistoryGraph::Vertex& v, added) { leavesFromRef.removeOne(v); } if (!leavesFromRef.isEmpty()) { addCombinedItemCategory(rootItem, leavesFromRef, i18nc("@title", "Derived Images"), ref, added); } foreach (const HistoryGraph::Vertex& v, added) { currentVersions.removeOne(v); } if (!currentVersions.isEmpty()) { addCombinedItemCategory(rootItem, currentVersions, i18nc("@title", "Related Images"), path.first(), added); } QList allInfos = graph().properties(ref).infos; if (allInfos.size() > 1) { addIdenticalItems(rootItem, ref, allInfos, i18nc("@title", "Identical Images")); } } void ItemHistoryGraphModel::Private::addCombinedItemCategory(HistoryTreeItem* parentItem, - QList& vertices, - const QString& title, - const HistoryGraph::Vertex& showActionsFrom, - QList& added) + QList& vertices, + const QString& title, + const HistoryGraph::Vertex& showActionsFrom, + QList& added) { parentItem->addItem(new CategoryItem(title)); std::sort(vertices.begin(), vertices.end(), sortBy(oldestInfoFirst)); bool isFirst = true; VertexItem* item = nullptr; foreach (const HistoryGraph::Vertex& v, vertices) { if (isFirst) { isFirst = false; } else { parentItem->addItem(new SeparatorItem); } item = createVertexItem(v); QList shortestPath = graph().shortestPath(showActionsFrom, v); // add all filter actions showActionsFrom -> v above item for (int i = 1 ; i < shortestPath.size() ; ++i) { HistoryEdgeProperties props = graph().properties(shortestPath.at(i), shortestPath.at(i-1)); foreach (const FilterAction& action, props.actions) { parentItem->addItem(createFilterActionItem(action)); } } parentItem->addItem(item); added << v; // Provide access to intermediates shortestPath.removeOne(showActionsFrom); shortestPath.removeOne(v); foreach (const HistoryGraph::Vertex& addedVertex, added) { shortestPath.removeOne(addedVertex); } addItemSubgroup(item, shortestPath, i18nc("@title", "Intermediate Steps:"), true); } } void ItemHistoryGraphModel::Private::addItemSubgroup(VertexItem* parent, - const QList& vertices, - const QString& title, - bool flat) + const QList& vertices, + const QString& title, + bool flat) { if (vertices.isEmpty()) + { return; + } HeaderItem* const header = new HeaderItem(title); parent->addItem(header); HistoryTreeItem* const addToItem = flat ? static_cast(parent) : static_cast(header); foreach (const HistoryGraph::Vertex& v, vertices) { addToItem->addItem(createVertexItem(v)); } } void ItemHistoryGraphModel::Private::addIdenticalItems(HistoryTreeItem* parentItem, - const HistoryGraph::Vertex& vertex, - const QList& infos, - const QString& title) + const HistoryGraph::Vertex& vertex, + const QList& infos, + const QString& title) { parentItem->addItem(new CategoryItem(title)); // the properties image info list is already sorted by proximity to subject VertexItem* item = nullptr; bool isFirst = true; for (int i = 1 ; i < infos.size() ; ++i) { if (isFirst) { isFirst = false; } else { parentItem->addItem(new SeparatorItem); } item = createVertexItem(vertex, infos.at(i)); parentItem->addItem(item); } } // ------------------------------------------------------------------------ ItemHistoryGraphModel::ItemHistoryGraphModel(QObject* const parent) : QAbstractItemModel(parent), d(new Private) { d->rootItem = new HistoryTreeItem; } ItemHistoryGraphModel::~ItemHistoryGraphModel() { delete d->rootItem; delete d; } void ItemHistoryGraphModel::setMode(Mode mode) { if (d->mode == mode) + { return; + } d->mode = mode; setHistory(d->info, d->historyGraph); } ItemHistoryGraphModel::Mode ItemHistoryGraphModel::mode() const { return d->mode; } void ItemHistoryGraphModel::setHistory(const ItemInfo& subject, const ItemHistoryGraph& graph) { beginResetModel(); d->info = subject; if (graph.isNull()) { d->historyGraph = ItemHistoryGraph::fromInfo(subject); } else { d->historyGraph = graph; d->historyGraph.prepareForDisplay(subject); } // fill helper model d->imageModel.clearItemInfos(); d->imageModel.addItemInfos(d->historyGraph.allImages()); d->build(); endResetModel(); } ItemInfo ItemHistoryGraphModel::subject() const { return d->info; } bool ItemHistoryGraphModel::isImage(const QModelIndex& index) const { HistoryTreeItem* const item = d->historyItem(index); return (item && item->isType(HistoryTreeItem::VertexItemType)); } bool ItemHistoryGraphModel::isFilterAction(const QModelIndex& index) const { HistoryTreeItem* const item = d->historyItem(index); return (item && item->isType(HistoryTreeItem::FilterActionItemType)); } FilterAction ItemHistoryGraphModel::filterAction(const QModelIndex& index) const { HistoryTreeItem* const item = d->historyItem(index); if_isItem(FilterActionItem, filterActionItem, item) { return filterActionItem->action; } return FilterAction(); } bool ItemHistoryGraphModel::hasImage(const ItemInfo& info) { return d->imageModel.hasImage(info); } ItemInfo ItemHistoryGraphModel::imageInfo(const QModelIndex& index) const { QModelIndex imageIndex = imageModelIndex(index); return ItemModel::retrieveItemInfo(imageIndex); } QModelIndex ItemHistoryGraphModel::indexForInfo(const ItemInfo& info) const { if (info.isNull()) { return QModelIndex(); } // try with primary info foreach (VertexItem* const item, d->vertexItems) { if (ItemModel::retrieveItemInfo(item->index) == info) { return createIndex(item->parent->children.indexOf(item), 0, item); } } // try all associated infos foreach (VertexItem* const item, d->vertexItems) { if (d->graph().properties(item->vertex).infos.contains(info)) { return createIndex(item->parent->children.indexOf(item), 0, item); } } return QModelIndex(); } QVariant ItemHistoryGraphModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } HistoryTreeItem* const item = d->historyItem(index); if_isItem(VertexItem, vertexItem, item) { if (vertexItem->index.isValid()) { QVariant data = vertexItem->index.data(role); switch (role) { case IsImageItemRole: { return true; } + case IsSubjectImageRole: { return (bool)d->graph().properties(vertexItem->vertex).infos.contains(d->info); } + case Qt::DisplayRole: { if (vertexItem->category & HistoryImageId::Original) { return i18nc("@item filename", "%1\n(Original Image)", data.toString()); } if (vertexItem->category & HistoryImageId::Source) { return i18nc("@item filename", "%1\n(Source Image)", data.toString()); } break; } } return data; } // else: read HistoryImageId from d->graph().properties(vertexItem->vertex)? } else if_isItem(FilterActionItem, filterActionItem, item) { switch (role) { case IsFilterActionItemRole: { return true; } + case Qt::DisplayRole: { return DImgFilterManager::instance()->i18nDisplayableName(filterActionItem->action); } + case Qt::DecorationRole: { QString iconName = DImgFilterManager::instance()->filterIcon(filterActionItem->action); return QIcon::fromTheme(iconName); } + case FilterActionRole: { return QVariant::fromValue(filterActionItem->action); } + default: { break; } } } else if_isItem(HeaderItem, headerItem, item) { switch (role) { case IsHeaderItemRole: return true; + case Qt::DisplayRole: //case Qt::ToolTipRole: return headerItem->title; break; } } else if_isItem(CategoryItem, categoryItem, item) { switch (role) { case IsCategoryItemRole: return true; + case Qt::DisplayRole: case DCategorizedSortFilterProxyModel::CategoryDisplayRole: //case Qt::ToolTipRole: return categoryItem->title; } } else if_isItem(SeparatorItem, separatorItem, item) { switch (role) { case IsSeparatorItemRole: return true; } } switch (role) { case IsImageItemRole: case IsFilterActionItemRole: case IsHeaderItemRole: case IsCategoryItemRole: case IsSubjectImageRole: return false; + default: return QVariant(); } } bool ItemHistoryGraphModel::setData(const QModelIndex& index, const QVariant& value, int role) { HistoryTreeItem* const item = d->historyItem(index); if_isItem(VertexItem, vertexItem, item) { if (vertexItem->index.isValid()) { return d->imageModel.setData(vertexItem->index, value, role); } } return false; } ItemListModel* ItemHistoryGraphModel::imageModel() const { return &d->imageModel; } QModelIndex ItemHistoryGraphModel::imageModelIndex(const QModelIndex& index) const { HistoryTreeItem* const item = d->historyItem(index); if_isItem(VertexItem, vertexItem, item) { return vertexItem->index; } return QModelIndex(); } QVariant ItemHistoryGraphModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section) Q_UNUSED(orientation) Q_UNUSED(role) return QVariant(); } int ItemHistoryGraphModel::rowCount(const QModelIndex& parent) const { return d->historyItem(parent)->childCount(); } int ItemHistoryGraphModel::columnCount(const QModelIndex&) const { return 1; } Qt::ItemFlags ItemHistoryGraphModel::flags(const QModelIndex& index) const { if (!index.isValid()) { return nullptr; } HistoryTreeItem* const item = d->historyItem(index); if_isItem(VertexItem, vertexItem, item) { return d->imageModel.flags(vertexItem->index); } if (item) { switch (item->type()) { case HistoryTreeItem::FilterActionItemType: return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + case HistoryTreeItem::HeaderItemType: case HistoryTreeItem::CategoryItemType: case HistoryTreeItem::SeparatorItemType: default: return Qt::ItemIsEnabled; } } return Qt::ItemIsEnabled; } -QModelIndex ItemHistoryGraphModel::index(int row, int column , const QModelIndex& parent) const +QModelIndex ItemHistoryGraphModel::index(int row, int column, const QModelIndex& parent) const { - if (column != 0 || row < 0) + if ((column != 0) || (row < 0)) { return QModelIndex(); } HistoryTreeItem* const item = d->historyItem(parent); if (row >= item->childCount()) { return QModelIndex(); } return createIndex(row, 0, item->child(row)); } bool ItemHistoryGraphModel::hasChildren(const QModelIndex& parent) const { return d->historyItem(parent)->childCount(); } QModelIndex ItemHistoryGraphModel::parent(const QModelIndex& index) const { HistoryTreeItem* const item = d->historyItem(index); HistoryTreeItem* const parent = item->parent; if (!parent) { return QModelIndex(); // index was an invalid index } HistoryTreeItem* const grandparent = parent->parent; if (!grandparent) { return QModelIndex(); // index was a top-level index, was the invisible rootItem as parent } return createIndex(grandparent->children.indexOf(parent), 0, parent); } } // namespace Digikam diff --git a/core/libs/database/history/itemhistorygraphmodel.h b/core/libs/database/history/itemhistorygraphmodel.h index 62a832c8b1..64daa7bb11 100644 --- a/core/libs/database/history/itemhistorygraphmodel.h +++ b/core/libs/database/history/itemhistorygraphmodel.h @@ -1,134 +1,134 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-10-27 * Description : Model to an ItemHistoryGraph * * Copyright (C) 2010-2011 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_ITEM_HISTORY_GRAPH_MODEL_H #define DIGIKAM_ITEM_HISTORY_GRAPH_MODEL_H // Qt includes #include // Local includes #include "dragdropimplementations.h" #include "itemhistorygraph.h" #include "digikam_export.h" namespace Digikam { class ItemHistoryGraph; class ItemInfo; class ItemListModel; class FilterAction; class DIGIKAM_DATABASE_EXPORT ItemHistoryGraphModel : public QAbstractItemModel, public DragDropModelImplementation { Q_OBJECT public: enum Mode { ImagesListMode, ImagesTreeMode, CombinedTreeMode }; enum ExtraRoles { IsImageItemRole = Qt::UserRole + 1000, IsFilterActionItemRole = Qt::UserRole + 1001, IsHeaderItemRole = Qt::UserRole + 1002, IsCategoryItemRole = Qt::UserRole + 1003, IsSeparatorItemRole = Qt::UserRole + 1004, IsSubjectImageRole = Qt::UserRole + 1010, FilterActionRole = Qt::UserRole + 1020 }; public: explicit ItemHistoryGraphModel(QObject* const parent = nullptr); ~ItemHistoryGraphModel(); void setMode(Mode mode); - Mode mode() const; + Mode mode() const; /** * Set the history subject and the history graph. * Per default, the subject's history graph is read. */ void setHistory(const ItemInfo& subject, const ItemHistoryGraph& graph = ItemHistoryGraph()); - ItemInfo subject() const; + ItemInfo subject() const; - bool isImage(const QModelIndex& index) const; + bool isImage(const QModelIndex& index) const; bool hasImage(const ItemInfo& info); - ItemInfo imageInfo(const QModelIndex& index) const; + ItemInfo imageInfo(const QModelIndex& index) const; /// Note: There may be multiple indexes for an info. The index found first is returned. - QModelIndex indexForInfo(const ItemInfo& info) const; + QModelIndex indexForInfo(const ItemInfo& info) const; - bool isFilterAction(const QModelIndex& index) const; - FilterAction filterAction(const QModelIndex& index) const; + bool isFilterAction(const QModelIndex& index) const; + FilterAction filterAction(const QModelIndex& index) const; ///@{ // QAbstractItemModel implementation virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; virtual Qt::ItemFlags flags(const QModelIndex& index) const override; virtual bool hasChildren(const QModelIndex& parent = QModelIndex()) const override; virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; virtual QModelIndex parent(const QModelIndex& index) const override; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override; ///@} DECLARE_MODEL_DRAG_DROP_METHODS /** * Returns an internal image model used for entries representing images. * Note: Set a thumbnail thread on this model if you need thumbnails. */ - ItemListModel* imageModel() const; + ItemListModel* imageModel() const; /** * If the given index is represented by the internal image model, * return the image model's index. * Otherwise an invalid index is returned. */ - QModelIndex imageModelIndex(const QModelIndex& index) const; + QModelIndex imageModelIndex(const QModelIndex& index) const; private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_ITEM_HISTORY_GRAPH_MODEL_H