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