2
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
3
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
5
* This program is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation, either version 2 or (at your option)
8
* version 3 of the License.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20
#include "config-keepassx.h"
22
#include "core/Config.h"
24
#ifdef WITH_XC_KEESHARE
25
#include "keeshare/KeeShare.h"
28
#include "core/Global.h"
29
#include "core/Metadata.h"
30
#include "core/Tools.h"
32
#include <QtConcurrent>
33
#include <QtConcurrentFilter>
35
const int Group::DefaultIconNumber = 48;
36
const int Group::OpenFolderIconNumber = 49;
37
const int Group::RecycleBinIconNumber = 43;
38
const QString Group::RootAutoTypeSequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}";
41
: m_customData(new CustomData(this))
42
, m_updateTimeinfo(true)
44
m_data.iconNumber = DefaultIconNumber;
45
m_data.isExpanded = true;
46
m_data.autoTypeEnabled = Inherit;
47
m_data.searchingEnabled = Inherit;
48
m_data.mergeMode = Default;
50
connect(m_customData, &CustomData::modified, this, &Group::modified);
51
connect(this, &Group::modified, this, &Group::updateTimeinfo);
52
connect(this, &Group::groupNonDataChange, this, &Group::updateTimeinfo);
57
setUpdateTimeinfo(false);
58
// Destroy entries and children manually so DeletedObjects can be added
60
const QList<Entry*> entries = m_entries;
61
for (Entry* entry : entries) {
65
const QList<Group*> children = m_children;
66
for (Group* group : children) {
70
if (m_db && m_parent) {
71
DeletedObject delGroup;
72
delGroup.deletionTime = Clock::currentDateTimeUtc();
73
delGroup.uuid = m_uuid;
74
m_db->addDeletedObject(delGroup);
80
template <class P, class V> inline bool Group::set(P& property, const V& value)
82
if (property != value) {
91
bool Group::canUpdateTimeinfo() const
93
return m_updateTimeinfo;
96
void Group::updateTimeinfo()
98
if (m_updateTimeinfo) {
99
m_data.timeInfo.setLastModificationTime(Clock::currentDateTimeUtc());
100
m_data.timeInfo.setLastAccessTime(Clock::currentDateTimeUtc());
104
void Group::setUpdateTimeinfo(bool value)
106
m_updateTimeinfo = value;
109
const QUuid& Group::uuid() const
114
const QString Group::uuidToHex() const
116
return Tools::uuidToHex(m_uuid);
119
QString Group::name() const
124
QString Group::notes() const
129
QString Group::tags() const
134
QString Group::fullPath() const
140
fullPath.insert(0, "/" + group->name());
141
group = group->parentGroup();
147
int Group::iconNumber() const
149
return m_data.iconNumber;
152
const QUuid& Group::iconUuid() const
154
return m_data.customIcon;
157
const TimeInfo& Group::timeInfo() const
159
return m_data.timeInfo;
162
bool Group::isExpanded() const
164
return m_data.isExpanded;
167
QString Group::defaultAutoTypeSequence() const
169
return m_data.defaultAutoTypeSequence;
173
* Determine the effective sequence that will be injected
174
* This function return an empty string if the current group or any parent has autotype disabled
176
QString Group::effectiveAutoTypeSequence() const
180
const Group* group = this;
182
if (group->autoTypeEnabled() == Group::Disable) {
186
sequence = group->defaultAutoTypeSequence();
187
group = group->parentGroup();
188
} while (group && sequence.isEmpty());
190
if (sequence.isEmpty()) {
191
sequence = RootAutoTypeSequence;
197
Group::TriState Group::autoTypeEnabled() const
199
return m_data.autoTypeEnabled;
202
Group::TriState Group::searchingEnabled() const
204
return m_data.searchingEnabled;
207
Group::MergeMode Group::mergeMode() const
209
if (m_data.mergeMode == Group::MergeMode::Default) {
211
return m_parent->mergeMode();
213
return Group::MergeMode::KeepNewer; // fallback
215
return m_data.mergeMode;
218
Entry* Group::lastTopVisibleEntry() const
220
return m_lastTopVisibleEntry;
223
bool Group::isRecycled() const
226
auto db = group->database();
228
auto recycleBin = db->metadata()->recycleBin();
230
if (group == recycleBin) {
233
group = group->m_parent;
240
bool Group::isExpired() const
242
return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < Clock::currentDateTimeUtc();
245
bool Group::isEmpty() const
247
return !hasChildren() && m_entries.isEmpty();
250
CustomData* Group::customData()
255
const CustomData* Group::customData() const
260
Group::TriState Group::resolveCustomDataTriState(const QString& key, bool checkParent) const
262
// If not defined, check our parent up to the root group
263
if (!m_customData->contains(key)) {
264
if (!m_parent || !checkParent) {
267
return m_parent->resolveCustomDataTriState(key);
271
return m_customData->value(key) == TRUE_STR ? Enable : Disable;
274
void Group::setCustomDataTriState(const QString& key, const Group::TriState& value)
278
m_customData->set(key, TRUE_STR);
281
m_customData->set(key, FALSE_STR);
284
m_customData->remove(key);
289
// Note that this returns an empty string both if the key is missing *or* if the key is present but value is empty.
290
QString Group::resolveCustomDataString(const QString& key, bool checkParent) const
292
// If not defined, check our parent up to the root group
293
if (!m_customData->contains(key)) {
294
if (!m_parent || !checkParent) {
297
return m_parent->resolveCustomDataString(key);
301
return m_customData->value(key);
304
bool Group::equals(const Group* other, CompareItemOptions options) const
309
if (m_uuid != other->m_uuid) {
312
if (!m_data.equals(other->m_data, options)) {
315
if (m_customData != other->m_customData) {
318
if (m_children.count() != other->m_children.count()) {
321
if (m_entries.count() != other->m_entries.count()) {
324
for (int i = 0; i < m_children.count(); ++i) {
325
if (m_children[i]->uuid() != other->m_children[i]->uuid()) {
329
for (int i = 0; i < m_entries.count(); ++i) {
330
if (m_entries[i]->uuid() != other->m_entries[i]->uuid()) {
337
void Group::setUuid(const QUuid& uuid)
342
void Group::setName(const QString& name)
344
if (set(m_data.name, name)) {
345
emit groupDataChanged(this);
349
void Group::setNotes(const QString& notes)
351
set(m_data.notes, notes);
354
void Group::setTags(const QString& tags)
356
set(m_data.tags, tags);
359
void Group::setIcon(int iconNumber)
361
if (iconNumber >= 0 && (m_data.iconNumber != iconNumber || !m_data.customIcon.isNull())) {
362
m_data.iconNumber = iconNumber;
363
m_data.customIcon = QUuid();
365
emit groupDataChanged(this);
369
void Group::setIcon(const QUuid& uuid)
371
if (!uuid.isNull() && m_data.customIcon != uuid) {
372
m_data.customIcon = uuid;
373
m_data.iconNumber = 0;
375
emit groupDataChanged(this);
379
void Group::setTimeInfo(const TimeInfo& timeInfo)
381
m_data.timeInfo = timeInfo;
384
void Group::setExpanded(bool expanded)
386
if (m_data.isExpanded != expanded) {
387
m_data.isExpanded = expanded;
388
emit groupNonDataChange();
392
void Group::setDefaultAutoTypeSequence(const QString& sequence)
394
set(m_data.defaultAutoTypeSequence, sequence);
397
void Group::setAutoTypeEnabled(TriState enable)
399
set(m_data.autoTypeEnabled, enable);
402
void Group::setSearchingEnabled(TriState enable)
404
set(m_data.searchingEnabled, enable);
407
void Group::setLastTopVisibleEntry(Entry* entry)
409
set(m_lastTopVisibleEntry, entry);
412
void Group::setExpires(bool value)
414
if (m_data.timeInfo.expires() != value) {
415
m_data.timeInfo.setExpires(value);
420
void Group::setExpiryTime(const QDateTime& dateTime)
422
if (m_data.timeInfo.expiryTime() != dateTime) {
423
m_data.timeInfo.setExpiryTime(dateTime);
428
void Group::setMergeMode(MergeMode newMode)
430
set(m_data.mergeMode, newMode);
433
Group* Group::parentGroup()
438
const Group* Group::parentGroup() const
443
void Group::setParent(Group* parent, int index, bool trackPrevious)
446
Q_ASSERT(index >= -1 && index <= parent->children().size());
447
// setting a new parent for root groups is not allowed
448
Q_ASSERT(!m_db || (m_db->rootGroup() != this));
450
bool moveWithinDatabase = (m_db && m_db == parent->m_db);
453
index = parent->children().size();
455
if (parentGroup() == parent) {
460
if (m_parent == parent && parent->children().indexOf(this) == index) {
464
if (!moveWithinDatabase) {
468
setPreviousParentGroup(nullptr);
469
recCreateDelObjects();
471
// copy custom icon to the new database
472
if (!iconUuid().isNull() && parent->m_db && m_db->metadata()->hasCustomIcon(iconUuid())
473
&& !parent->m_db->metadata()->hasCustomIcon(iconUuid())) {
474
parent->m_db->metadata()->addCustomIcon(iconUuid(), m_db->metadata()->customIcon(iconUuid()));
477
if (m_db != parent->m_db) {
478
connectDatabaseSignalsRecursive(parent->m_db);
480
QObject::setParent(parent);
481
emit groupAboutToAdd(this, index);
482
Q_ASSERT(index <= parent->m_children.size());
483
parent->m_children.insert(index, this);
485
emit aboutToMove(this, parent, index);
486
if (trackPrevious && m_parent != parent) {
487
setPreviousParentGroup(m_parent);
489
m_parent->m_children.removeAll(this);
491
QObject::setParent(parent);
492
Q_ASSERT(index <= parent->m_children.size());
493
parent->m_children.insert(index, this);
496
if (m_updateTimeinfo) {
497
m_data.timeInfo.setLocationChanged(Clock::currentDateTimeUtc());
502
if (!moveWithinDatabase) {
509
void Group::setParent(Database* db)
512
Q_ASSERT(db->rootGroup() == this);
517
connectDatabaseSignalsRecursive(db);
519
QObject::setParent(db);
522
QStringList Group::hierarchy(int height) const
524
QStringList hierarchy;
525
const Group* group = this;
526
const Group* parent = m_parent;
532
hierarchy.prepend(group->name());
535
bool heightReached = level == height;
537
while (parent && !heightReached) {
538
group = group->parentGroup();
539
parent = group->parentGroup();
540
heightReached = ++level == height;
542
hierarchy.prepend(group->name());
548
bool Group::hasChildren() const
550
return !children().isEmpty();
553
Database* Group::database()
558
const Database* Group::database() const
563
QList<Group*> Group::children()
568
const QList<Group*>& Group::children() const
573
QList<Entry*> Group::entries()
578
const QList<Entry*>& Group::entries() const
583
QList<Entry*> Group::entriesRecursive(bool includeHistoryItems) const
585
QList<Entry*> entryList;
587
entryList.append(m_entries);
589
if (includeHistoryItems) {
590
for (Entry* entry : m_entries) {
591
entryList.append(entry->historyItems());
595
for (Group* group : m_children) {
596
entryList.append(group->entriesRecursive(includeHistoryItems));
602
QList<Entry*> Group::referencesRecursive(const Entry* entry) const
604
auto entries = entriesRecursive();
605
return QtConcurrent::blockingFiltered(entries,
606
[entry](const Entry* e) { return e->hasReferencesTo(entry->uuid()); });
609
Entry* Group::findEntryByUuid(const QUuid& uuid, bool recursive) const
615
auto entries = m_entries;
617
entries = entriesRecursive(false);
620
for (auto entry : entries) {
621
if (entry->uuid() == uuid) {
629
Entry* Group::findEntryByPath(const QString& entryPath) const
631
if (entryPath.isEmpty()) {
635
// Add a beginning slash if the search string contains a slash
636
// We don't add a slash by default to allow searching by entry title
637
QString normalizedEntryPath = entryPath;
638
if (!normalizedEntryPath.startsWith("/") && normalizedEntryPath.contains("/")) {
639
normalizedEntryPath = "/" + normalizedEntryPath;
641
return findEntryByPathRecursive(normalizedEntryPath, "/");
644
Entry* Group::findEntryBySearchTerm(const QString& term, EntryReferenceType referenceType)
646
Q_ASSERT_X(referenceType != EntryReferenceType::Unknown,
647
"Database::findEntryRecursive",
648
"Can't search entry with \"referenceType\" parameter equal to \"Unknown\"");
650
const QList<Group*> groups = groupsRecursive(true);
652
for (const Group* group : groups) {
654
const QList<Entry*>& entryList = group->entries();
655
for (Entry* entry : entryList) {
656
switch (referenceType) {
657
case EntryReferenceType::Unknown:
659
case EntryReferenceType::Title:
660
found = entry->title() == term;
662
case EntryReferenceType::UserName:
663
found = entry->username() == term;
665
case EntryReferenceType::Password:
666
found = entry->password() == term;
668
case EntryReferenceType::Url:
669
found = entry->url() == term;
671
case EntryReferenceType::Notes:
672
found = entry->notes() == term;
674
case EntryReferenceType::QUuid:
675
found = entry->uuid() == QUuid::fromRfc4122(QByteArray::fromHex(term.toLatin1()));
677
case EntryReferenceType::CustomAttributes:
678
found = entry->attributes()->containsValue(term);
691
Entry* Group::findEntryByPathRecursive(const QString& entryPath, const QString& basePath) const
693
// Return the first entry that matches the full path OR if there is no leading
694
// slash, return the first entry title that matches
695
for (Entry* entry : entries()) {
697
if (entryPath == (basePath + entry->title())
698
|| (!entryPath.startsWith("/") && entry->title() == entryPath)) {
704
for (Group* group : children()) {
705
Entry* entry = group->findEntryByPathRecursive(entryPath, basePath + group->name() + "/");
706
if (entry != nullptr) {
714
Group* Group::findGroupByPath(const QString& groupPath)
716
// normalize the groupPath by adding missing front and rear slashes. once.
717
QString normalizedGroupPath;
719
if (groupPath.isEmpty()) {
720
normalizedGroupPath = QString("/"); // root group
723
normalizedGroupPath = (groupPath.startsWith("/") ? "" : "/")
725
+ (groupPath.endsWith("/") ? "" : "/");
728
return findGroupByPathRecursive(normalizedGroupPath, "/");
731
Group* Group::findGroupByPathRecursive(const QString& groupPath, const QString& basePath)
733
// paths must be normalized
734
Q_ASSERT(groupPath.startsWith("/") && groupPath.endsWith("/"));
735
Q_ASSERT(basePath.startsWith("/") && basePath.endsWith("/"));
737
if (groupPath == basePath) {
741
for (Group* innerGroup : children()) {
742
QString innerBasePath = basePath + innerGroup->name() + "/";
743
Group* group = innerGroup->findGroupByPathRecursive(groupPath, innerBasePath);
744
if (group != nullptr) {
752
QString Group::print(bool recursive, bool flatten, int depth)
758
const QString separator("/");
759
prefix = hierarchy(depth).join(separator);
760
if (!prefix.isEmpty()) {
764
prefix = QString(" ").repeated(depth);
767
if (entries().isEmpty() && children().isEmpty()) {
768
response += prefix + tr("[empty]", "group has no children") + "\n";
772
for (Entry* entry : entries()) {
773
response += prefix + entry->title() + "\n";
776
for (Group* innerGroup : children()) {
777
response += prefix + innerGroup->name() + "/\n";
779
response += innerGroup->print(recursive, flatten, depth + 1);
786
QList<const Group*> Group::groupsRecursive(bool includeSelf) const
788
QList<const Group*> groupList;
790
groupList.append(this);
793
for (const Group* group : asConst(m_children)) {
794
groupList.append(group->groupsRecursive(true));
800
QList<Group*> Group::groupsRecursive(bool includeSelf)
802
QList<Group*> groupList;
804
groupList.append(this);
807
for (Group* group : asConst(m_children)) {
808
groupList.append(group->groupsRecursive(true));
814
QSet<QUuid> Group::customIconsRecursive() const
818
if (!iconUuid().isNull()) {
819
result.insert(iconUuid());
822
const QList<Entry*> entryList = entriesRecursive(true);
823
for (Entry* entry : entryList) {
824
if (!entry->iconUuid().isNull()) {
825
result.insert(entry->iconUuid());
829
for (Group* group : m_children) {
830
result.unite(group->customIconsRecursive());
836
QList<QString> Group::usernamesRecursive(int topN) const
838
// Collect all usernames and sort for easy counting
839
QHash<QString, int> countedUsernames;
840
for (const auto* entry : entriesRecursive()) {
841
const auto username = entry->username();
842
if (!username.isEmpty() && !entry->isAttributeReference(EntryAttributes::UserNameKey)) {
843
countedUsernames.insert(username, ++countedUsernames[username]);
847
// Sort username/frequency pairs by frequency and name
848
QList<QPair<QString, int>> sortedUsernames;
849
for (const auto& key : countedUsernames.keys()) {
850
sortedUsernames.append({key, countedUsernames[key]});
853
auto comparator = [](const QPair<QString, int>& arg1, const QPair<QString, int>& arg2) {
854
if (arg1.second == arg2.second) {
855
return arg1.first < arg2.first;
857
return arg1.second > arg2.second;
860
std::sort(sortedUsernames.begin(), sortedUsernames.end(), comparator);
862
// Take first topN usernames if set
863
QList<QString> usernames;
864
int actualUsernames = topN < 0 ? sortedUsernames.size() : std::min(topN, sortedUsernames.size());
865
for (int i = 0; i < actualUsernames; i++) {
866
usernames.append(sortedUsernames[i].first);
872
Group* Group::findGroupByUuid(const QUuid& uuid)
878
for (Group* group : groupsRecursive(true)) {
879
if (group->uuid() == uuid) {
887
const Group* Group::findGroupByUuid(const QUuid& uuid) const
893
for (const Group* group : groupsRecursive(true)) {
894
if (group->uuid() == uuid) {
902
Group* Group::findChildByName(const QString& name)
904
for (Group* group : asConst(m_children)) {
905
if (group->name() == name) {
914
* Creates a duplicate of this group.
915
* Note that you need to copy the custom icons manually when inserting the
916
* new group into another database.
918
Group* Group::clone(Entry::CloneFlags entryFlags, Group::CloneFlags groupFlags) const
920
auto clonedGroup = new Group();
922
clonedGroup->setUpdateTimeinfo(false);
924
if (groupFlags & Group::CloneNewUuid) {
925
clonedGroup->setUuid(QUuid::createUuid());
927
clonedGroup->setUuid(this->uuid());
930
clonedGroup->m_data = m_data;
931
clonedGroup->m_customData->copyDataFrom(m_customData);
933
if (groupFlags & Group::CloneIncludeEntries) {
934
const QList<Entry*> entryList = entries();
935
for (Entry* entry : entryList) {
936
Entry* clonedEntry = entry->clone(entryFlags);
937
clonedEntry->setGroup(clonedGroup);
940
const QList<Group*> childrenGroups = children();
941
for (Group* groupChild : childrenGroups) {
942
Group* clonedGroupChild = groupChild->clone(entryFlags, groupFlags);
943
clonedGroupChild->setParent(clonedGroup);
947
clonedGroup->setUpdateTimeinfo(true);
948
if (groupFlags & Group::CloneResetTimeInfo) {
950
QDateTime now = Clock::currentDateTimeUtc();
951
clonedGroup->m_data.timeInfo.setCreationTime(now);
952
clonedGroup->m_data.timeInfo.setLastModificationTime(now);
953
clonedGroup->m_data.timeInfo.setLastAccessTime(now);
954
clonedGroup->m_data.timeInfo.setLocationChanged(now);
957
if (groupFlags & Group::CloneRenameTitle) {
958
clonedGroup->setName(tr("%1 - Clone").arg(name()));
964
void Group::copyDataFrom(const Group* other)
966
if (set(m_data, other->m_data)) {
967
emit groupDataChanged(this);
969
m_customData->copyDataFrom(other->m_customData);
970
m_lastTopVisibleEntry = other->m_lastTopVisibleEntry;
973
void Group::addEntry(Entry* entry)
976
Q_ASSERT(!m_entries.contains(entry));
978
emit entryAboutToAdd(entry);
981
connect(entry, &Entry::entryDataChanged, this, &Group::entryDataChanged);
983
connect(entry, &Entry::modified, m_db, &Database::markAsModified);
987
emit entryAdded(entry);
990
void Group::removeEntry(Entry* entry)
992
Q_ASSERT_X(m_entries.contains(entry),
994
QString("Group %1 does not contain %2").arg(this->name(), entry->title()).toLatin1());
996
emit entryAboutToRemove(entry);
998
entry->disconnect(this);
1000
entry->disconnect(m_db);
1002
m_entries.removeAll(entry);
1004
emit entryRemoved(entry);
1007
void Group::moveEntryUp(Entry* entry)
1009
int row = m_entries.indexOf(entry);
1014
emit entryAboutToMoveUp(row);
1015
m_entries.move(row, row - 1);
1016
emit entryMovedUp();
1017
emit groupNonDataChange();
1020
void Group::moveEntryDown(Entry* entry)
1022
int row = m_entries.indexOf(entry);
1023
if (row >= m_entries.size() - 1) {
1027
emit entryAboutToMoveDown(row);
1028
m_entries.move(row, row + 1);
1029
emit entryMovedDown();
1030
emit groupNonDataChange();
1033
void Group::connectDatabaseSignalsRecursive(Database* db)
1039
for (Entry* entry : asConst(m_entries)) {
1041
entry->disconnect(m_db);
1044
connect(entry, &Entry::modified, db, &Database::markAsModified);
1050
connect(this, &Group::groupDataChanged, db, &Database::groupDataChanged);
1051
connect(this, &Group::groupAboutToRemove, db, &Database::groupAboutToRemove);
1052
connect(this, &Group::groupRemoved, db, &Database::groupRemoved);
1053
connect(this, &Group::groupAboutToAdd, db, &Database::groupAboutToAdd);
1054
connect(this, &Group::groupAdded, db, &Database::groupAdded);
1055
connect(this, &Group::aboutToMove, db, &Database::groupAboutToMove);
1056
connect(this, &Group::groupMoved, db, &Database::groupMoved);
1057
connect(this, &Group::groupNonDataChange, db, &Database::markNonDataChange);
1058
connect(this, &Group::modified, db, &Database::markAsModified);
1064
for (Group* group : asConst(m_children)) {
1065
group->connectDatabaseSignalsRecursive(db);
1069
void Group::cleanupParent()
1072
emit groupAboutToRemove(this);
1073
m_parent->m_children.removeAll(this);
1075
emit groupRemoved();
1079
void Group::recCreateDelObjects()
1082
for (Entry* entry : asConst(m_entries)) {
1083
m_db->addDeletedObject(entry->uuid());
1086
for (Group* group : asConst(m_children)) {
1087
group->recCreateDelObjects();
1089
m_db->addDeletedObject(m_uuid);
1093
bool Group::resolveSearchingEnabled() const
1095
switch (m_data.searchingEnabled) {
1100
return m_parent->resolveSearchingEnabled();
1112
bool Group::resolveAutoTypeEnabled() const
1114
switch (m_data.autoTypeEnabled) {
1119
return m_parent->resolveAutoTypeEnabled();
1131
Entry* Group::addEntryWithPath(const QString& entryPath)
1133
if (entryPath.isEmpty() || findEntryByPath(entryPath)) {
1137
QStringList groups = entryPath.split("/");
1138
QString entryTitle = groups.takeLast();
1139
QString groupPath = groups.join("/");
1141
Group* group = findGroupByPath(groupPath);
1146
auto* entry = new Entry();
1147
entry->setTitle(entryTitle);
1148
entry->setUuid(QUuid::createUuid());
1149
entry->setGroup(group);
1154
void Group::applyGroupIconOnCreateTo(Entry* entry)
1158
if (!config()->get(Config::UseGroupIconOnEntryCreation).toBool()) {
1162
if ((iconNumber() == Group::DefaultIconNumber || iconNumber() == Group::OpenFolderIconNumber)
1163
&& iconUuid().isNull()) {
1167
applyGroupIconTo(entry);
1170
void Group::applyGroupIconTo(Entry* entry)
1174
if (iconUuid().isNull()) {
1175
entry->setIcon(iconNumber());
1177
entry->setIcon(iconUuid());
1181
void Group::applyGroupIconTo(Group* other)
1185
if (iconUuid().isNull()) {
1186
other->setIcon(iconNumber());
1188
other->setIcon(iconUuid());
1192
void Group::applyGroupIconToChildGroups()
1194
for (Group* recursiveChild : groupsRecursive(false)) {
1195
applyGroupIconTo(recursiveChild);
1199
void Group::applyGroupIconToChildEntries()
1201
for (Entry* recursiveEntry : entriesRecursive(false)) {
1202
applyGroupIconTo(recursiveEntry);
1206
void Group::sortChildrenRecursively(bool reverse)
1208
Group* recycleBin = nullptr;
1210
recycleBin = database()->metadata()->recycleBin();
1212
std::sort(m_children.begin(), m_children.end(), [=](const Group* childGroup1, const Group* childGroup2) -> bool {
1213
if (childGroup1 == recycleBin) {
1216
QString name1 = childGroup1->name();
1217
QString name2 = childGroup2->name();
1218
return reverse ? name1.compare(name2, Qt::CaseInsensitive) > 0 : name1.compare(name2, Qt::CaseInsensitive) < 0;
1221
for (auto child : m_children) {
1222
child->sortChildrenRecursively(reverse);
1228
const Group* Group::previousParentGroup() const
1230
if (!database() || !database()->rootGroup()) {
1233
return database()->rootGroup()->findGroupByUuid(m_data.previousParentGroupUuid);
1236
QUuid Group::previousParentGroupUuid() const
1238
return m_data.previousParentGroupUuid;
1241
void Group::setPreviousParentGroupUuid(const QUuid& uuid)
1243
set(m_data.previousParentGroupUuid, uuid);
1246
void Group::setPreviousParentGroup(const Group* group)
1248
setPreviousParentGroupUuid(group ? group->uuid() : QUuid());
1251
bool Group::GroupData::operator==(const Group::GroupData& other) const
1253
return equals(other, CompareItemDefault);
1256
bool Group::GroupData::operator!=(const Group::GroupData& other) const
1258
return !(*this == other);
1261
bool Group::GroupData::equals(const Group::GroupData& other, CompareItemOptions options) const
1263
if (::compare(name, other.name, options) != 0) {
1266
if (::compare(notes, other.notes, options) != 0) {
1269
if (::compare(tags, other.tags, options) != 0) {
1272
if (::compare(iconNumber, other.iconNumber) != 0) {
1275
if (::compare(customIcon, other.customIcon) != 0) {
1278
if (!timeInfo.equals(other.timeInfo, options)) {
1281
// TODO HNH: Some properties are configurable - should they be ignored?
1282
if (::compare(isExpanded, other.isExpanded, options) != 0) {
1285
if (::compare(defaultAutoTypeSequence, other.defaultAutoTypeSequence, options) != 0) {
1288
if (::compare(autoTypeEnabled, other.autoTypeEnabled, options) != 0) {
1291
if (::compare(searchingEnabled, other.searchingEnabled, options) != 0) {
1294
if (::compare(mergeMode, other.mergeMode, options) != 0) {