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