1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Designer of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "qtresourceview_p.h"
30 #include "qtresourcemodel_p.h"
31 #include "qtresourceeditordialog_p.h"
32 #include "iconloader_p.h"
33 
34 #include <QtDesigner/abstractformeditor.h>
35 #include <QtDesigner/abstractsettings.h>
36 
37 #include <QtWidgets/qtoolbar.h>
38 #include <QtWidgets/qaction.h>
39 #include <QtWidgets/qsplitter.h>
40 #include <QtWidgets/qtreewidget.h>
41 #include <QtWidgets/qlistwidget.h>
42 #include <QtWidgets/qheaderview.h>
43 #include <QtWidgets/qboxlayout.h>
44 #include <QtGui/qpainter.h>
45 #include <QtCore/qfileinfo.h>
46 #include <QtCore/qdir.h>
47 #include <QtCore/qqueue.h>
48 #include <QtGui/qpainter.h>
49 #include <QtWidgets/qdialogbuttonbox.h>
50 #include <QtWidgets/qpushbutton.h>
51 #include <QtWidgets/qmessagebox.h>
52 #include <QtWidgets/qapplication.h>
53 #if QT_CONFIG(clipboard)
54 #include <QtGui/qclipboard.h>
55 #endif
56 #include <QtWidgets/qmenu.h>
57 #include <QtWidgets/qlineedit.h>
58 #include <QtGui/qdrag.h>
59 #include <QtCore/qmimedata.h>
60 #include <QtXml/qdom.h>
61 
62 #include <algorithm>
63 
64 QT_BEGIN_NAMESPACE
65 
66 static const char *elementResourceData = "resource";
67 static const char *typeAttribute = "type";
68 static const char *typeImage = "image";
69 static const char *typeStyleSheet = "stylesheet";
70 static const char *typeOther = "other";
71 static const char *fileAttribute = "file";
72 static const char *SplitterPosition = "SplitterPosition";
73 static const char *Geometry = "Geometry";
74 static const char *ResourceViewDialogC = "ResourceDialog";
75 
76 // ---------------- ResourceListWidget: A list widget that has drag enabled
77 class ResourceListWidget : public QListWidget {
78 public:
79     ResourceListWidget(QWidget *parent = nullptr);
80 
81 protected:
82     void startDrag(Qt::DropActions supportedActions) override;
83 };
84 
ResourceListWidget(QWidget * parent)85 ResourceListWidget::ResourceListWidget(QWidget *parent) :
86     QListWidget(parent)
87 {
88     setDragEnabled(true);
89 }
90 
startDrag(Qt::DropActions supportedActions)91 void ResourceListWidget::startDrag(Qt::DropActions supportedActions)
92 {
93     if (supportedActions == Qt::MoveAction)
94         return;
95 
96     QListWidgetItem *item = currentItem();
97     if (!item)
98         return;
99 
100     const QString filePath = item->data(Qt::UserRole).toString();
101     const QIcon icon = item->icon();
102 
103     QMimeData *mimeData = new QMimeData;
104     const QtResourceView::ResourceType type = icon.isNull() ? QtResourceView::ResourceOther : QtResourceView::ResourceImage;
105     mimeData->setText(QtResourceView::encodeMimeData(type , filePath));
106 
107     QDrag *drag = new QDrag(this);
108     if (!icon.isNull()) {
109         const QSize size = icon.actualSize(iconSize());
110         drag->setPixmap(icon.pixmap(size));
111         drag->setHotSpot(QPoint(size.width() / 2, size.height() / 2));
112     }
113 
114     drag->setMimeData(mimeData);
115     drag->exec(Qt::CopyAction);
116 }
117 
118 /* TODO
119 
120    1) load the icons in separate thread...Hmm..if Qt is configured with threads....
121 */
122 
123 // ---------------------------- QtResourceViewPrivate
124 class QtResourceViewPrivate
125 {
126     QtResourceView *q_ptr = nullptr;
127     Q_DECLARE_PUBLIC(QtResourceView)
128 public:
129     QtResourceViewPrivate(QDesignerFormEditorInterface *core);
130 
131     void slotResourceSetActivated(QtResourceSet *resourceSet);
132     void slotCurrentPathChanged(QTreeWidgetItem *);
133     void slotCurrentResourceChanged(QListWidgetItem *);
134     void slotResourceActivated(QListWidgetItem *);
135     void slotEditResources();
136     void slotReloadResources();
137 #if QT_CONFIG(clipboard)
138     void slotCopyResourcePath();
139 #endif
140     void slotListWidgetContextMenuRequested(const QPoint &pos);
141     void slotFilterChanged(const QString &pattern);
142     void createPaths();
143     QTreeWidgetItem *createPath(const QString &path, QTreeWidgetItem *parent);
144     void createResources(const QString &path);
145     void storeExpansionState();
146     void applyExpansionState();
147     void restoreSettings();
148     void saveSettings();
149     void updateActions();
150     void filterOutResources();
151 
152     QPixmap makeThumbnail(const QPixmap &pix) const;
153 
154     QDesignerFormEditorInterface *m_core;
155     QtResourceModel *m_resourceModel = nullptr;
156     QToolBar *m_toolBar;
157     QWidget *m_filterWidget = nullptr;
158     QTreeWidget *m_treeWidget;
159     QListWidget *m_listWidget;
160     QSplitter *m_splitter = nullptr;
161     QMap<QString, QStringList>       m_pathToContents; // full path to contents file names (full path to its resource filenames)
162     QMap<QString, QString>           m_pathToParentPath; // full path to full parent path
163     QMap<QString, QStringList>       m_pathToSubPaths; // full path to full sub paths
164     QMap<QString, QTreeWidgetItem *> m_pathToItem;
165     QMap<QTreeWidgetItem *, QString> m_itemToPath;
166     QMap<QString, QListWidgetItem *> m_resourceToItem;
167     QMap<QListWidgetItem *, QString> m_itemToResource;
168     QAction *m_editResourcesAction = nullptr;
169     QAction *m_reloadResourcesAction = nullptr;
170     QAction *m_copyResourcePathAction = nullptr;
171 
172     QMap<QString, bool> m_expansionState;
173 
174     QString m_settingsKey;
175     QString m_filterPattern;
176     bool m_ignoreGuiSignals = false;
177     bool m_resourceEditingEnabled = true;
178 };
179 
QtResourceViewPrivate(QDesignerFormEditorInterface * core)180 QtResourceViewPrivate::QtResourceViewPrivate(QDesignerFormEditorInterface *core) :
181     m_core(core),
182     m_toolBar(new QToolBar),
183     m_treeWidget(new QTreeWidget),
184     m_listWidget(new ResourceListWidget)
185 {
186     m_toolBar->setIconSize(QSize(22, 22));
187 }
188 
restoreSettings()189 void QtResourceViewPrivate::restoreSettings()
190 {
191     if (m_settingsKey.isEmpty())
192         return;
193 
194     QDesignerSettingsInterface *settings = m_core->settingsManager();
195     settings->beginGroup(m_settingsKey);
196 
197     m_splitter->restoreState(settings->value(QLatin1String(SplitterPosition)).toByteArray());
198     settings->endGroup();
199 }
200 
saveSettings()201 void QtResourceViewPrivate::saveSettings()
202 {
203     if (m_settingsKey.isEmpty())
204         return;
205 
206     QDesignerSettingsInterface *settings = m_core->settingsManager();
207     settings->beginGroup(m_settingsKey);
208 
209     settings->setValue(QLatin1String(SplitterPosition), m_splitter->saveState());
210     settings->endGroup();
211 }
212 
slotEditResources()213 void QtResourceViewPrivate::slotEditResources()
214 {
215     const QString selectedResource
216             = QtResourceEditorDialog::editResources(m_core, m_resourceModel,
217                                                     m_core->dialogGui(), q_ptr);
218     if (!selectedResource.isEmpty())
219         q_ptr->selectResource(selectedResource);
220 }
221 
slotReloadResources()222 void QtResourceViewPrivate::slotReloadResources()
223 {
224     if (m_resourceModel) {
225         int errorCount;
226         QString errorMessages;
227         m_resourceModel->reload(&errorCount, &errorMessages);
228         if (errorCount)
229             QtResourceEditorDialog::displayResourceFailures(errorMessages, m_core->dialogGui(), q_ptr);
230     }
231 }
232 
233 #if QT_CONFIG(clipboard)
slotCopyResourcePath()234 void QtResourceViewPrivate::slotCopyResourcePath()
235 {
236     const QString path = q_ptr->selectedResource();
237     QClipboard *clipboard = QApplication::clipboard();
238     clipboard->setText(path);
239 }
240 #endif
241 
slotListWidgetContextMenuRequested(const QPoint & pos)242 void QtResourceViewPrivate::slotListWidgetContextMenuRequested(const QPoint &pos)
243 {
244     QMenu menu(q_ptr);
245     menu.addAction(m_copyResourcePathAction);
246     menu.exec(m_listWidget->mapToGlobal(pos));
247 }
248 
slotFilterChanged(const QString & pattern)249 void QtResourceViewPrivate::slotFilterChanged(const QString &pattern)
250 {
251     m_filterPattern = pattern;
252     filterOutResources();
253 }
254 
storeExpansionState()255 void QtResourceViewPrivate::storeExpansionState()
256 {
257     for (auto it = m_pathToItem.cbegin(), end = m_pathToItem.cend(); it != end; ++it)
258         m_expansionState.insert(it.key(), it.value()->isExpanded());
259 }
260 
applyExpansionState()261 void QtResourceViewPrivate::applyExpansionState()
262 {
263     for (auto it = m_pathToItem.cbegin(), end = m_pathToItem.cend(); it != end; ++it)
264         it.value()->setExpanded(m_expansionState.value(it.key(), true));
265 }
266 
makeThumbnail(const QPixmap & pix) const267 QPixmap QtResourceViewPrivate::makeThumbnail(const QPixmap &pix) const
268 {
269     int w = qMax(48, pix.width());
270     int h = qMax(48, pix.height());
271     QRect imgRect(0, 0, w, h);
272     QImage img(w, h, QImage::Format_ARGB32_Premultiplied);
273     img.fill(0);
274     if (!pix.isNull()) {
275         QRect r(0, 0, pix.width(), pix.height());
276         r.moveCenter(imgRect.center());
277         QPainter p(&img);
278         p.drawPixmap(r.topLeft(), pix);
279     }
280     return QPixmap::fromImage(img);
281 }
282 
updateActions()283 void QtResourceViewPrivate::updateActions()
284 {
285     bool resourceActive = false;
286     if (m_resourceModel)
287         resourceActive = m_resourceModel->currentResourceSet();
288 
289     m_editResourcesAction->setVisible(m_resourceEditingEnabled);
290     m_editResourcesAction->setEnabled(resourceActive);
291     m_reloadResourcesAction->setEnabled(resourceActive);
292     m_filterWidget->setEnabled(resourceActive);
293 }
294 
slotResourceSetActivated(QtResourceSet * resourceSet)295 void QtResourceViewPrivate::slotResourceSetActivated(QtResourceSet *resourceSet)
296 {
297     Q_UNUSED(resourceSet);
298 
299     updateActions();
300 
301     storeExpansionState();
302     const QString currentPath = m_itemToPath.value(m_treeWidget->currentItem());
303     const QString currentResource = m_itemToResource.value(m_listWidget->currentItem());
304     m_treeWidget->clear();
305     m_pathToContents.clear();
306     m_pathToParentPath.clear();
307     m_pathToSubPaths.clear();
308     m_pathToItem.clear();
309     m_itemToPath.clear();
310     m_listWidget->clear();
311     m_resourceToItem.clear();
312     m_itemToResource.clear();
313 
314     createPaths();
315     applyExpansionState();
316 
317     if (!currentResource.isEmpty())
318         q_ptr->selectResource(currentResource);
319     else if (!currentPath.isEmpty())
320         q_ptr->selectResource(currentPath);
321     filterOutResources();
322 }
323 
slotCurrentPathChanged(QTreeWidgetItem * item)324 void QtResourceViewPrivate::slotCurrentPathChanged(QTreeWidgetItem *item)
325 {
326     if (m_ignoreGuiSignals)
327         return;
328 
329     m_listWidget->clear();
330     m_resourceToItem.clear();
331     m_itemToResource.clear();
332 
333     if (!item)
334         return;
335 
336     const QString currentPath = m_itemToPath.value(item);
337     createResources(currentPath);
338 }
339 
slotCurrentResourceChanged(QListWidgetItem * item)340 void QtResourceViewPrivate::slotCurrentResourceChanged(QListWidgetItem *item)
341 {
342     m_copyResourcePathAction->setEnabled(item);
343     if (m_ignoreGuiSignals)
344         return;
345 
346     emit q_ptr->resourceSelected(m_itemToResource.value(item));
347 }
348 
slotResourceActivated(QListWidgetItem * item)349 void QtResourceViewPrivate::slotResourceActivated(QListWidgetItem *item)
350 {
351     if (m_ignoreGuiSignals)
352         return;
353 
354     emit q_ptr->resourceActivated(m_itemToResource.value(item));
355 }
356 
createPaths()357 void QtResourceViewPrivate::createPaths()
358 {
359     if (!m_resourceModel)
360         return;
361 
362     // Resource root up until 4.6 was ':', changed to ":/" as of 4.7
363     const QString root(QStringLiteral(":/"));
364 
365     QMap<QString, QString> contents = m_resourceModel->contents();
366     for (auto it = contents.cbegin(), end = contents.cend(); it != end; ++it) {
367         const QFileInfo fi(it.key());
368         QString dirPath = fi.absolutePath();
369         m_pathToContents[dirPath].append(fi.fileName());
370         while (!m_pathToParentPath.contains(dirPath) && dirPath != root) { // create all parent paths
371             const QFileInfo fd(dirPath);
372             const QString parentDirPath = fd.absolutePath();
373             m_pathToParentPath[dirPath] = parentDirPath;
374             m_pathToSubPaths[parentDirPath].append(dirPath);
375             dirPath = parentDirPath;
376         }
377     }
378 
379     QQueue<QPair<QString, QTreeWidgetItem *> > pathToParentItemQueue;
380     pathToParentItemQueue.enqueue(qMakePair(root, static_cast<QTreeWidgetItem *>(nullptr)));
381     while (!pathToParentItemQueue.isEmpty()) {
382         QPair<QString, QTreeWidgetItem *> pathToParentItem = pathToParentItemQueue.dequeue();
383         const QString path = pathToParentItem.first;
384         QTreeWidgetItem *item = createPath(path, pathToParentItem.second);
385         const QStringList subPaths = m_pathToSubPaths.value(path);
386         for (const QString &subPath : subPaths)
387             pathToParentItemQueue.enqueue(qMakePair(subPath, item));
388     }
389 }
390 
filterOutResources()391 void QtResourceViewPrivate::filterOutResources()
392 {
393     QMap<QString, bool> pathToMatchingContents; // true means the path has any matching contents
394     QMap<QString, bool> pathToVisible; // true means the path has to be shown
395 
396     // 1) we go from root path recursively.
397     // 2) we check every path if it contains at least one matching resource - if empty we add it
398     //                 to pathToMatchingContents and pathToVisible with false, if non empty
399     //                 we add it with true and change every parent path in pathToVisible to true.
400     // 3) we hide these items which has pathToVisible value false.
401 
402     const bool matchAll = m_filterPattern.isEmpty();
403     const QString root(QStringLiteral(":/"));
404 
405     QQueue<QString> pathQueue;
406     pathQueue.enqueue(root);
407     while (!pathQueue.isEmpty()) {
408         const QString path = pathQueue.dequeue();
409 
410         bool hasContents = matchAll;
411         if (!matchAll) { // the case filter is not empty - we check if the path contains anything
412             // the path contains at least one resource which matches the filter
413             const QStringList fileNames = m_pathToContents.value(path);
414             hasContents =
415                 std::any_of(fileNames.cbegin(), fileNames.cend(),
416                             [this] (const QString &f) { return f.contains(this->m_filterPattern, Qt::CaseInsensitive); });
417         }
418 
419         pathToMatchingContents[path] = hasContents;
420         pathToVisible[path] = hasContents;
421 
422         if (hasContents) { // if the path is going to be shown we need to show all its parent paths
423             QString parentPath = m_pathToParentPath.value(path);
424             while (!parentPath.isEmpty()) {
425                 QString p = parentPath;
426                 if (pathToVisible.value(p)) // parent path is already shown, we break the loop
427                     break;
428                 pathToVisible[p] = true;
429                 parentPath = m_pathToParentPath.value(p);
430             }
431         }
432 
433         const QStringList subPaths = m_pathToSubPaths.value(path); // we do the same for children paths
434         for (const QString &subPath : subPaths)
435             pathQueue.enqueue(subPath);
436     }
437 
438     // we setup here new path and resource to be activated
439     const QString currentPath = m_itemToPath.value(m_treeWidget->currentItem());
440     QString newCurrentPath = currentPath;
441     QString currentResource = m_itemToResource.value(m_listWidget->currentItem());
442     if (!matchAll) {
443         bool searchForNewPathWithContents = true;
444 
445         if (!currentPath.isEmpty()) { // if the currentPath is empty we will search for a new path too
446             QMap<QString, bool>::ConstIterator it = pathToMatchingContents.constFind(currentPath);
447             if (it != pathToMatchingContents.constEnd() && it.value()) // the current item has contents, we don't need to search for another path
448                 searchForNewPathWithContents = false;
449         }
450 
451         if (searchForNewPathWithContents) {
452             // we find the first path with the matching contents
453             QMap<QString, bool>::ConstIterator itContents = pathToMatchingContents.constBegin();
454             while (itContents != pathToMatchingContents.constEnd()) {
455                 if (itContents.value()) {
456                     newCurrentPath = itContents.key(); // the new path will be activated
457                     break;
458                 }
459 
460                 itContents++;
461             }
462         }
463 
464         QFileInfo fi(currentResource);
465         if (!fi.fileName().contains(m_filterPattern, Qt::CaseInsensitive)) { // the case when the current resource is filtered out
466             const QStringList fileNames = m_pathToContents.value(newCurrentPath);
467             // we try to select the first matching resource from the newCurrentPath
468             for (const QString &fileName : fileNames) {
469                 if (fileName.contains(m_filterPattern, Qt::CaseInsensitive)) {
470                     QDir dirPath(newCurrentPath);
471                     currentResource = dirPath.absoluteFilePath(fileName); // the new resource inside newCurrentPath will be activated
472                     break;
473                 }
474             }
475         }
476     }
477 
478     QTreeWidgetItem *newCurrentItem = m_pathToItem.value(newCurrentPath);
479     if (currentPath != newCurrentPath)
480         m_treeWidget->setCurrentItem(newCurrentItem);
481     else
482         slotCurrentPathChanged(newCurrentItem); // trigger filtering on the current path
483 
484     QListWidgetItem *currentResourceItem = m_resourceToItem.value(currentResource);
485     if (currentResourceItem) {
486         m_listWidget->setCurrentItem(currentResourceItem);
487         m_listWidget->scrollToItem(currentResourceItem);
488     }
489 
490     // hide all paths filtered out
491     for (auto it = pathToVisible.cbegin(), end = pathToVisible.cend(); it != end; ++it) {
492         if (QTreeWidgetItem *item = m_pathToItem.value(it.key()))
493             item->setHidden(!it.value());
494     }
495 }
496 
createPath(const QString & path,QTreeWidgetItem * parent)497 QTreeWidgetItem *QtResourceViewPrivate::createPath(const QString &path, QTreeWidgetItem *parent)
498 {
499     QTreeWidgetItem *item = nullptr;
500     if (parent)
501         item = new QTreeWidgetItem(parent);
502     else
503         item = new QTreeWidgetItem(m_treeWidget);
504     m_pathToItem[path] = item;
505     m_itemToPath[item] = path;
506     QString substPath;
507     if (parent) {
508         QFileInfo di(path);
509         substPath = di.fileName();
510     } else {
511         substPath = QStringLiteral("<resource root>");
512     }
513     item->setText(0, substPath);
514     item->setToolTip(0, path);
515     return item;
516 }
517 
createResources(const QString & path)518 void QtResourceViewPrivate::createResources(const QString &path)
519 {
520     const bool matchAll = m_filterPattern.isEmpty();
521 
522     QDir dir(path);
523     const QStringList fileNames = m_pathToContents.value(path);
524     for (const QString &fileName : fileNames) {
525         const bool showProperty = matchAll || fileName.contains(m_filterPattern, Qt::CaseInsensitive);
526         if (showProperty) {
527             QString filePath = dir.absoluteFilePath(fileName);
528             QFileInfo fi(filePath);
529             if (fi.isFile()) {
530                 QListWidgetItem *item = new QListWidgetItem(fi.fileName(), m_listWidget);
531                 const QPixmap pix = QPixmap(filePath);
532                 if (pix.isNull()) {
533                     item->setToolTip(filePath);
534                 } else {
535                     item->setIcon(QIcon(makeThumbnail(pix)));
536                     const QSize size = pix.size();
537                     item->setToolTip(QtResourceView::tr("Size: %1 x %2\n%3").arg(size.width()).arg(size.height()).arg(filePath));
538                 }
539                 item->setFlags(item->flags() | Qt::ItemIsDragEnabled);
540                 item->setData(Qt::UserRole, filePath);
541                 m_itemToResource[item] = filePath;
542                 m_resourceToItem[filePath] = item;
543             }
544         }
545     }
546 }
547 
548 // -------------- QtResourceView
549 
QtResourceView(QDesignerFormEditorInterface * core,QWidget * parent)550 QtResourceView::QtResourceView(QDesignerFormEditorInterface *core, QWidget *parent) :
551     QWidget(parent),
552     d_ptr(new QtResourceViewPrivate(core))
553 {
554     d_ptr->q_ptr = this;
555 
556     QIcon editIcon = QIcon::fromTheme(QStringLiteral("document-properties"), qdesigner_internal::createIconSet(QStringLiteral("edit.png")));
557     d_ptr->m_editResourcesAction = new QAction(editIcon, tr("Edit Resources..."), this);
558     d_ptr->m_toolBar->addAction(d_ptr->m_editResourcesAction);
559     connect(d_ptr->m_editResourcesAction, SIGNAL(triggered()), this, SLOT(slotEditResources()));
560     d_ptr->m_editResourcesAction->setEnabled(false);
561 
562     QIcon refreshIcon = QIcon::fromTheme(QStringLiteral("view-refresh"), qdesigner_internal::createIconSet(QStringLiteral("reload.png")));
563     d_ptr->m_reloadResourcesAction = new QAction(refreshIcon, tr("Reload"), this);
564 
565     d_ptr->m_toolBar->addAction(d_ptr->m_reloadResourcesAction);
566     connect(d_ptr->m_reloadResourcesAction, SIGNAL(triggered()), this, SLOT(slotReloadResources()));
567     d_ptr->m_reloadResourcesAction->setEnabled(false);
568 
569 #if QT_CONFIG(clipboard)
570     QIcon copyIcon = QIcon::fromTheme(QStringLiteral("edit-copy"), qdesigner_internal::createIconSet(QStringLiteral("editcopy.png")));
571     d_ptr->m_copyResourcePathAction = new QAction(copyIcon, tr("Copy Path"), this);
572     connect(d_ptr->m_copyResourcePathAction, SIGNAL(triggered()), this, SLOT(slotCopyResourcePath()));
573     d_ptr->m_copyResourcePathAction->setEnabled(false);
574 #endif
575 
576     d_ptr->m_filterWidget = new QWidget(d_ptr->m_toolBar);
577     QHBoxLayout *filterLayout = new QHBoxLayout(d_ptr->m_filterWidget);
578     filterLayout->setContentsMargins(0, 0, 0, 0);
579     QLineEdit *filterLineEdit = new QLineEdit(d_ptr->m_filterWidget);
580     connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotFilterChanged(QString)));
581     filterLineEdit->setPlaceholderText(tr("Filter"));
582     filterLineEdit->setClearButtonEnabled(true);
583     filterLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
584     filterLayout->addWidget(filterLineEdit);
585     d_ptr->m_toolBar->addWidget(d_ptr->m_filterWidget);
586 
587     d_ptr->m_splitter = new QSplitter;
588     d_ptr->m_splitter->setChildrenCollapsible(false);
589     d_ptr->m_splitter->addWidget(d_ptr->m_treeWidget);
590     d_ptr->m_splitter->addWidget(d_ptr->m_listWidget);
591 
592     QLayout *layout = new QVBoxLayout(this);
593     layout->setContentsMargins(QMargins());
594     layout->setSpacing(0);
595     layout->addWidget(d_ptr->m_toolBar);
596     layout->addWidget(d_ptr->m_splitter);
597 
598     d_ptr->m_treeWidget->setColumnCount(1);
599     d_ptr->m_treeWidget->header()->hide();
600     d_ptr->m_treeWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
601 
602     d_ptr->m_listWidget->setViewMode(QListView::IconMode);
603     d_ptr->m_listWidget->setResizeMode(QListView::Adjust);
604     d_ptr->m_listWidget->setIconSize(QSize(48, 48));
605     d_ptr->m_listWidget->setGridSize(QSize(64, 64));
606 
607     connect(d_ptr->m_treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
608                     this, SLOT(slotCurrentPathChanged(QTreeWidgetItem*)));
609     connect(d_ptr->m_listWidget, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
610                     this, SLOT(slotCurrentResourceChanged(QListWidgetItem*)));
611     connect(d_ptr->m_listWidget, SIGNAL(itemActivated(QListWidgetItem*)),
612                     this, SLOT(slotResourceActivated(QListWidgetItem*)));
613     d_ptr->m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
614     connect(d_ptr->m_listWidget, SIGNAL(customContextMenuRequested(QPoint)),
615                 this, SLOT(slotListWidgetContextMenuRequested(QPoint)));
616 }
617 
~QtResourceView()618 QtResourceView::~QtResourceView()
619 {
620     if (!d_ptr->m_settingsKey.isEmpty())
621         d_ptr->saveSettings();
622 }
623 
event(QEvent * event)624 bool QtResourceView::event(QEvent *event)
625 {
626     if (event->type() == QEvent::Show) {
627         d_ptr->m_listWidget->scrollToItem(d_ptr->m_listWidget->currentItem());
628         d_ptr->m_treeWidget->scrollToItem(d_ptr->m_treeWidget->currentItem());
629     }
630     return QWidget::event(event);
631 }
632 
model() const633 QtResourceModel *QtResourceView::model() const
634 {
635     return d_ptr->m_resourceModel;
636 }
637 
selectedResource() const638 QString QtResourceView::selectedResource() const
639 {
640     QListWidgetItem *item = d_ptr->m_listWidget->currentItem();
641     return d_ptr->m_itemToResource.value(item);
642 }
643 
selectResource(const QString & resource)644 void QtResourceView::selectResource(const QString &resource)
645 {
646     if (resource.isEmpty())
647         return;
648     QFileInfo fi(resource);
649     QDir dir = fi.absoluteDir();
650     if (fi.isDir())
651         dir = QDir(resource);
652     QString dirPath = dir.absolutePath();
653     const auto cend = d_ptr->m_pathToItem.constEnd();
654     auto it = cend;
655     while ((it = d_ptr->m_pathToItem.constFind(dirPath)) == cend) {
656         if (!dir.cdUp())
657             break;
658         dirPath = dir.absolutePath();
659     }
660     if (it != cend) {
661         QTreeWidgetItem *treeItem = it.value();
662         d_ptr->m_treeWidget->setCurrentItem(treeItem);
663         d_ptr->m_treeWidget->scrollToItem(treeItem);
664         // expand all up to current one is done by qt
665         // list widget is already propagated (currrent changed was sent by qt)
666         QListWidgetItem *item = d_ptr->m_resourceToItem.value(resource);
667         if (item) {
668             d_ptr->m_listWidget->setCurrentItem(item);
669             d_ptr->m_listWidget->scrollToItem(item);
670         }
671     }
672 }
673 
settingsKey() const674 QString QtResourceView::settingsKey() const
675 {
676     return d_ptr->m_settingsKey;
677 }
678 
setSettingsKey(const QString & key)679 void QtResourceView::setSettingsKey(const QString &key)
680 {
681     if (d_ptr->m_settingsKey == key)
682         return;
683 
684     d_ptr->m_settingsKey = key;
685 
686     if (key.isEmpty())
687         return;
688 
689     d_ptr->restoreSettings();
690 }
691 
setResourceModel(QtResourceModel * model)692 void QtResourceView::setResourceModel(QtResourceModel *model)
693 {
694     if (d_ptr->m_resourceModel) {
695         disconnect(d_ptr->m_resourceModel, SIGNAL(resourceSetActivated(QtResourceSet*,bool)),
696                     this, SLOT(slotResourceSetActivated(QtResourceSet*)));
697     }
698 
699     // clear here
700     d_ptr->m_treeWidget->clear();
701     d_ptr->m_listWidget->clear();
702 
703     d_ptr->m_resourceModel = model;
704 
705     if (!d_ptr->m_resourceModel)
706         return;
707 
708     connect(d_ptr->m_resourceModel, SIGNAL(resourceSetActivated(QtResourceSet*,bool)),
709             this, SLOT(slotResourceSetActivated(QtResourceSet*)));
710 
711     // fill new here
712     d_ptr->slotResourceSetActivated(d_ptr->m_resourceModel->currentResourceSet());
713 }
714 
isResourceEditingEnabled() const715 bool QtResourceView::isResourceEditingEnabled() const
716 {
717     return d_ptr->m_resourceEditingEnabled;
718 }
719 
setResourceEditingEnabled(bool enable)720 void QtResourceView::setResourceEditingEnabled(bool enable)
721 {
722     d_ptr->m_resourceEditingEnabled = enable;
723     d_ptr->updateActions();
724 }
725 
setDragEnabled(bool dragEnabled)726 void QtResourceView::setDragEnabled(bool dragEnabled)
727 {
728     d_ptr->m_listWidget->setDragEnabled(dragEnabled);
729 }
730 
dragEnabled() const731 bool QtResourceView::dragEnabled() const
732 {
733     return d_ptr->m_listWidget->dragEnabled();
734 }
735 
encodeMimeData(ResourceType resourceType,const QString & path)736 QString QtResourceView::encodeMimeData(ResourceType resourceType, const QString &path)
737 {
738     QDomDocument doc;
739     QDomElement elem = doc.createElement(QLatin1String(elementResourceData));
740     switch (resourceType) {
741     case ResourceImage:
742         elem.setAttribute(QLatin1String(typeAttribute), QLatin1String(typeImage));
743         break;
744     case ResourceStyleSheet:
745         elem.setAttribute(QLatin1String(typeAttribute), QLatin1String(typeStyleSheet));
746         break;
747     case ResourceOther:
748         elem.setAttribute(QLatin1String(typeAttribute), QLatin1String(typeOther));
749         break;
750     }
751     elem.setAttribute(QLatin1String(fileAttribute), path);
752     doc.appendChild(elem);
753     return doc.toString();
754 }
755 
decodeMimeData(const QMimeData * md,ResourceType * t,QString * file)756 bool QtResourceView::decodeMimeData(const QMimeData *md, ResourceType *t, QString *file)
757 {
758     return md->hasText() ? decodeMimeData(md->text(), t, file) : false;
759 }
760 
decodeMimeData(const QString & text,ResourceType * t,QString * file)761 bool QtResourceView::decodeMimeData(const QString &text, ResourceType *t, QString *file)
762 {
763 
764     const QString docElementName = QLatin1String(elementResourceData);
765     static const QString docElementString = QLatin1Char('<') + docElementName;
766 
767     if (text.isEmpty() || text.indexOf(docElementString) == -1)
768         return false;
769 
770     QDomDocument doc;
771     if (!doc.setContent(text))
772         return false;
773 
774     const QDomElement domElement = doc.documentElement();
775     if (domElement.tagName() != docElementName)
776         return false;
777 
778     if (t) {
779         const QString typeAttr = QLatin1String(typeAttribute);
780         if (domElement.hasAttribute (typeAttr)) {
781             const QString typeValue = domElement.attribute(typeAttr, QLatin1String(typeOther));
782             if (typeValue == QLatin1String(typeImage)) {
783                 *t = ResourceImage;
784             } else {
785                 *t = typeValue == QLatin1String(typeStyleSheet) ? ResourceStyleSheet : ResourceOther;
786             }
787         }
788     }
789     if (file) {
790         const QString fileAttr = QLatin1String(fileAttribute);
791         if (domElement.hasAttribute(fileAttr)) {
792             *file = domElement.attribute(fileAttr, QString());
793         } else {
794             file->clear();
795         }
796     }
797     return true;
798 }
799 
800 // ---------------------------- QtResourceViewDialogPrivate
801 
802 class QtResourceViewDialogPrivate
803 {
804     QtResourceViewDialog *q_ptr;
805     Q_DECLARE_PUBLIC(QtResourceViewDialog)
806 public:
807     QtResourceViewDialogPrivate(QDesignerFormEditorInterface *core);
808 
slotResourceSelected(const QString & resource)809     void slotResourceSelected(const QString &resource) { setOkButtonEnabled(!resource.isEmpty()); }
setOkButtonEnabled(bool v)810     void setOkButtonEnabled(bool v)                    { m_box->button(QDialogButtonBox::Ok)->setEnabled(v); }
811 
812     QDesignerFormEditorInterface *m_core;
813     QtResourceView *m_view;
814     QDialogButtonBox *m_box;
815 };
816 
QtResourceViewDialogPrivate(QDesignerFormEditorInterface * core)817 QtResourceViewDialogPrivate::QtResourceViewDialogPrivate(QDesignerFormEditorInterface *core) :
818     q_ptr(nullptr),
819     m_core(core),
820     m_view(new QtResourceView(core)),
821     m_box(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel))
822 {
823     m_view->setSettingsKey(QLatin1String(ResourceViewDialogC));
824 }
825 
826 // ------------ QtResourceViewDialog
QtResourceViewDialog(QDesignerFormEditorInterface * core,QWidget * parent)827 QtResourceViewDialog::QtResourceViewDialog(QDesignerFormEditorInterface *core, QWidget *parent) :
828     QDialog(parent),
829     d_ptr(new QtResourceViewDialogPrivate(core))
830 {
831     setWindowTitle(tr("Select Resource"));
832     setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
833     d_ptr->q_ptr = this;
834     QVBoxLayout *layout = new QVBoxLayout(this);
835     layout->addWidget(d_ptr->m_view);
836     layout->addWidget(d_ptr->m_box);
837     connect(d_ptr->m_box, &QDialogButtonBox::accepted, this, &QDialog::accept);
838     connect(d_ptr->m_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
839     connect(d_ptr->m_view, &QtResourceView::resourceActivated, this, &QDialog::accept);
840     connect(d_ptr->m_view, SIGNAL(resourceSelected(QString)), this, SLOT(slotResourceSelected(QString)));
841     d_ptr->setOkButtonEnabled(false);
842     d_ptr->m_view->setResourceModel(core->resourceModel());
843 
844     QDesignerSettingsInterface *settings = core->settingsManager();
845     settings->beginGroup(QLatin1String(ResourceViewDialogC));
846 
847     const QVariant geometry = settings->value(QLatin1String(Geometry));
848     if (geometry.type() == QVariant::ByteArray) // Used to be a QRect up until 5.4.0, QTBUG-43374.
849         restoreGeometry(geometry.toByteArray());
850 
851     settings->endGroup();
852 }
853 
~QtResourceViewDialog()854 QtResourceViewDialog::~QtResourceViewDialog()
855 {
856     QDesignerSettingsInterface *settings = d_ptr->m_core->settingsManager();
857     settings->beginGroup(QLatin1String(ResourceViewDialogC));
858 
859     settings->setValue(QLatin1String(Geometry), saveGeometry());
860 
861     settings->endGroup();
862 }
863 
selectedResource() const864 QString QtResourceViewDialog::selectedResource() const
865 {
866     return d_ptr->m_view->selectedResource();
867 }
868 
selectResource(const QString & path)869 void QtResourceViewDialog::selectResource(const QString &path)
870 {
871     d_ptr->m_view->selectResource(path);
872 }
873 
isResourceEditingEnabled() const874 bool QtResourceViewDialog::isResourceEditingEnabled() const
875 {
876     return d_ptr->m_view->isResourceEditingEnabled();
877 }
878 
setResourceEditingEnabled(bool enable)879 void QtResourceViewDialog::setResourceEditingEnabled(bool enable)
880 {
881     d_ptr->m_view->setResourceEditingEnabled(enable);
882 }
883 
884 QT_END_NAMESPACE
885 
886 #include "moc_qtresourceview_p.cpp"
887