1 | /* |
2 | Copyright (c) 2014 Daniel Vr??til <dvratil@redhat.com> |
3 | |
4 | This library is free software; you can redistribute it and/or modify it |
5 | under the terms of the GNU Library General Public License as published by |
6 | the Free Software Foundation; either version 2 of the License, or (at your |
7 | option) any later version. |
8 | |
9 | This library is distributed in the hope that it will be useful, but WITHOUT |
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public |
12 | License for more details. |
13 | |
14 | You should have received a copy of the GNU Library General Public License |
15 | along with this library; see the file COPYING.LIB. If not, write to the |
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
17 | 02110-1301, USA. |
18 | */ |
19 | |
20 | #include "tagmodel_p.h" |
21 | #include "tagmodel.h" |
22 | |
23 | #include "monitor.h" |
24 | #include <akonadi/tagfetchjob.h> |
25 | #include <QDebug> |
26 | |
27 | using namespace Akonadi; |
28 | |
29 | TagModelPrivate::TagModelPrivate(TagModel *parent) |
30 | : mMonitor(0) |
31 | , q_ptr(parent) |
32 | { |
33 | // Root tag |
34 | mTags.insert(0, Tag(0)); |
35 | } |
36 | |
37 | TagModelPrivate::~TagModelPrivate() |
38 | { |
39 | } |
40 | |
41 | void TagModelPrivate::init(Monitor *monitor) |
42 | { |
43 | Q_Q(TagModel); |
44 | |
45 | mMonitor = monitor; |
46 | |
47 | q->connect(mMonitor, SIGNAL(tagAdded(Akonadi::Tag)), |
48 | q, SLOT(monitoredTagAdded(Akonadi::Tag))); |
49 | q->connect(mMonitor, SIGNAL(tagChanged(Akonadi::Tag)), |
50 | q, SLOT(monitoredTagChanged(Akonadi::Tag))); |
51 | q->connect(mMonitor, SIGNAL(tagRemoved(Akonadi::Tag)), |
52 | q, SLOT(monitoredTagRemoved(Akonadi::Tag))); |
53 | |
54 | TagFetchJob *fetchJob = new TagFetchJob(q); |
55 | fetchJob->setFetchScope(mMonitor->tagFetchScope()); |
56 | q->connect(fetchJob, SIGNAL(tagsReceived(Akonadi::Tag::List)), |
57 | q, SLOT(tagsFetched(Akonadi::Tag::List))); |
58 | q->connect(fetchJob, SIGNAL(finished(KJob*)), |
59 | q, SLOT(tagsFetchDone(KJob*))); |
60 | } |
61 | |
62 | QModelIndex TagModelPrivate::indexForTag(const qint64 tagId) const |
63 | { |
64 | Q_Q(const TagModel); |
65 | |
66 | if (!mTags.contains(tagId)) { |
67 | return QModelIndex(); |
68 | } |
69 | |
70 | const Tag tag = mTags.value(tagId); |
71 | if (!tag.isValid()) { |
72 | return QModelIndex(); |
73 | } |
74 | |
75 | const Tag::Id parentId = tag.parent().id(); |
76 | const int row = mChildTags.value(parentId).indexOf(tag); |
77 | if (row != -1) { |
78 | return q->createIndex(row, 0, (int) parentId); |
79 | } |
80 | |
81 | return QModelIndex(); |
82 | } |
83 | |
84 | Tag TagModelPrivate::tagForIndex(const QModelIndex &index) const |
85 | { |
86 | if (!index.isValid()) { |
87 | return Tag(); |
88 | } |
89 | |
90 | const Tag::Id parentId = index.internalId(); |
91 | const Tag::List &children = mChildTags.value(parentId); |
92 | return children.at(index.row()); |
93 | } |
94 | |
95 | void TagModelPrivate::monitoredTagAdded(const Tag &tag) |
96 | { |
97 | Q_Q(TagModel); |
98 | |
99 | const qint64 parentId = tag.parent().id(); |
100 | |
101 | // Parent not yet in model, defer for later |
102 | if (!mTags.contains(parentId)) { |
103 | Tag::List &list = mPendingTags[parentId]; |
104 | list.append(tag); |
105 | return; |
106 | } |
107 | |
108 | Tag::List &children = mChildTags[parentId]; |
109 | |
110 | q->beginInsertRows(indexForTag(parentId), children.count(), children.count()); |
111 | mTags.insert(tag.id(), tag); |
112 | children.append(tag); |
113 | q->endInsertRows(); |
114 | |
115 | // If there are any child tags waiting for this parent, insert them |
116 | if (mPendingTags.contains(tag.id())) { |
117 | const Tag::List pendingChildren = mPendingTags.take(tag.id()); |
118 | Tag::List &children = mChildTags[tag.id()]; |
119 | q->beginInsertRows(indexForTag(tag.id()), 0, pendingChildren.count() - 1); |
120 | Q_FOREACH (const Tag &child, pendingChildren) { |
121 | mTags.insert(child.id(), child); |
122 | children.append(child); |
123 | } |
124 | q->endInsertRows(); |
125 | } |
126 | } |
127 | |
128 | void TagModelPrivate::removeTagsRecursively(qint64 tagId) |
129 | { |
130 | const Tag tag = mTags.value(tagId); |
131 | |
132 | // Remove all children first |
133 | const Tag::List childTags = mChildTags.take(tagId); |
134 | Q_FOREACH (const Tag &child, childTags) { |
135 | removeTagsRecursively(child.id()); |
136 | } |
137 | |
138 | // Remove the actual tag |
139 | Tag::List &siblings = mChildTags[tag.parent().id()]; |
140 | siblings.removeOne(tag); |
141 | mTags.remove(tag.id()); |
142 | } |
143 | |
144 | void TagModelPrivate::monitoredTagRemoved(const Tag &tag) |
145 | { |
146 | Q_Q(TagModel); |
147 | |
148 | // Better lookup parent in our cache |
149 | qint64 parentId = mTags.value(tag.id()).parent().id(); |
150 | if (parentId == -1) { |
151 | kWarning() << "Got removal notification for unknown tag" << tag.id(); |
152 | return; |
153 | } |
154 | |
155 | Tag::List &siblings = mChildTags[parentId]; |
156 | const int pos = siblings.indexOf(tag); |
157 | Q_ASSERT(pos != -1); |
158 | |
159 | q->beginRemoveRows(indexForTag(parentId), pos, pos); |
160 | removeTagsRecursively(tag.id()); |
161 | q->endRemoveRows(); |
162 | } |
163 | |
164 | void TagModelPrivate::monitoredTagChanged(const Tag &tag) |
165 | { |
166 | Q_Q(TagModel); |
167 | |
168 | if (!mTags.contains(tag.id())) { |
169 | kWarning() << "Got change notifications for unknown tag" << tag.id(); |
170 | return; |
171 | } |
172 | |
173 | const Tag oldTag = mTags.value(tag.id()); |
174 | // Replace existing tag in cache |
175 | mTags.insert(tag.id(), tag); |
176 | |
177 | // Check whether the tag has been reparented |
178 | const qint64 oldParent = oldTag.parent().id(); |
179 | const qint64 newParent = tag.parent().id(); |
180 | if (oldParent != newParent) { |
181 | const QModelIndex sourceParent = indexForTag(oldParent); |
182 | const int sourcePos = mChildTags.value(oldParent).indexOf(oldTag); |
183 | const QModelIndex destParent = indexForTag(newParent); |
184 | const int destPos = mChildTags.value(newParent).count(); |
185 | |
186 | q->beginMoveRows(sourceParent, sourcePos, sourcePos, destParent, destPos); |
187 | Tag::List &oldSiblings = mChildTags[oldParent]; |
188 | oldSiblings.removeAt(sourcePos); |
189 | Tag::List &newSiblings = mChildTags[newParent]; |
190 | newSiblings.append(tag); |
191 | q->endMoveRows(); |
192 | } else { |
193 | Tag::List &children = mChildTags[oldParent]; |
194 | const int sourcePos = children.indexOf(oldTag); |
195 | if (sourcePos != -1) { |
196 | children[sourcePos] = tag; |
197 | } |
198 | |
199 | const QModelIndex index = indexForTag(tag.id()); |
200 | q->dataChanged(index, index); |
201 | } |
202 | } |
203 | |
204 | void TagModelPrivate::tagsFetched(const Tag::List &tags) |
205 | { |
206 | Q_FOREACH (const Tag &tag, tags) { |
207 | monitoredTagAdded(tag); |
208 | } |
209 | } |
210 | |
211 | void TagModelPrivate::tagsFetchDone(KJob *job) |
212 | { |
213 | if (job->error()) { |
214 | kWarning() << job->errorString(); |
215 | return; |
216 | } |
217 | |
218 | if (!mPendingTags.isEmpty()) { |
219 | kWarning() << "Fetched all tags from server, but there are still" << mPendingTags.count() << "orphan tags" ; |
220 | return; |
221 | } |
222 | } |
223 | |