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>
16#include <QtDesigner/abstractformeditor.h>
17#include <QtDesigner/abstractdnditem.h>
18#include <QtDesigner/abstractsettings.h>
20#include <QtUiPlugin/customwidget.h>
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>
28#include <QtGui/qaction.h>
29#include <QtGui/qactiongroup.h>
30#include <QtGui/qevent.h>
32#include <QtCore/qfile.h>
33#include <QtCore/qtimer.h>
34#include <QtCore/qdebug.h>
38using namespace Qt::StringLiterals;
58 item->setData(0, Qt::UserRole, QVariant(tlr));
63 return static_cast<
TopLevelRole>(item->data(0, Qt::UserRole).toInt());
72 m_scratchPadDeleteTimer(
nullptr)
74 setFocusPolicy(Qt::NoFocus);
76 setRootIsDecorated(
false);
79 header()->setSectionResizeMode(QHeaderView::Stretch);
80 setTextElideMode(Qt::ElideMiddle);
81 setVerticalScrollMode(ScrollPerPixel);
83 setItemDelegate(
new SheetDelegate(
this,
this));
85 connect(
this, &QTreeWidget::itemPressed,
86 this, &WidgetBoxTreeWidget::handleMousePress);
91 if (iconName.isEmpty())
92 return qdesigner_internal::qtLogoIcon();
94 if (iconName.startsWith(iconPrefixC)) {
95 const auto it = m_pluginIcons.constFind(iconName);
96 if (it != m_pluginIcons.constEnd())
99 return createIconSet(iconName);
105 if (QTreeWidgetItem *cat_item = topLevelItem(idx))
106 if (QTreeWidgetItem *embedItem = cat_item->child(0))
118 QStringList closedCategories;
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));
126 QDesignerSettingsInterface *settings = m_core->settingsManager();
127 settings->beginGroup(widgetBoxSettingsGroupC);
128 settings->setValue(widgetBoxExpandedKeyC, closedCategories);
129 settings->setValue(widgetBoxViewModeKeyC, m_iconMode);
130 settings->endGroup();
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();
140 const auto &closedCategoryList = settings->value(groupKey + widgetBoxExpandedKeyC, QStringList()).toStringList();
141 const StringSet closedCategories(closedCategoryList.cbegin(), closedCategoryList.cend());
143 if (closedCategories.isEmpty())
147 for (
int i = 0; i < numCategories; ++i) {
148 QTreeWidgetItem *item = topLevelItem(i);
149 if (closedCategories.contains(item->text(0)))
150 item->setExpanded(
false);
162 m_file_name = file_name;
172 if (fileName().isEmpty())
175 QFile file(fileName());
176 if (!file.open(QIODevice::WriteOnly))
179 CategoryList cat_list;
181 for (
int i = 0; i < count; ++i)
182 cat_list.append(category(i));
184 QXmlStreamWriter writer(&file);
185 writer.setAutoFormatting(
true);
186 writer.setAutoFormattingIndent(1);
187 writer.writeStartDocument();
188 writeCategories(writer, cat_list);
189 writer.writeEndDocument();
204 if (QApplication::mouseButtons() != Qt::LeftButton)
207 if (item->parent() ==
nullptr) {
208 item->setExpanded(!item->isExpanded());
215 const int existingIndex = indexOfScratchpad();
216 if (existingIndex != -1)
217 return existingIndex;
219 QTreeWidgetItem *scratch_item =
new QTreeWidgetItem(
this);
220 scratch_item->setText(0, tr(
"Scratchpad"));
222 addCategoryView(scratch_item,
false);
228 QTreeWidgetItem *embed_item =
new QTreeWidgetItem(parent);
229 embed_item->setFlags(Qt::ItemIsEnabled);
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);
240 setItemWidget(embed_item, 0, categoryView);
246 if (
const int numTopLevels = topLevelItemCount()) {
247 for (
int i = numTopLevels - 1; i >= 0; --i) {
257 const int topLevelCount = topLevelItemCount();
258 for (
int i = 0; i < topLevelCount; ++i) {
259 if (topLevelItem(i)->text(0) == name)
268 case QDesignerWidgetBox::LoadReplace:
271 case QDesignerWidgetBox::LoadCustomWidgetsOnly:
272 addCustomCategories(
true);
279 const QString name = fileName();
282 if (!f.open(QIODevice::ReadOnly))
285 const QString contents = QString::fromUtf8(f.readAll());
286 if (!loadContents(contents))
288 if (topLevelItemCount() > 0) {
291 const auto itemHeight = visualItemRect(topLevelItem(0)).height();
292 verticalScrollBar()->setSingleStep(itemHeight);
299 QString errorMessage;
300 CategoryList cat_list;
301 if (!readCategories(m_file_name, contents, &cat_list, &errorMessage)) {
306 for (
const Category &cat : std::as_const(cat_list))
309 addCustomCategories(
false);
311 restoreExpandedState();
319 if (
const int numTopLevels = topLevelItemCount()) {
320 for (
int t = 0; t < numTopLevels ; ++t)
325 const CategoryList customList = loadCustomCategoryList();
326 for (
const auto &c : customList)
330static inline QString
msgXmlError(
const QString &fileName,
const QXmlStreamReader &r)
332 return QDesignerWidgetBox::tr(
"An error has been encountered at line %1 of %2: %3")
333 .arg(r.lineNumber()).arg(fileName, r.errorString());
337 CategoryList *cats, QString *errorMessage)
347 QXmlStreamReader reader(contents);
351 bool ignoreEntries =
false;
353 while (!reader.atEnd()) {
354 switch (reader.readNext()) {
355 case QXmlStreamReader::StartElement: {
356 const auto tag = reader.name();
357 if (tag == widgetBoxRootElementC) {
361 if (tag == categoryElementC) {
363 const QXmlStreamAttributes attributes = reader.attributes();
364 const QString categoryName = attributes.value(wbNameAttributeC).toString();
365 if (categoryName == invisibleNameC) {
366 ignoreEntries =
true;
368 Category category(categoryName);
369 if (attributes.value(typeAttributeC) == scratchPadValueC)
370 category.setType(Category::Scratchpad);
371 cats->push_back(category);
375 if (tag == categoryEntryElementC) {
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()
384 WidgetBoxTreeWidget::Widget::Custom :
385 WidgetBoxTreeWidget::Widget::Default;
388 w.setName(widgetName);
389 w.setIconName(widgetIcon);
390 w.setType(widgetType);
391 if (!readWidget(&w, contents, reader))
394 cats->back().addWidget(w);
400 case QXmlStreamReader::EndElement: {
401 const auto tag = reader.name();
402 if (tag == widgetBoxRootElementC) {
405 if (tag == categoryElementC) {
406 ignoreEntries =
false;
409 if (tag == categoryEntryElementC) {
418 if (reader.hasError()) {
419 *errorMessage = msgXmlError(fileName, reader);
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
451 qint64 startTagPosition =0, endTagPosition = 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) {
462 const auto name = r.name();
463 if (name == uiElementC) {
464 startTagPosition = currentPosition;
466 if (name == wbWidgetElementC) {
467 startTagPosition = currentPosition;
468 parsedWidgetTag =
true;
470 r.raiseError(QDesignerWidgetBox::tr(
"Unexpected element <%1> encountered when parsing for <widget> or <ui>").arg(name.toString()));
476 if (!parsedWidgetTag && r.name() == wbWidgetElementC) {
477 parsedWidgetTag =
true;
481 case QXmlStreamReader::EndElement:
483 if (--nesting == 0) {
484 endTagPosition = r.characterOffset();
485 endEncountered =
true;
488 case QXmlStreamReader::EndDocument:
489 r.raiseError(QDesignerWidgetBox::tr(
"Unexpected end of file encountered when parsing widgets."));
491 case QXmlStreamReader::Invalid:
497 if (!parsedWidgetTag) {
498 r.raiseError(QDesignerWidgetBox::tr(
"A widget element could not be found."));
502 QString widgetXml = xml.mid(startTagPosition, endTagPosition - startTagPosition);
503 if (!widgetXml.startsWith(u'<'))
504 widgetXml.prepend(u'<');
505 w->setDomXml(widgetXml);
509void WidgetBoxTreeWidget::writeCategories(QXmlStreamWriter &writer,
const CategoryList &cat_list)
const
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;
534 writer.writeStartElement(widgetbox);
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);
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)
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);
554 const DomUI *domUI = QDesignerWidgetBox::xmlToUi(wgt.name(), WidgetBoxCategoryListView::widgetDomXml(wgt),
false);
556 domUI->write(writer);
560 writer.writeEndElement();
562 writer.writeEndElement();
565 writer.writeEndElement();
568static int findCategory(
const QString &name,
const WidgetBoxTreeWidget::CategoryList &list)
571 for (
const WidgetBoxTreeWidget::Category &cat : list) {
572 if (cat.name() == name)
581 if (!icon.isNull()) {
582 const auto availableSizes = icon.availableSizes();
583 return !availableSizes.isEmpty() && !availableSizes.constFirst().isEmpty();
592 const QDesignerPluginManager *pm = m_core->pluginManager();
593 const QDesignerPluginManager::CustomWidgetList customWidgets = pm->registeredCustomWidgets();
594 if (customWidgets.isEmpty())
597 static const QString customCatName = tr(
"Custom Widgets");
599 const QString invisible = invisibleNameC;
600 const QString iconPrefix = iconPrefixC;
602 for (QDesignerCustomWidgetInterface *c : customWidgets) {
603 const QString dom_xml = c->domXml();
604 if (dom_xml.isEmpty())
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;
613 QString cat_name = c->group();
614 if (cat_name.isEmpty())
615 cat_name = customCatName;
616 else if (cat_name == invisible)
619 int idx = findCategory(cat_name, result);
621 result.append(Category(cat_name));
622 idx = result.size() - 1;
624 Category &cat = result[idx];
626 const QIcon icon = c->icon();
629 if (isValidIcon(icon)) {
630 icon_name = iconPrefix;
631 icon_name += pluginName;
632 m_pluginIcons.insert(icon_name, icon);
635 cat.addWidget(Widget(displayName, dom_xml, icon_name, Widget::Custom));
643 QTreeWidgetItem *embedItem = cat_item->child(0);
644 if (embedItem ==
nullptr)
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));
657 return topLevelItemCount();
662 if (cat_idx >= topLevelItemCount())
665 QTreeWidgetItem *cat_item = topLevelItem(cat_idx);
667 QTreeWidgetItem *embedItem = cat_item->child(0);
670 Category result = categoryView->category();
671 result.setName(cat_item->text(0));
673 switch (topLevelRole(cat_item)) {
675 result.setType(Category::Scratchpad);
678 result.setType(Category::Default);
686 if (cat.widgetCount() == 0)
689 const bool isScratchPad = cat.type() == Category::Scratchpad;
691 QTreeWidgetItem *cat_item;
694 const int idx = ensureScratchpad();
695 categoryView = categoryViewAt(idx);
696 cat_item = topLevelItem(idx);
698 const int existingIndex = indexOfCategory(cat.name());
699 if (existingIndex == -1) {
700 cat_item =
new QTreeWidgetItem();
701 cat_item->setText(0, cat.name());
704 const int scratchPadIndex = indexOfScratchpad();
705 if (scratchPadIndex == -1) {
706 addTopLevelItem(cat_item);
708 insertTopLevelItem(scratchPadIndex, cat_item);
710 cat_item->setExpanded(
true);
711 categoryView = addCategoryView(cat_item, m_iconMode);
713 categoryView = categoryViewAt(existingIndex);
714 cat_item = topLevelItem(existingIndex);
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);
724 adjustSubListSize(cat_item);
729 if (cat_idx >= topLevelItemCount())
731 delete takeTopLevelItem(cat_idx);
736 if (cat_idx >= topLevelItemCount())
739 return categoryViewAt(cat_idx)->count(WidgetBoxCategoryListView::UnfilteredAccess);
744 if (cat_idx >= topLevelItemCount())
748 return categoryView->widgetAt(WidgetBoxCategoryListView::UnfilteredAccess, wgt_idx);
753 if (cat_idx >= topLevelItemCount())
756 QTreeWidgetItem *cat_item = topLevelItem(cat_idx);
759 const bool scratch = topLevelRole(cat_item) == SCRATCHPAD_ITEM;
760 categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), scratch);
761 adjustSubListSize(cat_item);
766 if (cat_idx >= topLevelItemCount())
772 const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::UnfilteredAccess;
773 if (wgt_idx >= categoryView->count(am))
776 categoryView->removeRow(am, wgt_idx);
781 const int scratch_idx = indexOfScratchpad();
782 QTreeWidgetItem *scratch_item = topLevelItem(scratch_idx);
783 adjustSubListSize(scratch_item);
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,
797 if (!m_scratchPadDeleteTimer->isActive())
798 m_scratchPadDeleteTimer->start();
803 const int idx = indexOfScratchpad();
806 delete takeTopLevelItem(idx);
825 if (
const int numTopLevels = topLevelItemCount()) {
826 for (
int i = numTopLevels - 1; i >= 0; --i) {
827 QTreeWidgetItem *topLevel = topLevelItem(i);
829 const QListView::ViewMode viewMode = m_iconMode && (topLevelRole(topLevel) != SCRATCHPAD_ITEM) ? QListView::IconMode : QListView::ListMode;
831 if (viewMode != categoryView->viewMode()) {
832 categoryView->setViewMode(viewMode);
833 adjustSubListSize(topLevelItem(i));
843 QTreeWidget::resizeEvent(e);
844 if (
const int numTopLevels = topLevelItemCount()) {
845 for (
int i = numTopLevels - 1; i >= 0; --i)
846 adjustSubListSize(topLevelItem(i));
852 QTreeWidgetItem *item = itemAt(e->pos());
854 const bool scratchpad_menu = item !=
nullptr
855 && item->parent() !=
nullptr
856 && topLevelRole(item->parent()) == SCRATCHPAD_ITEM;
859 menu.addAction(tr(
"Expand all"),
this, &WidgetBoxTreeWidget::expandAll);
860 menu.addAction(tr(
"Collapse all"),
this, &WidgetBoxTreeWidget::collapseAll);
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);
871 iconModeAction->setChecked(
true);
873 listModeAction->setChecked(
true);
877 if (scratchpad_menu) {
886 menu.exec(mapToGlobal(e->pos()));
891 QTreeWidgetItem *scratch_item =
nullptr;
895 for (QDesignerDnDItemInterface *item : item_list) {
896 QWidget *w = item->widget();
900 DomUI *dom_ui = item->domUi();
901 if (dom_ui ==
nullptr)
904 const int scratch_idx = ensureScratchpad();
905 scratch_item = topLevelItem(scratch_idx);
906 categoryView = categoryViewAt(scratch_idx);
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);
915 dom_ui->setElementWidget(fakeTopLevel);
922 QXmlStreamWriter writer(&xml);
923 writer.setAutoFormatting(
true);
924 writer.setAutoFormattingIndent(1);
925 writer.writeStartDocument();
926 dom_ui->write(writer);
927 writer.writeEndDocument();
931 dom_ui->takeElementWidget();
932 dom_ui->setElementWidget(fakeTopLevel);
934 const Widget wgt = Widget(w->objectName(), xml);
935 categoryView->addWidget(wgt, iconForWidget(wgt.iconName()),
true);
936 scratch_item->setExpanded(
true);
944 const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::FilteredAccess;
945 if (
const int count = categoryView->count(am))
946 categoryView->setCurrentItem(am, count - 1);
947 categoryView->adjustSize();
948 adjustSubListSize(scratch_item);
950 scrollToItem(scratch_item, PositionAtTop);
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);
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) {
968 const bool categoryEnabled = newCount > 0 || empty;
969 if (categoryEnabled) {
970 categoryView->adjustSize();
971 adjustSubListSize(tl);
973 setRowHidden (i, QModelIndex(), !categoryEnabled);
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