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