1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
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 "itemlibraryview.h"
27 #include "itemlibrarywidget.h"
28 #include "itemlibraryassetimportdialog.h"
29 #include "metainfo.h"
30 #include <asynchronousimagecache.h>
31 #include <bindingproperty.h>
32 #include <coreplugin/icore.h>
33 #include <imagecache/imagecachecollector.h>
34 #include <imagecache/imagecacheconnectionmanager.h>
35 #include <imagecache/imagecachefontcollector.h>
36 #include <imagecache/imagecachegenerator.h>
37 #include <imagecache/imagecachestorage.h>
38 #include <imagecache/timestampprovider.h>
39 #include <import.h>
40 #include <nodelistproperty.h>
41 #include <projectexplorer/kit.h>
42 #include <projectexplorer/project.h>
43 #include <projectexplorer/session.h>
44 #include <projectexplorer/target.h>
45 #include <rewriterview.h>
46 #include <sqlitedatabase.h>
47 #include <synchronousimagecache.h>
48 #include <utils/algorithm.h>
49 #include <qmldesignerplugin.h>
50 #include <qmlitemnode.h>
51 #include <qmldesignerconstants.h>
52 
53 namespace QmlDesigner {
54 
55 namespace {
activeTarget(ProjectExplorer::Project * project)56 ProjectExplorer::Target *activeTarget(ProjectExplorer::Project *project)
57 {
58     if (project)
59         return project->activeTarget();
60 
61     return {};
62 }
63 } // namespace
64 
65 class ImageCacheData
66 {
67 public:
68     Sqlite::Database database{Utils::PathString{
69                                   Core::ICore::cacheResourcePath("imagecache-v2.db").toString()},
70                               Sqlite::JournalMode::Wal,
71                               Sqlite::LockingMode::Normal};
72     ImageCacheStorage<Sqlite::Database> storage{database};
73     ImageCacheConnectionManager connectionManager;
74     ImageCacheCollector collector{connectionManager};
75     ImageCacheFontCollector fontCollector;
76     ImageCacheGenerator generator{collector, storage};
77     ImageCacheGenerator fontGenerator{fontCollector, storage};
78     TimeStampProvider timeStampProvider;
79     AsynchronousImageCache cache{storage, generator, timeStampProvider};
80     AsynchronousImageCache asynchronousFontImageCache{storage, fontGenerator, timeStampProvider};
81     SynchronousImageCache synchronousFontImageCache{storage, timeStampProvider, fontCollector};
82 };
83 
ItemLibraryView(QObject * parent)84 ItemLibraryView::ItemLibraryView(QObject* parent)
85     : AbstractView(parent)
86 
87 {}
88 
~ItemLibraryView()89 ItemLibraryView::~ItemLibraryView()
90 {
91 }
92 
hasWidget() const93 bool ItemLibraryView::hasWidget() const
94 {
95     return true;
96 }
97 
widgetInfo()98 WidgetInfo ItemLibraryView::widgetInfo()
99 {
100     if (m_widget.isNull()) {
101         m_widget = new ItemLibraryWidget{imageCacheData()->cache,
102                                          imageCacheData()->asynchronousFontImageCache,
103                                          imageCacheData()->synchronousFontImageCache};
104     }
105 
106     return createWidgetInfo(m_widget.data(),
107                             new WidgetInfo::ToolBarWidgetDefaultFactory<ItemLibraryWidget>(m_widget.data()),
108                             QStringLiteral("Library"),
109                             WidgetInfo::LeftPane,
110                             0,
111                             tr("Library"));
112 }
113 
modelAttached(Model * model)114 void ItemLibraryView::modelAttached(Model *model)
115 {
116     AbstractView::modelAttached(model);
117 
118     m_widget->clearSearchFilter();
119     m_widget->setModel(model);
120     updateImports();
121     m_hasErrors = !rewriterView()->errors().isEmpty();
122     m_widget->setFlowMode(QmlItemNode(rootModelNode()).isFlowView());
123     setResourcePath(DocumentManager::currentResourcePath().toFileInfo().absoluteFilePath());
124 }
125 
modelAboutToBeDetached(Model * model)126 void ItemLibraryView::modelAboutToBeDetached(Model *model)
127 {
128     AbstractView::modelAboutToBeDetached(model);
129 
130     m_widget->setModel(nullptr);
131 }
132 
importsChanged(const QList<Import> & addedImports,const QList<Import> & removedImports)133 void ItemLibraryView::importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports)
134 {
135     updateImports();
136 
137     // TODO: generalize the logic below to allow adding/removing any Qml component when its import is added/removed
138     bool simulinkImportAdded = std::any_of(addedImports.cbegin(), addedImports.cend(), [](const Import &import) {
139         return import.url() == "SimulinkConnector";
140     });
141     if (simulinkImportAdded) {
142         // add SLConnector component when SimulinkConnector import is added
143         ModelNode node = createModelNode("SLConnector", 1, 0);
144         node.bindingProperty("root").setExpression(rootModelNode().validId());
145         rootModelNode().defaultNodeListProperty().reparentHere(node);
146     } else {
147         bool simulinkImportRemoved = std::any_of(removedImports.cbegin(), removedImports.cend(), [](const Import &import) {
148             return import.url() == "SimulinkConnector";
149         });
150 
151         if (simulinkImportRemoved) {
152             // remove SLConnector component when SimulinkConnector import is removed
153             const QList<ModelNode> slConnectors = Utils::filtered(rootModelNode().directSubModelNodes(),
154                                                                   [](const ModelNode &node) {
155                 return node.type() == "SLConnector" || node.type() == "SimulinkConnector.SLConnector";
156             });
157 
158             for (ModelNode node : slConnectors)
159                 node.destroy();
160 
161             resetPuppet();
162         }
163     }
164 }
165 
possibleImportsChanged(const QList<Import> & possibleImports)166 void ItemLibraryView::possibleImportsChanged(const QList<Import> &possibleImports)
167 {
168     m_widget->updatePossibleImports(possibleImports);
169 }
170 
usedImportsChanged(const QList<Import> & usedImports)171 void ItemLibraryView::usedImportsChanged(const QList<Import> &usedImports)
172 {
173     m_widget->updateUsedImports(usedImports);
174 }
175 
setResourcePath(const QString & resourcePath)176 void ItemLibraryView::setResourcePath(const QString &resourcePath)
177 {
178     if (m_widget.isNull())
179         m_widget = new ItemLibraryWidget{m_imageCacheData->cache,
180                                          m_imageCacheData->asynchronousFontImageCache,
181                                          m_imageCacheData->synchronousFontImageCache};
182 
183     m_widget->setResourcePath(resourcePath);
184 }
185 
imageCacheData()186 ImageCacheData *ItemLibraryView::imageCacheData()
187 {
188     std::call_once(imageCacheFlag, [this]() {
189         m_imageCacheData = std::make_unique<ImageCacheData>();
190         auto setTargetInImageCache =
191             [imageCacheData = m_imageCacheData.get()](ProjectExplorer::Target *target) {
192                 if (target == imageCacheData->collector.target())
193                     return;
194 
195                 if (target)
196                     imageCacheData->cache.clean();
197 
198                 imageCacheData->collector.setTarget(target);
199             };
200 
201         if (auto project = ProjectExplorer::SessionManager::startupProject(); project) {
202             m_imageCacheData->collector.setTarget(project->activeTarget());
203             connect(project,
204                     &ProjectExplorer::Project::activeTargetChanged,
205                     this,
206                     setTargetInImageCache);
207         }
208         connect(ProjectExplorer::SessionManager::instance(),
209                 &ProjectExplorer::SessionManager::startupProjectChanged,
210                 this,
211                 [=](ProjectExplorer::Project *project) {
212                     setTargetInImageCache(activeTarget(project));
213                 });
214     });
215     return m_imageCacheData.get();
216 }
217 
imageCache()218 AsynchronousImageCache &ItemLibraryView::imageCache()
219 {
220     return imageCacheData()->cache;
221 }
222 
documentMessagesChanged(const QList<DocumentMessage> & errors,const QList<DocumentMessage> &)223 void ItemLibraryView::documentMessagesChanged(const QList<DocumentMessage> &errors, const QList<DocumentMessage> &)
224 {
225     if (m_hasErrors && errors.isEmpty())
226         updateImports();
227 
228      m_hasErrors = !errors.isEmpty();
229 }
230 
updateImports()231 void ItemLibraryView::updateImports()
232 {
233     m_widget->delayedUpdateModel();
234 }
235 
updateImport3DSupport(const QVariantMap & supportMap)236 void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap)
237 {
238     QVariantMap extMap = qvariant_cast<QVariantMap>(supportMap.value("extensions"));
239     if (m_importableExtensions3DMap != extMap) {
240         DesignerActionManager *actionManager =
241                  &QmlDesignerPlugin::instance()->viewManager().designerActionManager();
242 
243         // All things importable by QSSGAssetImportManager are considered to be in the same category
244         // so we don't get multiple separate import dialogs when different file types are imported.
245         const QString category = tr("3D Assets");
246 
247         if (!m_importableExtensions3DMap.isEmpty())
248             actionManager->unregisterAddResourceHandlers(category);
249 
250         m_importableExtensions3DMap = extMap;
251 
252         auto handle3DModel = [this](const QStringList &fileNames, const QString &defaultDir) -> bool {
253             auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir,
254                                                               m_importableExtensions3DMap,
255                                                               m_importOptions3DMap, {}, {},
256                                                               Core::ICore::mainWindow());
257             importDlg->show();
258             return true;
259         };
260 
261         auto add3DHandler = [&](const QString &category, const QString &ext) {
262             const QString filter = QStringLiteral("*.%1").arg(ext);
263             actionManager->registerAddResourceHandler(
264                         AddResourceHandler(category, filter, handle3DModel, 10));
265         };
266 
267         const auto groups = extMap.keys();
268         for (const auto &group : groups) {
269             const QStringList exts = extMap[group].toStringList();
270             for (const auto &ext : exts)
271                 add3DHandler(category, ext);
272         }
273     }
274 
275     m_importOptions3DMap = qvariant_cast<QVariantMap>(supportMap.value("options"));
276 }
277 
customNotification(const AbstractView * view,const QString & identifier,const QList<ModelNode> & nodeList,const QList<QVariant> & data)278 void ItemLibraryView::customNotification(const AbstractView *view, const QString &identifier,
279                                          const QList<ModelNode> &nodeList, const QList<QVariant> &data)
280 {
281     if (identifier == "UpdateImported3DAsset" && nodeList.size() > 0) {
282         ItemLibraryAssetImportDialog::updateImport(nodeList[0], m_importableExtensions3DMap,
283                                                    m_importOptions3DMap);
284     } else {
285         AbstractView::customNotification(view, identifier, nodeList, data);
286     }
287 }
288 
289 } // namespace QmlDesigner
290