1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Jochen Becher
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "modelindexer.h"
27 
28 #include "modeleditor_constants.h"
29 
30 #include "qmt/infrastructure/exceptions.h"
31 #include "qmt/infrastructure/uid.h"
32 
33 #include "qmt/serializer/projectserializer.h"
34 
35 #include "qmt/project/project.h"
36 #include "qmt/model_controller/mvoidvisitor.h"
37 #include "qmt/model/mpackage.h"
38 #include "qmt/model/mdiagram.h"
39 
40 #include "qmt/tasks/findrootdiagramvisitor.h"
41 
42 #include <projectexplorer/project.h>
43 #include <projectexplorer/session.h>
44 #include <projectexplorer/projectnodes.h>
45 
46 #include <utils/mimetypes/mimetype.h>
47 #include <utils/mimetypes/mimedatabase.h>
48 #include <utils/qtcassert.h>
49 
50 #include <QQueue>
51 #include <QMutex>
52 #include <QMutexLocker>
53 #include <QThread>
54 #include <QDateTime>
55 
56 #include <QLoggingCategory>
57 #include <QDebug>
58 #include <QPointer>
59 
60 namespace ModelEditor {
61 namespace Internal {
62 
63 class ModelIndexer::QueuedFile
64 {
65     friend uint qHash(const ModelIndexer::QueuedFile &queuedFile);
66     friend bool operator==(const ModelIndexer::QueuedFile &lhs,
67                            const ModelIndexer::QueuedFile &rhs);
68 
69 public:
70     QueuedFile() = default;
71 
QueuedFile(const QString & file,ProjectExplorer::Project * project)72     QueuedFile(const QString &file, ProjectExplorer::Project *project)
73         : m_file(file),
74           m_project(project)
75     {
76     }
77 
QueuedFile(const QString & file,ProjectExplorer::Project * project,const QDateTime & lastModified)78     QueuedFile(const QString &file, ProjectExplorer::Project *project,
79                const QDateTime &lastModified)
80         : m_file(file),
81           m_project(project),
82           m_lastModified(lastModified)
83     {
84     }
85 
isValid() const86     bool isValid() const { return !m_file.isEmpty() && m_project; }
file() const87     QString file() const { return m_file; }
project() const88     ProjectExplorer::Project *project() const { return m_project; }
lastModified() const89     QDateTime lastModified() const { return m_lastModified; }
90 
91 private:
92     QString m_file;
93     ProjectExplorer::Project *m_project = nullptr;
94     QDateTime m_lastModified;
95 };
96 
operator ==(const ModelIndexer::QueuedFile & lhs,const ModelIndexer::QueuedFile & rhs)97 bool operator==(const ModelIndexer::QueuedFile &lhs, const ModelIndexer::QueuedFile &rhs)
98 {
99     return lhs.m_file == rhs.m_file && lhs.m_project == rhs.m_project;
100 }
101 
qHash(const ModelIndexer::QueuedFile & queuedFile)102 uint qHash(const ModelIndexer::QueuedFile &queuedFile)
103 {
104     return qHash(queuedFile.m_project) + qHash(queuedFile.m_project);
105 }
106 
107 class ModelIndexer::IndexedModel
108 {
109 public:
IndexedModel(const QString & modelFile,const QDateTime & lastModified)110     IndexedModel(const QString &modelFile, const QDateTime &lastModified)
111         : m_modelFile(modelFile),
112           m_lastModified(lastModified)
113     {
114     }
115 
reset(const QDateTime & lastModified)116     void reset(const QDateTime &lastModified)
117     {
118         m_lastModified = lastModified;
119         m_modelUid = qmt::Uid::invalidUid();
120         m_diagrams.clear();
121     }
122 
file() const123     QString file() const { return m_modelFile; }
lastModified() const124     QDateTime lastModified() const { return m_lastModified; }
owningProjects() const125     QSet<ProjectExplorer::Project *> owningProjects() const { return m_owningProjects; }
addOwningProject(ProjectExplorer::Project * project)126     void addOwningProject(ProjectExplorer::Project *project) { m_owningProjects.insert(project); }
removeOwningProject(ProjectExplorer::Project * project)127     void removeOwningProject(ProjectExplorer::Project *project)
128     {
129         m_owningProjects.remove(project);
130     }
modelUid() const131     qmt::Uid modelUid() const { return m_modelUid; }
setModelUid(const qmt::Uid & modelUid)132     void setModelUid(const qmt::Uid &modelUid) { m_modelUid = modelUid; }
diagrams() const133     QSet<qmt::Uid> diagrams() const { return m_diagrams; }
addDiagram(const qmt::Uid & diagram)134     void addDiagram(const qmt::Uid &diagram) { m_diagrams.insert(diagram); }
135 
136 private:
137     QString m_modelFile;
138     QDateTime m_lastModified;
139     QSet<ProjectExplorer::Project *> m_owningProjects;
140     qmt::Uid m_modelUid;
141     QSet<qmt::Uid> m_diagrams;
142 };
143 
144 class ModelIndexer::IndexedDiagramReference
145 {
146 public:
IndexedDiagramReference(const QString & file,const QDateTime & lastModified)147     IndexedDiagramReference(const QString &file, const QDateTime &lastModified)
148         : m_file(file),
149           m_lastModified(lastModified)
150     {
151     }
152 
reset(const QDateTime & lastModified)153     void reset(const QDateTime &lastModified)
154     {
155         m_lastModified = lastModified;
156         m_modelUid = qmt::Uid::invalidUid();
157         m_diagramUid = qmt::Uid::invalidUid();
158     }
159 
file() const160     QString file() const { return m_file; }
lastModified() const161     QDateTime lastModified() const { return m_lastModified; }
owningProjects() const162     QSet<ProjectExplorer::Project *> owningProjects() const { return m_owningProjects; }
addOwningProject(ProjectExplorer::Project * project)163     void addOwningProject(ProjectExplorer::Project *project) { m_owningProjects.insert(project); }
removeOwningProject(ProjectExplorer::Project * project)164     void removeOwningProject(ProjectExplorer::Project *project)
165     {
166         m_owningProjects.remove(project);
167     }
modelUid() const168     qmt::Uid modelUid() const { return m_modelUid; }
setModelUid(const qmt::Uid & modelUid)169     void setModelUid(const qmt::Uid &modelUid) { m_modelUid = modelUid; }
diagramUid() const170     qmt::Uid diagramUid() const { return m_diagramUid; }
setDiagramUid(const qmt::Uid & diagramUid)171     void setDiagramUid(const qmt::Uid &diagramUid) { m_diagramUid = diagramUid; }
172 
173 private:
174     QString m_file;
175     QDateTime m_lastModified;
176     QSet<ProjectExplorer::Project *> m_owningProjects;
177     qmt::Uid m_modelUid;
178     qmt::Uid m_diagramUid;
179 };
180 
181 class ModelIndexer::IndexerThread :
182         public QThread
183 {
184 public:
IndexerThread(ModelIndexer * indexer)185     IndexerThread(ModelIndexer *indexer)
186         : QThread(),
187           m_indexer(indexer)
188     {
189     }
190 
191     void onQuitIndexerThread();
192     void onFilesQueued();
193 
194 private:
195     ModelIndexer *m_indexer;
196 };
197 
198 class ModelIndexer::DiagramsCollectorVisitor :
199         public qmt::MVoidConstVisitor
200 {
201 public:
202     DiagramsCollectorVisitor(ModelIndexer::IndexedModel *indexedModel);
203 
204     void visitMObject(const qmt::MObject *object) final;
205     void visitMDiagram(const qmt::MDiagram *diagram) final;
206 
207 private:
208     ModelIndexer::IndexedModel *m_indexedModel;
209 };
210 
DiagramsCollectorVisitor(IndexedModel * indexedModel)211 ModelIndexer::DiagramsCollectorVisitor::DiagramsCollectorVisitor(IndexedModel *indexedModel)
212     : qmt::MVoidConstVisitor(),
213       m_indexedModel(indexedModel)
214 {
215 }
216 
visitMObject(const qmt::MObject * object)217 void ModelIndexer::DiagramsCollectorVisitor::visitMObject(const qmt::MObject *object)
218 {
219     for (const qmt::Handle<qmt::MObject> &child : object->children()) {
220         if (child.hasTarget())
221             child.target()->accept(this);
222     }
223     visitMElement(object);
224 }
225 
visitMDiagram(const qmt::MDiagram * diagram)226 void ModelIndexer::DiagramsCollectorVisitor::visitMDiagram(const qmt::MDiagram *diagram)
227 {
228     qCDebug(logger) << "add diagram " << diagram->name() << " to index";
229     m_indexedModel->addDiagram(diagram->uid());
230     visitMObject(diagram);
231 }
232 
233 class ModelIndexer::ModelIndexerPrivate
234 {
235 public:
~ModelIndexerPrivate()236     ~ModelIndexerPrivate()
237     {
238         QMT_CHECK(filesQueue.isEmpty());
239         QMT_CHECK(queuedFilesSet.isEmpty());
240         QMT_CHECK(indexedModels.isEmpty());
241         QMT_CHECK(indexedModelsByUid.isEmpty());
242         QMT_CHECK(indexedDiagramReferences.isEmpty());
243         QMT_CHECK(indexedDiagramReferencesByDiagramUid.isEmpty());
244         delete indexerThread;
245     }
246 
247     QMutex indexerMutex;
248 
249     QQueue<ModelIndexer::QueuedFile> filesQueue;
250     QSet<ModelIndexer::QueuedFile> queuedFilesSet;
251     QSet<ModelIndexer::QueuedFile> defaultModelFiles;
252 
253     QHash<QString, ModelIndexer::IndexedModel *> indexedModels;
254     QHash<qmt::Uid, QSet<ModelIndexer::IndexedModel *> > indexedModelsByUid;
255 
256     QHash<QString, ModelIndexer::IndexedDiagramReference *> indexedDiagramReferences;
257     QHash<qmt::Uid, QSet<ModelIndexer::IndexedDiagramReference *> > indexedDiagramReferencesByDiagramUid;
258 
259     ModelIndexer::IndexerThread *indexerThread = nullptr;
260 };
261 
onQuitIndexerThread()262 void ModelIndexer::IndexerThread::onQuitIndexerThread()
263 {
264     QThread::exit(0);
265 }
266 
onFilesQueued()267 void ModelIndexer::IndexerThread::onFilesQueued()
268 {
269     QMutexLocker locker(&m_indexer->d->indexerMutex);
270 
271     while (!m_indexer->d->filesQueue.isEmpty()) {
272         ModelIndexer::QueuedFile queuedFile = m_indexer->d->filesQueue.takeFirst();
273         m_indexer->d->queuedFilesSet.remove(queuedFile);
274         qCDebug(logger) << "handle queued file " << queuedFile.file()
275                         << "from project " << queuedFile.project()->displayName();
276 
277         bool scanModel = false;
278         IndexedModel *indexedModel = m_indexer->d->indexedModels.value(queuedFile.file());
279         if (!indexedModel) {
280             qCDebug(logger) << "create new indexed model";
281             indexedModel = new IndexedModel(queuedFile.file(),
282                                             queuedFile.lastModified());
283             indexedModel->addOwningProject(queuedFile.project());
284             m_indexer->d->indexedModels.insert(queuedFile.file(), indexedModel);
285             scanModel = true;
286         } else if (queuedFile.lastModified() > indexedModel->lastModified()) {
287             qCDebug(logger) << "update indexed model";
288             indexedModel->addOwningProject(queuedFile.project());
289             indexedModel->reset(queuedFile.lastModified());
290             scanModel = true;
291         }
292         if (scanModel) {
293             locker.unlock();
294             // load model file
295             qmt::ProjectSerializer projectSerializer;
296             qmt::Project project;
297             try {
298                 projectSerializer.load(queuedFile.file(), &project);
299             } catch (const qmt::Exception &e) {
300                 qWarning() << e.errorMessage();
301                 return;
302             }
303             locker.relock();
304             indexedModel->setModelUid(project.uid());
305             // add indexedModel to set of indexedModelsByUid
306             QSet<IndexedModel *> indexedModels = m_indexer->d->indexedModelsByUid.value(project.uid());
307             indexedModels.insert(indexedModel);
308             m_indexer->d->indexedModelsByUid.insert(project.uid(), indexedModels);
309             // collect all diagrams of model
310             DiagramsCollectorVisitor visitor(indexedModel);
311             project.rootPackage()->accept(&visitor);
312             if (m_indexer->d->defaultModelFiles.contains(queuedFile)) {
313                 m_indexer->d->defaultModelFiles.remove(queuedFile);
314                 // check if model has a diagram which could be opened
315                 qmt::FindRootDiagramVisitor diagramVisitor;
316                 project.rootPackage()->accept(&diagramVisitor);
317                 if (diagramVisitor.diagram())
318                     emit m_indexer->openDefaultModel(project.uid());
319             }
320         }
321     }
322 }
323 
ModelIndexer(QObject * parent)324 ModelIndexer::ModelIndexer(QObject *parent)
325     : QObject(parent),
326       d(new ModelIndexerPrivate())
327 {
328     d->indexerThread = new IndexerThread(this);
329     connect(this, &ModelIndexer::quitIndexerThread,
330             d->indexerThread, &ModelIndexer::IndexerThread::onQuitIndexerThread);
331     connect(this, &ModelIndexer::filesQueued,
332             d->indexerThread, &ModelIndexer::IndexerThread::onFilesQueued);
333     d->indexerThread->start();
334     connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::projectAdded,
335             this, &ModelIndexer::onProjectAdded);
336     connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::aboutToRemoveProject,
337             this, &ModelIndexer::onAboutToRemoveProject);
338 }
339 
~ModelIndexer()340 ModelIndexer::~ModelIndexer()
341 {
342     emit quitIndexerThread();
343     d->indexerThread->wait();
344     delete d;
345 }
346 
findModel(const qmt::Uid & modelUid)347 QString ModelIndexer::findModel(const qmt::Uid &modelUid)
348 {
349     QMutexLocker locker(&d->indexerMutex);
350     QSet<IndexedModel *> indexedModels = d->indexedModelsByUid.value(modelUid);
351     if (indexedModels.isEmpty())
352         return QString();
353     IndexedModel *indexedModel = *indexedModels.cbegin();
354     QMT_ASSERT(indexedModel, return QString());
355     return indexedModel->file();
356 }
357 
findDiagram(const qmt::Uid & modelUid,const qmt::Uid & diagramUid)358 QString ModelIndexer::findDiagram(const qmt::Uid &modelUid, const qmt::Uid &diagramUid)
359 {
360     Q_UNUSED(modelUid) // avoid warning in release mode
361 
362     QMutexLocker locker(&d->indexerMutex);
363     QSet<IndexedDiagramReference *> indexedDiagramReferences = d->indexedDiagramReferencesByDiagramUid.value(diagramUid);
364     if (indexedDiagramReferences.isEmpty())
365         return QString();
366     IndexedDiagramReference *indexedDiagramReference = *indexedDiagramReferences.cbegin();
367     QMT_ASSERT(indexedDiagramReference, return QString());
368     QMT_ASSERT(indexedDiagramReference->modelUid() == modelUid, return QString());
369     return indexedDiagramReference->file();
370 }
371 
onProjectAdded(ProjectExplorer::Project * project)372 void ModelIndexer::onProjectAdded(ProjectExplorer::Project *project)
373 {
374     connect(project,
375             &ProjectExplorer::Project::fileListChanged,
376             this,
377             [this, p = QPointer(project)] { if (p) onProjectFileListChanged(p.data()); },
378             Qt::QueuedConnection);
379     scanProject(project);
380 }
381 
onAboutToRemoveProject(ProjectExplorer::Project * project)382 void ModelIndexer::onAboutToRemoveProject(ProjectExplorer::Project *project)
383 {
384     disconnect(project, &ProjectExplorer::Project::fileListChanged, this, nullptr);
385     forgetProject(project);
386 }
387 
onProjectFileListChanged(ProjectExplorer::Project * project)388 void ModelIndexer::onProjectFileListChanged(ProjectExplorer::Project *project)
389 {
390     scanProject(project);
391 }
392 
scanProject(ProjectExplorer::Project * project)393 void ModelIndexer::scanProject(ProjectExplorer::Project *project)
394 {
395     if (!project->rootProjectNode())
396         return;
397 
398     // TODO harmonize following code with findFirstModel()?
399     const Utils::FilePaths files = project->files(ProjectExplorer::Project::SourceFiles);
400     QQueue<QueuedFile> filesQueue;
401     QSet<QueuedFile> filesSet;
402 
403     const Utils::MimeType modelMimeType = Utils::mimeTypeForName(Constants::MIME_TYPE_MODEL);
404     if (modelMimeType.isValid()) {
405         for (const Utils::FilePath &file : files) {
406             const QFileInfo fileInfo = file.toFileInfo();
407             if (modelMimeType.suffixes().contains(fileInfo.completeSuffix())) {
408                 QueuedFile queuedFile(file.toString(), project, fileInfo.lastModified());
409                 filesQueue.append(queuedFile);
410                 filesSet.insert(queuedFile);
411             }
412         }
413     }
414 
415     // FIXME: This potentially iterates over all files again.
416     QString defaultModelFile = findFirstModel(project->rootProjectNode(), modelMimeType);
417 
418     bool filesAreQueued = false;
419     {
420         QMutexLocker locker(&d->indexerMutex);
421 
422         // remove deleted files from queue
423         for (int i = 0; i < d->filesQueue.size();) {
424             if (d->filesQueue.at(i).project() == project) {
425                 if (filesSet.contains(d->filesQueue.at(i))) {
426                     ++i;
427                 } else {
428                     d->queuedFilesSet.remove(d->filesQueue.at(i));
429                     d->filesQueue.removeAt(i);
430                 }
431             }
432         }
433 
434         // remove deleted files from indexed models
435         foreach (const QString &file, d->indexedModels.keys()) {
436             if (!filesSet.contains(QueuedFile(file, project)))
437                 removeModelFile(file, project);
438         }
439 
440         // remove deleted files from indexed diagrams
441         foreach (const QString &file, d->indexedDiagramReferences.keys()) {
442             if (!filesSet.contains(QueuedFile(file, project)))
443                 removeDiagramReferenceFile(file, project);
444         }
445 
446         // queue files
447         while (!filesQueue.isEmpty()) {
448             QueuedFile queuedFile = filesQueue.takeFirst();
449             if (!d->queuedFilesSet.contains(queuedFile)) {
450                 QMT_CHECK(!d->filesQueue.contains(queuedFile));
451                 d->filesQueue.append(queuedFile);
452                 d->queuedFilesSet.insert(queuedFile);
453                 filesAreQueued = true;
454             }
455         }
456 
457         // auto-open model file only if project is already configured
458         if (!defaultModelFile.isEmpty() && !project->targets().isEmpty()) {
459             d->defaultModelFiles.insert(QueuedFile(defaultModelFile, project, QDateTime()));
460         }
461     }
462 
463     if (filesAreQueued)
464         emit filesQueued();
465 }
466 
findFirstModel(ProjectExplorer::FolderNode * folderNode,const Utils::MimeType & mimeType)467 QString ModelIndexer::findFirstModel(ProjectExplorer::FolderNode *folderNode,
468                                      const Utils::MimeType &mimeType)
469 {
470     if (!mimeType.isValid())
471         return QString();
472     foreach (ProjectExplorer::FileNode *fileNode, folderNode->fileNodes()) {
473         if (mimeType.suffixes().contains(fileNode->filePath().completeSuffix()))
474             return fileNode->filePath().toString();
475     }
476     foreach (ProjectExplorer::FolderNode *subFolderNode, folderNode->folderNodes()) {
477         QString modelFileName = findFirstModel(subFolderNode, mimeType);
478         if (!modelFileName.isEmpty())
479             return modelFileName;
480     }
481     return QString();
482 }
483 
forgetProject(ProjectExplorer::Project * project)484 void ModelIndexer::forgetProject(ProjectExplorer::Project *project)
485 {
486     const Utils::FilePaths files = project->files(ProjectExplorer::Project::SourceFiles);
487 
488     QMutexLocker locker(&d->indexerMutex);
489     for (const Utils::FilePath &file : files) {
490         const QString fileString = file.toString();
491         // remove file from queue
492         QueuedFile queuedFile(fileString, project);
493         if (d->queuedFilesSet.contains(queuedFile)) {
494             QMT_CHECK(d->filesQueue.contains(queuedFile));
495             d->filesQueue.removeOne(queuedFile);
496             QMT_CHECK(!d->filesQueue.contains(queuedFile));
497             d->queuedFilesSet.remove(queuedFile);
498         }
499         removeModelFile(fileString, project);
500         removeDiagramReferenceFile(fileString, project);
501     }
502 }
503 
removeModelFile(const QString & file,ProjectExplorer::Project * project)504 void ModelIndexer::removeModelFile(const QString &file, ProjectExplorer::Project *project)
505 {
506     IndexedModel *indexedModel = d->indexedModels.value(file);
507     if (indexedModel && indexedModel->owningProjects().contains(project)) {
508         qCDebug(logger) << "remove model file " << file
509                         << " from project " << project->displayName();
510         indexedModel->removeOwningProject(project);
511         if (indexedModel->owningProjects().isEmpty()) {
512             qCDebug(logger) << "delete indexed model " << project->displayName();
513             d->indexedModels.remove(file);
514 
515             // remove indexedModel from set of indexedModelsByUid
516             QMT_CHECK(d->indexedModelsByUid.contains(indexedModel->modelUid()));
517             QSet<IndexedModel *> indexedModels = d->indexedModelsByUid.value(indexedModel->modelUid());
518             QMT_CHECK(indexedModels.contains(indexedModel));
519             indexedModels.remove(indexedModel);
520             if (indexedModels.isEmpty())
521                 d->indexedModelsByUid.remove(indexedModel->modelUid());
522             else
523                 d->indexedModelsByUid.insert(indexedModel->modelUid(), indexedModels);
524 
525             delete indexedModel;
526         }
527     }
528 }
529 
removeDiagramReferenceFile(const QString & file,ProjectExplorer::Project * project)530 void ModelIndexer::removeDiagramReferenceFile(const QString &file,
531                                               ProjectExplorer::Project *project)
532 {
533     IndexedDiagramReference *indexedDiagramReference = d->indexedDiagramReferences.value(file);
534     if (indexedDiagramReference) {
535         QMT_CHECK(indexedDiagramReference->owningProjects().contains(project));
536         qCDebug(logger) << "remove diagram reference file "
537                         << file << " from project " << project->displayName();
538         indexedDiagramReference->removeOwningProject(project);
539         if (indexedDiagramReference->owningProjects().isEmpty()) {
540             qCDebug(logger) << "delete indexed diagram reference from " << file;
541             d->indexedDiagramReferences.remove(file);
542 
543             // remove indexedDiagramReference from set of indexedDiagramReferecesByDiagramUid
544             QMT_CHECK(d->indexedDiagramReferencesByDiagramUid.contains(indexedDiagramReference->diagramUid()));
545             QSet<IndexedDiagramReference *> indexedDiagramReferences = d->indexedDiagramReferencesByDiagramUid.value(indexedDiagramReference->diagramUid());
546             QMT_CHECK(indexedDiagramReferences.contains(indexedDiagramReference));
547             indexedDiagramReferences.remove(indexedDiagramReference);
548             if (indexedDiagramReferences.isEmpty()) {
549                 d->indexedDiagramReferencesByDiagramUid.remove(
550                             indexedDiagramReference->diagramUid());
551             } else {
552                 d->indexedDiagramReferencesByDiagramUid.insert(
553                             indexedDiagramReference->diagramUid(), indexedDiagramReferences);
554             }
555 
556             delete indexedDiagramReference;
557         }
558     }
559 }
560 
logger()561 const QLoggingCategory &ModelIndexer::logger()
562 {
563     static const QLoggingCategory category("qtc.modeleditor.modelindexer", QtWarningMsg);
564     return category;
565 }
566 
567 } // namespace Internal
568 } // namespace ModelEditor
569