1 /*
2  *  Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
3  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 2 or (at your option)
7  *  version 3 of the License.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "Group.h"
19 
20 #include "core/Config.h"
21 #include "core/Global.h"
22 #include "core/DatabaseIcons.h"
23 #include "core/Metadata.h"
24 
25 const int Group::DefaultIconNumber = 48;
26 const int Group::RecycleBinIconNumber = 43;
27 
Group()28 Group::Group()
29     : m_updateTimeinfo(true)
30 {
31     m_data.iconNumber = DefaultIconNumber;
32     m_data.isExpanded = true;
33     m_data.autoTypeEnabled = Inherit;
34     m_data.searchingEnabled = Inherit;
35 }
36 
~Group()37 Group::~Group()
38 {
39     // Destroy entries and children manually so DeletedObjects can be added
40     // to database.
41     const QList<Entry*> entries = m_entries;
42     for (Entry* entry : entries) {
43         delete entry;
44     }
45 
46     const QList<Group*> children = m_children;
47     for (Group* group : children) {
48         delete group;
49     }
50 
51     if (m_db && m_parent) {
52         DeletedObject delGroup;
53         delGroup.deletionTime = QDateTime::currentDateTimeUtc();
54         delGroup.uuid = m_uuid;
55         m_db->addDeletedObject(delGroup);
56     }
57 
58     cleanupParent();
59 }
60 
createRecycleBin()61 Group* Group::createRecycleBin()
62 {
63     Group* recycleBin = new Group();
64     recycleBin->setUuid(Uuid::random());
65     recycleBin->setName(tr("Recycle Bin"));
66     recycleBin->setIcon(RecycleBinIconNumber);
67     recycleBin->setSearchingEnabled(Group::Disable);
68     recycleBin->setAutoTypeEnabled(Group::Disable);
69     return recycleBin;
70 }
71 
set(P & property,const V & value)72 template <class P, class V> inline bool Group::set(P& property, const V& value) {
73     if (property != value) {
74         property = value;
75         updateTimeinfo();
76         Q_EMIT modified();
77         return true;
78     }
79     else {
80         return false;
81     }
82 }
83 
updateTimeinfo()84 void Group::updateTimeinfo()
85 {
86     if (m_updateTimeinfo) {
87         m_data.timeInfo.setLastModificationTime(QDateTime::currentDateTimeUtc());
88         m_data.timeInfo.setLastAccessTime(QDateTime::currentDateTimeUtc());
89     }
90 }
91 
setUpdateTimeinfo(bool value)92 void Group::setUpdateTimeinfo(bool value)
93 {
94     m_updateTimeinfo = value;
95 }
96 
uuid() const97 Uuid Group::uuid() const
98 {
99     return m_uuid;
100 }
101 
name() const102 QString Group::name() const
103 {
104     return m_data.name;
105 }
106 
notes() const107 QString Group::notes() const
108 {
109     return m_data.notes;
110 }
111 
icon() const112 QImage Group::icon() const
113 {
114     if (m_data.customIcon.isNull()) {
115         return databaseIcons()->icon(m_data.iconNumber);
116     }
117     else {
118         Q_ASSERT(m_db);
119 
120         if (m_db) {
121             return m_db->metadata()->customIcon(m_data.customIcon);
122         }
123         else {
124             return QImage();
125         }
126     }
127 }
128 
iconPixmap() const129 QPixmap Group::iconPixmap() const
130 {
131     if (m_data.customIcon.isNull()) {
132         return databaseIcons()->iconPixmap(m_data.iconNumber);
133     }
134     else {
135         Q_ASSERT(m_db);
136 
137         if (m_db) {
138             return m_db->metadata()->customIconPixmap(m_data.customIcon);
139         }
140         else {
141             return QPixmap();
142         }
143     }
144 }
145 
iconScaledPixmap() const146 QPixmap Group::iconScaledPixmap() const
147 {
148     if (m_data.customIcon.isNull()) {
149         // built-in icons are 16x16 so don't need to be scaled
150         return databaseIcons()->iconPixmap(m_data.iconNumber);
151     }
152     else {
153         Q_ASSERT(m_db);
154 
155         if (m_db) {
156             return m_db->metadata()->customIconScaledPixmap(m_data.customIcon);
157         }
158         else {
159             return QPixmap();
160         }
161     }
162 }
163 
iconNumber() const164 int Group::iconNumber() const
165 {
166     return m_data.iconNumber;
167 }
168 
iconUuid() const169 Uuid Group::iconUuid() const
170 {
171     return m_data.customIcon;
172 }
173 
timeInfo() const174 TimeInfo Group::timeInfo() const
175 {
176     return m_data.timeInfo;
177 }
178 
isExpanded() const179 bool Group::isExpanded() const
180 {
181     return m_data.isExpanded;
182 }
183 
defaultAutoTypeSequence() const184 QString Group::defaultAutoTypeSequence() const
185 {
186     return m_data.defaultAutoTypeSequence;
187 }
188 
autoTypeEnabled() const189 Group::TriState Group::autoTypeEnabled() const
190 {
191     return m_data.autoTypeEnabled;
192 }
193 
searchingEnabled() const194 Group::TriState Group::searchingEnabled() const
195 {
196     return m_data.searchingEnabled;
197 }
198 
lastTopVisibleEntry() const199 Entry* Group::lastTopVisibleEntry() const
200 {
201     return m_lastTopVisibleEntry;
202 }
203 
isExpired() const204 bool Group::isExpired() const
205 {
206     return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < QDateTime::currentDateTimeUtc();
207 }
208 
setUuid(const Uuid & uuid)209 void Group::setUuid(const Uuid& uuid)
210 {
211     set(m_uuid, uuid);
212 }
213 
setName(const QString & name)214 void Group::setName(const QString& name)
215 {
216     if (set(m_data.name, name)) {
217         Q_EMIT dataChanged(this);
218     }
219 }
220 
setNotes(const QString & notes)221 void Group::setNotes(const QString& notes)
222 {
223     set(m_data.notes, notes);
224 }
225 
setIcon(int iconNumber)226 void Group::setIcon(int iconNumber)
227 {
228     Q_ASSERT(iconNumber >= 0);
229 
230     if (m_data.iconNumber != iconNumber || !m_data.customIcon.isNull()) {
231         m_data.iconNumber = iconNumber;
232         m_data.customIcon = Uuid();
233 
234         updateTimeinfo();
235         Q_EMIT modified();
236         Q_EMIT dataChanged(this);
237     }
238 }
239 
setIcon(const Uuid & uuid)240 void Group::setIcon(const Uuid& uuid)
241 {
242     Q_ASSERT(!uuid.isNull());
243 
244     if (m_data.customIcon != uuid) {
245         m_data.customIcon = uuid;
246         m_data.iconNumber = 0;
247 
248         updateTimeinfo();
249         Q_EMIT modified();
250         Q_EMIT dataChanged(this);
251     }
252 }
253 
setTimeInfo(const TimeInfo & timeInfo)254 void Group::setTimeInfo(const TimeInfo& timeInfo)
255 {
256     m_data.timeInfo = timeInfo;
257 }
258 
setExpanded(bool expanded)259 void Group::setExpanded(bool expanded)
260 {
261     if (m_data.isExpanded != expanded) {
262         m_data.isExpanded = expanded;
263         updateTimeinfo();
264         Q_EMIT modified();
265     }
266 }
267 
setDefaultAutoTypeSequence(const QString & sequence)268 void Group::setDefaultAutoTypeSequence(const QString& sequence)
269 {
270     set(m_data.defaultAutoTypeSequence, sequence);
271 }
272 
setAutoTypeEnabled(TriState enable)273 void Group::setAutoTypeEnabled(TriState enable)
274 {
275     set(m_data.autoTypeEnabled, enable);
276 }
277 
setSearchingEnabled(TriState enable)278 void Group::setSearchingEnabled(TriState enable)
279 {
280     set(m_data.searchingEnabled, enable);
281 }
282 
setLastTopVisibleEntry(Entry * entry)283 void Group::setLastTopVisibleEntry(Entry* entry)
284 {
285     set(m_lastTopVisibleEntry, entry);
286 }
287 
setExpires(bool value)288 void Group::setExpires(bool value)
289 {
290     if (m_data.timeInfo.expires() != value) {
291         m_data.timeInfo.setExpires(value);
292         updateTimeinfo();
293         Q_EMIT modified();
294     }
295 }
296 
setExpiryTime(const QDateTime & dateTime)297 void Group::setExpiryTime(const QDateTime& dateTime)
298 {
299     if (m_data.timeInfo.expiryTime() != dateTime) {
300         m_data.timeInfo.setExpiryTime(dateTime);
301         updateTimeinfo();
302         Q_EMIT modified();
303     }
304 }
305 
parentGroup()306 Group* Group::parentGroup()
307 {
308     return m_parent;
309 }
310 
parentGroup() const311 const Group* Group::parentGroup() const
312 {
313     return m_parent;
314 }
315 
setParent(Group * parent,int index)316 void Group::setParent(Group* parent, int index)
317 {
318     Q_ASSERT(parent);
319     Q_ASSERT(index >= -1 && index <= parent->children().size());
320     // setting a new parent for root groups is not allowed
321     Q_ASSERT(!m_db || (m_db->rootGroup() != this));
322 
323     bool moveWithinDatabase = (m_db && m_db == parent->m_db);
324 
325     if (index == -1) {
326         index = parent->children().size();
327 
328         if (parentGroup() == parent) {
329             index--;
330         }
331     }
332 
333     if (m_parent == parent && parent->children().indexOf(this) == index) {
334         return;
335     }
336 
337     if (!moveWithinDatabase) {
338         cleanupParent();
339         m_parent = parent;
340         if (m_db) {
341             recCreateDelObjects();
342 
343             // copy custom icon to the new database
344             if (!iconUuid().isNull() && parent->m_db
345                     && m_db->metadata()->containsCustomIcon(iconUuid())
346                     && !parent->m_db->metadata()->containsCustomIcon(iconUuid())) {
347                 parent->m_db->metadata()->addCustomIcon(iconUuid(), icon());
348             }
349         }
350         if (m_db != parent->m_db) {
351             recSetDatabase(parent->m_db);
352         }
353         QObject::setParent(parent);
354         Q_EMIT aboutToAdd(this, index);
355         Q_ASSERT(index <= parent->m_children.size());
356         parent->m_children.insert(index, this);
357     }
358     else {
359         Q_EMIT aboutToMove(this, parent, index);
360         m_parent->m_children.removeAll(this);
361         m_parent = parent;
362         QObject::setParent(parent);
363         Q_ASSERT(index <= parent->m_children.size());
364         parent->m_children.insert(index, this);
365     }
366 
367     if (m_updateTimeinfo) {
368         m_data.timeInfo.setLocationChanged(QDateTime::currentDateTimeUtc());
369     }
370 
371     Q_EMIT modified();
372 
373     if (!moveWithinDatabase) {
374         Q_EMIT added();
375     }
376     else {
377         Q_EMIT moved();
378     }
379 }
380 
setParent(Database * db)381 void Group::setParent(Database* db)
382 {
383     Q_ASSERT(db);
384     Q_ASSERT(db->rootGroup() == this);
385 
386     cleanupParent();
387 
388     m_parent = nullptr;
389     recSetDatabase(db);
390 
391     QObject::setParent(db);
392 }
393 
database()394 Database* Group::database()
395 {
396     return m_db;
397 }
398 
database() const399 const Database* Group::database() const
400 {
401     return m_db;
402 }
403 
children()404 QList<Group*> Group::children()
405 {
406     return m_children;
407 }
408 
children() const409 const QList<Group*>& Group::children() const
410 {
411     return m_children;
412 }
413 
entries()414 QList<Entry*> Group::entries()
415 {
416     return m_entries;
417 }
418 
entries() const419 const QList<Entry*>& Group::entries() const
420 {
421     return m_entries;
422 }
423 
entriesRecursive(bool includeHistoryItems) const424 QList<Entry*> Group::entriesRecursive(bool includeHistoryItems) const
425 {
426     QList<Entry*> entryList;
427 
428     entryList.append(m_entries);
429 
430     if (includeHistoryItems) {
431         for (Entry* entry : m_entries) {
432             entryList.append(entry->historyItems());
433         }
434     }
435 
436     for (Group* group : m_children) {
437         entryList.append(group->entriesRecursive(includeHistoryItems));
438     }
439 
440     return entryList;
441 }
442 
groupsRecursive(bool includeSelf) const443 QList<const Group*> Group::groupsRecursive(bool includeSelf) const
444 {
445     QList<const Group*> groupList;
446     if (includeSelf) {
447         groupList.append(this);
448     }
449 
450     for (const Group* group : m_children) {
451         groupList.append(group->groupsRecursive(true));
452     }
453 
454     return groupList;
455 }
456 
groupsRecursive(bool includeSelf)457 QList<Group*> Group::groupsRecursive(bool includeSelf)
458 {
459     QList<Group*> groupList;
460     if (includeSelf) {
461         groupList.append(this);
462     }
463 
464     for (Group* group : asConst(m_children)) {
465         groupList.append(group->groupsRecursive(true));
466     }
467 
468     return groupList;
469 }
470 
customIconsRecursive() const471 QSet<Uuid> Group::customIconsRecursive() const
472 {
473     QSet<Uuid> result;
474 
475     if (!iconUuid().isNull()) {
476         result.insert(iconUuid());
477     }
478 
479     const QList<Entry*> entryList = entriesRecursive(true);
480     for (Entry* entry : entryList) {
481         if (!entry->iconUuid().isNull()) {
482             result.insert(entry->iconUuid());
483         }
484     }
485 
486     for (Group* group : m_children) {
487         result.unite(group->customIconsRecursive());
488     }
489 
490     return result;
491 }
492 
clone(Entry::CloneFlags entryFlags) const493 Group* Group::clone(Entry::CloneFlags entryFlags) const
494 {
495     Group* clonedGroup = new Group();
496 
497     clonedGroup->setUpdateTimeinfo(false);
498 
499     clonedGroup->setUuid(Uuid::random());
500     clonedGroup->m_data = m_data;
501 
502     const QList<Entry*> entryList = entries();
503     for (Entry* entry : entryList) {
504         Entry* clonedEntry = entry->clone(entryFlags);
505         clonedEntry->setGroup(clonedGroup);
506     }
507 
508     const QList<Group*> childrenGroups = children();
509     for (Group* groupChild : childrenGroups) {
510         Group* clonedGroupChild = groupChild->clone(entryFlags);
511         clonedGroupChild->setParent(clonedGroup);
512     }
513 
514     clonedGroup->setUpdateTimeinfo(true);
515 
516     QDateTime now = QDateTime::currentDateTimeUtc();
517     clonedGroup->m_data.timeInfo.setCreationTime(now);
518     clonedGroup->m_data.timeInfo.setLastModificationTime(now);
519     clonedGroup->m_data.timeInfo.setLastAccessTime(now);
520     clonedGroup->m_data.timeInfo.setLocationChanged(now);
521 
522     return clonedGroup;
523 }
524 
copyDataFrom(const Group * other)525 void Group::copyDataFrom(const Group* other)
526 {
527     m_data = other->m_data;
528     m_lastTopVisibleEntry = other->m_lastTopVisibleEntry;
529 }
530 
addEntry(Entry * entry)531 void Group::addEntry(Entry* entry)
532 {
533     Q_ASSERT(entry);
534     Q_ASSERT(!m_entries.contains(entry));
535 
536     Q_EMIT entryAboutToAdd(entry);
537 
538     m_entries << entry;
539     connect(entry, SIGNAL(dataChanged(Entry*)), SIGNAL(entryDataChanged(Entry*)));
540     if (m_db) {
541         connect(entry, SIGNAL(modified()), m_db, SIGNAL(modifiedImmediate()));
542     }
543 
544     Q_EMIT modified();
545     Q_EMIT entryAdded(entry);
546 }
547 
removeEntry(Entry * entry)548 void Group::removeEntry(Entry* entry)
549 {
550     Q_ASSERT(m_entries.contains(entry));
551 
552     Q_EMIT entryAboutToRemove(entry);
553 
554     entry->disconnect(this);
555     if (m_db) {
556         entry->disconnect(m_db);
557     }
558     m_entries.removeAll(entry);
559     Q_EMIT modified();
560     Q_EMIT entryRemoved(entry);
561 }
562 
recSetDatabase(Database * db)563 void Group::recSetDatabase(Database* db)
564 {
565     if (m_db) {
566         disconnect(SIGNAL(dataChanged(Group*)), m_db);
567         disconnect(SIGNAL(aboutToRemove(Group*)), m_db);
568         disconnect(SIGNAL(removed()), m_db);
569         disconnect(SIGNAL(aboutToAdd(Group*,int)), m_db);
570         disconnect(SIGNAL(added()), m_db);
571         disconnect(SIGNAL(aboutToMove(Group*,Group*,int)), m_db);
572         disconnect(SIGNAL(moved()), m_db);
573         disconnect(SIGNAL(modified()), m_db);
574     }
575 
576     for (Entry* entry : asConst(m_entries)) {
577         if (m_db) {
578             entry->disconnect(m_db);
579         }
580         if (db) {
581             connect(entry, SIGNAL(modified()), db, SIGNAL(modifiedImmediate()));
582         }
583     }
584 
585     if (db) {
586         connect(this, SIGNAL(dataChanged(Group*)), db, SIGNAL(groupDataChanged(Group*)));
587         connect(this, SIGNAL(aboutToRemove(Group*)), db, SIGNAL(groupAboutToRemove(Group*)));
588         connect(this, SIGNAL(removed()), db, SIGNAL(groupRemoved()));
589         connect(this, SIGNAL(aboutToAdd(Group*,int)), db, SIGNAL(groupAboutToAdd(Group*,int)));
590         connect(this, SIGNAL(added()), db, SIGNAL(groupAdded()));
591         connect(this, SIGNAL(aboutToMove(Group*,Group*,int)), db, SIGNAL(groupAboutToMove(Group*,Group*,int)));
592         connect(this, SIGNAL(moved()), db, SIGNAL(groupMoved()));
593         connect(this, SIGNAL(modified()), db, SIGNAL(modifiedImmediate()));
594     }
595 
596     m_db = db;
597 
598     for (Group* group : asConst(m_children)) {
599         group->recSetDatabase(db);
600     }
601 }
602 
cleanupParent()603 void Group::cleanupParent()
604 {
605     if (m_parent) {
606         Q_EMIT aboutToRemove(this);
607         m_parent->m_children.removeAll(this);
608         Q_EMIT modified();
609         Q_EMIT removed();
610     }
611 }
612 
recCreateDelObjects()613 void Group::recCreateDelObjects()
614 {
615     if (m_db) {
616         for (Entry* entry : asConst(m_entries)) {
617             m_db->addDeletedObject(entry->uuid());
618         }
619 
620         for (Group* group : asConst(m_children)) {
621             group->recCreateDelObjects();
622         }
623         m_db->addDeletedObject(m_uuid);
624     }
625 }
626 
resolveSearchingEnabled() const627 bool Group::resolveSearchingEnabled() const
628 {
629     switch (m_data.searchingEnabled) {
630     case Inherit:
631         if (!m_parent) {
632             return true;
633         }
634         else {
635             return m_parent->resolveSearchingEnabled();
636         }
637     case Enable:
638         return true;
639     case Disable:
640         return false;
641     default:
642         Q_ASSERT(false);
643         return false;
644     }
645 }
646 
resolveAutoTypeEnabled() const647 bool Group::resolveAutoTypeEnabled() const
648 {
649     switch (m_data.autoTypeEnabled) {
650     case Inherit:
651         if (!m_parent) {
652             return true;
653         }
654         else {
655             return m_parent->resolveAutoTypeEnabled();
656         }
657     case Enable:
658         return true;
659     case Disable:
660         return false;
661     default:
662         Q_ASSERT(false);
663         return false;
664     }
665 }
666