File indexing completed on 2024-04-28 15:39:58
0001 // SPDX-FileCopyrightText: 2003 - 2022 Jesper K. Pedersen <blackie@kde.org> 0002 // SPDX-FileCopyrightText: 2023 - 2024 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0003 // 0004 // SPDX-License-Identifier: GPL-2.0-or-later 0005 0006 #ifndef MEMBERMAP_H 0007 #define MEMBERMAP_H 0008 #include <kpabase/StringSet.h> 0009 0010 #include <qmap.h> 0011 #include <qobject.h> 0012 #include <qstringlist.h> 0013 0014 namespace DB 0015 { 0016 using Utilities::StringSet; 0017 0018 class Category; 0019 0020 /** 0021 * @brief The MemberMap class represents tag groups for all categories. 0022 * The MemberMap can handle direct tag group membership, and also computes the membership closure (i.e. indirect membership). 0023 * 0024 * Tag groups in a category are a directed acyclic graph (DAG). 0025 * All MemberMap functions preserve the validity of the DAG (after loading the database is completed). 0026 */ 0027 class MemberMap : public QObject 0028 { 0029 Q_OBJECT 0030 public: 0031 MemberMap(); 0032 MemberMap(const MemberMap &); 0033 virtual MemberMap &operator=(const MemberMap &); 0034 0035 // TODO: this should return a StringSet 0036 /** 0037 * @brief groups 0038 * @param category the category name 0039 * @return the groups directly available from category (non closure that is) 0040 */ 0041 virtual QStringList groups(const QString &category) const; 0042 /** 0043 * @brief deleteGroup deletes the a group from the category. 0044 * Does nothing if category does not have any groups or groupName is not a member of category. 0045 * @param category the category name 0046 * @param groupName 0047 */ 0048 virtual void deleteGroup(const QString &category, const QString &groupName); 0049 0050 // TODO: this should return a StringSet 0051 /** 0052 * @brief members 0053 * 0054 * @param category the name of the category 0055 * @param memberGroup the name of the tag group 0056 * @param closure if \c true, return indirect members as well. If \c false, only return direct members. 0057 * 0058 * @return all the members of memberGroup 0059 */ 0060 virtual QStringList members(const QString &category, const QString &memberGroup, bool closure) const; 0061 /** 0062 * @brief setMembers set (direct) members for a tag group, ensuring no cycles in the group membership DAG. 0063 * 0064 * If the group already exists, its contents are replaced. 0065 * 0066 * @param category a valid category name 0067 * @param memberGroup a tag group name 0068 * @param members a list of tags 0069 */ 0070 virtual void setMembers(const QString &category, const QString &memberGroup, const QStringList &members); 0071 /** 0072 * @brief isEmpty 0073 * @return \c true, if the MemberMap is empty, \c false otherwise. 0074 */ 0075 virtual bool isEmpty() const; 0076 /** 0077 * @brief isGroup 0078 * @param category 0079 * @param memberGroup 0080 * @return \c true if item is a group for category, \c false otherwise. 0081 */ 0082 virtual bool isGroup(const QString &category, const QString &memberGroup) const; 0083 /** 0084 * @brief groupMap returns a map of all groups in the given category. 0085 * Note: the returned map contains direct and indirect members for each group (i.e. all closure members). 0086 * 0087 * Example: { USA |-> [Chicago, Grand Canyon, Santa Clara], Denmark |-> [Esbjerg, Odense] } 0088 * 0089 * @param category 0090 * @return a map from groupName to list of items for category, or an empty map if category has no groups. 0091 */ 0092 virtual QMap<QString, StringSet> groupMap(const QString &category) const; 0093 0094 // TODO(jzarl): document MemberMap::inverseMap 0095 virtual QMap<QString, StringSet> inverseMap(const QString &category) const; 0096 /** 0097 * @brief renameGroup renames a group in category. 0098 * Does nothing, if category does not have any groups, if oldName is not a member of category, or if newName does already exist. 0099 * @param category the category name 0100 * The string \c newName is trimmed to disallow leading or trailing whitespace in names. 0101 * @param oldName 0102 * @param newName 0103 */ 0104 virtual void renameGroup(const QString &category, const QString &oldName, const QString &newName); 0105 /** 0106 * @brief renameCategory renames the category from oldName to newName 0107 * Does nothing, if the oldName does not have any groups, if oldName is the same as newName, or if newName already exists. 0108 * The string \c newName is trimmed to disallow leading or trailing whitespace in names. 0109 * @param oldName 0110 * @param newName 0111 */ 0112 virtual void renameCategory(const QString &oldName, const QString &newName); 0113 0114 /** 0115 * @brief addGroup adds the tag group to category. 0116 * The string \c group is trimmed to disallow leading or trailing whitespace in names. 0117 * Does nothing if the group name is empty. 0118 * @param category the category name 0119 * @param group the tag group name 0120 */ 0121 virtual void addGroup(const QString &category, const QString &group); 0122 /** 0123 * @brief canAddMemberToGroup checks if adding a tag to a group is possible. 0124 * Adding a tag to a group is not possible, if doing so would create a cycle in the group membership DAG. 0125 * @param category 0126 * @param group 0127 * @param item 0128 * @return \c true, if it is possible to add a tag to the group, or \c false otherwise. 0129 */ 0130 bool canAddMemberToGroup(const QString &category, const QString &group, const QString &item) const 0131 { 0132 // If there already is a path from item to group then adding the 0133 // item to group would create a cycle, which we don't want. 0134 return !hasPath(category, item, group); 0135 } 0136 virtual void addMemberToGroup(const QString &category, const QString &group, const QString &item); 0137 /** 0138 * @brief removeMemberFromGroup removes a tag from a tag group in the given category. 0139 * Do nothing if the category or group is not known, or if the tag does not exist. 0140 * @param category 0141 * @param group the name of the tag group 0142 * @param item the name of the tag 0143 */ 0144 virtual void removeMemberFromGroup(const QString &category, const QString &group, const QString &item); 0145 0146 /** 0147 * @brief memberMap 0148 * @return the raw member map data 0149 */ 0150 virtual const QMap<QString, QMap<QString, StringSet>> &memberMap() const { return m_members; } 0151 0152 /** 0153 * @brief hasPath checks if the group directly or indirectly contains the tag. 0154 * 0155 * @param category a category name 0156 * @param from a group name 0157 * @param to a tag name 0158 * @return \c true if tag \c to can be reached from the \c from group in the group membership DAG, \c false otherwise. 0159 */ 0160 virtual bool hasPath(const QString &category, const QString &from, const QString &to) const; 0161 /** 0162 * @brief contains checks if item is a member of category. 0163 * @param category 0164 * @param item 0165 * @return \c true, if item is contained in category. 0166 */ 0167 virtual bool contains(const QString &category, const QString &item) const; 0168 0169 protected: 0170 /** 0171 * @brief Recalculate indirect group memberships. 0172 * 0173 * This methods create the map _closureMembers from _members 0174 * This is to avoid finding the closure each and every time it is needed. 0175 * 0176 * This method needs to be called whenever indirect group membership is affected by an action. 0177 */ 0178 void calculate() const; 0179 /** 0180 * @brief calculateClosure calculates the closure for group, that is finds all members for group. 0181 * 0182 * Imagine there is a group called USA, and that this groups has a group inside it called Califonia, 0183 * Califonia consists of members San Fransisco and Los Angeless. 0184 * This function then maps USA to include Califonia, San Fransisco and Los Angeless. 0185 * @param resultSoFar 0186 * @param category 0187 * @param group 0188 * @return 0189 */ 0190 QStringList calculateClosure(QMap<QString, StringSet> &resultSoFar, const QString &category, const QString &group) const; 0191 0192 public Q_SLOTS: 0193 /** 0194 * @brief deleteCategory deletes the category and its groups from the MemberMap 0195 * Does nothing if category does not have any groups. 0196 * @param category 0197 */ 0198 virtual void deleteCategory(const QString &category); 0199 /** 0200 * @brief deleteItem deletes a tag from the category 0201 * Does nothing if the category is not known, or if the tag does not exist. 0202 * @param category a valid pointer to a category 0203 * @param name name of the tag 0204 */ 0205 virtual void deleteItem(DB::Category *category, const QString &name); 0206 /** 0207 * @brief renameItem renames a tag of the given category. 0208 * Does nothing if the category is not known, if oldName does not exist, or if the new name is the same as the old name. 0209 * The string \c newName is trimmed to disallow leading or trailing whitespace in names. 0210 * @param category a valid pointer to a category 0211 * @param oldName 0212 * @param newName 0213 */ 0214 virtual void renameItem(DB::Category *category, const QString &oldName, const QString &newName); 0215 /** 0216 * @brief setLoading tells the MemberMap to avoid costly calculations and checks while the database is loaded from disk. 0217 * @param isLoading 0218 */ 0219 void setLoading(bool isLoading); 0220 0221 Q_SIGNALS: 0222 void dirty(); 0223 0224 private: 0225 void markDirty(const QString &category); 0226 void regenerateFlatList(const QString &category); 0227 // This is the primary data structure 0228 // { category |-> { group |-> [ member ] } } <- VDM syntax ;-) 0229 QMap<QString, QMap<QString, StringSet>> m_members; 0230 mutable QMap<QString, QSet<QString>> m_flatMembers; 0231 0232 // These are the data structures used to develop closures, they are only 0233 // needed to speed up the program *SIGNIFICANTLY* ;-) 0234 mutable bool m_dirty; 0235 mutable QMap<QString, QMap<QString, StringSet>> m_closureMembers; 0236 0237 bool m_loading; 0238 }; 0239 } 0240 0241 #endif /* MEMBERMAP_H */ 0242 0243 // vi:expandtab:tabstop=4 shiftwidth=4: