1 /*
2 SmartNameCache.cpp
3
4 This file is part of Charm, a task-based time tracking application.
5
6 Copyright (C) 2012-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
7
8 Author: Frank Osterfeld <frank.osterfeld@kdab.com>
9
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "SmartNameCache.h"
25
26 struct IdLessThan
27 {
operator ()IdLessThan28 bool operator()(const Task &lhs, const Task &rhs) const
29 {
30 return lhs.id() < rhs.id();
31 }
32 };
33
setAllTasks(const TaskList & taskList)34 void SmartNameCache::setAllTasks(const TaskList &taskList)
35 {
36 m_tasks = taskList;
37 sortTasks();
38 regenerateSmartNames();
39 }
40
sortTasks()41 void SmartNameCache::sortTasks()
42 {
43 qSort(m_tasks.begin(), m_tasks.end(), IdLessThan());
44 }
45
46 //single add/modify/delete rebuild the cache each time.
47 //If necessary, this could be optimized by keeping the tasks in a "inverse" tree
48 //with the task names below the root and their parents further down
49
modifyTask(const Task & task)50 void SmartNameCache::modifyTask(const Task &task)
51 {
52 const TaskList::Iterator it
53 = qBinaryFind(m_tasks.begin(), m_tasks.end(), Task(task.id(), QString()), IdLessThan());
54 if (it != m_tasks.end())
55 *it = task;
56 sortTasks();
57 regenerateSmartNames();
58 }
59
deleteTask(const Task & task)60 void SmartNameCache::deleteTask(const Task &task)
61 {
62 const auto it = qBinaryFind(m_tasks.begin(), m_tasks.end(), Task(task.id(),
63 QString()), IdLessThan());
64 if (it != m_tasks.end()) {
65 m_tasks.erase(it);
66 regenerateSmartNames();
67 }
68 }
69
clearTasks()70 void SmartNameCache::clearTasks()
71 {
72 m_tasks.clear();
73 m_smartTaskNamesById.clear();
74 }
75
findTask(TaskId id) const76 Task SmartNameCache::findTask(TaskId id) const
77 {
78 const auto it = qBinaryFind(m_tasks.begin(), m_tasks.end(), Task(id, QString()), IdLessThan());
79 if (it != m_tasks.constEnd()) {
80 return *it;
81 } else {
82 return Task();
83 }
84 }
85
addTask(const Task & task)86 void SmartNameCache::addTask(const Task &task)
87 {
88 m_tasks.append(task);
89 sortTasks();
90 regenerateSmartNames();
91 }
92
smartName(const TaskId & id) const93 QString SmartNameCache::smartName(const TaskId &id) const
94 {
95 return m_smartTaskNamesById.value(id);
96 }
97
makeCombined(const Task & task) const98 QString SmartNameCache::makeCombined(const Task &task) const
99 {
100 Q_ASSERT(task.isValid() || task.name().isEmpty()); // an invalid task (id == 0) must not have a name
101 if (!task.isValid())
102 return QString();
103 const Task parent = findTask(task.parent());
104
105 if (parent.isValid()) {
106 return QObject::tr("%1/%2", "parent task name/task name").arg(parent.name(), task.name());
107 } else {
108 return task.name();
109 }
110 }
111
regenerateSmartNames()112 void SmartNameCache::regenerateSmartNames()
113 {
114 m_smartTaskNamesById.clear();
115 typedef QPair<TaskId, TaskId> TaskParentPair;
116
117 QMap<QString, QVector<TaskParentPair> > byName;
118
119 Q_FOREACH (const Task &task, m_tasks)
120 byName[makeCombined(task)].append(qMakePair(task.id(), task.parent()));
121
122 QSet<QString> cannotMakeUnique;
123
124 while (!byName.isEmpty()) {
125 QMap<QString, QVector<TaskParentPair> > newByName;
126 for (QMap<QString, QVector<TaskParentPair> >::ConstIterator it = byName.constBegin();
127 it != byName.constEnd();
128 ++it) {
129 const QString currentName = it.key();
130 const auto &taskPairs = it.value();
131 Q_ASSERT(!taskPairs.isEmpty());
132 if (taskPairs.size() == 1 || cannotMakeUnique.contains(currentName)) {
133 Q_FOREACH (const TaskParentPair &i, taskPairs)
134 m_smartTaskNamesById.insert(i.first, currentName);
135 } else {
136 Q_FOREACH (const TaskParentPair &taskPair, taskPairs) {
137 const Task parent = findTask(taskPair.second);
138 if (parent.isValid()) {
139 const QString newName = parent.name() + QLatin1Char('/') + currentName;
140 newByName[newName].append(qMakePair(taskPair.first, parent.parent()));
141 } else {
142 const auto existing = newByName.constFind(currentName);
143 if (existing != newByName.constEnd())
144 cannotMakeUnique.insert(currentName);
145 newByName[currentName].append(taskPair);
146 }
147 }
148 }
149 }
150 byName = newByName;
151 }
152 }
153