1 /**
2 * UGENE - Integrated Bioinformatics Tools.
3 * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4 * http://ugene.net
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #include "ProjectViewFilterModel.h"
23
24 #include <QFontMetrics>
25
26 #include <U2Core/AppContext.h>
27 #include <U2Core/BunchMimeData.h>
28 #include <U2Core/L10n.h>
29 #include <U2Core/ProjectModel.h>
30 #include <U2Core/U2ObjectDbi.h>
31 #include <U2Core/U2SafePoints.h>
32
33 #include "FilteredProjectGroup.h"
34 #include "ProjectFilterNames.h"
35 #include "ProjectUtils.h"
36 #include "ProjectViewModel.h"
37
38 namespace U2 {
39
ProjectViewFilterModel(ProjectViewModel * srcModel,const ProjectTreeControllerModeSettings & settings,QObject * p)40 ProjectViewFilterModel::ProjectViewFilterModel(ProjectViewModel *srcModel, const ProjectTreeControllerModeSettings &settings, QObject *p)
41 : QAbstractItemModel(p), settings(settings), srcModel(srcModel) {
42 SAFE_POINT(nullptr != srcModel, L10N::nullPointerError("Project view model"), );
43 connect(&filterController, SIGNAL(si_objectsFiltered(const QString &, const QList<QPointer<GObject>> &)), SLOT(sl_objectsFiltered(const QString &, const QList<QPointer<GObject>> &)));
44 connect(&filterController, SIGNAL(si_filteringStarted()), SIGNAL(si_filteringStarted()));
45 connect(&filterController, SIGNAL(si_filteringFinished()), SIGNAL(si_filteringFinished()));
46
47 connect(srcModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), SLOT(sl_rowsAboutToBeRemoved(const QModelIndex &, int, int)));
48 connect(srcModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), SLOT(sl_dataChanged(const QModelIndex &, const QModelIndex &)));
49 }
50
~ProjectViewFilterModel()51 ProjectViewFilterModel::~ProjectViewFilterModel() {
52 clearFilterGroups();
53 }
54
55 namespace {
56
getAllDocumentsSafely()57 QList<QPointer<Document>> getAllDocumentsSafely() {
58 QList<QPointer<Document>> result;
59
60 Project *proj = AppContext::getProject();
61 SAFE_POINT(nullptr != proj, L10N::nullPointerError("project"), result);
62 foreach (Document *doc, proj->getDocuments()) {
63 result.append(doc);
64 }
65 return result;
66 }
67
68 } // namespace
69
updateSettings(const ProjectTreeControllerModeSettings & newSettings)70 void ProjectViewFilterModel::updateSettings(const ProjectTreeControllerModeSettings &newSettings) {
71 settings = newSettings;
72 clearFilterGroups();
73
74 if (settings.isObjectFilterActive()) {
75 const QList<QPointer<Document>> allDocs = getAllDocumentsSafely();
76 CHECK(!allDocs.isEmpty(), );
77 filterController.startFiltering(settings, allDocs);
78 }
79 }
80
addFilteredObject(const QString & filterGroupName,GObject * obj)81 void ProjectViewFilterModel::addFilteredObject(const QString &filterGroupName, GObject *obj) {
82 SAFE_POINT(!filterGroupName.isEmpty(), "Empty project filter group name", );
83 SAFE_POINT(nullptr != obj, L10N::nullPointerError("object"), );
84
85 if (!hasFilterGroup(filterGroupName)) {
86 addFilterGroup(filterGroupName);
87 }
88
89 FilteredProjectGroup *group = findFilterGroup(filterGroupName);
90 SAFE_POINT(nullptr != group, L10N::nullPointerError("project filter group"), );
91
92 #ifdef _DEBUG
93 SAFE_POINT(!group->contains(obj), "Attempting to add duplicate to a filter group", );
94 #endif
95
96 const int objectRow = group->getNewObjectNumber(obj);
97 beginInsertRows(getIndexForGroup(group), objectRow, objectRow);
98 group->addObject(obj, objectRow);
99 endInsertRows();
100 }
101
findFilterGroup(const QString & name) const102 FilteredProjectGroup *ProjectViewFilterModel::findFilterGroup(const QString &name) const {
103 SAFE_POINT(!name.isEmpty(), "Empty project filter group name", nullptr);
104
105 if (ProjectFilterNames::OBJ_NAME_FILTER_NAME == name) {
106 return filterGroups.isEmpty() ? nullptr : *(filterGroups.constBegin());
107 } else {
108 FilteredProjectGroup testGroup(name);
109 const QList<FilteredProjectGroup *>::const_iterator begin = filterGroups.constBegin();
110 const QList<FilteredProjectGroup *>::const_iterator end = filterGroups.constEnd();
111
112 QList<FilteredProjectGroup *>::const_iterator posNextToResult = std::upper_bound(begin, end, &testGroup, FilteredProjectGroup::groupLessThan);
113 return (posNextToResult != begin && (*(--posNextToResult))->getGroupName() == name) ? *posNextToResult : nullptr;
114 }
115 }
116
getIndexForGroup(FilteredProjectGroup * group) const117 QModelIndex ProjectViewFilterModel::getIndexForGroup(FilteredProjectGroup *group) const {
118 const int groupRow = filterGroups.indexOf(group);
119 SAFE_POINT(-1 != groupRow, "Unexpected filter project group detected", QModelIndex());
120 return createIndex(groupRow, 0, group);
121 }
122
getIndexForObject(const QString & groupName,GObject * obj) const123 QModelIndex ProjectViewFilterModel::getIndexForObject(const QString &groupName, GObject *obj) const {
124 FilteredProjectGroup *group = findFilterGroup(groupName);
125 SAFE_POINT(nullptr != group, L10N::nullPointerError("project filter group"), QModelIndex());
126
127 WrappedObject *wrappedObj = group->getWrappedObject(obj);
128 SAFE_POINT(nullptr != wrappedObj, L10N::nullPointerError("filtered object"), QModelIndex());
129 return createIndex(group->getWrappedObjectNumber(wrappedObj), 0, wrappedObj);
130 }
131
addFilterGroup(const QString & name)132 void ProjectViewFilterModel::addFilterGroup(const QString &name) {
133 SAFE_POINT(!name.isEmpty(), "Empty project filter group name", );
134 #ifdef _DEBUG
135 SAFE_POINT(!hasFilterGroup(name), "Attempting to add a duplicate filter group", );
136 #endif
137
138 FilteredProjectGroup *newGroup = new FilteredProjectGroup(name);
139 QList<FilteredProjectGroup *>::iterator insertionPlace = std::upper_bound(filterGroups.begin(), filterGroups.end(), newGroup, FilteredProjectGroup::groupLessThan);
140
141 const int groupNumber = insertionPlace - filterGroups.begin();
142 beginInsertRows(QModelIndex(), groupNumber, groupNumber);
143 filterGroups.insert(insertionPlace, newGroup);
144 endInsertRows();
145
146 emit si_filterGroupAdded(createIndex(groupNumber, 0, newGroup));
147 }
148
hasFilterGroup(const QString & name) const149 bool ProjectViewFilterModel::hasFilterGroup(const QString &name) const {
150 return nullptr != findFilterGroup(name);
151 }
152
clearFilterGroups()153 void ProjectViewFilterModel::clearFilterGroups() {
154 filterController.stopFiltering();
155
156 beginResetModel();
157 qDeleteAll(filterGroups);
158 filterGroups.clear();
159 endResetModel();
160 }
161
mapToSource(const QModelIndex & proxyIndex) const162 QModelIndex ProjectViewFilterModel::mapToSource(const QModelIndex &proxyIndex) const {
163 switch (getType(proxyIndex)) {
164 case GROUP:
165 return QModelIndex();
166 case OBJECT: {
167 WrappedObject *obj = toObject(proxyIndex);
168 return srcModel->getIndexForObject(obj->getObject());
169 }
170 default:
171 FAIL("Unexpected parent item type", QModelIndex());
172 }
173 }
174
rowCount(const QModelIndex & parent) const175 int ProjectViewFilterModel::rowCount(const QModelIndex &parent) const {
176 CHECK(parent.isValid(), filterGroups.size());
177
178 switch (getType(parent)) {
179 case GROUP: {
180 FilteredProjectGroup *parentFilterGroup = toGroup(parent);
181 return parentFilterGroup->getObjectsCount();
182 }
183 case OBJECT:
184 return 0;
185 default:
186 FAIL("Unexpected parent item type", 0);
187 }
188 }
189
columnCount(const QModelIndex &) const190 int ProjectViewFilterModel::columnCount(const QModelIndex & /*parent*/) const {
191 return 1;
192 }
193
sl_objectsFiltered(const QString & groupName,const QList<QPointer<GObject>> & objs)194 void ProjectViewFilterModel::sl_objectsFiltered(const QString &groupName, const QList<QPointer<GObject>> &objs) {
195 foreach (const QPointer<GObject> &obj, objs) {
196 const QString objPath = srcModel->getObjectFolder(obj->getDocument(), obj.data());
197 if (!obj.isNull() && !ProjectUtils::isFolderInRecycleBinSubtree(objPath)) {
198 addFilteredObject(groupName, obj.data());
199 }
200 }
201 }
202
index(int row,int column,const QModelIndex & parent) const203 QModelIndex ProjectViewFilterModel::index(int row, int column, const QModelIndex &parent) const {
204 if (!parent.isValid()) {
205 CHECK(row < filterGroups.size(), QModelIndex());
206 return createIndex(row, column, filterGroups[row]);
207 }
208
209 switch (getType(parent)) {
210 case GROUP:
211 return createIndex(row, column, toGroup(parent)->getWrappedObject(row));
212 default:
213 FAIL("Unexpected parent item type", QModelIndex());
214 }
215 }
216
parent(const QModelIndex & index) const217 QModelIndex ProjectViewFilterModel::parent(const QModelIndex &index) const {
218 CHECK(index.isValid(), QModelIndex());
219
220 switch (getType(index)) {
221 case GROUP:
222 return QModelIndex();
223 case OBJECT:
224 return getIndexForGroup(toObject(index)->getParentGroup());
225 default:
226 FAIL("Unexpected parent item type", QModelIndex());
227 }
228 }
229
flags(const QModelIndex & index) const230 Qt::ItemFlags ProjectViewFilterModel::flags(const QModelIndex &index) const {
231 CHECK(index.isValid(), QAbstractItemModel::flags(index));
232
233 switch (getType(index)) {
234 case GROUP:
235 return QAbstractItemModel::flags(index);
236 default: {
237 Qt::ItemFlags result = srcModel->flags(mapToSource(index));
238 result &= ~Qt::ItemIsEditable;
239 result &= ~Qt::ItemIsDropEnabled;
240 return result;
241 }
242 }
243 }
244
getGroupData(const QModelIndex & index,int role) const245 QVariant ProjectViewFilterModel::getGroupData(const QModelIndex &index, int role) const {
246 SAFE_POINT(0 <= index.row() && index.row() < filterGroups.size(), "Project group number out of range", QVariant());
247
248 switch (role) {
249 case Qt::DisplayRole:
250 return filterGroups[index.row()]->getGroupName();
251 default:
252 return QVariant();
253 }
254 }
255
data(const QModelIndex & index,int role) const256 QVariant ProjectViewFilterModel::data(const QModelIndex &index, int role) const {
257 const ItemType itemType = getType(index);
258 switch (itemType) {
259 case GROUP:
260 return getGroupData(index, role);
261 case OBJECT:
262 return getObjectData(index, role);
263 default:
264 FAIL("Unexpected model item type", QVariant());
265 }
266 }
267
sl_dataChanged(const QModelIndex & before,const QModelIndex & after)268 void ProjectViewFilterModel::sl_dataChanged(const QModelIndex &before, const QModelIndex &after) {
269 SAFE_POINT(before == after, "Unexpected project item index change", );
270
271 if (ProjectViewModel::itemType(before) == ProjectViewModel::OBJECT) {
272 GObject *object = ProjectViewModel::toObject(before);
273 foreach (FilteredProjectGroup *group, filterGroups) {
274 if (group->contains(object)) {
275 const QModelIndex proxyObjIndex = getIndexForObject(group->getGroupName(), object);
276 emit dataChanged(proxyObjIndex, proxyObjIndex);
277 }
278 }
279 }
280 }
281
sl_rowsAboutToBeRemoved(const QModelIndex & parent,int first,int last)282 void ProjectViewFilterModel::sl_rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) {
283 SAFE_POINT(first == last, "Unexpected row range", );
284
285 const QModelIndex removedIndex = srcModel->index(first, 0, parent);
286 QList<GObject *> objectsBeingRemoved;
287 switch (ProjectViewModel::itemType(removedIndex)) {
288 case ProjectViewModel::OBJECT:
289 objectsBeingRemoved.append(ProjectViewModel::toObject(removedIndex));
290 break;
291 case ProjectViewModel::FOLDER: {
292 Folder *folder = ProjectViewModel::toFolder(removedIndex);
293 objectsBeingRemoved.append(srcModel->getFolderObjects(folder->getDocument(), folder->getFolderPath()));
294 } break;
295 case ProjectViewModel::DOCUMENT:
296 objectsBeingRemoved.append(ProjectViewModel::toDocument(removedIndex)->getObjects());
297 break;
298 default:
299 FAIL("Unexpected project item type", );
300 }
301
302 foreach (GObject *obj, objectsBeingRemoved) {
303 foreach (FilteredProjectGroup *group, filterGroups) {
304 WrappedObject *wrappedObj = group->getWrappedObject(obj);
305 if (nullptr != wrappedObj) {
306 const QModelIndex parentIndex = getIndexForGroup(group);
307 const int objNumber = group->getWrappedObjectNumber(wrappedObj);
308 SAFE_POINT(-1 != objNumber, "Unexpected object number", );
309 beginRemoveRows(parentIndex, objNumber, objNumber);
310 group->removeAt(objNumber);
311 endRemoveRows();
312 }
313 }
314 }
315 }
316
getStyledObjectName(GObject * obj,FilteredProjectGroup * group) const317 QString ProjectViewFilterModel::getStyledObjectName(GObject *obj, FilteredProjectGroup *group) const {
318 SAFE_POINT(nullptr != obj && nullptr != group, "Invalid arguments supplied", QString());
319
320 QString result = obj->getGObjectName();
321 if (group->getGroupName() == ProjectFilterNames::OBJ_NAME_FILTER_NAME) {
322 const QString stylePattern = "<span style=\"background-color:yellow;color:black\">%1</span>";
323 foreach (const QString &token, settings.tokensToShow) {
324 int nextTokenPos = -1;
325 const int tokenSize = token.length();
326 while (-1 != (nextTokenPos = result.indexOf(token, nextTokenPos + 1, Qt::CaseInsensitive))) {
327 const QString coloredText = QString(stylePattern).arg(result.mid(nextTokenPos, tokenSize));
328 result.replace(nextTokenPos, tokenSize, coloredText);
329 nextTokenPos += coloredText.size();
330 }
331 }
332 }
333
334 const QVariant objFontData = srcModel->getIndexForObject(obj).data(Qt::FontRole);
335 const QFont objectFont = objFontData.isValid() ? objFontData.value<QFont>() : QFont();
336 if (objectFont.bold()) {
337 result = QString("<b>%1</b>").arg(result);
338 }
339 return result;
340 }
341
getObjectData(const QModelIndex & index,int role) const342 QVariant ProjectViewFilterModel::getObjectData(const QModelIndex &index, int role) const {
343 QVariant result = srcModel->data(mapToSource(index), role);
344
345 if (Qt::DisplayRole == role) {
346 GObject *object = toObject(index)->getObject();
347 Document *parentDoc = object->getDocument();
348 if (nullptr != parentDoc) {
349 const QString objectPath = srcModel->getObjectFolder(parentDoc, object);
350 const bool isDatabase = parentDoc->isDatabaseConnection();
351 const QString itemDocInfo = parentDoc->getName() + (isDatabase ? ": " + objectPath : QString());
352 const QString objectName = getStyledObjectName(object, toGroup(index.parent()));
353 result = QString("%1<p style=\"margin-top:0px;font-size:small;\">%2</p>").arg(objectName).arg(itemDocInfo);
354 }
355 }
356 return result;
357 }
358
mimeData(const QModelIndexList & indexes) const359 QMimeData *ProjectViewFilterModel::mimeData(const QModelIndexList &indexes) const {
360 QSet<GObject *> uniqueObjs;
361 foreach (const QModelIndex &index, indexes) {
362 if (isObject(index)) {
363 uniqueObjs.insert(toObject(index)->getObject());
364 }
365 }
366
367 QModelIndexList reducedIndexes;
368 foreach (GObject *obj, uniqueObjs) {
369 reducedIndexes.append(srcModel->getIndexForObject(obj));
370 }
371
372 return srcModel->mimeData(reducedIndexes);
373 }
374
mimeTypes() const375 QStringList ProjectViewFilterModel::mimeTypes() const {
376 QStringList result;
377 result << GObjectMimeData::MIME_TYPE;
378 result << BunchMimeData::MIME_TYPE;
379 return result;
380 }
381
getType(const QModelIndex & index)382 ProjectViewFilterModel::ItemType ProjectViewFilterModel::getType(const QModelIndex &index) {
383 QObject *data = toQObject(index);
384 CHECK(nullptr != data, GROUP);
385
386 if (nullptr != qobject_cast<WrappedObject *>(data)) {
387 return OBJECT;
388 } else if (nullptr != qobject_cast<FilteredProjectGroup *>(data)) {
389 return GROUP;
390 } else {
391 FAIL("Unexpected data type", GROUP);
392 }
393 }
394
isObject(const QModelIndex & index)395 bool ProjectViewFilterModel::isObject(const QModelIndex &index) {
396 return OBJECT == getType(index);
397 }
398
toQObject(const QModelIndex & index)399 QObject *ProjectViewFilterModel::toQObject(const QModelIndex &index) {
400 QObject *internalObj = static_cast<QObject *>(index.internalPointer());
401 SAFE_POINT(nullptr != internalObj, "Invalid index data", nullptr);
402 return internalObj;
403 }
404
toGroup(const QModelIndex & index)405 FilteredProjectGroup *ProjectViewFilterModel::toGroup(const QModelIndex &index) {
406 return qobject_cast<FilteredProjectGroup *>(toQObject(index));
407 }
408
toObject(const QModelIndex & index)409 WrappedObject *ProjectViewFilterModel::toObject(const QModelIndex &index) {
410 return qobject_cast<WrappedObject *>(toQObject(index));
411 }
412
413 } // namespace U2
414