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 | |
85 | ResourceListWidget::ResourceListWidget(QWidget *parent) : |
86 | QListWidget(parent) |
87 | { |
88 | setDragEnabled(true); |
89 | } |
90 | |
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(role: 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(resourceType: type , path: filePath)); |
106 | |
107 | QDrag *drag = new QDrag(this); |
108 | if (!icon.isNull()) { |
109 | const QSize size = icon.actualSize(size: 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(supportedActions: 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 | |
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 | |
189 | void QtResourceViewPrivate::restoreSettings() |
190 | { |
191 | if (m_settingsKey.isEmpty()) |
192 | return; |
193 | |
194 | QDesignerSettingsInterface *settings = m_core->settingsManager(); |
195 | settings->beginGroup(prefix: m_settingsKey); |
196 | |
197 | m_splitter->restoreState(state: settings->value(key: QLatin1String(SplitterPosition)).toByteArray()); |
198 | settings->endGroup(); |
199 | } |
200 | |
201 | void QtResourceViewPrivate::saveSettings() |
202 | { |
203 | if (m_settingsKey.isEmpty()) |
204 | return; |
205 | |
206 | QDesignerSettingsInterface *settings = m_core->settingsManager(); |
207 | settings->beginGroup(prefix: m_settingsKey); |
208 | |
209 | settings->setValue(key: QLatin1String(SplitterPosition), value: m_splitter->saveState()); |
210 | settings->endGroup(); |
211 | } |
212 | |
213 | void QtResourceViewPrivate::slotEditResources() |
214 | { |
215 | const QString selectedResource |
216 | = QtResourceEditorDialog::editResources(core: m_core, model: m_resourceModel, |
217 | dlgGui: m_core->dialogGui(), parent: q_ptr); |
218 | if (!selectedResource.isEmpty()) |
219 | q_ptr->selectResource(resource: selectedResource); |
220 | } |
221 | |
222 | void QtResourceViewPrivate::slotReloadResources() |
223 | { |
224 | if (m_resourceModel) { |
225 | int errorCount; |
226 | QString errorMessages; |
227 | m_resourceModel->reload(errorCount: &errorCount, errorMessages: &errorMessages); |
228 | if (errorCount) |
229 | QtResourceEditorDialog::displayResourceFailures(logOutput: errorMessages, dlgGui: m_core->dialogGui(), parent: q_ptr); |
230 | } |
231 | } |
232 | |
233 | #if QT_CONFIG(clipboard) |
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 | |
242 | void QtResourceViewPrivate::(const QPoint &pos) |
243 | { |
244 | QMenu (q_ptr); |
245 | menu.addAction(action: m_copyResourcePathAction); |
246 | menu.exec(pos: m_listWidget->mapToGlobal(pos)); |
247 | } |
248 | |
249 | void QtResourceViewPrivate::slotFilterChanged(const QString &pattern) |
250 | { |
251 | m_filterPattern = pattern; |
252 | filterOutResources(); |
253 | } |
254 | |
255 | void QtResourceViewPrivate::storeExpansionState() |
256 | { |
257 | for (auto it = m_pathToItem.cbegin(), end = m_pathToItem.cend(); it != end; ++it) |
258 | m_expansionState.insert(akey: it.key(), avalue: it.value()->isExpanded()); |
259 | } |
260 | |
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(akey: it.key(), adefaultValue: true)); |
265 | } |
266 | |
267 | QPixmap QtResourceViewPrivate::makeThumbnail(const QPixmap &pix) const |
268 | { |
269 | int w = qMax(a: 48, b: pix.width()); |
270 | int h = qMax(a: 48, b: pix.height()); |
271 | QRect imgRect(0, 0, w, h); |
272 | QImage img(w, h, QImage::Format_ARGB32_Premultiplied); |
273 | img.fill(pixel: 0); |
274 | if (!pix.isNull()) { |
275 | QRect r(0, 0, pix.width(), pix.height()); |
276 | r.moveCenter(p: imgRect.center()); |
277 | QPainter p(&img); |
278 | p.drawPixmap(p: r.topLeft(), pm: pix); |
279 | } |
280 | return QPixmap::fromImage(image: img); |
281 | } |
282 | |
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 | |
295 | void QtResourceViewPrivate::slotResourceSetActivated(QtResourceSet *resourceSet) |
296 | { |
297 | Q_UNUSED(resourceSet); |
298 | |
299 | updateActions(); |
300 | |
301 | storeExpansionState(); |
302 | const QString currentPath = m_itemToPath.value(akey: m_treeWidget->currentItem()); |
303 | const QString currentResource = m_itemToResource.value(akey: 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(resource: currentResource); |
319 | else if (!currentPath.isEmpty()) |
320 | q_ptr->selectResource(resource: currentPath); |
321 | filterOutResources(); |
322 | } |
323 | |
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(akey: item); |
337 | createResources(path: currentPath); |
338 | } |
339 | |
340 | void QtResourceViewPrivate::slotCurrentResourceChanged(QListWidgetItem *item) |
341 | { |
342 | m_copyResourcePathAction->setEnabled(item); |
343 | if (m_ignoreGuiSignals) |
344 | return; |
345 | |
346 | emit q_ptr->resourceSelected(resource: m_itemToResource.value(akey: item)); |
347 | } |
348 | |
349 | void QtResourceViewPrivate::slotResourceActivated(QListWidgetItem *item) |
350 | { |
351 | if (m_ignoreGuiSignals) |
352 | return; |
353 | |
354 | emit q_ptr->resourceActivated(resource: m_itemToResource.value(akey: item)); |
355 | } |
356 | |
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(t: fi.fileName()); |
370 | while (!m_pathToParentPath.contains(akey: 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(t: dirPath); |
375 | dirPath = parentDirPath; |
376 | } |
377 | } |
378 | |
379 | QQueue<QPair<QString, QTreeWidgetItem *> > pathToParentItemQueue; |
380 | pathToParentItemQueue.enqueue(t: qMakePair(x: root, y: 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, parent: pathToParentItem.second); |
385 | const QStringList subPaths = m_pathToSubPaths.value(akey: path); |
386 | for (const QString &subPath : subPaths) |
387 | pathToParentItemQueue.enqueue(t: qMakePair(x: subPath, y: item)); |
388 | } |
389 | } |
390 | |
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(t: 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(akey: path); |
414 | hasContents = |
415 | std::any_of(first: fileNames.cbegin(), last: fileNames.cend(), |
416 | pred: [this] (const QString &f) { return f.contains(s: this->m_filterPattern, cs: 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(akey: path); |
424 | while (!parentPath.isEmpty()) { |
425 | QString p = parentPath; |
426 | if (pathToVisible.value(akey: p)) // parent path is already shown, we break the loop |
427 | break; |
428 | pathToVisible[p] = true; |
429 | parentPath = m_pathToParentPath.value(akey: p); |
430 | } |
431 | } |
432 | |
433 | const QStringList subPaths = m_pathToSubPaths.value(akey: path); // we do the same for children paths |
434 | for (const QString &subPath : subPaths) |
435 | pathQueue.enqueue(t: subPath); |
436 | } |
437 | |
438 | // we setup here new path and resource to be activated |
439 | const QString currentPath = m_itemToPath.value(akey: m_treeWidget->currentItem()); |
440 | QString newCurrentPath = currentPath; |
441 | QString currentResource = m_itemToResource.value(akey: 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(akey: 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(s: m_filterPattern, cs: Qt::CaseInsensitive)) { // the case when the current resource is filtered out |
466 | const QStringList fileNames = m_pathToContents.value(akey: newCurrentPath); |
467 | // we try to select the first matching resource from the newCurrentPath |
468 | for (const QString &fileName : fileNames) { |
469 | if (fileName.contains(s: m_filterPattern, cs: 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(akey: newCurrentPath); |
479 | if (currentPath != newCurrentPath) |
480 | m_treeWidget->setCurrentItem(newCurrentItem); |
481 | else |
482 | slotCurrentPathChanged(item: newCurrentItem); // trigger filtering on the current path |
483 | |
484 | QListWidgetItem *currentResourceItem = m_resourceToItem.value(akey: currentResource); |
485 | if (currentResourceItem) { |
486 | m_listWidget->setCurrentItem(currentResourceItem); |
487 | m_listWidget->scrollToItem(item: 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(akey: it.key())) |
493 | item->setHidden(!it.value()); |
494 | } |
495 | } |
496 | |
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(column: 0, atext: substPath); |
514 | item->setToolTip(column: 0, atoolTip: path); |
515 | return item; |
516 | } |
517 | |
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(akey: path); |
524 | for (const QString &fileName : fileNames) { |
525 | const bool showProperty = matchAll || fileName.contains(s: m_filterPattern, cs: 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(s: "Size: %1 x %2\n%3" ).arg(a: size.width()).arg(a: size.height()).arg(a: filePath)); |
538 | } |
539 | item->setFlags(item->flags() | Qt::ItemIsDragEnabled); |
540 | item->setData(role: Qt::UserRole, value: filePath); |
541 | m_itemToResource[item] = filePath; |
542 | m_resourceToItem[filePath] = item; |
543 | } |
544 | } |
545 | } |
546 | } |
547 | |
548 | // -------------- QtResourceView |
549 | |
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" ), fallback: qdesigner_internal::createIconSet(QStringLiteral("edit.png" ))); |
557 | d_ptr->m_editResourcesAction = new QAction(editIcon, tr(s: "Edit Resources..." ), this); |
558 | d_ptr->m_toolBar->addAction(action: d_ptr->m_editResourcesAction); |
559 | connect(sender: d_ptr->m_editResourcesAction, SIGNAL(triggered()), receiver: this, SLOT(slotEditResources())); |
560 | d_ptr->m_editResourcesAction->setEnabled(false); |
561 | |
562 | QIcon refreshIcon = QIcon::fromTheme(QStringLiteral("view-refresh" ), fallback: qdesigner_internal::createIconSet(QStringLiteral("reload.png" ))); |
563 | d_ptr->m_reloadResourcesAction = new QAction(refreshIcon, tr(s: "Reload" ), this); |
564 | |
565 | d_ptr->m_toolBar->addAction(action: d_ptr->m_reloadResourcesAction); |
566 | connect(sender: d_ptr->m_reloadResourcesAction, SIGNAL(triggered()), receiver: this, SLOT(slotReloadResources())); |
567 | d_ptr->m_reloadResourcesAction->setEnabled(false); |
568 | |
569 | #if QT_CONFIG(clipboard) |
570 | QIcon copyIcon = QIcon::fromTheme(QStringLiteral("edit-copy" ), fallback: qdesigner_internal::createIconSet(QStringLiteral("editcopy.png" ))); |
571 | d_ptr->m_copyResourcePathAction = new QAction(copyIcon, tr(s: "Copy Path" ), this); |
572 | connect(sender: d_ptr->m_copyResourcePathAction, SIGNAL(triggered()), receiver: 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(left: 0, top: 0, right: 0, bottom: 0); |
579 | QLineEdit *filterLineEdit = new QLineEdit(d_ptr->m_filterWidget); |
580 | connect(sender: filterLineEdit, SIGNAL(textChanged(QString)), receiver: this, SLOT(slotFilterChanged(QString))); |
581 | filterLineEdit->setPlaceholderText(tr(s: "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(widget: 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(widget: d_ptr->m_treeWidget); |
590 | d_ptr->m_splitter->addWidget(widget: d_ptr->m_listWidget); |
591 | |
592 | QLayout *layout = new QVBoxLayout(this); |
593 | layout->setContentsMargins(QMargins()); |
594 | layout->setSpacing(0); |
595 | layout->addWidget(w: d_ptr->m_toolBar); |
596 | layout->addWidget(w: 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(sender: d_ptr->m_treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), |
608 | receiver: this, SLOT(slotCurrentPathChanged(QTreeWidgetItem*))); |
609 | connect(sender: d_ptr->m_listWidget, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), |
610 | receiver: this, SLOT(slotCurrentResourceChanged(QListWidgetItem*))); |
611 | connect(sender: d_ptr->m_listWidget, SIGNAL(itemActivated(QListWidgetItem*)), |
612 | receiver: this, SLOT(slotResourceActivated(QListWidgetItem*))); |
613 | d_ptr->m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu); |
614 | connect(sender: d_ptr->m_listWidget, SIGNAL(customContextMenuRequested(QPoint)), |
615 | receiver: this, SLOT(slotListWidgetContextMenuRequested(QPoint))); |
616 | } |
617 | |
618 | QtResourceView::~QtResourceView() |
619 | { |
620 | if (!d_ptr->m_settingsKey.isEmpty()) |
621 | d_ptr->saveSettings(); |
622 | } |
623 | |
624 | bool QtResourceView::event(QEvent *event) |
625 | { |
626 | if (event->type() == QEvent::Show) { |
627 | d_ptr->m_listWidget->scrollToItem(item: d_ptr->m_listWidget->currentItem()); |
628 | d_ptr->m_treeWidget->scrollToItem(item: d_ptr->m_treeWidget->currentItem()); |
629 | } |
630 | return QWidget::event(event); |
631 | } |
632 | |
633 | QtResourceModel *QtResourceView::model() const |
634 | { |
635 | return d_ptr->m_resourceModel; |
636 | } |
637 | |
638 | QString QtResourceView::selectedResource() const |
639 | { |
640 | QListWidgetItem *item = d_ptr->m_listWidget->currentItem(); |
641 | return d_ptr->m_itemToResource.value(akey: item); |
642 | } |
643 | |
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(akey: 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(item: 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(akey: resource); |
667 | if (item) { |
668 | d_ptr->m_listWidget->setCurrentItem(item); |
669 | d_ptr->m_listWidget->scrollToItem(item); |
670 | } |
671 | } |
672 | } |
673 | |
674 | QString QtResourceView::settingsKey() const |
675 | { |
676 | return d_ptr->m_settingsKey; |
677 | } |
678 | |
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 | |
692 | void QtResourceView::setResourceModel(QtResourceModel *model) |
693 | { |
694 | if (d_ptr->m_resourceModel) { |
695 | disconnect(sender: d_ptr->m_resourceModel, SIGNAL(resourceSetActivated(QtResourceSet*,bool)), |
696 | receiver: 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(sender: d_ptr->m_resourceModel, SIGNAL(resourceSetActivated(QtResourceSet*,bool)), |
709 | receiver: this, SLOT(slotResourceSetActivated(QtResourceSet*))); |
710 | |
711 | // fill new here |
712 | d_ptr->slotResourceSetActivated(resourceSet: d_ptr->m_resourceModel->currentResourceSet()); |
713 | } |
714 | |
715 | bool QtResourceView::isResourceEditingEnabled() const |
716 | { |
717 | return d_ptr->m_resourceEditingEnabled; |
718 | } |
719 | |
720 | void QtResourceView::setResourceEditingEnabled(bool enable) |
721 | { |
722 | d_ptr->m_resourceEditingEnabled = enable; |
723 | d_ptr->updateActions(); |
724 | } |
725 | |
726 | void QtResourceView::setDragEnabled(bool dragEnabled) |
727 | { |
728 | d_ptr->m_listWidget->setDragEnabled(dragEnabled); |
729 | } |
730 | |
731 | bool QtResourceView::dragEnabled() const |
732 | { |
733 | return d_ptr->m_listWidget->dragEnabled(); |
734 | } |
735 | |
736 | QString QtResourceView::encodeMimeData(ResourceType resourceType, const QString &path) |
737 | { |
738 | QDomDocument doc; |
739 | QDomElement elem = doc.createElement(tagName: QLatin1String(elementResourceData)); |
740 | switch (resourceType) { |
741 | case ResourceImage: |
742 | elem.setAttribute(name: QLatin1String(typeAttribute), value: QLatin1String(typeImage)); |
743 | break; |
744 | case ResourceStyleSheet: |
745 | elem.setAttribute(name: QLatin1String(typeAttribute), value: QLatin1String(typeStyleSheet)); |
746 | break; |
747 | case ResourceOther: |
748 | elem.setAttribute(name: QLatin1String(typeAttribute), value: QLatin1String(typeOther)); |
749 | break; |
750 | } |
751 | elem.setAttribute(name: QLatin1String(fileAttribute), value: path); |
752 | doc.appendChild(newChild: elem); |
753 | return doc.toString(); |
754 | } |
755 | |
756 | bool QtResourceView::decodeMimeData(const QMimeData *md, ResourceType *t, QString *file) |
757 | { |
758 | return md->hasText() ? decodeMimeData(text: md->text(), t, file) : false; |
759 | } |
760 | |
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(s: 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 (name: typeAttr)) { |
781 | const QString typeValue = domElement.attribute(name: typeAttr, defValue: 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(name: fileAttr)) { |
792 | *file = domElement.attribute(name: fileAttr, defValue: 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 | |
809 | void slotResourceSelected(const QString &resource) { setOkButtonEnabled(!resource.isEmpty()); } |
810 | void setOkButtonEnabled(bool v) { m_box->button(which: QDialogButtonBox::Ok)->setEnabled(v); } |
811 | |
812 | QDesignerFormEditorInterface *m_core; |
813 | QtResourceView *m_view; |
814 | QDialogButtonBox *m_box; |
815 | }; |
816 | |
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 |
827 | QtResourceViewDialog::QtResourceViewDialog(QDesignerFormEditorInterface *core, QWidget *parent) : |
828 | QDialog(parent), |
829 | d_ptr(new QtResourceViewDialogPrivate(core)) |
830 | { |
831 | setWindowTitle(tr(s: "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(sender: d_ptr->m_box, signal: &QDialogButtonBox::accepted, receiver: this, slot: &QDialog::accept); |
838 | connect(sender: d_ptr->m_box, signal: &QDialogButtonBox::rejected, receiver: this, slot: &QDialog::reject); |
839 | connect(sender: d_ptr->m_view, signal: &QtResourceView::resourceActivated, receiver: this, slot: &QDialog::accept); |
840 | connect(sender: d_ptr->m_view, SIGNAL(resourceSelected(QString)), receiver: 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(prefix: QLatin1String(ResourceViewDialogC)); |
846 | |
847 | const QVariant geometry = settings->value(key: QLatin1String(Geometry)); |
848 | if (geometry.type() == QVariant::ByteArray) // Used to be a QRect up until 5.4.0, QTBUG-43374. |
849 | restoreGeometry(geometry: geometry.toByteArray()); |
850 | |
851 | settings->endGroup(); |
852 | } |
853 | |
854 | QtResourceViewDialog::~QtResourceViewDialog() |
855 | { |
856 | QDesignerSettingsInterface *settings = d_ptr->m_core->settingsManager(); |
857 | settings->beginGroup(prefix: QLatin1String(ResourceViewDialogC)); |
858 | |
859 | settings->setValue(key: QLatin1String(Geometry), value: saveGeometry()); |
860 | |
861 | settings->endGroup(); |
862 | } |
863 | |
864 | QString QtResourceViewDialog::selectedResource() const |
865 | { |
866 | return d_ptr->m_view->selectedResource(); |
867 | } |
868 | |
869 | void QtResourceViewDialog::selectResource(const QString &path) |
870 | { |
871 | d_ptr->m_view->selectResource(resource: path); |
872 | } |
873 | |
874 | bool QtResourceViewDialog::isResourceEditingEnabled() const |
875 | { |
876 | return d_ptr->m_view->isResourceEditingEnabled(); |
877 | } |
878 | |
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 | |