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 "itemlibrarywidget.h"
27 
28 #include "itemlibraryassetsmodel.h"
29 #include "itemlibraryiconimageprovider.h"
30 #include "itemlibraryimport.h"
31 
32 #include <theme.h>
33 
34 #include <designeractionmanager.h>
35 #include <designermcumanager.h>
36 #include <itemlibraryimageprovider.h>
37 #include <itemlibraryinfo.h>
38 #include <itemlibrarymodel.h>
39 #include <itemlibraryaddimportmodel.h>
40 #include "itemlibraryassetsiconprovider.h"
41 #include <metainfo.h>
42 #include <model.h>
43 #include <rewritingexception.h>
44 #include <qmldesignerconstants.h>
45 #include <qmldesignerplugin.h>
46 
47 #include <utils/algorithm.h>
48 #include <utils/flowlayout.h>
49 #include <utils/fileutils.h>
50 #include <utils/filesystemwatcher.h>
51 #include <utils/stylehelper.h>
52 #include <utils/qtcassert.h>
53 #include <utils/utilsicons.h>
54 
55 #include <coreplugin/coreconstants.h>
56 #include <coreplugin/icore.h>
57 #include <coreplugin/messagebox.h>
58 
59 #include <QApplication>
60 #include <QDrag>
61 #include <QFileDialog>
62 #include <QFileInfo>
63 #include <QFileSystemModel>
64 #include <QVBoxLayout>
65 #include <QImageReader>
66 #include <QMenu>
67 #include <QMimeData>
68 #include <QMouseEvent>
69 #include <QShortcut>
70 #include <QStackedWidget>
71 #include <QTabBar>
72 #include <QTimer>
73 #include <QToolButton>
74 #include <QWheelEvent>
75 #include <QQmlContext>
76 #include <QQuickItem>
77 
78 namespace QmlDesigner {
79 
propertyEditorResourcesPath()80 static QString propertyEditorResourcesPath()
81 {
82 #ifdef SHARE_QML_PATH
83     if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
84         return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources";
85 #endif
86     return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString();
87 }
88 
eventFilter(QObject * obj,QEvent * event)89 bool ItemLibraryWidget::eventFilter(QObject *obj, QEvent *event)
90 {
91     if (event->type() == QEvent::FocusOut) {
92         if (obj == m_itemViewQuickWidget.data())
93             QMetaObject::invokeMethod(m_itemViewQuickWidget->rootObject(), "closeContextMenu");
94         else if (obj == m_assetsWidget.data())
95             QMetaObject::invokeMethod(m_assetsWidget->rootObject(), "handleViewFocusOut");
96     } else if (event->type() == QMouseEvent::MouseMove) {
97         if (m_itemToDrag.isValid()) {
98             QMouseEvent *me = static_cast<QMouseEvent *>(event);
99             if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 10) {
100                 ItemLibraryEntry entry = m_itemToDrag.value<ItemLibraryEntry>();
101                 // For drag to be handled correctly, we must have the component properly imported
102                 // beforehand, so we import the module immediately when the drag starts
103                 if (!entry.requiredImport().isEmpty()) {
104                     Import import = Import::createLibraryImport(entry.requiredImport());
105                     if (!m_model->hasImport(import, true, true)) {
106                         const QList<Import> possImports = m_model->possibleImports();
107                         for (const auto &possImport : possImports) {
108                             if (possImport.url() == import.url()) {
109                                 m_model->changeImports({possImport}, {});
110                                 break;
111                             }
112                         }
113                     }
114                 }
115 
116                 auto drag = new QDrag(this);
117                 drag->setPixmap(Utils::StyleHelper::dpiSpecificImageFile(entry.libraryEntryIconPath()));
118                 drag->setMimeData(m_itemLibraryModel->getMimeData(entry));
119                 drag->exec();
120                 drag->deleteLater();
121 
122                 m_itemToDrag = {};
123             }
124         } else if (!m_assetsToDrag.isEmpty()) {
125             QMouseEvent *me = static_cast<QMouseEvent *>(event);
126             if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 10) {
127                 auto drag = new QDrag(this);
128                 drag->setPixmap(m_assetsIconProvider->requestPixmap(m_assetsToDrag[0], nullptr, {128, 128}));
129                 QMimeData *mimeData = new QMimeData;
130                 mimeData->setData("application/vnd.bauhaus.libraryresource", m_assetsToDrag.join(',').toUtf8());
131                 drag->setMimeData(mimeData);
132                 drag->exec();
133                 drag->deleteLater();
134 
135                 m_assetsToDrag.clear();
136             }
137         }
138     } else if (event->type() == QMouseEvent::MouseButtonRelease) {
139         m_itemToDrag = {};
140         m_assetsToDrag.clear();
141     }
142 
143     return QObject::eventFilter(obj, event);
144 }
145 
ItemLibraryWidget(AsynchronousImageCache & imageCache,AsynchronousImageCache & asynchronousFontImageCache,SynchronousImageCache & synchronousFontImageCache)146 ItemLibraryWidget::ItemLibraryWidget(AsynchronousImageCache &imageCache,
147                                      AsynchronousImageCache &asynchronousFontImageCache,
148                                      SynchronousImageCache &synchronousFontImageCache)
149     : m_itemIconSize(24, 24)
150     , m_fontImageCache(synchronousFontImageCache)
151     , m_itemLibraryModel(new ItemLibraryModel(this))
152     , m_itemLibraryAddImportModel(new ItemLibraryAddImportModel(this))
153     , m_assetsIconProvider(new ItemLibraryAssetsIconProvider(synchronousFontImageCache))
154     , m_fileSystemWatcher(new Utils::FileSystemWatcher(this))
155     , m_assetsModel(new ItemLibraryAssetsModel(synchronousFontImageCache, m_fileSystemWatcher, this))
156     , m_headerWidget(new QQuickWidget(this))
157     , m_addImportWidget(new QQuickWidget(this))
158     , m_itemViewQuickWidget(new QQuickWidget(this))
159     , m_assetsWidget(new QQuickWidget(this))
160     , m_imageCache{imageCache}
161 {
162     m_compressionTimer.setInterval(200);
163     m_compressionTimer.setSingleShot(true);
164     m_assetCompressionTimer.setInterval(200);
165     m_assetCompressionTimer.setSingleShot(true);
166     ItemLibraryModel::registerQmlTypes();
167 
168     setWindowTitle(tr("Library", "Title of library view"));
169     setMinimumWidth(100);
170 
171     // create header widget
172     m_headerWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
173     m_headerWidget->setMinimumHeight(75);
174     m_headerWidget->setMinimumWidth(100);
175     Theme::setupTheme(m_headerWidget->engine());
176     m_headerWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
177     m_headerWidget->setClearColor(Theme::getColor(Theme::Color::DSpanelBackground));
178     m_headerWidget->rootContext()->setContextProperty("rootView", QVariant::fromValue(this));
179 
180     // create add imports widget
181     m_addImportWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
182     Theme::setupTheme(m_addImportWidget->engine());
183     m_addImportWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
184     m_addImportWidget->setClearColor(Theme::getColor(Theme::Color::DSpanelBackground));
185     m_addImportWidget->rootContext()->setContextProperties({
186         {"addImportModel", QVariant::fromValue(m_itemLibraryAddImportModel.data())},
187         {"rootView", QVariant::fromValue(this)},
188     });
189 
190     // set up Item Library view and model
191     m_itemViewQuickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
192     m_itemViewQuickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
193 
194     m_itemViewQuickWidget->rootContext()->setContextProperties(QVector<QQmlContext::PropertyPair>{
195         {{"itemLibraryModel"}, QVariant::fromValue(m_itemLibraryModel.data())},
196         {{"itemLibraryIconWidth"}, m_itemIconSize.width()},
197         {{"itemLibraryIconHeight"}, m_itemIconSize.height()},
198         {{"rootView"}, QVariant::fromValue(this)},
199         {{"highlightColor"}, Utils::StyleHelper::notTooBrightHighlightColor()},
200     });
201 
202     m_previewTooltipBackend = std::make_unique<PreviewTooltipBackend>(m_imageCache);
203     m_itemViewQuickWidget->rootContext()->setContextProperty("tooltipBackend",
204                                                              m_previewTooltipBackend.get());
205 
206     m_itemViewQuickWidget->setClearColor(Theme::getColor(Theme::Color::DSpanelBackground));
207     m_itemViewQuickWidget->engine()->addImageProvider(QStringLiteral("qmldesigner_itemlibrary"),
208                                                       new Internal::ItemLibraryImageProvider);
209     Theme::setupTheme(m_itemViewQuickWidget->engine());
210     m_itemViewQuickWidget->installEventFilter(this);
211     m_assetsWidget->installEventFilter(this);
212 
213     m_fontPreviewTooltipBackend = std::make_unique<PreviewTooltipBackend>(asynchronousFontImageCache);
214     // Note: Though the text specified here appears in UI, it shouldn't be translated, as it's
215     // a commonly used sentence to preview the font glyphs in latin fonts.
216     // For fonts that do not have latin glyphs, the font family name will have to suffice for preview.
217     m_fontPreviewTooltipBackend->setAuxiliaryData(
218         ImageCache::FontCollectorSizeAuxiliaryData{QSize{300, 300},
219                                                    Theme::getColor(Theme::DStextColor).name(),
220                                                    QStringLiteral("The quick brown fox jumps\n"
221                                                                   "over the lazy dog\n"
222                                                                   "1234567890")});
223     // create assets widget
224     m_assetsWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
225     Theme::setupTheme(m_assetsWidget->engine());
226     m_assetsWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
227     m_assetsWidget->setClearColor(Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate));
228     m_assetsWidget->engine()->addImageProvider("qmldesigner_assets", m_assetsIconProvider);
229     m_assetsWidget->rootContext()->setContextProperties(QVector<QQmlContext::PropertyPair>{
230         {{"assetsModel"}, QVariant::fromValue(m_assetsModel.data())},
231         {{"rootView"}, QVariant::fromValue(this)},
232         {{"tooltipBackend"}, QVariant::fromValue(m_fontPreviewTooltipBackend.get())}
233     });
234 
235     // If project directory contents change, or one of the asset files is modified, we must
236     // reconstruct the model to update the icons
__anon3d6fc6170102(const QString & changedDirPath) 237     connect(m_fileSystemWatcher, &Utils::FileSystemWatcher::directoryChanged, [this](const QString & changedDirPath) {
238         Q_UNUSED(changedDirPath)
239         m_assetCompressionTimer.start();
240     });
241 
242     m_stackedWidget = new QStackedWidget(this);
243     m_stackedWidget->addWidget(m_itemViewQuickWidget.data());
244     m_stackedWidget->addWidget(m_assetsWidget.data());
245     m_stackedWidget->addWidget(m_addImportWidget.data());
246     m_stackedWidget->setMinimumHeight(30);
247     m_stackedWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
248 
249     auto layout = new QVBoxLayout(this);
250     layout->setContentsMargins({});
251     layout->setSpacing(0);
252     layout->addWidget(m_headerWidget.data());
253     layout->addWidget(m_stackedWidget.data());
254 
255     updateSearch();
256 
257     /* style sheets */
258     setStyleSheet(Theme::replaceCssColors(
259         QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
260 
261     m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F5), this);
262     connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &ItemLibraryWidget::reloadQmlSource);
263 
264     connect(&m_compressionTimer, &QTimer::timeout, this, &ItemLibraryWidget::updateModel);
__anon3d6fc6170202() 265     connect(&m_assetCompressionTimer, &QTimer::timeout, this, [this]() {
266         // TODO: find a clever way to only refresh the changed directory part of the model
267 
268         // Don't bother with asset updates after model has detached, project is probably closing
269         if (!m_model.isNull()) {
270             if (QApplication::activeModalWidget()) {
271                 // Retry later, as updating file system watchers can crash when there is an active
272                 // modal widget
273                 m_assetCompressionTimer.start();
274             } else {
275                 m_assetsModel->refresh();
276                 // reload assets qml so that an overridden file's image shows the new image
277                 QTimer::singleShot(100, this, [this] {
278                     const QString assetsQmlPath = qmlSourcesPath() + "/Assets.qml";
279                     m_assetsWidget->engine()->clearComponentCache();
280                     m_assetsWidget->setSource(QUrl::fromLocalFile(assetsQmlPath));
281                 });
282             }
283         }
284     });
285 
286     m_itemViewQuickWidget->engine()->addImageProvider("itemlibrary_preview",
287                                                       new ItemLibraryIconImageProvider{m_imageCache});
288 
289     // init the first load of the QML UI elements
290     reloadQmlSource();
291 }
292 
293 ItemLibraryWidget::~ItemLibraryWidget() = default;
294 
setItemLibraryInfo(ItemLibraryInfo * itemLibraryInfo)295 void ItemLibraryWidget::setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo)
296 {
297     if (m_itemLibraryInfo.data() == itemLibraryInfo)
298         return;
299 
300     if (m_itemLibraryInfo) {
301         disconnect(m_itemLibraryInfo.data(), &ItemLibraryInfo::entriesChanged,
302                    this, &ItemLibraryWidget::delayedUpdateModel);
303         disconnect(m_itemLibraryInfo.data(), &ItemLibraryInfo::priorityImportsChanged,
304                    this, &ItemLibraryWidget::handlePriorityImportsChanged);
305     }
306     m_itemLibraryInfo = itemLibraryInfo;
307     if (itemLibraryInfo) {
308         connect(m_itemLibraryInfo.data(), &ItemLibraryInfo::entriesChanged,
309                 this, &ItemLibraryWidget::delayedUpdateModel);
310         connect(m_itemLibraryInfo.data(), &ItemLibraryInfo::priorityImportsChanged,
311                 this, &ItemLibraryWidget::handlePriorityImportsChanged);
312         m_itemLibraryAddImportModel->setPriorityImports(m_itemLibraryInfo->priorityImports());
313     }
314     delayedUpdateModel();
315 }
316 
createToolBarWidgets()317 QList<QToolButton *> ItemLibraryWidget::createToolBarWidgets()
318 {
319 //    TODO: implement
320     QList<QToolButton *> buttons;
321     return buttons;
322 }
323 
handleSearchfilterChanged(const QString & filterText)324 void ItemLibraryWidget::handleSearchfilterChanged(const QString &filterText)
325 {
326     m_filterText = filterText;
327 
328     updateSearch();
329 }
330 
handleAddModule()331 void ItemLibraryWidget::handleAddModule()
332 {
333     QMetaObject::invokeMethod(m_headerWidget->rootObject(), "setTab", Q_ARG(QVariant, 0));
334     handleTabChanged(2);
335 }
336 
handleAddAsset()337 void ItemLibraryWidget::handleAddAsset()
338 {
339     addResources({});
340 }
341 
handleAddImport(int index)342 void ItemLibraryWidget::handleAddImport(int index)
343 {
344     Import import = m_itemLibraryAddImportModel->getImportAt(index);
345     if (import.isLibraryImport() && (import.url().startsWith("QtQuick")
346                                      || import.url().startsWith("SimulinkConnector"))) {
347         QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_IMPORT_ADDED
348                                                + import.toImportString());
349     }
350 
351     auto document = QmlDesignerPlugin::instance()->currentDesignDocument();
352     document->documentModel()->changeImports({import}, {});
353     document->updateSubcomponentManagerImport(import);
354 
355     m_stackedWidget->setCurrentIndex(0); // switch to the Components view after import is added
356     updateSearch();
357 }
358 
isSearchActive() const359 bool ItemLibraryWidget::isSearchActive() const
360 {
361     return !m_filterText.isEmpty();
362 }
363 
handleFilesDrop(const QStringList & filesPaths)364 void ItemLibraryWidget::handleFilesDrop(const QStringList &filesPaths)
365 {
366     addResources(filesPaths);
367 }
368 
delayedUpdateModel()369 void ItemLibraryWidget::delayedUpdateModel()
370 {
371     static bool disableTimer = DesignerSettings::getValue(DesignerSettingsKey::DISABLE_ITEM_LIBRARY_UPDATE_TIMER).toBool();
372     if (disableTimer)
373         updateModel();
374     else
375         m_compressionTimer.start();
376 }
377 
setModel(Model * model)378 void ItemLibraryWidget::setModel(Model *model)
379 {
380     m_model = model;
381     if (!model)
382         return;
383 
384     setItemLibraryInfo(model->metaInfo().itemLibraryInfo());
385 }
386 
handleTabChanged(int index)387 void ItemLibraryWidget::handleTabChanged(int index)
388 {
389     m_stackedWidget->setCurrentIndex(index);
390     updateSearch();
391 }
392 
qmlSourcesPath()393 QString ItemLibraryWidget::qmlSourcesPath()
394 {
395 #ifdef SHARE_QML_PATH
396     if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
397         return QLatin1String(SHARE_QML_PATH) + "/itemLibraryQmlSources";
398 #endif
399     return Core::ICore::resourcePath("qmldesigner/itemLibraryQmlSources").toString();
400 }
401 
clearSearchFilter()402 void ItemLibraryWidget::clearSearchFilter()
403 {
404     QMetaObject::invokeMethod(m_headerWidget->rootObject(), "clearSearchFilter");
405 }
406 
reloadQmlSource()407 void ItemLibraryWidget::reloadQmlSource()
408 {
409     const QString libraryHeaderQmlPath = qmlSourcesPath() + "/LibraryHeader.qml";
410     QTC_ASSERT(QFileInfo::exists(libraryHeaderQmlPath), return);
411     m_headerWidget->engine()->clearComponentCache();
412     m_headerWidget->setSource(QUrl::fromLocalFile(libraryHeaderQmlPath));
413 
414     const QString addImportQmlPath = qmlSourcesPath() + "/AddImport.qml";
415     QTC_ASSERT(QFileInfo::exists(addImportQmlPath), return);
416     m_addImportWidget->engine()->clearComponentCache();
417     m_addImportWidget->setSource(QUrl::fromLocalFile(addImportQmlPath));
418 
419     const QString itemLibraryQmlPath = qmlSourcesPath() + "/ItemsView.qml";
420     QTC_ASSERT(QFileInfo::exists(itemLibraryQmlPath), return);
421     m_itemViewQuickWidget->engine()->clearComponentCache();
422     m_itemViewQuickWidget->setSource(QUrl::fromLocalFile(itemLibraryQmlPath));
423 
424     const QString assetsQmlPath = qmlSourcesPath() + "/Assets.qml";
425     QTC_ASSERT(QFileInfo::exists(assetsQmlPath), return);
426     m_assetsWidget->engine()->clearComponentCache();
427     m_assetsWidget->setSource(QUrl::fromLocalFile(assetsQmlPath));
428 }
429 
updateModel()430 void ItemLibraryWidget::updateModel()
431 {
432     QTC_ASSERT(m_itemLibraryModel, return);
433 
434     if (m_compressionTimer.isActive()) {
435         m_updateRetry = false;
436         m_compressionTimer.stop();
437     }
438 
439     m_itemLibraryModel->update(m_itemLibraryInfo.data(), m_model.data());
440 
441     if (m_itemLibraryModel->rowCount() == 0 && !m_updateRetry) {
442         m_updateRetry = true; // Only retry once to avoid endless loops
443         m_compressionTimer.start();
444     } else {
445         m_updateRetry = false;
446     }
447     updateSearch();
448 }
449 
updatePossibleImports(const QList<Import> & possibleImports)450 void ItemLibraryWidget::updatePossibleImports(const QList<Import> &possibleImports)
451 {
452     m_itemLibraryAddImportModel->update(possibleImports);
453     delayedUpdateModel();
454 }
455 
updateUsedImports(const QList<Import> & usedImports)456 void ItemLibraryWidget::updateUsedImports(const QList<Import> &usedImports)
457 {
458     m_itemLibraryModel->updateUsedImports(usedImports);
459 }
460 
updateSearch()461 void ItemLibraryWidget::updateSearch()
462 {
463     if (m_stackedWidget->currentIndex() == 0) { // Item Library tab selected
464         m_itemLibraryModel->setSearchText(m_filterText);
465         m_itemViewQuickWidget->update();
466     } else if (m_stackedWidget->currentIndex() == 1) { // Assets tab selected
467         m_assetsModel->setSearchText(m_filterText);
468     } else if (m_stackedWidget->currentIndex() == 2) {  // QML imports tab selected
469         m_itemLibraryAddImportModel->setSearchText(m_filterText);
470     }
471 }
472 
handlePriorityImportsChanged()473 void ItemLibraryWidget::handlePriorityImportsChanged()
474 {
475     if (!m_itemLibraryInfo.isNull()) {
476         m_itemLibraryAddImportModel->setPriorityImports(m_itemLibraryInfo->priorityImports());
477         m_itemLibraryAddImportModel->update(m_model->possibleImports());
478     }
479 }
480 
setResourcePath(const QString & resourcePath)481 void ItemLibraryWidget::setResourcePath(const QString &resourcePath)
482 {
483     m_assetsModel->setRootPath(resourcePath);
484     updateSearch();
485 }
486 
startDragAndDrop(const QVariant & itemLibEntry,const QPointF & mousePos)487 void ItemLibraryWidget::startDragAndDrop(const QVariant &itemLibEntry, const QPointF &mousePos)
488 {
489     // Actual drag is created after mouse has moved to avoid a QDrag bug that causes drag to stay
490     // active (and blocks mouse release) if mouse is released at the same spot of the drag start.
491     m_itemToDrag = itemLibEntry;
492     m_dragStartPoint = mousePos.toPoint();
493 }
494 
startDragAsset(const QStringList & assetPaths,const QPointF & mousePos)495 void ItemLibraryWidget::startDragAsset(const QStringList &assetPaths, const QPointF &mousePos)
496 {
497     // Actual drag is created after mouse has moved to avoid a QDrag bug that causes drag to stay
498     // active (and blocks mouse release) if mouse is released at the same spot of the drag start.
499     m_assetsToDrag = assetPaths;
500     m_dragStartPoint = mousePos.toPoint();
501 }
502 
getAssetTypeAndData(const QString & assetPath)503 QPair<QString, QByteArray> ItemLibraryWidget::getAssetTypeAndData(const QString &assetPath)
504 {
505     QString suffix = "*." + assetPath.split('.').last().toLower();
506     if (!suffix.isEmpty()) {
507         if (ItemLibraryAssetsModel::supportedImageSuffixes().contains(suffix)) {
508             // Data: Image format (suffix)
509             return {"application/vnd.bauhaus.libraryresource.image", suffix.toUtf8()};
510         } else if (ItemLibraryAssetsModel::supportedFontSuffixes().contains(suffix)) {
511             // Data: Font family name
512             QRawFont font(assetPath, 10);
513             QString fontFamily = font.isValid() ? font.familyName() : "";
514             return {"application/vnd.bauhaus.libraryresource.font", fontFamily.toUtf8()};
515         } else if (ItemLibraryAssetsModel::supportedShaderSuffixes().contains(suffix)) {
516             // Data: shader type, frament (f) or vertex (v)
517             return {"application/vnd.bauhaus.libraryresource.shader",
518                 ItemLibraryAssetsModel::supportedFragmentShaderSuffixes().contains(suffix) ? "f" : "v"};
519         } else if (ItemLibraryAssetsModel::supportedAudioSuffixes().contains(suffix)) {
520             // No extra data for sounds
521             return {"application/vnd.bauhaus.libraryresource.sound", {}};
522         } else if (ItemLibraryAssetsModel::supportedTexture3DSuffixes().contains(suffix)) {
523             // Data: Image format (suffix)
524             return {"application/vnd.bauhaus.libraryresource.texture3d", suffix.toUtf8()};
525         }
526     }
527     return {};
528 }
529 
setFlowMode(bool b)530 void ItemLibraryWidget::setFlowMode(bool b)
531 {
532     m_itemLibraryModel->setFlowMode(b);
533 }
534 
removeImport(const QString & importUrl)535 void ItemLibraryWidget::removeImport(const QString &importUrl)
536 {
537     QTC_ASSERT(m_model, return);
538 
539     ItemLibraryImport *importSection = m_itemLibraryModel->importByUrl(importUrl);
540     if (importSection) {
541         importSection->showAllCategories();
542         m_model->changeImports({}, {importSection->importEntry()});
543     }
544 }
545 
addImportForItem(const QString & importUrl)546 void ItemLibraryWidget::addImportForItem(const QString &importUrl)
547 {
548     QTC_ASSERT(m_itemLibraryModel, return);
549     QTC_ASSERT(m_model, return);
550 
551     Import import = m_itemLibraryAddImportModel->getImport(importUrl);
552     m_model->changeImports({import}, {});
553 }
554 
addResources(const QStringList & files)555 void ItemLibraryWidget::addResources(const QStringList &files)
556 {
557     auto document = QmlDesignerPlugin::instance()->currentDesignDocument();
558 
559     QTC_ASSERT(document, return);
560 
561     QList<AddResourceHandler> handlers = QmlDesignerPlugin::instance()->viewManager().designerActionManager().addResourceHandler();
562 
563     QMultiMap<QString, QString> map;
564     for (const AddResourceHandler &handler : handlers) {
565         map.insert(handler.category, handler.filter);
566     }
567 
568     QMap<QString, QString> reverseMap;
569     for (const AddResourceHandler &handler : handlers) {
570         reverseMap.insert(handler.filter, handler.category);
571     }
572 
573     QMap<QString, int> priorities;
574     for (const AddResourceHandler &handler : handlers) {
575         priorities.insert(handler.category, handler.piority);
576     }
577 
578     QStringList sortedKeys = map.uniqueKeys();
579     Utils::sort(sortedKeys, [&priorities](const QString &first,
580                 const QString &second){
581         return priorities.value(first) < priorities.value(second);
582     });
583 
584     QStringList fileNames = files;
585     if (fileNames.isEmpty()) {
586         QStringList filters;
587 
588         for (const QString &key : qAsConst(sortedKeys)) {
589             QString str = key + " (";
590             str.append(map.values(key).join(" "));
591             str.append(")");
592             filters.append(str);
593         }
594 
595         filters.prepend(tr("All Files (%1)").arg(map.values().join(" ")));
596 
597         static QString lastDir;
598         const QString currentDir = lastDir.isEmpty() ? document->fileName().parentDir().toString() : lastDir;
599 
600         fileNames = QFileDialog::getOpenFileNames(Core::ICore::dialogParent(),
601                                                   tr("Add Assets"),
602                                                   currentDir,
603                                                   filters.join(";;"));
604 
605         if (!fileNames.isEmpty()) {
606             lastDir = QFileInfo(fileNames.first()).absolutePath();
607             // switch to assets view after an asset is added
608             m_stackedWidget->setCurrentIndex(1);
609             QMetaObject::invokeMethod(m_headerWidget->rootObject(), "setTab", Q_ARG(QVariant, 1));
610         }
611     }
612 
613     QMultiMap<QString, QString> partitionedFileNames;
614 
615     for (const QString &fileName : qAsConst(fileNames)) {
616         const QString suffix = "*." + QFileInfo(fileName).suffix().toLower();
617         const QString category = reverseMap.value(suffix);
618         partitionedFileNames.insert(category, fileName);
619     }
620 
621     for (const QString &category : partitionedFileNames.uniqueKeys()) {
622          for (const AddResourceHandler &handler : handlers) {
623              QStringList fileNames = partitionedFileNames.values(category);
624              if (handler.category == category) {
625                  QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_RESOURCE_IMPORTED + category);
626                  if (!handler.operation(fileNames, document->fileName().parentDir().toString()))
627                      Core::AsynchronousMessageBox::warning(tr("Failed to Add Files"), tr("Could not add %1 to project.").arg(fileNames.join(" ")));
628                  break;
629              }
630          }
631     }
632 }
633 
634 } // namespace QmlDesigner
635