Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
widgetboxtreewidget.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant reason:default
4
7
8// shared
9#include <iconloader_p.h>
10#include <sheet_delegate_p.h>
11#include <QtDesigner/private/ui4_p.h>
12#include <qdesigner_utils_p.h>
13#include <pluginmanager_p.h>
14
15// sdk
16#include <QtDesigner/abstractformeditor.h>
17#include <QtDesigner/abstractdnditem.h>
18#include <QtDesigner/abstractsettings.h>
19
20#include <QtUiPlugin/customwidget.h>
21
22#include <QtWidgets/qapplication.h>
23#include <QtWidgets/qheaderview.h>
24#include <QtWidgets/qmenu.h>
25#include <QtWidgets/qscrollbar.h>
26#include <QtWidgets/qtreewidget.h>
27
28#include <QtGui/qaction.h>
29#include <QtGui/qactiongroup.h>
30#include <QtGui/qevent.h>
31
32#include <QtCore/qfile.h>
33#include <QtCore/qtimer.h>
34#include <QtCore/qdebug.h>
35
37
38using namespace Qt::StringLiterals;
39
40static constexpr auto widgetBoxRootElementC = "widgetbox"_L1;
41static constexpr auto wbWidgetElementC = "widget"_L1;
42static constexpr auto uiElementC = "ui"_L1;
43static constexpr auto categoryElementC = "category"_L1;
44static constexpr auto categoryEntryElementC = "categoryentry"_L1;
45static constexpr auto wbNameAttributeC = "name"_L1;
46static constexpr auto typeAttributeC = "type"_L1;
47static constexpr auto iconAttributeC = "icon"_L1;
48static constexpr auto defaultTypeValueC = "default"_L1;
49static constexpr auto customValueC = "custom"_L1;
50static constexpr auto iconPrefixC = "__qt_icon__"_L1;
51static constexpr auto scratchPadValueC = "scratchpad"_L1;
52static constexpr auto invisibleNameC = "[invisible]"_L1;
53
55
56static void setTopLevelRole(TopLevelRole tlr, QTreeWidgetItem *item)
57{
58 item->setData(0, Qt::UserRole, QVariant(tlr));
59}
60
61static TopLevelRole topLevelRole(const QTreeWidgetItem *item)
62{
63 return static_cast<TopLevelRole>(item->data(0, Qt::UserRole).toInt());
64}
65
66namespace qdesigner_internal {
67
68WidgetBoxTreeWidget::WidgetBoxTreeWidget(QDesignerFormEditorInterface *core, QWidget *parent) :
70 m_core(core),
71 m_iconMode(false),
72 m_scratchPadDeleteTimer(nullptr)
73{
74 setFocusPolicy(Qt::NoFocus);
75 setIndentation(0);
76 setRootIsDecorated(false);
77 setColumnCount(1);
78 header()->hide();
79 header()->setSectionResizeMode(QHeaderView::Stretch);
80 setTextElideMode(Qt::ElideMiddle);
81 setVerticalScrollMode(ScrollPerPixel);
82
83 setItemDelegate(new SheetDelegate(this, this));
84
85 connect(this, &QTreeWidget::itemPressed,
86 this, &WidgetBoxTreeWidget::handleMousePress);
87}
88
89QIcon WidgetBoxTreeWidget::iconForWidget(const QString &iconName) const
90{
91 if (iconName.isEmpty())
92 return qdesigner_internal::qtLogoIcon();
93
94 if (iconName.startsWith(iconPrefixC)) {
95 const auto it = m_pluginIcons.constFind(iconName);
96 if (it != m_pluginIcons.constEnd())
97 return it.value();
98 }
99 return createIconSet(iconName);
100}
101
102WidgetBoxCategoryListView *WidgetBoxTreeWidget::categoryViewAt(int idx) const
103{
104 WidgetBoxCategoryListView *rc = nullptr;
105 if (QTreeWidgetItem *cat_item = topLevelItem(idx))
106 if (QTreeWidgetItem *embedItem = cat_item->child(0))
107 rc = qobject_cast<WidgetBoxCategoryListView*>(itemWidget(embedItem, 0));
108 Q_ASSERT(rc);
109 return rc;
110}
111
112static constexpr auto widgetBoxSettingsGroupC = "WidgetBox"_L1;
113static constexpr auto widgetBoxExpandedKeyC = "Closed categories"_L1;
114static constexpr auto widgetBoxViewModeKeyC = "View mode"_L1;
115
116void WidgetBoxTreeWidget::saveExpandedState() const
117{
118 QStringList closedCategories;
119 if (const int numCategories = categoryCount()) {
120 for (int i = 0; i < numCategories; ++i) {
121 const QTreeWidgetItem *cat_item = topLevelItem(i);
122 if (!cat_item->isExpanded())
123 closedCategories.append(cat_item->text(0));
124 }
125 }
126 QDesignerSettingsInterface *settings = m_core->settingsManager();
127 settings->beginGroup(widgetBoxSettingsGroupC);
128 settings->setValue(widgetBoxExpandedKeyC, closedCategories);
129 settings->setValue(widgetBoxViewModeKeyC, m_iconMode);
130 settings->endGroup();
131}
132
133void WidgetBoxTreeWidget::restoreExpandedState()
134{
135 using StringSet = QSet<QString>;
136 QDesignerSettingsInterface *settings = m_core->settingsManager();
137 const QString groupKey = widgetBoxSettingsGroupC + u'/';
138 m_iconMode = settings->value(groupKey + widgetBoxViewModeKeyC).toBool();
139 updateViewMode();
140 const auto &closedCategoryList = settings->value(groupKey + widgetBoxExpandedKeyC, QStringList()).toStringList();
141 const StringSet closedCategories(closedCategoryList.cbegin(), closedCategoryList.cend());
142 expandAll();
143 if (closedCategories.isEmpty())
144 return;
145
146 if (const int numCategories = categoryCount()) {
147 for (int i = 0; i < numCategories; ++i) {
148 QTreeWidgetItem *item = topLevelItem(i);
149 if (closedCategories.contains(item->text(0)))
150 item->setExpanded(false);
151 }
152 }
153}
154
156{
157 saveExpandedState();
158}
159
160void WidgetBoxTreeWidget::setFileName(const QString &file_name)
161{
162 m_file_name = file_name;
163}
164
166{
167 return m_file_name;
168}
169
171{
172 if (fileName().isEmpty())
173 return false;
174
175 QFile file(fileName());
176 if (!file.open(QIODevice::WriteOnly))
177 return false;
178
179 CategoryList cat_list;
180 const int count = categoryCount();
181 for (int i = 0; i < count; ++i)
182 cat_list.append(category(i));
183
184 QXmlStreamWriter writer(&file);
185 writer.setAutoFormatting(true);
186 writer.setAutoFormattingIndent(1);
187 writer.writeStartDocument();
188 writeCategories(writer, cat_list);
189 writer.writeEndDocument();
190
191 return true;
192}
193
194void WidgetBoxTreeWidget::slotSave()
195{
196 save();
197}
198
199void WidgetBoxTreeWidget::handleMousePress(QTreeWidgetItem *item)
200{
201 if (item == nullptr)
202 return;
203
204 if (QApplication::mouseButtons() != Qt::LeftButton)
205 return;
206
207 if (item->parent() == nullptr) {
208 item->setExpanded(!item->isExpanded());
209 return;
210 }
211}
212
213int WidgetBoxTreeWidget::ensureScratchpad()
214{
215 const int existingIndex = indexOfScratchpad();
216 if (existingIndex != -1)
217 return existingIndex;
218
219 QTreeWidgetItem *scratch_item = new QTreeWidgetItem(this);
220 scratch_item->setText(0, tr("Scratchpad"));
221 setTopLevelRole(SCRATCHPAD_ITEM, scratch_item);
222 addCategoryView(scratch_item, false); // Scratchpad in list mode.
223 return categoryCount() - 1;
224}
225
226WidgetBoxCategoryListView *WidgetBoxTreeWidget::addCategoryView(QTreeWidgetItem *parent, bool iconMode)
227{
228 QTreeWidgetItem *embed_item = new QTreeWidgetItem(parent);
229 embed_item->setFlags(Qt::ItemIsEnabled);
230 WidgetBoxCategoryListView *categoryView = new WidgetBoxCategoryListView(m_core, this);
231 categoryView->setViewMode(iconMode ? QListView::IconMode : QListView::ListMode);
232 connect(categoryView, &WidgetBoxCategoryListView::scratchPadChanged,
233 this, &WidgetBoxTreeWidget::slotSave);
234 connect(categoryView, &WidgetBoxCategoryListView::widgetBoxPressed,
235 this, &WidgetBoxTreeWidget::widgetBoxPressed);
236 connect(categoryView, &WidgetBoxCategoryListView::itemRemoved,
237 this, &WidgetBoxTreeWidget::slotScratchPadItemDeleted);
238 connect(categoryView, &WidgetBoxCategoryListView::lastItemRemoved,
239 this, &WidgetBoxTreeWidget::slotLastScratchPadItemDeleted);
240 setItemWidget(embed_item, 0, categoryView);
241 return categoryView;
242}
243
244int WidgetBoxTreeWidget::indexOfScratchpad() const
245{
246 if (const int numTopLevels = topLevelItemCount()) {
247 for (int i = numTopLevels - 1; i >= 0; --i) {
248 if (topLevelRole(topLevelItem(i)) == SCRATCHPAD_ITEM)
249 return i;
250 }
251 }
252 return -1;
253}
254
255int WidgetBoxTreeWidget::indexOfCategory(const QString &name) const
256{
257 const int topLevelCount = topLevelItemCount();
258 for (int i = 0; i < topLevelCount; ++i) {
259 if (topLevelItem(i)->text(0) == name)
260 return i;
261 }
262 return -1;
263}
264
265bool WidgetBoxTreeWidget::load(QDesignerWidgetBox::LoadMode loadMode)
266{
267 switch (loadMode) {
268 case QDesignerWidgetBox::LoadReplace:
269 clear();
270 break;
271 case QDesignerWidgetBox::LoadCustomWidgetsOnly:
272 addCustomCategories(true);
273 updateGeometries();
274 return true;
275 default:
276 break;
277 }
278
279 const QString name = fileName();
280
281 QFile f(name);
282 if (!f.open(QIODevice::ReadOnly)) // Might not exist at first startup
283 return false;
284
285 const QString contents = QString::fromUtf8(f.readAll());
286 if (!loadContents(contents))
287 return false;
288 if (topLevelItemCount() > 0) {
289 // QTBUG-93099: Set the single step to the item height to have some
290 // size-related value.
291 const auto itemHeight = visualItemRect(topLevelItem(0)).height();
292 verticalScrollBar()->setSingleStep(itemHeight);
293 }
294 return true;
295}
296
297bool WidgetBoxTreeWidget::loadContents(const QString &contents)
298{
299 QString errorMessage;
300 CategoryList cat_list;
301 if (!readCategories(m_file_name, contents, &cat_list, &errorMessage)) {
303 return false;
304 }
305
306 for (const Category &cat : std::as_const(cat_list))
307 addCategory(cat);
308
309 addCustomCategories(false);
310 // Restore which items are expanded
311 restoreExpandedState();
312 return true;
313}
314
315void WidgetBoxTreeWidget::addCustomCategories(bool replace)
316{
317 if (replace) {
318 // clear out all existing custom widgets
319 if (const int numTopLevels = topLevelItemCount()) {
320 for (int t = 0; t < numTopLevels ; ++t)
321 categoryViewAt(t)->removeCustomWidgets();
322 }
323 }
324 // re-add
325 const CategoryList customList = loadCustomCategoryList();
326 for (const auto &c : customList)
327 addCategory(c);
328}
329
330static inline QString msgXmlError(const QString &fileName, const QXmlStreamReader &r)
331{
332 return QDesignerWidgetBox::tr("An error has been encountered at line %1 of %2: %3")
333 .arg(r.lineNumber()).arg(fileName, r.errorString());
334}
335
336bool WidgetBoxTreeWidget::readCategories(const QString &fileName, const QString &contents,
337 CategoryList *cats, QString *errorMessage)
338{
339 // Read widget box XML:
340 //
341 //<widgetbox version="4.5">
342 // <category name="Layouts">
343 // <categoryentry name="Vertical Layout" icon="win/editvlayout.png" type="default">
344 // <widget class="QListWidget" ...>
345 // ...
346
347 QXmlStreamReader reader(contents);
348
349
350 // Entries of category with name="invisible" should be ignored
351 bool ignoreEntries = false;
352
353 while (!reader.atEnd()) {
354 switch (reader.readNext()) {
355 case QXmlStreamReader::StartElement: {
356 const auto tag = reader.name();
357 if (tag == widgetBoxRootElementC) {
358 //<widgetbox version="4.5">
359 continue;
360 }
361 if (tag == categoryElementC) {
362 // <category name="Layouts">
363 const QXmlStreamAttributes attributes = reader.attributes();
364 const QString categoryName = attributes.value(wbNameAttributeC).toString();
365 if (categoryName == invisibleNameC) {
366 ignoreEntries = true;
367 } else {
368 Category category(categoryName);
369 if (attributes.value(typeAttributeC) == scratchPadValueC)
370 category.setType(Category::Scratchpad);
371 cats->push_back(category);
372 }
373 continue;
374 }
375 if (tag == categoryEntryElementC) {
376 // <categoryentry name="Vertical Layout" icon="win/editvlayout.png" type="default">
377 if (!ignoreEntries) {
378 QXmlStreamAttributes attr = reader.attributes();
379 const QString widgetName = attr.value(wbNameAttributeC).toString();
380 const QString widgetIcon = attr.value(iconAttributeC).toString();
381 const WidgetBoxTreeWidget::Widget::Type widgetType =
382 attr.value(typeAttributeC).toString()
383 == customValueC ?
384 WidgetBoxTreeWidget::Widget::Custom :
385 WidgetBoxTreeWidget::Widget::Default;
386
387 Widget w;
388 w.setName(widgetName);
389 w.setIconName(widgetIcon);
390 w.setType(widgetType);
391 if (!readWidget(&w, contents, reader))
392 continue;
393
394 cats->back().addWidget(w);
395 } // ignoreEntries
396 continue;
397 }
398 break;
399 }
400 case QXmlStreamReader::EndElement: {
401 const auto tag = reader.name();
402 if (tag == widgetBoxRootElementC) {
403 continue;
404 }
405 if (tag == categoryElementC) {
406 ignoreEntries = false;
407 continue;
408 }
409 if (tag == categoryEntryElementC) {
410 continue;
411 }
412 break;
413 }
414 default: break;
415 }
416 }
417
418 if (reader.hasError()) {
419 *errorMessage = msgXmlError(fileName, reader);
420 return false;
421 }
422
423 return true;
424}
425
426/*!
427 * Read out a widget within a category. This can either be
428 * enclosed in a <ui> element or a (legacy) <widget> element which may
429 * contain nested <widget> elements.
430 *
431 * Examples:
432 *
433 * <ui language="c++">
434 * <widget class="MultiPageWidget" name="multipagewidget"> ... </widget>
435 * <customwidgets>...</customwidgets>
436 * <ui>
437 *
438 * or
439 *
440 * <widget>
441 * <widget> ... </widget>
442 * ...
443 * <widget>
444 *
445 * Returns true on success, false if end was reached or an error has been encountered
446 * in which case the reader has its error flag set. If successful, the current item
447 * of the reader will be the closing element (</ui> or </widget>)
448 */
449bool WidgetBoxTreeWidget::readWidget(Widget *w, const QString &xml, QXmlStreamReader &r)
450{
451 qint64 startTagPosition =0, endTagPosition = 0;
452
453 int nesting = 0;
454 bool endEncountered = false;
455 bool parsedWidgetTag = false;
456 while (!endEncountered) {
457 const qint64 currentPosition = r.characterOffset();
458 switch(r.readNext()) {
459 case QXmlStreamReader::StartElement:
460 if (nesting++ == 0) {
461 // First element must be <ui> or (legacy) <widget>
462 const auto name = r.name();
463 if (name == uiElementC) {
464 startTagPosition = currentPosition;
465 } else {
466 if (name == wbWidgetElementC) {
467 startTagPosition = currentPosition;
468 parsedWidgetTag = true;
469 } else {
470 r.raiseError(QDesignerWidgetBox::tr("Unexpected element <%1> encountered when parsing for <widget> or <ui>").arg(name.toString()));
471 return false;
472 }
473 }
474 } else {
475 // We are within <ui> looking for the first <widget> tag
476 if (!parsedWidgetTag && r.name() == wbWidgetElementC) {
477 parsedWidgetTag = true;
478 }
479 }
480 break;
481 case QXmlStreamReader::EndElement:
482 // Reached end of widget?
483 if (--nesting == 0) {
484 endTagPosition = r.characterOffset();
485 endEncountered = true;
486 }
487 break;
488 case QXmlStreamReader::EndDocument:
489 r.raiseError(QDesignerWidgetBox::tr("Unexpected end of file encountered when parsing widgets."));
490 return false;
491 case QXmlStreamReader::Invalid:
492 return false;
493 default:
494 break;
495 }
496 }
497 if (!parsedWidgetTag) {
498 r.raiseError(QDesignerWidgetBox::tr("A widget element could not be found."));
499 return false;
500 }
501 // Oddity: Startposition is 1 off
502 QString widgetXml = xml.mid(startTagPosition, endTagPosition - startTagPosition);
503 if (!widgetXml.startsWith(u'<'))
504 widgetXml.prepend(u'<');
505 w->setDomXml(widgetXml);
506 return true;
507}
508
509void WidgetBoxTreeWidget::writeCategories(QXmlStreamWriter &writer, const CategoryList &cat_list) const
510{
511 const QString widgetbox = widgetBoxRootElementC;
512 const QString name = wbNameAttributeC;
513 const QString type = typeAttributeC;
514 const QString icon = iconAttributeC;
515 const QString defaultType = defaultTypeValueC;
516 const QString category = categoryElementC;
517 const QString categoryEntry = categoryEntryElementC;
518 const QString iconPrefix = iconPrefixC;
519
520 //
521 // <widgetbox>
522 // <category name="Layouts">
523 // <categoryEntry name="Vertical Layout" type="default" icon="win/editvlayout.png">
524 // <ui>
525 // ...
526 // </ui>
527 // </categoryEntry>
528 // ...
529 // </category>
530 // ...
531 // </widgetbox>
532 //
533
534 writer.writeStartElement(widgetbox);
535
536 for (const Category &cat : cat_list) {
537 writer.writeStartElement(category);
538 writer.writeAttribute(name, cat.name());
539 if (cat.type() == Category::Scratchpad)
540 writer.writeAttribute(type, scratchPadValueC);
541
542 const int widgetCount = cat.widgetCount();
543 for (int i = 0; i < widgetCount; ++i) {
544 const Widget wgt = cat.widget(i);
545 if (wgt.type() == Widget::Custom)
546 continue;
547
548 writer.writeStartElement(categoryEntry);
549 writer.writeAttribute(name, wgt.name());
550 if (!wgt.iconName().startsWith(iconPrefix))
551 writer.writeAttribute(icon, wgt.iconName());
552 writer.writeAttribute(type, defaultType);
553
554 const DomUI *domUI = QDesignerWidgetBox::xmlToUi(wgt.name(), WidgetBoxCategoryListView::widgetDomXml(wgt), false);
555 if (domUI) {
556 domUI->write(writer);
557 delete domUI;
558 }
559
560 writer.writeEndElement(); // categoryEntry
561 }
562 writer.writeEndElement(); // categoryEntry
563 }
564
565 writer.writeEndElement(); // widgetBox
566}
567
568static int findCategory(const QString &name, const WidgetBoxTreeWidget::CategoryList &list)
569{
570 int idx = 0;
571 for (const WidgetBoxTreeWidget::Category &cat : list) {
572 if (cat.name() == name)
573 return idx;
574 ++idx;
575 }
576 return -1;
577}
578
579static inline bool isValidIcon(const QIcon &icon)
580{
581 if (!icon.isNull()) {
582 const auto availableSizes = icon.availableSizes();
583 return !availableSizes.isEmpty() && !availableSizes.constFirst().isEmpty();
584 }
585 return false;
586}
587
588WidgetBoxTreeWidget::CategoryList WidgetBoxTreeWidget::loadCustomCategoryList() const
589{
590 CategoryList result;
591
592 const QDesignerPluginManager *pm = m_core->pluginManager();
593 const QDesignerPluginManager::CustomWidgetList customWidgets = pm->registeredCustomWidgets();
594 if (customWidgets.isEmpty())
595 return result;
596
597 static const QString customCatName = tr("Custom Widgets");
598
599 const QString invisible = invisibleNameC;
600 const QString iconPrefix = iconPrefixC;
601
602 for (QDesignerCustomWidgetInterface *c : customWidgets) {
603 const QString dom_xml = c->domXml();
604 if (dom_xml.isEmpty())
605 continue;
606
607 const QString pluginName = c->name();
608 const QDesignerCustomWidgetData data = pm->customWidgetData(c);
609 QString displayName = data.xmlDisplayName();
610 if (displayName.isEmpty())
611 displayName = pluginName;
612
613 QString cat_name = c->group();
614 if (cat_name.isEmpty())
615 cat_name = customCatName;
616 else if (cat_name == invisible)
617 continue;
618
619 int idx = findCategory(cat_name, result);
620 if (idx == -1) {
621 result.append(Category(cat_name));
622 idx = result.size() - 1;
623 }
624 Category &cat = result[idx];
625
626 const QIcon icon = c->icon();
627
628 QString icon_name;
629 if (isValidIcon(icon)) {
630 icon_name = iconPrefix;
631 icon_name += pluginName;
632 m_pluginIcons.insert(icon_name, icon);
633 }
634
635 cat.addWidget(Widget(displayName, dom_xml, icon_name, Widget::Custom));
636 }
637
638 return result;
639}
640
641void WidgetBoxTreeWidget::adjustSubListSize(QTreeWidgetItem *cat_item)
642{
643 QTreeWidgetItem *embedItem = cat_item->child(0);
644 if (embedItem == nullptr)
645 return;
646
647 WidgetBoxCategoryListView *list_widget = static_cast<WidgetBoxCategoryListView*>(itemWidget(embedItem, 0));
648 list_widget->setFixedWidth(header()->width());
649 list_widget->doItemsLayout();
650 const int height = qMax(list_widget->contentsSize().height() ,1);
651 list_widget->setFixedHeight(height);
652 embedItem->setSizeHint(0, QSize(-1, height - 1));
653}
654
656{
657 return topLevelItemCount();
658}
659
661{
662 if (cat_idx >= topLevelItemCount())
663 return Category();
664
665 QTreeWidgetItem *cat_item = topLevelItem(cat_idx);
666
667 QTreeWidgetItem *embedItem = cat_item->child(0);
668 WidgetBoxCategoryListView *categoryView = static_cast<WidgetBoxCategoryListView*>(itemWidget(embedItem, 0));
669
670 Category result = categoryView->category();
671 result.setName(cat_item->text(0));
672
673 switch (topLevelRole(cat_item)) {
674 case SCRATCHPAD_ITEM:
675 result.setType(Category::Scratchpad);
676 break;
677 default:
678 result.setType(Category::Default);
679 break;
680 }
681 return result;
682}
683
684void WidgetBoxTreeWidget::addCategory(const Category &cat)
685{
686 if (cat.widgetCount() == 0)
687 return;
688
689 const bool isScratchPad = cat.type() == Category::Scratchpad;
690 WidgetBoxCategoryListView *categoryView;
691 QTreeWidgetItem *cat_item;
692
693 if (isScratchPad) {
694 const int idx = ensureScratchpad();
695 categoryView = categoryViewAt(idx);
696 cat_item = topLevelItem(idx);
697 } else {
698 const int existingIndex = indexOfCategory(cat.name());
699 if (existingIndex == -1) {
700 cat_item = new QTreeWidgetItem();
701 cat_item->setText(0, cat.name());
702 setTopLevelRole(NORMAL_ITEM, cat_item);
703 // insert before scratchpad
704 const int scratchPadIndex = indexOfScratchpad();
705 if (scratchPadIndex == -1) {
706 addTopLevelItem(cat_item);
707 } else {
708 insertTopLevelItem(scratchPadIndex, cat_item);
709 }
710 cat_item->setExpanded(true);
711 categoryView = addCategoryView(cat_item, m_iconMode);
712 } else {
713 categoryView = categoryViewAt(existingIndex);
714 cat_item = topLevelItem(existingIndex);
715 }
716 }
717 // The same categories are read from the file $HOME, avoid duplicates
718 const int widgetCount = cat.widgetCount();
719 for (int i = 0; i < widgetCount; ++i) {
720 const Widget w = cat.widget(i);
721 if (!categoryView->containsWidget(w.name()))
722 categoryView->addWidget(w, iconForWidget(w.iconName()), isScratchPad);
723 }
724 adjustSubListSize(cat_item);
725}
726
728{
729 if (cat_idx >= topLevelItemCount())
730 return;
731 delete takeTopLevelItem(cat_idx);
732}
733
734int WidgetBoxTreeWidget::widgetCount(int cat_idx) const
735{
736 if (cat_idx >= topLevelItemCount())
737 return 0;
738 // SDK functions want unfiltered access
739 return categoryViewAt(cat_idx)->count(WidgetBoxCategoryListView::UnfilteredAccess);
740}
741
742WidgetBoxTreeWidget::Widget WidgetBoxTreeWidget::widget(int cat_idx, int wgt_idx) const
743{
744 if (cat_idx >= topLevelItemCount())
745 return Widget();
746 // SDK functions want unfiltered access
747 WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx);
748 return categoryView->widgetAt(WidgetBoxCategoryListView::UnfilteredAccess, wgt_idx);
749}
750
751void WidgetBoxTreeWidget::addWidget(int cat_idx, const Widget &wgt)
752{
753 if (cat_idx >= topLevelItemCount())
754 return;
755
756 QTreeWidgetItem *cat_item = topLevelItem(cat_idx);
757 WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx);
758
759 const bool scratch = topLevelRole(cat_item) == SCRATCHPAD_ITEM;
760 categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), scratch);
761 adjustSubListSize(cat_item);
762}
763
764void WidgetBoxTreeWidget::removeWidget(int cat_idx, int wgt_idx)
765{
766 if (cat_idx >= topLevelItemCount())
767 return;
768
769 WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx);
770
771 // SDK functions want unfiltered access
772 const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::UnfilteredAccess;
773 if (wgt_idx >= categoryView->count(am))
774 return;
775
776 categoryView->removeRow(am, wgt_idx);
777}
778
779void WidgetBoxTreeWidget::slotScratchPadItemDeleted()
780{
781 const int scratch_idx = indexOfScratchpad();
782 QTreeWidgetItem *scratch_item = topLevelItem(scratch_idx);
783 adjustSubListSize(scratch_item);
784 save();
785}
786
787void WidgetBoxTreeWidget::slotLastScratchPadItemDeleted()
788{
789 // Remove the scratchpad in the next idle loop
790 if (!m_scratchPadDeleteTimer) {
791 m_scratchPadDeleteTimer = new QTimer(this);
792 m_scratchPadDeleteTimer->setSingleShot(true);
793 m_scratchPadDeleteTimer->setInterval(0);
794 connect(m_scratchPadDeleteTimer, &QTimer::timeout,
795 this, &WidgetBoxTreeWidget::deleteScratchpad);
796 }
797 if (!m_scratchPadDeleteTimer->isActive())
798 m_scratchPadDeleteTimer->start();
799}
800
801void WidgetBoxTreeWidget::deleteScratchpad()
802{
803 const int idx = indexOfScratchpad();
804 if (idx == -1)
805 return;
806 delete takeTopLevelItem(idx);
807 save();
808}
809
810
811void WidgetBoxTreeWidget::slotListMode()
812{
813 m_iconMode = false;
814 updateViewMode();
815}
816
817void WidgetBoxTreeWidget::slotIconMode()
818{
819 m_iconMode = true;
820 updateViewMode();
821}
822
823void WidgetBoxTreeWidget::updateViewMode()
824{
825 if (const int numTopLevels = topLevelItemCount()) {
826 for (int i = numTopLevels - 1; i >= 0; --i) {
827 QTreeWidgetItem *topLevel = topLevelItem(i);
828 // Scratch pad stays in list mode.
829 const QListView::ViewMode viewMode = m_iconMode && (topLevelRole(topLevel) != SCRATCHPAD_ITEM) ? QListView::IconMode : QListView::ListMode;
830 WidgetBoxCategoryListView *categoryView = categoryViewAt(i);
831 if (viewMode != categoryView->viewMode()) {
832 categoryView->setViewMode(viewMode);
833 adjustSubListSize(topLevelItem(i));
834 }
835 }
836 }
837
838 updateGeometries();
839}
840
841void WidgetBoxTreeWidget::resizeEvent(QResizeEvent *e)
842{
843 QTreeWidget::resizeEvent(e);
844 if (const int numTopLevels = topLevelItemCount()) {
845 for (int i = numTopLevels - 1; i >= 0; --i)
846 adjustSubListSize(topLevelItem(i));
847 }
848}
849
850void WidgetBoxTreeWidget::contextMenuEvent(QContextMenuEvent *e)
851{
852 QTreeWidgetItem *item = itemAt(e->pos());
853
854 const bool scratchpad_menu = item != nullptr
855 && item->parent() != nullptr
856 && topLevelRole(item->parent()) == SCRATCHPAD_ITEM;
857
858 QMenu menu;
859 menu.addAction(tr("Expand all"), this, &WidgetBoxTreeWidget::expandAll);
860 menu.addAction(tr("Collapse all"), this, &WidgetBoxTreeWidget::collapseAll);
861 menu.addSeparator();
862
863 QAction *listModeAction = menu.addAction(tr("List View"));
864 QAction *iconModeAction = menu.addAction(tr("Icon View"));
865 listModeAction->setCheckable(true);
866 iconModeAction->setCheckable(true);
867 QActionGroup *viewModeGroup = new QActionGroup(&menu);
868 viewModeGroup->addAction(listModeAction);
869 viewModeGroup->addAction(iconModeAction);
870 if (m_iconMode)
871 iconModeAction->setChecked(true);
872 else
873 listModeAction->setChecked(true);
874 connect(listModeAction, &QAction::triggered, this, &WidgetBoxTreeWidget::slotListMode);
875 connect(iconModeAction, &QAction::triggered, this, &WidgetBoxTreeWidget::slotIconMode);
876
877 if (scratchpad_menu) {
878 menu.addSeparator();
879 WidgetBoxCategoryListView *listView = qobject_cast<WidgetBoxCategoryListView *>(itemWidget(item, 0));
880 Q_ASSERT(listView);
881 menu.addAction(tr("Remove"), listView, &WidgetBoxCategoryListView::removeCurrentItem);
882 if (!m_iconMode)
883 menu.addAction(tr("Edit name"), listView, &WidgetBoxCategoryListView::editCurrentItem);
884 }
885 e->accept();
886 menu.exec(mapToGlobal(e->pos()));
887}
888
889void WidgetBoxTreeWidget::dropWidgets(const QList<QDesignerDnDItemInterface*> &item_list)
890{
891 QTreeWidgetItem *scratch_item = nullptr;
892 WidgetBoxCategoryListView *categoryView = nullptr;
893 bool added = false;
894
895 for (QDesignerDnDItemInterface *item : item_list) {
896 QWidget *w = item->widget();
897 if (w == nullptr)
898 continue;
899
900 DomUI *dom_ui = item->domUi();
901 if (dom_ui == nullptr)
902 continue;
903
904 const int scratch_idx = ensureScratchpad();
905 scratch_item = topLevelItem(scratch_idx);
906 categoryView = categoryViewAt(scratch_idx);
907
908 // Temporarily remove the fake toplevel in-between
909 DomWidget *fakeTopLevel = dom_ui->takeElementWidget();
910 DomWidget *firstWidget = nullptr;
911 if (fakeTopLevel && !fakeTopLevel->elementWidget().isEmpty()) {
912 firstWidget = fakeTopLevel->elementWidget().constFirst();
913 dom_ui->setElementWidget(firstWidget);
914 } else {
915 dom_ui->setElementWidget(fakeTopLevel);
916 continue;
917 }
918
919 // Serialize to XML
920 QString xml;
921 {
922 QXmlStreamWriter writer(&xml);
923 writer.setAutoFormatting(true);
924 writer.setAutoFormattingIndent(1);
925 writer.writeStartDocument();
926 dom_ui->write(writer);
927 writer.writeEndDocument();
928 }
929
930 // Insert fake toplevel again
931 dom_ui->takeElementWidget();
932 dom_ui->setElementWidget(fakeTopLevel);
933
934 const Widget wgt = Widget(w->objectName(), xml);
935 categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), true);
936 scratch_item->setExpanded(true);
937 added = true;
938 }
939
940 if (added) {
941 save();
942 activateWindow();
943 // Is the new item visible in filtered mode?
944 const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::FilteredAccess;
945 if (const int count = categoryView->count(am))
946 categoryView->setCurrentItem(am, count - 1);
947 categoryView->adjustSize(); // XXX
948 adjustSubListSize(scratch_item);
949 doItemsLayout();
950 scrollToItem(scratch_item, PositionAtTop);
951 }
952}
953
954void WidgetBoxTreeWidget::filter(const QString &f)
955{
956 const bool empty = f.isEmpty();
957 const int numTopLevels = topLevelItemCount();
958 bool changed = false;
959 for (int i = 0; i < numTopLevels; i++) {
960 QTreeWidgetItem *tl = topLevelItem(i);
961 WidgetBoxCategoryListView *categoryView = categoryViewAt(i);
962 // Anything changed? -> Enable the category
963 const int oldCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess);
964 categoryView->filter(f, Qt::CaseInsensitive);
965 const int newCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess);
966 if (oldCount != newCount) {
967 changed = true;
968 const bool categoryEnabled = newCount > 0 || empty;
969 if (categoryEnabled) {
970 categoryView->adjustSize();
971 adjustSubListSize(tl);
972 }
973 setRowHidden (i, QModelIndex(), !categoryEnabled);
974 }
975 }
976 if (changed)
977 updateGeometries();
978}
979
980} // namespace qdesigner_internal
981
982QT_END_NAMESPACE
Widget widget(int cat_idx, int wgt_idx) const
void contextMenuEvent(QContextMenuEvent *e) override
void resizeEvent(QResizeEvent *e) override
WidgetBoxTreeWidget(QDesignerFormEditorInterface *core, QWidget *parent=nullptr)
void addWidget(int cat_idx, const Widget &wgt)
bool load(QDesignerWidgetBox::LoadMode loadMode)
void removeWidget(int cat_idx, int wgt_idx)
void dropWidgets(const QList< QDesignerDnDItemInterface * > &item_list)
Combined button and popup list for selecting options.
Auxiliary methods to store/retrieve settings.
static int findCategory(const QString &name, const WidgetBoxTreeWidget::CategoryList &list)
static bool isValidIcon(const QIcon &icon)
static QString msgXmlError(const QString &fileName, const QXmlStreamReader &r)
static constexpr auto widgetBoxSettingsGroupC
static constexpr auto widgetBoxViewModeKeyC
QDESIGNER_SHARED_EXPORT void designerWarning(const QString &message)
static constexpr auto widgetBoxExpandedKeyC
static constexpr auto uiElementC
static constexpr auto wbWidgetElementC
static constexpr auto typeAttributeC
static constexpr auto invisibleNameC
static constexpr auto widgetBoxRootElementC
@ SCRATCHPAD_ITEM
static constexpr auto iconPrefixC
static constexpr auto iconAttributeC
static TopLevelRole topLevelRole(const QTreeWidgetItem *item)
static constexpr auto categoryElementC
static void setTopLevelRole(TopLevelRole tlr, QTreeWidgetItem *item)
static constexpr auto categoryEntryElementC
static constexpr auto scratchPadValueC
static constexpr auto wbNameAttributeC
static constexpr auto defaultTypeValueC
static constexpr auto customValueC